Tracing
Traces & Spans

Traces & Spans

Learn how to create traces and spans to instrument your AI agent.

The trace Class

tracing.trace is a class that works as both a decorator and a context manager. Use whichever style fits your code.

  • Decorator mode -- wrap a function, and inputs/outputs are captured automatically.
  • Context manager mode -- open a with block and set inputs/outputs yourself.

Decorator Mode

The simplest way to trace a function. The SDK inspects the function signature, serializes the arguments as input, and captures the return value as output.

import mantisdk.tracing as tracing
 
@tracing.trace
def process_query(query: str) -> dict:
    """Input and output are captured automatically."""
    result = analyze(query)
    return {"result": result}

You can also pass a custom name:

@tracing.trace(name="custom-operation")
def another_function(data):
    process(data)

Disabling Auto-Capture

Large payloads or sensitive data? Turn off capture for either side:

# Don't record the return value
@tracing.trace(capture_output=False)
def sensitive_function(input_data):
    return process_sensitive(input_data)
 
# Don't record the function arguments
@tracing.trace(capture_input=False)
def function_with_large_input(big_payload: bytes):
    return summarize(big_payload)
 
# Turn off both -- only timing and status are recorded
@tracing.trace(capture_input=False, capture_output=False)
def fully_silent(data):
    return transform(data)

capture_input and capture_output only apply in decorator mode. In context manager mode you control I/O explicitly, so these flags are ignored.

Context Manager Mode

When you need full control over what gets recorded, use trace as a context manager. You pass input up front and set the output on the span directly.

import mantisdk.tracing as tracing
 
def my_workflow(query: str) -> str:
    with tracing.trace("my-workflow", input=query) as span:
        result = do_work(query)
 
        # Set output explicitly
        span.set_attribute(tracing.semconv.TRACE_OUTPUT, result)
 
        # Add custom attributes
        span.set_attribute("custom_key", "custom_value")
 
        return result

The with block yields an OpenTelemetry Span object. You can call any standard span method on it (set_attribute, add_event, record_exception, etc.).

Parameters

tracing.trace(
    name_or_func=None,           # Span name (str) or function to decorate
    name=None,                   # Explicit span name (keyword-only)
    input=None,                  # Input value (context manager mode)
    kind=SpanKind.INTERNAL,      # OpenTelemetry span kind
    attributes=None,             # Initial attributes dict
    capture_input=True,          # Auto-capture input (decorator mode only)
    capture_output=True,         # Auto-capture output (decorator mode only)
    record_exception=True,       # Record exceptions on the span
    set_status_on_exception=True # Set ERROR status on exception
)
ParameterDecoratorContext ManagerDescription
name_or_funcThe functionThe span name stringFirst positional argument
nameCustom span name--Overrides auto-detected function name
input--Input to recordIgnored in decorator mode (use capture_input instead)
capture_inputYes--Serialize function args as input
capture_outputYes--Serialize return value as output
attributesYesYesDict of initial span attributes
record_exceptionYesYesAuto-record exceptions
set_status_on_exceptionYesYesSet span status to ERROR on exception

The span Function

tracing.span() creates child spans within an active trace. Use it to break a trace into logical steps.

import mantisdk.tracing as tracing
 
def my_workflow(query: str):
    with tracing.trace("workflow", input=query):
        # Step 1 -- child of workflow
        with tracing.span("step.classify", input=query) as s1:
            query_type = classify(query)
            s1.set_attribute("query_type", query_type)
 
        # Step 2 -- another child of workflow
        with tracing.span("step.process") as s2:
            result = process(query, query_type)
            s2.set_attribute(tracing.semconv.OBSERVATION_OUTPUT, result)
 
        # Nested spans
        with tracing.span("step.generate"):
            with tracing.span("step.generate.format"):
                formatted = format_output(result)
            with tracing.span("step.generate.validate"):
                validated = validate(formatted)

Parameters

