tracelane — Python SDK
Instrumentation for Python AI agents, built on OpenTelemetry. Spans are emitted
via OTLP/HTTP (protobuf) to the endpoint you configure — https://gateway.tracelane.dev
on Tracelane Cloud, or a receiver you run. Instrumentation is explicit — you
choose what to wrap.
Install
pip install tracelane
Fastest path — route through the gateway (no SDK)
For Tracelane Cloud, the shortest path to your first trace needs no SDK at
all: point your existing client's base URL at the gateway and use your
tlane_… key. The gateway routes the call and captures the trace.
import os
from openai import OpenAI
# The gateway is OpenAI-compatible. It mounts /v1/chat/completions and reads the
# key from the `authorization` header — an Anthropic-native client pointed here
# would POST /v1/messages with `x-api-key` and get a 404.
client = OpenAI(
base_url="https://gateway.tracelane.dev/v1",
api_key=os.environ["TRACELANE_API_KEY"], # tlane_… from app.tracelane.dev
)
client.chat.completions.create(
model="claude-sonnet-4-6",
messages=[{"role": "user", "content": "Hello"}],
)
# → Trace visible at https://app.tracelane.dev/traces
That captures the model call. Use this SDK when you want the shape of your agent — the planner step, each tool call, the summariser — as a nested trace rather than one span per model call. It exports OTLP to Tracelane Cloud's gateway, a self-hosted Tracelane ingest, or your own collector.
Sessions — group turns into one conversation
/sessions groups traces by conversation
id. The id travels as the x-conversation-id request header (the gateway also
accepts x-session-id), and it lands on the span as gen_ai.conversation.id.
This SDK is what sets it — an un-sessioned call sends no session header and
carries no conversation id.
Wrap the turn and every call inside the block joins the session:
from tracelane import instrument_openai, use_session
instrument_openai(client)
with use_session(conversation_id):
client.chat.completions.create(model="claude-sonnet-4-6", messages=messages)
client.chat.completions.create(model="claude-sonnet-4-6", messages=follow_up)
# Both calls land in the same session.
use_session is backed by a ContextVar, so overlapping conversations never
bleed into each other across asyncio tasks or threads, and the previous value is
restored on exit — including when the block raises. For a single-conversation
script, set_session(id) sets it without the automatic restore, and
get_session() reads back whichever is active.
Auto-attach covers the three adapters that forward extra_headers:
instrument_openai, instrument_anthropic and instrument_openrouter. For
every other client — including the no-SDK gateway path above — pass the header
yourself:
from tracelane import session_headers
client.chat.completions.create(
model="claude-sonnet-4-6",
messages=messages,
extra_headers=session_headers(conversation_id),
)
session_headers() with no argument returns the active session, or {} when
there is none, so it is always safe to pass. A header you set explicitly always
wins over the ambient session.
A session id must be non-empty visible ASCII, at most 256 characters. Anything else raises at the call you wrote, rather than being dropped in transit and leaving the session silently empty — and it is rejected, never truncated, because a truncated id would split one conversation into two.
SDK quick start (OTLP export)
Your key needs the
ingestscope. Keys minted before scopes existed carry the full API surface and work as-is. A key scoped tochatandreaddoes not — the gateway answers403 insufficient_scopeand namesingestin the body. Tick Ingest in Settings → API Keys; it is on by default for new keys.
Call init() once (endpoint + api_key are required — no env-var auto-read), then
wrap each client. Two ways to wrap:
from tracelane import init, instrument_anthropic
import anthropic
init(
endpoint="https://gateway.tracelane.dev", # or a receiver you run
api_key="tlane_...", # needs the `ingest` scope
service_name="my-agent",
)
client = anthropic.Anthropic()
instrument_anthropic(client) # now client.messages.create() emits spans
client.messages.create(
model="claude-sonnet-4-6",
messages=[{"role": "user", "content": "Hello"}],
max_tokens=128,
)
init() arguments: endpoint (required, an OTLP HTTP receiver you can reach),
api_key (required), service_name (default "unknown-service"), sample_rate
(default 1.0). Call shutdown() on exit to flush pending spans.
Best-effort auto-instrumentation
auto_instrument() wraps a small, fixed set of installed libraries —
anthropic, openai, litellm, langgraph, and claude_code. Each attempt
prints its outcome to stderr. Anthropic/OpenAI only wrap a new default client;
wrap your own clients explicitly. With the langgraph extra, OpenInference
LangChain callbacks capture graph and child runs through the existing exporter.
Inputs/outputs are redacted; custom graph metadata can still be captured. Existing
LangChain instrumentation keeps its configuration. If the optional integration is
unavailable, call instrument_langgraph(graph) after compiling for graph-level
spans. Other libraries need an explicit instrument_* call.
from tracelane import init, auto_instrument
init(endpoint="http://localhost:4318", api_key="tlane_...")
auto_instrument() # prints attachment outcomes; LangGraph includes child runs
Streaming (v1 limitation)
Streamed calls (stream=True) pass through untouched and still produce a
span with model + latency, marked tracelane.streaming = True. Token usage
and finish reason are not captured for streamed responses yet — that
is not implemented. A once-per-process UserWarning says exactly this.
Instrumented libraries
Each library has its own instrument_* function — construct the object, then call
it. auto_instrument() covers only the five above (including LangChain callbacks for LangGraph); the rest are explicit:
LLM providers: instrument_anthropic, instrument_openai,
instrument_openai_async, instrument_azure_openai, instrument_bedrock,
instrument_openrouter, instrument_vertexai, instrument_litellm
Agent frameworks: instrument_langchain, instrument_langgraph,
instrument_llamaindex, instrument_crewai, instrument_autogen,
instrument_pydantic_ai, instrument_openai_agents, instrument_magentic_one,
instrument_smolagents, instrument_haystack
Memory & vector: instrument_pinecone, instrument_qdrant, instrument_mem0,
instrument_letta
Tools & browser: instrument_browserbase, instrument_e2b,
instrument_firecrawl, instrument_composio, instrument_mcp,
instrument_claude_code
Each activates only if the corresponding package is installed.
Custom spans
The SDK sets up a standard OpenTelemetry tracer provider, so custom spans use the OTel API directly:
from opentelemetry import trace
tracer = trace.get_tracer("my-agent")
with tracer.start_as_current_span("retrieval") as span:
span.set_attribute("retrieval.top_k", 10)
results = vector_store.search(query, top_k=10)
Sampling
import os
from tracelane import init
init(
endpoint="http://localhost:4318",
api_key="tlane_...",
sample_rate=0.1 if os.getenv("ENV") == "production" else 1.0,
)
Design invariants
- Telemetry goes to your configured
endpointonly — the SDK never calls home. wrapt-based monkey-patch;instrument_*wraps a client without changing your call sites.- Prompt and completion text is not captured, and there is no switch you need
to set. The SDK never sends it, and the gateway stores it only for tenants an
operator has explicitly allowlisted in its own
tracelane.yaml— off for everyone otherwise. Earlier revisions of this file told you to setTRACELANE_TRACE_CONTENT=false; no component has ever read that variable, so the instruction was inert and the honest statement is the stronger one. - No dependency on
litellmorarize-phoenix.
Documentation
Full docs at docs.tracelane.dev/sdk-python.
Stack
Python 3.12+, Pydantic v2, Ruff (lint + format), pytest + pytest-asyncio.
License
Apache 2.0 — see LICENSE.
Metadata
Release files for tracelane 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| tracelane-0.4.0.tar.gz | 49.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| tracelane-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 110.7 kB
Release files / tracelane-0.4.0.tar.gz
| Download URL | tracelane-0.4.0.tar.gz |
|---|---|
| Size | 49.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
42cf00063cfd25c9989bada07bd466d3af814cd15e417896c6e3e02d05ff3d90
|
|
BLAKE2b-256 checksum How to use checksums |
f5aec3d31b4af6db27221827e8b7d89e9c057311fd3c90537e2f6dffb7ea26bd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.
Transparency logRelease files / tracelane-0.4.0-py3-none-any.whl
| Download URL | tracelane-0.4.0-py3-none-any.whl |
|---|---|
| Size | 61.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9853d9ff6aaf377a7033f9c14c69f21f9d7f943480329193bb6e5b51fe736ca1
|
|
BLAKE2b-256 checksum How to use checksums |
112f4371049c8ebdb33762f654cd50bad2ad4471e80339d5c97a9fd1cf9e3784
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.
Transparency log