Python SDK
The complete Python surface — every exported name, its signature, and where the behaviour is documented in full.
On this page
The complete public surface of tokentriage for Python, in one place. Behaviour is documented on
the topic pages; this is the list of what exists and what each call takes.
import tokentriage as tt
Requires Python 3.10 or newer. Local unreleased version 1.0.0rc2, contract version 1, zero runtime dependencies.
Configured provider client
The unreleased tokentriage.client.TokenTriage accepts base_url, api_key
and mode="managed" | "self_hosted". It reads TOKENTRIAGE_BASE_URL and
TOKENTRIAGE_API_KEY when arguments are omitted.
Use openai() or async_openai() to construct official provider clients.
openai_options() and async_openai_options() support your own constructor
and borrowed HTTP transport. metadata(response) and error_details(error)
read information tied to that response or error.
Those four, plus native() and async_native(), take the saved-mode binding:
gateway_id (required), environment_id, base_url, credential_source and
provider_api_key. Supplying gateway_id selects saved mode, where api_key
becomes the Authorization bearer and the gateway and environment selector
headers are stamped for you; send the literal "gateway" as the model.
provider_api_key needs credential_source="request_supplied", and a provider
key passed without it raises ConfigurationError naming
provider_key_not_allowed before a request is built.
Start with a routed request and async and streaming examples.
Guardrail refusals
A gateway that refuses a request on a guardrail answers 403 for a policy block and 503 for a
guard whose detector could not run, with the typed refusal at the top level of the body and
x-tt-guardrail: blocked | unavailable on the response. The SDK-owned transport raises it as its
own exception rather than handing back a response the provider client would read as an empty error.
GuardrailRefusal (from tokentriage.client), raised by openai(), async_openai() and
native() clients:
from tokentriage.client import GuardrailRefusal
try:
client.chat.completions.create(model="gateway", messages=messages)
except GuardrailRefusal as refusal:
print(refusal.type, refusal.binding, refusal.category, refusal.phase, refusal)
Every failure that is not a guardrail refusal keeps its own provider class and propagates, so a broken route cannot be mistaken for refused content.
| Attribute | What it says |
|---|---|
type |
"guardrail_blocked" or "guardrail_unavailable"; None mid-stream |
binding |
the guardrail binding that acted |
category |
pii, secrets, injection, … when the refusal reached one |
phase |
input, output or stream |
rule_ref |
the policy rule that fired — a block only |
reason |
which detector was suspended — an unavailable refusal only |
status |
403 or 503; None mid-stream |
disclosure |
the x-tt-guardrail value, when the response carried it |
mid_stream |
the refusal was read from stream bytes, not from a response |
response |
the raw response, for the safe routing headers; None mid-stream |
rule_ref and reason are never both set, and neither is ever filled in for the other: a block
means “do not retry this content”, an unavailable guard means “retry”. refusal.blocked and
refusal.unavailable are the two predicates, and str(refusal) is a sentence naming what acted.
It carries no credential, no header value and no request content.
guardrail_refusal(error) narrows any exception to a GuardrailRefusal or None, including
one raised by a borrowed transport this SDK did not wrap. stream_guardrail_refusal(data) reads
a mid-stream refusal out of relayed SSE bytes: that case is not an error at all, just a terminal
delta carrying finish_reason: "content_filter" (exported as STREAM_FINISH_REASON) and no
explanation, because a refusal that said why would be an oracle for the detector that fired. The
full disclosure is on the request’s row, never on the wire.
TypeScript cannot raise its own class here and narrows instead; see parity.
Configuration
init(base_url=None, disabled=None, log=None, emit_traceparent=False, tier_header=None, admin_url=None, exporters=(), *, agent=None, user=None, tenant=None, feature=None, tags=None)
Installs the process configuration. Resolution is argument, then environment, then default. Invalid instrumentation configuration raises TokenTriageConfigError. The
five keyword-only dimension arguments form the lowest precedence layer.
check(admin_url=None, *, probe=True) → CheckReport
The first-run doctor. Lints configuration and probes the admin plane only, not the data plane. Prints its report and returns it. See install and configure.
Scopes
trace(agent=None, user=None, tenant=None, feature=None, tags=None, trace_id=None)
step(agent=None, step_class=None, user=None, tenant=None, feature=None, tags=None, parent=None, step_id=None)
Both are context managers and decorators. step_class must be user_turn, tool_loop or
provider_state. parent accepts a step id or a Step, and is the only identity override.
observe(agent=None, step_class=None, user=None, tenant=None, feature=None, tags=None, parent=None)
Decorator sugar over step. No TypeScript equivalent.
current_step() → Step | None
The innermost open explicit step, not an adapter’s implicit one.
Behaviour: traces, steps, and context.
Wire
headers(**overrides) → dict[str, str]
The resolved x-tt-* map. Override keys are agent, tier, cache, user, tenant,
feature and tags; tier and cache are accepted here only. Overrides are ephemeral. Returns
{} when disabled.
receipt(source, status=None) → Receipt
Parses a response, an error response, or a plain header mapping. Never raises. Returns an
all-absent receipt rather than None on any failure.
last_receipt() → Receipt | None
The most recent receipt on the nearest open scope, step before trace. Outside any scope, None
plus TT-SDK-013.
Behaviour: from your call to the wire and reading the receipt.
Adapters
| Call | Wraps |
|---|---|
httpx_client(*, origins=None, base_url=None, **kwargs) |
a new httpx.Client |
async_httpx_client(...) |
a new httpx.AsyncClient |
instrument_httpx(client, ...) |
an existing httpx client, in place |
instrument_requests(session, ...) |
a requests session |
from tokentriage.adapters.langchain import TTCallbackHandler |
LangChain / LangGraph runs |
from tokentriage.adapters.crewai import instrument_crew |
a CrewAI crew |
from tokentriage.adapters import otel → otel.bridge() |
OpenTelemetry span ids |
Each needs its extra installed. Importing one patches nothing.
Behaviour: adapters.
Ledger
from tokentriage import ledger
All synchronous. All use the credential-containment protocol and read the token from
TOKENTRIAGE_ADMIN_TOKEN only.
ledger.fetch(receipt_or_req_id, admin_url=None, wait_s=0.0) → RowView
ledger.fetch_trace(receipt_or_trace_id, admin_url=None, wait_s=0.0) → TraceView
ledger.feedback(receipt_or_req_id, ok, note=None, admin_url=None) → FeedbackAck
ledger.outcomes(receipt_or_req_id, metrics, source, evaluator, eval_ver, labels=None, admin_url=None, basis=...) → OutcomeAck
metrics is a non-empty sequence of {"metric": str, "value": float, "pass": bool} mappings,
where value is required and pass is optional. basis is accepted and never sent.
Errors: PlaneCollision, AdminAuthRequired, NotFound, LedgerError. feedback is the one
call that is not shielded and raises on failure.
Behaviour: fetching a costed row and outcomes and feedback.
Types
Receipt, Cost, Rewrite, CacheInfo, Refusal, Trace, Step, StepRecord, Exporter,
TokenTriageConfigError, plus CheckReport and CheckFinding.
All receipt types are immutable. Cost has no public constructor.
Receipt carries request_id, mode, decision, model_rewrite, cache, refusal, cost,
extra and status_code, plus the cost_display property and dashboard_url(admin_url=None).
Exporter
register_exporter(exporter) installs an object with on_step_end(record). Survives a later
init().
DebugExporter() prints one line per closed step, locally, with no network:
[tt] step trace=b2728443b6c6bfb8b6029a4bcb24debe step=66fcd77192c62295 parent=None agent=planner class=user_turn status=ok dur_us=5.0 receipts=0
An exporter that raises cannot break a scope. The default registry is empty.
Testing
from tokentriage.testing import RecordingTransport, assert_tt_headers
Needs httpx. See testing instrumented code.
Management
from tokentriage.management import Client
A separate, generated client for the admin API, on its own contract version. Not re-exported from the package root. See the management client.
What is imported when
Importing tokentriage binds the scopes, the wire calls, the types and the exporter. Everything
else resolves on first use: ledger, check, testing and the adapters.
That is why import tokentriage opens no socket. The module that can open one is not imported
until you call something that needs it, which also means an uninstalled extra surfaces its error
at first use with a message naming what to install, rather than at import.