tracing.span(
    name,                        # Span name (required)
    input=None,                  # Input value
    kind=SpanKind.INTERNAL,      # OpenTelemetry span kind
    attributes=None,             # Initial attributes
    record_exception=True,       # Record exceptions
    set_status_on_exception=True # Set ERROR status
)

The tool Function

tracing.tool() creates spans for tool/function calls with CLIENT span kind. This shows up differently in the Insight UI, making it easy to spot external calls.

import mantisdk.tracing as tracing
 
def search_database(query: str) -> list:
    """Trace database search as a tool call."""
    with tracing.tool("search_database", input=query) as span:
        results = db.execute(query)
        span.set_attribute(tracing.semconv.OBSERVATION_OUTPUT, str(len(results)))
        return results
 
def call_external_api(endpoint: str, payload: dict) -> dict:
    """Trace API call as a tool."""
    with tracing.tool("api_call", input=payload) as span:
        span.set_attribute("endpoint", endpoint)
        response = requests.post(endpoint, json=payload)
        span.set_attribute("status_code", response.status_code)
        return response.json()

Parameters

tracing.tool(
    name,                        # Tool/span name (required)
    input=None,                  # Tool input
    tool_name=None,              # Explicit tool.name attribute
    attributes=None,             # Initial attributes
    record_exception=True,       # Record exceptions
    set_status_on_exception=True # Set ERROR status
)

Working with Spans

Setting Attributes

with tracing.trace("workflow") as span:
    # Standard attributes
    span.set_attribute("key", "value")
    span.set_attribute("count", 42)
    span.set_attribute("enabled", True)
 
    # Semantic conventions
    span.set_attribute(tracing.semconv.USER_ID, "user-123")
    span.set_attribute(tracing.semconv.SESSION_ID, "session-456")
    span.set_attribute(tracing.semconv.TRACE_OUTPUT, "result")

To group multiple traces into a session (e.g., a multi-turn conversation), use tracing_context(session_id=...) instead of setting the attribute on each span individually. See Context Tagging -- Session Tracking for examples.

Getting the Current Span

import mantisdk.tracing as tracing
 
def inner_function():
    """Access the current active span."""
    span = tracing.get_current_span()
    span.set_attribute("inner_data", "value")
 
with tracing.trace("outer"):
    inner_function()  # Can access the outer span

Recording Exceptions

Exceptions are automatically recorded, but you can also record them manually:

with tracing.span("risky_operation") as span:
    try:
        risky_code()
    except ValueError as e:
        span.record_exception(e)
        span.set_status(StatusCode.ERROR, str(e))
        # Handle or re-raise

Span Hierarchy Example

import mantisdk.tracing as tracing
 
def agent_workflow(user_query: str):
    """
    Creates this span hierarchy:
 
    agent_workflow
    +-- classify
    +-- retrieve
    |   +-- search_db (tool)
    |   +-- search_web (tool)
    +-- generate
        +-- format
        +-- validate
    """
    with tracing.trace("agent_workflow", input=user_query) as root:
        root.set_attribute(tracing.semconv.USER_ID, "user-123")
 
        # Classify
        with tracing.span("classify", input=user_query) as s:
            query_type = classify(user_query)
            s.set_attribute("query_type", query_type)
 
        # Retrieve (with tool calls)
        with tracing.span("retrieve") as s:
            with tracing.tool("search_db", input=user_query):
                db_results = search_db(user_query)
            with tracing.tool("search_web", input=user_query):
                web_results = search_web(user_query)
            s.set_attribute("total_results", len(db_results) + len(web_results))
 
        # Generate
        with tracing.span("generate") as s:
            with tracing.span("format"):
                formatted = format_results(db_results, web_results)
            with tracing.span("validate"):
                validated = validate_output(formatted)
            s.set_attribute(tracing.semconv.OBSERVATION_OUTPUT, validated)
 
        root.set_attribute(tracing.semconv.TRACE_OUTPUT, validated)
        return validated

Next Steps