Python SDK
The 2Signal Python SDK instruments your AI agents with automatic tracing, token counting, and cost tracking. Python 3.9+. Two dependencies (httpx, pydantic).
Installation
# core SDK
pip install twosignal
# with LLM provider wrappers
pip install twosignal[openai]
pip install twosignal[anthropic,google]
# with CLI or TUI
pip install twosignal[cli]
pip install twosignal[tui]Quick Start
from twosignal import TwoSignal, observe
from twosignal.wrappers.openai import wrap_openai
from openai import OpenAI
ts = TwoSignal() # reads TWOSIGNAL_API_KEY from env
client = wrap_openai(OpenAI()) # auto-trace every LLM call
@observe
def support_agent(query: str) -> str:
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": query}],
)
return response.choices[0].message.content
# run your agent — trace appears in the dashboard
answer = support_agent("What is your refund policy?")
ts.shutdown() # flush remaining events before exitThis creates a trace with two spans: an AGENT span for support_agent and a child LLM span for the OpenAI call — with model, tokens, cost, input, and output all captured automatically.
Initialization
from twosignal import TwoSignal
ts = TwoSignal(
api_key="ts_...", # or TWOSIGNAL_API_KEY env var
base_url="https://...", # or TWOSIGNAL_BASE_URL env var
enabled=True, # set False to disable all tracing
flush_interval=1.0, # seconds between batch flushes
max_batch_size=100, # max events per batch
)The SDK is a singleton — only one instance should be created. It starts a background daemon thread that batches and flushes events. The thread exits automatically when your process exits.
Configuration
| Parameter | Env Var | Default | Description |
|---|---|---|---|
api_key | TWOSIGNAL_API_KEY | — | Your project API key |
base_url | TWOSIGNAL_BASE_URL | https://api.2signal.dev | API endpoint |
enabled | TWOSIGNAL_ENABLED | True | Enable/disable all tracing |
flush_interval | — | 1.0 | Seconds between background flushes |
max_batch_size | — | 100 | Max events per HTTP request |
Methods
ts.span()
Context manager for creating a span. Spans nest automatically — inner spans become children of outer spans.
with ts.span(name="retrieve-docs", span_type="RETRIEVAL") as ctx:
docs = vector_db.search(query)
ctx["output"] = docs
ctx["metadata"] = {"db": "pinecone", "top_k": 5}You can set any of these fields on the context object:
| Field | Description |
|---|---|
ctx["input"] | Span input (any JSON-serializable value) |
ctx["output"] | Span output |
ctx["metadata"] | Arbitrary metadata dict |
ctx["model"] | Model name (for LLM spans) |
ctx["usage"] | {"prompt_tokens": N, "completion_tokens": N} |
ctx["cost"] | Cost in USD (float) |
If no active trace exists, one is created automatically. If an exception occurs inside the span, it's recorded and re-raised.
ts.flush()
Force-flush all pending events to the API immediately. Useful in serverless environments or before logging results.
ts.flush()ts.shutdown()
Gracefully shut down the client: flushes remaining events, stops the background thread, and closes the HTTP connection. Call this before your process exits.
ts.shutdown()In long-running services (web servers, workers), you typically don't need to call shutdown() — the daemon thread exits automatically when the process does.
Span Types
| Type | Use for | Example |
|---|---|---|
AGENT | Top-level agent function | Your main agent entrypoint |
LLM | LLM API calls | OpenAI, Anthropic, etc. |
TOOL | Tool / function calls | Web search, calculator, API call |
RETRIEVAL | RAG / vector search | Pinecone, Chroma, Weaviate |
CHAIN | Pipeline steps | Prompt template, output parser |
CUSTOM | Everything else | Default if not specified |
Background Flush Behavior
The SDK queues events in memory and flushes them to the API in the background. This means:
- Zero latency impact — your agent code never waits for API calls
- Batched requests — events are grouped into a single HTTP POST (up to
max_batch_size) - Automatic retry — failed flushes are retried on the next interval
- Graceful degradation — if the API is unreachable, events are dropped silently (your agent keeps running)
Error Handling
The SDK never throws. All internal errors are logged and swallowed. If the SDK fails to create a span or flush events, your agent code continues running normally. Exceptions raised by your code are always re-raised — the SDK only catches its own errors.
# safe — even if 2Signal is down, your agent works
with ts.span(name="my-step") as ctx:
result = do_work() # if this throws, the exception propagates normally
ctx["output"] = resultDisabling Tracing
Disable tracing without changing your code:
# via constructor
ts = TwoSignal(enabled=False)
# via environment variable
export TWOSIGNAL_ENABLED=falseWhen disabled, all SDK methods become no-ops with zero overhead.
Thread Safety
The SDK uses thread-local storage for trace context, so spans nest correctly even in multi-threaded applications. Each thread gets its own trace context. The background exporter is thread-safe.