Python SDK
The Kelet Python SDK instruments your agent code to send traces and signals. It wraps LLM calls with OpenTelemetry-compatible spans, handles session context automatically, and exposes a signal() API for capturing user feedback. A single kelet.configure() call at startup is all most frameworks need.
pip install kelet | Requires Python ≥3.10
Install
Section titled “Install”bash pip install kelet bash uv add kelet API keys
Section titled “API keys”Two keys — never mix them:
- Secret key (
sk_...): server-only. Use this inkelet.configure(). Never expose it in frontend code. - Publishable key (
pk_...): frontend-safe, for the React SDK only. Cannot send traces.
Get both from Settings → API Keys in the console.
kelet.configure()
Section titled “kelet.configure()”Call once at startup, before any agent code runs. Kelet auto-configures supported integrations whether it creates the global TracerProvider or attaches to one that already exists.
import kelet
kelet.configure( api_key="...", # or set KELET_API_KEY env var project="my-agent", # required; can override per session with agentic_session(project=...))| Parameter | Type | Description |
|---|---|---|
api_key | str | API key. Defaults to KELET_API_KEY env var |
project | str | Required. Project name. Set here or via KELET_PROJECT env var. |
base_url | str | API URL. Defaults to KELET_API_URL env var |
auto_instrument | bool | Auto-configure detected integrations. Default True |
span_processor | SpanProcessor | Optional. Use this processor instead of Kelet’s default (for wrapping/filtering) |
additional_span_processors | Sequence[SpanProcessor] | Optional. Extra processors to attach when creating a new TracerProvider |
strict | bool | Optional. If True, raise when credentials are missing. Default False (log warning and disable telemetry) |
Supported integrations: pydantic-ai, Anthropic SDK, OpenAI SDK, LangChain/LangGraph, LiteLLM, and Google ADK (OpenInference instrumentation preferred, falls back to ADK’s native OTEL spans).
Install extras to pick up instrumentation: kelet[anthropic], kelet[openai], kelet[langchain], kelet[google-adk], or kelet[all] for everything.
Integrating with existing OTEL setups
Section titled “Integrating with existing OTEL setups”If another tool (logfire, custom TracerProvider) owns the global provider, configure() attaches to it automatically. For setups where you build the provider yourself, use create_kelet_processor():
import logfireimport kelet
logfire.configure(additional_span_processors=[kelet.create_kelet_processor()])logfire.instrument_pydantic_ai()kelet.configure() # sets config for signal() + auto-instrumentationkelet.agentic_session()
Section titled “kelet.agentic_session()”Groups all LLM calls and tool uses that belong to one unit of work. Required when you own the orchestration loop. Skip for pydantic-ai, LangChain, or LangGraph — they set sessions automatically.
Works as both a context manager and a decorator (sync and async).
Context manager:
async with kelet.agentic_session(session_id="abc123", user_id="user-1"): result = await agent.run(...)Decorator:
@kelet.agentic_session(session_id="abc123")async def handle_request(): return await agent.run(...)| Parameter | Type | Description |
|---|---|---|
session_id | str | Required. Unique ID for this unit of work |
user_id | str | Optional. Associates the session with a user |
project | str | Optional. Overrides the project set in configure(). Propagates via W3C baggage |
**kwargs | Any | Optional. Extra metadata stamped as metadata.{key} on every span in the session |
Streaming: wrap the entire generator body, including the final sentinel. Trailing spans are silently lost if the session exits before the stream finishes.
async def stream_response(session_id: str): async with kelet.agentic_session(session_id=session_id): async for chunk in llm.stream(...): yield chunkkelet.agent()
Section titled “kelet.agent()”Names an agent within a session for multi-agent attribution. Use when multiple agents run in one session, or when your framework doesn’t expose agent names (pydantic-ai does; raw OpenAI/Anthropic SDK calls don’t). Creates an explicit OTEL span (gen_ai.operation.name=invoke_agent) — all LLM calls inside become children of that span.
Works as a sync/async context manager and as a decorator.
async with kelet.agent(name="retriever"): docs = await fetch_docs(query)
@kelet.agent(name="responder")async def respond(question): return await anthropic_client.messages.create(...)| Parameter | Type | Description |
|---|---|---|
name | str | Required. Agent name for attribution |
kelet.signal()
Section titled “kelet.signal()”Sends a signal. Must be awaited. Call inside agentic_session() — session ID resolves from context automatically.
await kelet.signal( kind=kelet.SignalKind.FEEDBACK, source=kelet.SignalSource.HUMAN, trigger_name="user-vote", score=1.0,)| Parameter | Type | Description |
|---|---|---|
kind | SignalKind | FEEDBACK, EDIT, EVENT, METRIC, or ARBITRARY |
source | SignalSource | HUMAN, LABEL, or SYNTHETIC |
trigger_name | str | Optional. Use source-action format: user-vote, user-edit |
score | float | Optional. 0.0–1.0. For votes: 1.0 = positive, 0.0 = negative |
value | Any | Optional. Feedback text, diff, or any JSON-serializable payload |
confidence | float | Optional. 0.0–1.0. How confident the source is in the signal |
metadata | dict[str, Any] | Optional. Arbitrary key/value payload attached to the signal |
timestamp | datetime | Optional. Event timestamp. Defaults to “now” server-side |
session_id | str | Optional. Resolved from context if omitted |
trace_id | str | Optional. Resolved from the active span if omitted |
raise_on_failure | bool | Re-raise transport/HTTP errors after retries. Default False |
Transport failures
Section titled “Transport failures”Transport and HTTP errors are retried with exponential backoff (3 attempts). By default, failures after the final attempt are logged via logging and swallowed — signal() is a telemetry call and won’t crash your code path. Pass raise_on_failure=True to opt into re-raising after retries are exhausted. Validation errors (bad score / confidence ranges, missing identifier) always raise.
kelet.shutdown()
Section titled “kelet.shutdown()”Flushes any pending spans and releases SDK resources. Registered automatically via atexit, so most apps never need to call it directly. Use it manually before a hard os._exit(0) or in tests that need to force a drain.
kelet.shutdown()Errors from individual processors are logged and swallowed (best-effort).
Context helpers
Section titled “Context helpers”kelet.get_session_id() # current session IDkelet.get_trace_id() # current trace ID from active spankelet.get_user_id() # current user IDkelet.get_agent_name() # current agent name (set by kelet.agent())kelet.get_metadata_kwargs() # metadata kwargs dict (empty if unset)Returns None (or {} for get_metadata_kwargs) if called outside agentic_session() / agent().
Environment variables
Section titled “Environment variables”| Variable | Description |
|---|---|
KELET_API_KEY | API key (required if not passed to configure()) |
KELET_PROJECT | Required project name. SDK throws at startup if missing. |
KELET_API_URL | API base URL |