Addis AI — Python Client
Python client for the Addis AI API: chat / LLM completion, speech-to-text, translation and text-to-speech for Amharic (አማርኛ), Afan Oromo and other languages.
Modular, fully typed, sync and async, with Pydantic v2 models —
modeled after the Pyrogram codebase layout: one method
per file under methods/, typed response objects under types/, and a proper exception
hierarchy under errors/.
addis_ai/
├── client.py # Client — composes every sync method mixin
├── async_client.py # AsyncClient — same surface, every method awaitable
├── connection/ # HTTP transports: auth, retries, SSE, error normalization
│ ├── base.py # shared policy (backoff, envelopes, SSE decoding)
│ ├── transport.py # httpx.Client
│ └── async_transport.py # httpx.AsyncClient
├── methods/
│ ├── chat/ # generate_chat, stream_chat (+ async variants)
│ ├── speech/ # transcribe (+ async)
│ ├── translation/ # translate (+ async)
│ └── voice/ # generate_speech, get_voices, clips, ... (+ async)
├── types/ # Pydantic models: ChatResponse, Clip, Voice, ...
├── errors/ # AddisAIError → APIStatusError → RateLimitError, ...
└── enums/ # Language, STTLanguage, OutputFormat, FinishReason, ...
Installation
pip install pyaddisai
Requires Python 3.9+, httpx and pydantic v2.
Quick start
from addis_ai import Client
# Reads ADDIS_AI_API_KEY from the environment if api_key is omitted
with Client("YOUR_API_KEY") as app:
response = app.generate_chat("ሰላም እንዴት ነህ?", target_language="am")
print(response.text)
Async
AsyncClient mirrors the entire Client surface — every method is a coroutine, and
stream_chat / get_clips are async generators:
import asyncio
from addis_ai import AsyncClient
async def main():
async with AsyncClient("YOUR_API_KEY") as app:
response = await app.generate_chat("ሰላም እንዴት ነህ?", target_language="am")
print(response.text)
async for chunk in app.stream_chat("ስለ ኢትዮጵያ ንገረኝ"):
if chunk.text:
print(chunk.text, end="", flush=True)
async for clip in app.get_clips(limit=10):
print(clip.id)
asyncio.run(main())
Chat
from addis_ai import Client, types, enums
app = Client("YOUR_API_KEY")
response = app.generate_chat(
"የዛሬው የአየር ሁኔታ ምን ይመስላል?",
target_language=enums.Language.AMHARIC,
system="You are concise and friendly.",
conversation_history=[
types.Message("user", "ሰላም"),
types.Message("assistant", "ሰላም! እንዴት ልረዳህ?"),
],
temperature=0.7,
max_output_tokens=512,
)
print(response.text)
print(response.usage.total_token_count)
Streaming
for chunk in app.stream_chat("ስለ ኢትዮጵያ ታሪክ ንገረኝ", target_language="am"):
if chunk.text:
print(chunk.text, end="", flush=True)
Attachments & voice input
response = app.generate_chat(
"What is in this picture?",
target_language="am",
attachments=["photo.png"], # paths, bytes, file objects or tuples
)
response = app.generate_chat(
"", # prompt comes from the audio
target_language="am",
audio_input="command.wav",
)
print(response.transcription_clean) # what the user said
print(response.text) # the assistant's answer
Function calling
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
},
},
}]
response = app.generate_chat(" አዲስ አበባ ውስጥ ስንት ዲግሪ ነው?", tools=tools)
if response.finish_reason == enums.FinishReason.TOOL_CALLS:
for call in response.tool_calls:
result = run_my_tool(call.name, call.arguments) # execute locally
# send the result back folded into an assistant message,
# echoing call.to_request() (preserves addis_tool_state)
Speech-to-text
result = app.transcribe("call.wav", language_code=enums.STTLanguage.AMHARIC)
print(result.text, result.confidence)
Translation
result = app.translate("Good morning", source_language="en", target_language="am")
print(result.text) # ደህና እደሩ...
Text-to-speech
# Browse the catalog
voices = app.get_voices(language="am", gender="female")
voice = voices[0]
# Optional: check the price first
estimate = app.estimate_speech("ሰላም ለዓለም", voice_id=voice.id, language="am")
assert estimate.can_generate
# Synthesize (idempotent — a ULID client_request_id is generated automatically)
clip = app.generate_speech(
"ሰላም ለዓለም",
voice_id=voice.id,
language="am",
output_format=enums.OutputFormat.MP3_44100,
voice_settings=types.VoiceSettings(speed=50, stability=60),
)
clip.download("hello.mp3") # fetches the signed audio_url
# (await clip.download(...) with AsyncClient)
# Manage clips
for clip in app.get_clips(limit=20): # cursor pagination handled for you
print(clip.id, clip.text_preview)
app.delete_clip(clip.id)
# Wallet
usage = app.get_voice_usage()
print(usage.formatted_balance)
Error handling
All errors derive from addis_ai.errors.AddisAIError. HTTP errors carry the
normalized message, code, details, request_id and retry_after:
from addis_ai import errors
try:
clip = app.generate_speech("ሰላም", voice_id="voice_abc")
except errors.InsufficientCreditsError as e:
print("Top up your wallet:", e.details)
except errors.RateLimitError as e:
print("Slow down, retry after", e.retry_after)
except errors.APIStatusError as e:
print(e.status_code, e.code, e.message, e.request_id)
Transient failures (408, 425, 429, 5xx and 409 GENERATION_IN_PROGRESS) are retried
automatically up to 3 times with exponential backoff, jitter and Retry-After support.
409 IDEMPOTENCY_CONFLICT is never retried.
Typed models
Every response is a Pydantic v2 model — validated, with full IDE completion, and tolerant
of new server fields (extra="allow"):
clip = app.get_clip("clip_123")
clip.model_dump() # plain dict (or clip.to_dict())
clip.model_dump_json(indent=2) # JSON
types.Clip.model_validate(raw) # parse your own payloads
types.VoiceSettings(speed=500) # ValidationError: must be 0-100
Configuration
app = Client( # same signature for AsyncClient
api_key="...", # or ADDIS_AI_API_KEY env var; JWTs auto-detected
base_url="https://api.addisassistant.com",
timeout=60.0, # voice generation enforces a ~95s floor
max_retries=3,
http_transport=None, # custom httpx transport (e.g. MockTransport in tests)
)
License
MIT
Built against the reverse-engineered HTTP contract in ADDIS_AI_API.md
(from addisai-js v0.1.0). Not an official Addis AI product.
Metadata
Release files for pyaddisai 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| pyaddisai-0.2.0.tar.gz | 50.8 kB | Without provenance | Details for pyaddisai-0.2.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| pyaddisai-0.2.0-py3-none-any.whl | Python 3 | none | any | Details for pyaddisai-0.2.0-py3-none-any.whl |
Total release size: 102.5 kB
Release files / pyaddisai-0.2.0.tar.gz
| Download URL | pyaddisai-0.2.0.tar.gz |
|---|---|
| Size | 50.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9b5f5c98af8d6004611652762047e396b15a99bcf6129b1cda632263d4de3353
|
|
BLAKE2b-256 checksum How to use checksums |
3700dc84e5511409e097ddc0bf47446b4e8f403fdba395850ef0b250b5a7eb45
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.1
|
Release files / pyaddisai-0.2.0-py3-none-any.whl
| Download URL | pyaddisai-0.2.0-py3-none-any.whl |
|---|---|
| Size | 51.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c022e57e50ace87be1972f5b24bc994810024de185082896662b8d9c598c43da
|
|
BLAKE2b-256 checksum How to use checksums |
947585f468afb05e06c71b126449e64b74b47107da2de556a828329cb934aecb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.1
|
Security for pyaddisai 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.