# Python (/ro/docs/sdks/python)

Pachetul codai-sdk pentru Python 3.9+ — start rapid, fiecare metodă, extensiile codai și tratarea erorilor.

`codai-sdk` este clientul oficial Python. Zero dependențe (stdlib `urllib`), Python 3.9+, sincron. Numele de import este `codai`.

```bash
pip install codai-sdk
```

<Callout type="warn">
Pachetul se numește **`codai-sdk`**. `pip install codai` instalează un proiect fără legătură cu noi, al altui autor, care folosește același nume de import și ascunde acest SDK. Dacă `from codai import Codai` dă eroare, rulează `pip uninstall codai`, apoi `pip install codai-sdk`.
</Callout>

## Start rapid [#start-rapid]

```python
import os
from codai import Codai

client = Codai(api_key=os.environ["CODAI_API_KEY"], session_id="my-project")

result = client.chat([{"role": "user", "content": "Explică asyncio.gather într-o linie."}])
print(result.content)
print(result.routed_to)  # modelul upstream care a servit efectiv

for delta in client.chat_stream([{"role": "user", "content": "salut"}]):
    print(delta, end="", flush=True)

run = client.agents_run("Găsește și rezumă TODO-urile din acest codebase")
print(run.result)

if result.request_id:
    client.feedback(result.request_id, 1)
```

## Configurare [#configurare]

```python
client = Codai(
    api_key=os.environ["CODAI_API_KEY"],  # obligatoriu — ridică ValueError dacă e gol
    base_url="https://ai.codai.ro",        # implicit
    session_id="my-project",               # opțional — memorie de sesiune + sticky routing
    timeout=120.0,                         # secunde, implicit
    max_retries=2,                         # reîncercări pe 429 / 5xx cu backoff exponențial
)
```

## Grupuri de resurse [#grupuri-de-resurse]

Începând cu 0.2.0 fiecare operație a gateway-ului este accesibilă ca metodă pe client, grupată pe tag exact ca în [SDK-ul TypeScript](./typescript.mdx) (`camelCase` devine `snake_case`). Metodele de nivel superior din 0.1.x de mai jos funcționează neschimbate; `chat`, `embeddings`, `models` și `feedback` sunt grupuri de resurse apelabile, deci `client.chat([...])` și `client.chat.completions.create({...})` sunt același apel.

