This release is a pre-release and may not be stable for production use.
to11ai-sdk
Python SDK for to11.ai — the AI engineering platform for prompt management, tracing, and evals.
Use it to render 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
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.
Upgrading from 1.x
2.0 reshapes prompt access around a single render() call and trims the public surface to what we keep stable. prompts.resolve() is gone, the per-call env argument is now label, and routing rules are no longer exported. See Migrating from 1.x to 2.0 for the full list of renames and removals.
Quick start
Create a client, render a prompt for the current environment, and send it through the gateway with your normal provider client:
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. config["model"] is sent as written, and the hosted gateway needs it to name a provider — set the prompt's model as <provider-slug>::<model> (for example openai::gpt-4o, where openai is the provider's routing identifier in to11).
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.
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
formatthe 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
formatyou get the neutralRenderedPrompt:.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:
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:
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 exposes the project-environments registry.
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:
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://to11.ai/docs/reference/python-sdk. Licensed under MPL-2.0.
Metadata
Release files for to11ai-sdk 2.1.0rc0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| to11ai_sdk-2.1.0rc0.tar.gz | 123.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| to11ai_sdk-2.1.0rc0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 178.4 kB
Release files / to11ai_sdk-2.1.0rc0.tar.gz
| Download URL | to11ai_sdk-2.1.0rc0.tar.gz |
|---|---|
| Size | 123.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a6d4201f75a0b6fe16719ba0d1f14923c14bff49492dcc98b70a84c1b756f3a9
|
|
BLAKE2b-256 checksum How to use checksums |
c4ca7961408aeea61eba9a421b3d21958ca5fb46f0294f10568115d9c5313b1e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / to11ai_sdk-2.1.0rc0-py3-none-any.whl
| Download URL | to11ai_sdk-2.1.0rc0-py3-none-any.whl |
|---|---|
| Size | 55.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9b31b47a55bd99dc0c52e7273dd3ccc7047f33773e613891ff9679ced39f451c
|
|
BLAKE2b-256 checksum How to use checksums |
b498970a0540e1420d11ff1e5d11abe865bf4a714452d9e9daf4519018afa706
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|