Types and errors
The exported dataclasses — Config, TokenEvent, AgentContext — field by field, plus BudgetExceededError and the one thing to know about catching it.
Four exported types. Three are plain dataclasses you can type-hint against; the fourth is the only exception the SDK raises on purpose.
Config
The configuration init() builds. You rarely construct one, but you may want to
read it, and three field names differ from the init() keyword that sets them:
init() keyword | Config field |
|---|---|
agent_name | default_agent |
team | default_team |
gateway | default_gateway |
Every other field matches its keyword: api_key, endpoint, env,
flush_interval_ms, enable_routing, routing_rules,
routing_refresh_interval, critical_task_types, enable_budget_enforcement,
budget_check_timeout, enable_prompt_analysis, prompt_size_threshold,
on_oversized_prompt, debug.
endpoint is normalised on construction — a trailing slash is stripped — so the
value you read back may differ by one character from the value you passed.
TokenEvent
One captured LLM call, and the unit the backend ingests.
Identity
| Field | Notes |
|---|---|
id | UUID for the event. |
timestamp | ISO-8601, UTC. |
request_id | Separate UUID used as the deduplication key, so a retried batch cannot double-count. |
The call
| Field | Notes |
|---|---|
model | What actually ran. |
provider | "anthropic", "openai", "google", … |
input_tokens, output_tokens | Reported by the provider, not estimated. |
cache_read_tokens, cache_write_tokens | Prompt-cache traffic, priced at their own rates. |
cost_usd | Computed in the SDK from the pricing catalogue. |
latency_ms | End-to-end. |
caller_file | The file and line in your code that made the call, resolved past framework internals. |
Attribution
| Field | Notes |
|---|---|
agent_id | Defaults to "unknown" rather than being dropped. |
env | From init(env=…). |
team_id, task_type, session_id, customer_id, cost_center | From the scope in force. |
user_id | Reserved; not set by the SDK. |
gateway | The gateway the call rode through, or None for a direct call. Orthogonal to provider. Added in 0.4.0. |
Routing
| Field | Notes |
|---|---|
was_routed | Whether a rule fired. |
original_model | What was requested, when model is not it. |
Errors
| Field | Notes |
|---|---|
is_error | The provider call raised instead of returning. |
error_type | The exception class name. |
A failed call still costs money at some providers and always costs latency, so it is recorded rather than dropped.
Prompt analysis
Set only with enable_prompt_analysis=True, and never containing prompt text:
system_prompt_hash, system_prompt_tokens, message_count,
conversation_tokens, has_few_shot, few_shot_pairs, tool_result_tokens,
conversation_turns, context_window_utilization.
Methods
to_dict() returns a JSON-serialisable dict with None values omitted.
Values are clamped on construction
Every event normalises itself into the backend's ingest contract as it is built:
string fields truncate (model, provider, and gateway at 64 characters;
agent_id, team_id, task_type, customer_id, cost_center, and env at
128; caller_file at 512), counters floor at zero, and
context_window_utilization clamps into 0–1.
This is not cosmetic. The backend parses a whole ingest batch as one model, so a
single out-of-range field would reject every event in that flush — and the
emitter would retry the same payload and then drop all of it. A 200-character
customer_id costs you the end of one string instead of a batch of events.
AgentContext
The scope object pushed by agent(), tag(), and the framework helpers. Every
field is optional: agent, team, task_type, session_id, customer_id,
cost_center.
Scopes live in contextvars, so each thread and each asyncio task sees its own
stack. Reading one directly is rarely necessary; it is exported so you can
type-hint code that builds attribution generically.
BudgetExceededError
class BudgetExceededError(RuntimeError)
Raised before the provider call when the pre-call budget check returns a blocking decision. The message comes from the backend and names the budget.
It subclasses RuntimeError, which is the detail worth knowing: a broad
except Exception around your LLM call will swallow enforcement and turn a
deliberate block into a silent failure. Catch it explicitly if you want to
degrade instead.
try:
response = client.messages.create(...)
except capsera.BudgetExceededError as exc:
return fallback_response(str(exc))
This is the only exception the SDK raises deliberately. Everything else — delivery failure, an unreachable backend, an unparseable response, a broken gateway install — degrades to missing telemetry. See Reliability.