Skip to main content

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.

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 from addisai-js v0.1.0. Not an official Addis AI product.

Metadata

Release files for pyaddisai 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pyaddisai 0.2.1
File Size Uploaded Provenance Actions
pyaddisai-0.2.1.tar.gz 51.6 kB Without provenance Details for pyaddisai-0.2.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for pyaddisai 0.2.1
File Interpreter ABI Platform Actions
pyaddisai-0.2.1-py3-none-any.whl Python 3 none any Details for pyaddisai-0.2.1-py3-none-any.whl

Total release size: 103.2 kB

Release files / pyaddisai-0.2.1.tar.gz

Download URL pyaddisai-0.2.1.tar.gz
Size 51.6 kB
Tags Source
SHA-256 checksum
How to use checksums
cd5f7f02fe31fbdb6aff18cd178f0b43b322eec1fe13c8dbbe4e85a914160c05
BLAKE2b-256 checksum
How to use checksums
b6ff2761519508cbcd9bfdf801157b7897b0d6d6d92ef87db5a5ed54b75dd973
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.1-py3-none-any.whl

Download URL pyaddisai-0.2.1-py3-none-any.whl
Size 51.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1dcfc7c2e4ed9acb1c6804817399a2c6cd3c25f34990f999fe6d11868bceff05
BLAKE2b-256 checksum
How to use checksums
1f5822f367fb7b4977d9eb9d0715151b642432a73fae07caf599988911db66d2
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.1

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.

Release history Release notifications | RSS feed

This release

0.2.1 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page