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 exit

This 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

ParameterEnv VarDefaultDescription
api_keyTWOSIGNAL_API_KEYYour project API key
base_urlTWOSIGNAL_BASE_URLhttps://api.2signal.devAPI endpoint
enabledTWOSIGNAL_ENABLEDTrueEnable/disable all tracing
flush_interval1.0Seconds between background flushes
max_batch_size100Max 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:

FieldDescription
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

TypeUse forExample
AGENTTop-level agent functionYour main agent entrypoint
LLMLLM API callsOpenAI, Anthropic, etc.
TOOLTool / function callsWeb search, calculator, API call
RETRIEVALRAG / vector searchPinecone, Chroma, Weaviate
CHAINPipeline stepsPrompt template, output parser
CUSTOMEverything elseDefault 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"] = result

Disabling Tracing

Disable tracing without changing your code:

# via constructor
ts = TwoSignal(enabled=False)

# via environment variable
export TWOSIGNAL_ENABLED=false

When 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.

Have questions? Join our community!

Connect with other developers and the 2Signal team.

Join Discord