Metadata-Version: 2.4
Name: to11ai-sdk
Version: 2.0.0rc0
Summary: Python SDK for the to11.ai platform
Project-URL: Homepage, https://to11.ai
Project-URL: Repository, https://github.com/to11ai/platform
License-Expression: MPL-2.0
License-File: LICENSE
Requires-Python: >=3.10
Requires-Dist: pydantic>=2.13
Description-Content-Type: text/markdown

# to11ai-sdk

Python SDK for [to11.ai](https://to11.ai) — the AI engineering platform for prompt management, tracing, and evals.

Use it to **fetch managed prompts** at runtime, **wire your provider client** (OpenAI, Anthropic, …) to the to11 gateway with the right auth/trace headers, and group model calls into **sessions, conversations, and turns** for tracing. Synchronous, snake_case, Python 3.10+. Only runtime dependency: Pydantic.

## Installation

```bash
pip install to11ai-sdk
# or: uv add to11ai-sdk  ·  poetry add to11ai-sdk
```

The SDK's only runtime dependency is Pydantic. The provider client libraries used in the examples below (`openai`, `anthropic`) are **not** dependencies — install whichever you use yourself, e.g. `pip install openai`.

## Quick start

Create a client, fetch a prompt for the current environment, and send it through the gateway with your normal provider client:

```python
import os
from to11ai_sdk import create_client
from openai import OpenAI

to11 = create_client(
    # Each also resolves from an env var, so on a server you can pass nothing:
    base_url=os.environ.get("TO11_API_URL"),        # -> https://api.to11.ai
    api_key=os.environ.get("TO11_API_KEY"),         # -> TO11_API_KEY
    project_id=os.environ.get("TO11_PROJECT_ID"),   # -> TO11_PROJECT_ID
    env=os.environ.get("TO11_ENV"),                 # "prod", "staging", … -> TO11_ENV
)

# Fetch a released prompt for the client's project + environment, shaped for OpenAI.
prompt = to11.prompts.render("welcome-message", variables={"name": "Ada"}, format="openai")

# Point your provider client at the gateway and send it.
openai = OpenAI(**to11.openai_options())
completion = openai.chat.completions.create(
    **prompt["config"],
    messages=prompt["messages"],
    extra_headers=to11.turn().headers(prompt),  # auth + trace id + prompt provenance
)
```

`prompt["config"]` carries the model parameters, `prompt["messages"]` the rendered messages, and `turn().headers(prompt)` the headers that authenticate the gateway call and record which prompt produced the trace.

## Configuring the client

`create_client(...)` takes keyword-only args. Each URL/credential resolves **option → environment variable → built-in default**, so a server app can construct it from the environment alone:

| Argument | Resolves from | Default |
| --- | --- | --- |
| `base_url` | option → `TO11_API_URL` | `https://api.to11.ai` |
| `gateway_url` | option → `TO11_GATEWAY_URL` | `https://gw.to11.ai` |
| `api_key` | option → `TO11_API_KEY` | — (required) |
| `project_id` | option → `TO11_PROJECT_ID` | — |
| `env` | option → `TO11_ENV` | — (lowercase-kebab, 1–63 chars) |
| `provider_api_key` | option | — (BYOK; carried by `openai_options()`/`anthropic_options()`) |
| `format` | option | — (`"openai"` \| `"anthropic"`; omit for the neutral result) |
| `on_unknown_role` | option | `"drop"` |
| `timeout_s` / `max_retries` | option | `30.0` / `3` |
| `behavior_overrides` | option | `None` |

The client exposes the resolved values as read-only attributes — **`to11.base_url`**, **`to11.gateway_url`**, **`to11.api_key`**, **`to11.project_id`**, and **`to11.env`**. Use the non-secret ones (`base_url`/`gateway_url`/`project_id`/`env`) for diagnostics; `api_key` is exposed to build manual auth headers — treat it as a secret and never log it. `provider_api_key` (BYOK) is deliberately **not** exposed back off the client.

## Fetching prompts

`prompts.render(slug, ...)` resolves the released prompt for the client's `project_id` + `env` in one request. Set `format` (on the client or per call) to get a result shaped for that provider; omit it for the provider-neutral result.

```python
prompt = to11.prompts.render(
    "weather-concierge",
    variables={"city": "New York", "units": "fahrenheit"},
    # label=...      # override the client's environment for this call
    # subject=...    # a stable id so a weighted release pins the same caller to a variant
    # fallback=...   # a previously-rendered RenderedPrompt served on a network error
    # format="openai" | "anthropic"
    # on_unknown_role="drop" | "warn" | "error"
)
```

- **With `format`** the result is spread-ready: `{"messages": ..., "config": ..., "metadata": ...}` (`config` = the version's model params; `metadata` = prompt/version/release provenance). For Anthropic it also carries a top-level `"system"`.
- **Without `format`** you get the neutral `RenderedPrompt`: `.messages`, `.tools`, `.tool_choice`, `.prompt_id`, `.version`, `.version_id`, `.release_id`, `.variant_name`, `.content_hash`, `.label`, `.slug`, `.block_render_record`, `.model_config_` (trailing underscore avoids Pydantic's reserved name).

Authored `developer` blocks come back as-is; the SDK maps them per provider (OpenAI keeps `developer`; Anthropic folds `system`/`developer` into the top-level `system`). `on_unknown_role` controls how an unrecognized role is handled during provider conversion.

## Sending through the gateway

`openai_options()` / `anthropic_options()` return spread-ready kwargs (`base_url`, `api_key`, `default_headers`) for a provider client pointed at the gateway; `default_headers` carries the static tenant auth. Add `turn().headers(prompt)` per call for the trace id and prompt provenance:

```python
from anthropic import Anthropic
anthropic = Anthropic(**to11.anthropic_options())
```

By default the gateway runs the call on the project's configured provider credential (managed). For **BYOK**, set `provider_api_key` on `create_client`.

## Sessions, conversations & turns

Group model calls for tracing. `to11.turn()` uses the client's default session; create explicit ones with `to11.session(id=None)` / `to11.conversation(id=None)`. `turn().headers(prompt=None)` emits the session/conversation ids + a fresh `traceparent` (plus prompt provenance when a rendered/shaped prompt is passed) — for the gateway call you make. (This is separate from the SDK's OTel-based traceparent on its own control-plane requests.)

## Managing prompts, versions & labels

Beyond `render()`, the `prompts` namespace covers the control-plane lifecycle. **All of these default `project_id` from the client** — pass it per call only to override:

```python
to11 = create_client(api_key="...", project_id="proj_abc123")

to11.prompts.list()                                        # uses the bound project_id
to11.prompts.get(prompt_id="prompt_1")
to11.prompts.get_version(prompt_id="prompt_1", version_number=3)
to11.prompts.move_label(prompt_id="prompt_1", label="production", version_id="ver_9", reason="promote")
```

Full set: `create`, `list`, `get`, `update`, `archive`, `create_version`, `list_versions`, `get_version`, `move_label`, `list_labels`, and the project-label registry (`list_project_labels`, `create_project_label`, `get_project_label`, `update_project_label`, `delete_project_label`, `list_project_label_usages`, `list_label_events`). `to11.project_environments` and `to11.routing_rules` expose the environment and routing-rule surfaces.

`update_project_label(description=None)` means "leave unchanged"; pass `description=""` to clear it.

## Error handling

`To11aiError` is the base; catch it broadly or match a subclass:

```python
from to11ai_sdk import To11aiRateLimitError, PromptNotFoundError

try:
    prompt = to11.prompts.render("welcome-message")
except To11aiRateLimitError as exc:
    ...  # exc.retry_after_seconds
except PromptNotFoundError:
    ...
```

| Class | When |
| --- | --- |
| `To11aiApiError` | non-2xx API response (`status_code`, `error_code`, `details`) |
| `To11aiAuthError` | 401/403 |
| `To11aiRateLimitError` | 429 (`retry_after_seconds`) |
| `To11aiValidationError` | 400 |
| `To11aiNetworkError` | network failure / timeout |
| `PromptNotFoundError` · `LabelNotFoundError` · `MissingLabelError` · `NoActiveReleaseError` · `PromptPolicyViolationError` · `PromptLabelNotRegisteredError` | typed prompt-resolution errors |
| `UnknownRoleError` | an unrecognized message role with `on_unknown_role="error"` |
| `To11aiResponseValidationError` | a 2xx body that fails schema validation |

## Documentation

Full reference: <https://docs.to11.ai/reference/python-sdk>. Licensed under MPL-2.0.