| Atribut | Metode | Gateway |
| --- | --- | --- |
| `chat.completions` | `create`, `stream` | `POST /v1/chat/completions` |
| `messages` | `create`, `stream` | `POST /v1/messages` — protocolul Anthropic Messages |
| `responses` | `create`, `stream` | `POST /v1/responses` — protocolul OpenAI Responses |
| `embeddings` | `create` | `POST /v1/embeddings` |
| `audio` | `transcribe`, `transcribe_detailed`, `speech`, `speech_detailed` | `/v1/audio/*` |
| `tokens` | `create` | `POST /v1/tokens` |
| `models` | `list` | `GET /v1/models` |
| `health` | `get`, `ready`, `status` | `/health`, `/health/ready`, `/status` |
| `agents` | `run`; `runs.create`, `runs.get`, `runs.steps`, `runs.stats`, `runs.cancel`, `runs.stream` | `/v1/agents/*` |
| `tools` | `search`, `fetch`, `ground` | `/v1/tools/*` — [unelte web](../agents/tools.mdx#tool-uri-web-un-singur-contract-peste-tot) |
| `research` | `create`, `retrieve`, `wait`, `run` | `/v1/research` — [cercetare aprofundată](../api/gateway/research.mdx) |
| `vault` | `account`, `items`, `put_item`, `decide`, `catalog`, `use`, `http_call`, …; `devices.challenge`, `devices.enroll`, `devices.list`, `devices.rename`, `devices.revoke`; seifurile organizației `members.list`, `members.candidates`, `members.add`, `members.update`, `members.remove`, `rotate_key`, `set_quorum`, `vote`; `simulate_policy`, `receipt_keys` | `/v1/vault/*` — [seif](../api/gateway/vault.mdx) |
| `cards` | `weather`, `places`, `geocode`, `static_map`, `fx`, `unfurl` | `/v1/cards/*` — [carduri](../agents/cards.mdx) |
| `tasks` | `list`, `pending`, `stats`, `get`, `confirm` | `/v1/tasks/*` |
| `sessions` | `create`, `list`, `get`, `update`, `delete`, `dispatch`, `stream`; `events.*`, `controls.*`, `lease.*`, `shares.*` | `/v1/sessions/*` |
| `devices` | `list`, `update`, `delete`, `dispatch_inbox` | `/v1/devices/*` |
| `hosts` | `list`, `exec`, `post_result`, `stream` | `/v1/hosts/*` |
| `orgs` | `create`, `list`; `members.list`, `members.add`, `members.remove` | `/v1/orgs/*` |
| `account` | `get`, `update` | `/v1/account` |
| `receipt` | `get` | `GET /v1/receipt` |
| `feedback` | `submit` | `POST /v1/feedback` |
| `phone_models` | `list` | `GET /v1/phone/models` |

Fiecare metodă acceptă un argument opțional `ext={...}` care acoperă toate headerele de cerere `X-Codai-*` documentate (`effort`, `thinking`, `thinking_budget`, `cache`, `no_task`, `task_id`, `incognito`, `mode`, `best_of`, `compact`, `device`, `share_token`, … plus `headers` brute) — vezi [headere](../gateway/headers.mdx). Formele cererilor și răspunsurilor sunt disponibile ca `TypedDict`-uri în `codai._types`, generate din specificația OpenAPI.

```python
client = Codai(api_key=os.environ["CODAI_API_KEY"], device="5dc0de00-0000-4000-8000-00000000c0da")

r = client.chat.completions.create(
    {"messages": messages, "model": "codai"},
    ext={"effort": "high", "thinking": True, "thinking_budget": 8192},
)

stream = client.chat.completions.stream({"messages": messages})
for delta in stream:                 # delte de text; stream.chunks() dă chunk-urile brute
    print(delta, end="")
print(stream.final.usage, stream.final.tool_calls)

# fluxurile SSE native codai sunt generatoare de cadre {"event", "data"}
run = client.agents.runs.create({"task": "Rezumă README-ul repo-ului."})
for ev in client.agents.runs.stream(run["id"]):
    if ev["event"] == "done":
        print(ev["data"]["status"], ev["data"]["result"])

# sesiunile partajate au nevoie de un id de dispozitiv pe client
s = client.sessions.create({"title": "pairing"})
for ev in client.sessions.stream(s["id"], after=0):
    print(ev["event"], ev["data"].get("seq"))
```

## Chat [#chat]

```python
result = client.chat(
    messages=[{"role": "user", "content": "salut"}],
    model="codai",          # implicit
    temperature=None,
    max_tokens=None,
    tools=None,             # tool-uri OpenAI de tip funcție — tu deții bucla de tool-uri
    agent_mode=False,       # X-Codai-Mode: agent (planul Starter sau mai sus)
    compact=None,           # "auto" → X-Codai-Compact: auto
    best_of=None,           # 3 forțează best-of-N, 0 dezactivează
    session_id=None,        # suprascriere per apel
)
```

`ChatResult` este un dataclass:

| Câmp | Tip | Sursă |
| --- | --- | --- |
| `content` | `str` | Textul asistentului din prima alegere. |
| `raw` | `dict` | Răspunsul complet de formă OpenAI. |
| `request_id` | `str \| None` | `x-request-id` — îl trimiți la `feedback()`. |
| `routed_to` | `str \| None` | `x-codai-routed-to`. |
| `exec_verify` | `str \| None` | `x-codai-exec-verify` pe `codai-labs`. |
| `usage` | `dict \| None` | `{"prompt_tokens", "completion_tokens"}`. |

### Tool calls [#tool-calls]

```python
tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]},
    },
}]

first = client.chat([{"role": "user", "content": "Vremea la Cluj?"}], tools=tools)
message = first.raw["choices"][0]["message"]
for call in message.get("tool_calls") or []:
    args = json.loads(call["function"]["arguments"])
    output = get_weather(args["city"])
    final = client.chat([
        {"role": "user", "content": "Vremea la Cluj?"},
        message,
        {"role": "tool", "tool_call_id": call["id"], "content": json.dumps(output)},
    ], tools=tools)
    print(final.content)
```

## Streaming [#streaming]

`chat_stream()` este un generator de **delta-uri de text**. Parsează frame-urile `data:`, ignoră keep-alive-urile și se oprește la `[DONE]`.

```python
for delta in client.chat_stream(
    [{"role": "user", "content": "Scrie un haiku despre Python."}],
    model="codai",
    temperature=None,
    max_tokens=None,
    agent_mode=False,
    session_id=None,
):
    print(delta, end="", flush=True)
```

`chat_stream()` dă doar text. Pentru tool calls asamblate, frame-ul final de usage, motivul de oprire și headerele de rutare ale unui răspuns în stream folosești `client.chat.completions.stream()` și citești `.final` după iterare (vezi [grupuri de resurse](#grupuri-de-resurse)); `.chunks()` dă dict-urile brute `chat.completion.chunk`. O eroare HTTP înaintea primului byte ridică `CodaiError`.

## Agent server-side [#agent-server-side]

```python
run = client.agents_run(
    task="Rezumă punctele cheie ale textului furnizat.",  # obligatoriu, ≤ 64 000 caractere
    context="…input-ul tău…",                              # opțional, ≤ 256 000 caractere
    system="Răspunde în română.",                          # opțional, ≤ 32 000 caractere
    model=None,                                            # opțional, implicit codai
)
run.result    # str
run.model     # upstream-ul care a servit
run.event_id  # id-ul rândului de usage — îl trimiți la feedback()
run.usage     # dict | None
```

## Feedback [#feedback]

```python
client.feedback(request_id, 1)                    # 👍
client.feedback(request_id, -1, comment="greșit") # 👎, comment ≤ 500 caractere
```

SDK-ul trimite `request_id` ca `event_id`. Voturile alimentează priorurile de rutare pentru toți.

## Embeddings [#embeddings]

```python
vectors = client.embeddings(["salut", "lume"], model="codai-embed", dimensions=None)
vectors[0]  # list[float]
```

Returnează direct o listă de vectori (nu anvelopa brută).

## Audio [#audio]

```python
# Speech-to-text — încărcare multipart, maximum 25 MB. Returnează transcrierea.
with open("clip.webm", "rb") as f:
    text = client.transcribe(f.read(), filename="clip.webm", model="codai-transcribe")

# Text-to-speech — returnează byte-i audio (mp3 implicit).
audio = client.speech("Salut de la codai.", model="codai-tts", voice="alloy")
with open("salut.mp3", "wb") as f:
    f.write(audio)
```

`transcribe()` nu reîncearcă — un body multipart nu poate fi retrimis în siguranță.

## Tokeni efemeri [#tokeni-efemeri]

```python
minted = client.mint_token("realtime", ttl_seconds=600)
minted["token"]       # "codai_eph_v1.…" — îl dai unui client din browser
minted["expires_at"]  # timestamp ISO
minted["scope"]       # "realtime"
```

Scopuri: `"realtime"`, `"audio"`, `"embeddings"`. TTL 60–3 600 s. Vezi [tokeni efemeri](../gateway/embeddings-audio.mdx#tokeni-efemeri).

## Modele [#modele]

```python
for m in client.models():
    print(m["id"], m["codai"]["kind"])  # de ex. "codai alias"
```

Returnează lista `data` din `GET /v1/models`.

## Extensii codai [#extensii-codai]

| Opțiune | Header trimis | Efect |
| --- | --- | --- |
| `session_id` | `X-Codai-Session-Id` | Memorie de sesiune, sticky routing, chitanțe per sesiune. La nivel de client sau per apel. |
| `agent_mode=True` | `X-Codai-Mode: agent` | Buclă plan-and-execute pe gateway (planul Starter sau mai sus). |
| `compact="auto"` | `X-Codai-Compact: auto` | Compactare deterministă server-side a contextului. Doar `chat()`. |
| `best_of=3` / `best_of=0` | `X-Codai-Best-Of` | Forțează sau dezactivează best-of-N. Doar `chat()`. |

Orice altceva — tier-uri de effort, pin-uri de thinking, id-uri de sarcină, dispozitiv, tokeni de partajare — trece prin argumentul `ext={...}` acceptat de fiecare metodă (inclusiv `chat()` și `chat_stream()`), cu chei snake_case numite după header (`effort`, `thinking_pin`, `task_id`, …) și un dict `headers` brut ca portiță de scăpare; vezi [grupuri de resurse](#grupuri-de-resurse) și pagina [header-e](../gateway/headers.mdx).

## Erori [#erori]

```python
from codai import Codai, CodaiError

try:
    client.chat([{"role": "user", "content": "salut"}])
except CodaiError as e:
    print(e.status, e)          # status HTTP (0 pentru eșecuri de rețea după reîncercări)
    code = (e.body or {}).get("error", {}).get("code") if isinstance(e.body, dict) else None
    if code == "quota_exceeded":
        ...  # alocarea de task-uri a planului consumată — aștepți
```

`CodaiError.body` este anvelopa JSON de eroare parsată când gateway-ul a returnat una, altfel `None` sau byte-ii bruți. Valorile stabile de `code` sunt listate la [limite și prețuri](../gateway/limits-pricing.mdx#contractul-de-erori).

## Async [#async]

Nu există un client `asyncio` în 0.1.1. Metodele sunt blocante; într-o aplicație async le învelești cu `asyncio.to_thread`, sau apelezi gateway-ul cu un client HTTP async la alegere — [wire formats](../gateway/wire-formats.mdx) sunt HTTP simplu.

```python
result = await asyncio.to_thread(client.chat, [{"role": "user", "content": "salut"}])
```
