Python SDK

Core Tracing API

Learn how to manually trace operations, handle concurrency, and capture span payloads.

The core tracing API allows you to manually structure traces and spans to observe any Python execution flow, independent of any specific agent framework.


Traces

A Trace represents a single logical request or workflow run. It is the root node containing all child tool executions and LLM calls.

infralo.start_trace()

Start a trace context. This returns a Trace object and sets it as the active trace inside a ContextVar.

with infralo.start_trace(session_id="user-123") as trace:
    # Operations run here are linked to this trace
    print(f"Active Trace ID: {trace.trace_id}")
  • session_id (optional): Group multiple traces under a single user session for end-to-end user path analysis.

To retrieve the current active trace elsewhere in your code, use:

from infralo import get_active_trace

trace = get_active_trace()

Tool Spans

A ToolSpan represents a discrete unit of work (e.g. a tool execution, database query, or API call).

Wrap code blocks inside a trace.tool context manager. This automatically starts a span, times it, catches exceptions, and updates status.

with trace.tool(name="fetch_user", tool_call_id="call_abc") as span:
    # 1. Record input arguments
    span.set_input({"user_id": 42})
    
    user = db.fetch(42)
    
    # 2. Record output
    span.set_output(user)
  • If an exception occurs inside the with block, the span status is set to error and the exception is re-raised automatically.

Manual Control

For execution paths where a context manager isn't convenient (e.g., across async callbacks), use start_tool_span and end:

span = trace.start_tool_span(name="send_email", tool_call_id="call_xyz")
try:
    span.set_input({"to": "user@example.com"})
    send_email()
    span.end(output={"status": "sent"})
except Exception as e:
    span.end(error=str(e), status="error")

Span Payloads

Use these methods inside a span to enrich its captured data:

  • span.set_input(data: Any, capture_payload: bool = True): Record the inputs.
  • span.set_output(data: Any, capture_payload: bool = True): Record the output.
  • span.set_metadata(**kwargs: Any): Attach custom tags (e.g., model name, version, provider).

Concurrency & Parallelism

trace.parallel()

When running concurrent tools (e.g., via asyncio.gather), you must coordinate sequence numbers so they aren't recorded as sequential. Use trace.parallel() to pin the sequence context:

async with trace.parallel():
    # Tools called inside inherit the same sequence index
    results = await asyncio.gather(
        fetch_url("https://example.com/1"),
        fetch_url("https://example.com/2")
    )

trace.gather()

A convenience wrapper around asyncio.gather that handles the parallel() sequence pinning automatically:

results = await trace.gather(
    fetch_url("https://example.com/1"),
    fetch_url("https://example.com/2")
)

Decorators

@infralo.tool()

Decorate functions to automatically instrument them when executed within an active trace:

from infralo.decorators import tool

@tool(name="get_weather")
def get_weather(city: str) -> str:
    return f"Sunny in {city}"

with infralo.start_trace():
    # Starts and ends a ToolSpan named "get_weather" automatically
    get_weather("San Francisco")

LLM Span Linking

trace.update_from_response()

Read incoming HTTP gateway headers from your LLM response (such as x-infralo-span-id) and link subsequent tool spans as its children.

response = openai_client.chat.completions.create(...)

# Auto-extracts x-infralo-span-id from headers and sets it as the parent span ID
trace.update_from_response(response)

Complete Examples

You can view or download the complete, ready-to-run examples:

On this page