Skip to content

Spans Deep Dive

You’ll learn about the four span types, their lifecycle, how nesting works, and how to attach rich data to each span.

Span Types

Every span has a kind that describes what type of operation it represents.

llm_call — LLM API Calls

Captures calls to language model APIs with provider, model, tokens, and cost data.

with opswald.span('generate-reply', kind='llm_call', provider='openai', model='gpt-4o') as s:
s.set_input({'messages': [{'role': 'user', 'content': 'Hello'}]})
result = openai.chat.completions.create(
model='gpt-4o',
messages=[{'role': 'user', 'content': 'Hello'}]
)
s.set_output({'response': result.choices[0].message.content})
s.set_tokens(input_tokens=10, output_tokens=25)

tool_call — Tool Invocations

Captures when your agent calls external tools or functions.

with opswald.span('search-knowledge-base', kind='tool_call') as s:
s.set_input({'query': 'refund policy', 'tool': 'vector_search'})
results = vector_db.search('refund policy')
s.set_output({'results': results, 'count': len(results)})

error — Error Events

Captures exceptions and error states. Useful for debugging failures.

with opswald.span('risky-operation', kind='error') as s:
try:
result = perform_operation()
s.set_output({'result': result})
except Exception as e:
s.set_output({'error': str(e), 'type': type(e).__name__})
raise

custom — Custom Operations

Track any operation that doesn’t fit the other categories.

with opswald.span('format-response', kind='custom') as s:
s.set_input({'raw_response': raw})
formatted = format_for_user(raw)
s.set_output({'formatted': formatted})

Span Lifecycle

Every span follows this lifecycle:

  1. Created — Span is opened with a name and kind
  2. Active — Input is set, your code runs, output is recorded
  3. Closed — Span ends, duration is calculated, data is sent to Opswald

When using context managers or callbacks, the lifecycle is automatic. The span closes when you exit the block, even if an exception occurs.

Nesting Spans

Spans nest naturally inside each other:

with opswald.trace('agent-run') as t:
with opswald.span('process-request', kind='custom') as parent:
with opswald.span('call-llm', kind='llm_call', provider='openai', model='gpt-4o') as child:
# child is nested inside parent
...
with opswald.span('call-tool', kind='tool_call') as sibling:
# sibling is at the same level as child
...

This produces the tree:

process-request (custom)
├── call-llm (llm_call)
└── call-tool (tool_call)

Attaching Data

Every span supports:

MethodPurpose
set_input(data)What went into this step
set_output(data)What came out of this step
set_tokens(input, output)Token counts for LLM calls

All data is JSON serializable and fully searchable in the dashboard.

Next Steps