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
withblock 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 resultThe 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
)| Parameter | Decorator | Context Manager | Description |
|---|---|---|---|
name_or_func | The function | The span name string | First positional argument |
name | Custom span name | -- | Overrides auto-detected function name |
input | -- | Input to record | Ignored in decorator mode (use capture_input instead) |
capture_input | Yes | -- | Serialize function args as input |
capture_output | Yes | -- | Serialize return value as output |
attributes | Yes | Yes | Dict of initial span attributes |
record_exception | Yes | Yes | Auto-record exceptions |
set_status_on_exception | Yes | Yes | Set 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 spanRecording 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-raiseSpan 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 validatedNext Steps
- Semantic Conventions - Standard attribute names
- Async Support - Async span creation
- Auto-Instrumentation - Framework support