@observe() Decorator

SDK

Instrument your functions with the @observe() decorator.

The @observe() decorator is the simplest way to add tracing to your functions. It automatically creates spans, captures arguments and results, handles errors, and supports both sync and async functions.

Basic Usage

Add the decorator to any function you want to trace:

agent.py
python
from traccia import observe
@observe()
def my_agent(query: str) -> str:
"""This function will be traced."""
# Function name becomes span name
return process(query)
# When called, creates a span named "my_agent"
result = my_agent("What is quantum computing?")

Decorator Parameters

nameOptional[str]

Custom name for the span. If not provided, uses the function name.

python
@observe(name="research-query")
def process_query(query: str):
# Span will be named "research-query" instead of "process_query"
pass
attributesOptional[Dict[str, Any]]

Custom key-value attributes to add to the span. Values must be OpenTelemetry-compatible types (bool, str, int, float, bytes, or sequences of these).

python
@observe(attributes={
"team": "ml-research",
"environment": "production",
"version": "2.1.0",
"priority": 1
})
def research_agent(topic: str):
pass
tagsOptional[Iterable[str]]

List of string tags for categorizing the span. Stored as span.tags attribute.

python
@observe(tags=["experimental", "ml", "v2"])
def new_feature(data):
pass
as_typestr = "span"

Span type for categorization. Options: "span", "llm", "tool". Traccia can auto-detect LLM and tool spans from attributes. (Note: TypeScript SDK uses type).

python
@observe(as_type="tool")
def database_query(sql: str):
pass
@observe(as_type="llm")
def call_model(prompt: str):
pass
skip_argsOptional[Iterable[str]]

List of argument names to exclude from automatic capture. Useful for sensitive data or large objects. (Note: TypeScript SDK uses skipArgs).

python
@observe(skip_args=["password", "api_key", "credentials"])
def authenticate(username: str, password: str, api_key: str):
# password and api_key won't be captured as attributes
pass
skip_resultbool = False

If True, the function's return value won't be captured as a span attribute. Useful for large results or sensitive data. (Note: TypeScript SDK uses skipResult).

python
@observe(skip_result=True)
def fetch_large_dataset():
# Result won't be added to span attributes
return huge_dataframe

Async Functions

The @observe() decorator automatically detects and handles async functions:

async_agent.py
python
from traccia import observe
import asyncio
@observe()
async def async_agent(query: str) -> str:
# Works seamlessly with async/await
result = await async_llm_call(query)
return result
@observe()
async def async_llm_call(prompt: str):
await asyncio.sleep(1) # Simulated API call
return "response"
# Usage
result = await async_agent("What is AI?")

Async support

Traccia automatically detects coroutines and wraps them appropriately. You don't need separate decorators for async functions.

Nested Spans & Hierarchy

When decorated functions call other decorated functions, Traccia automatically creates parent-child span relationships:

nested.py
python
from traccia import observe
@observe() # Root span
def main_agent(query: str):
# This is the parent span
plan = create_plan(query)
results = execute_plan(plan)
return summarize(results)
@observe() # Child span of main_agent
def create_plan(query: str):
return {"steps": ["step1", "step2"]}
@observe() # Child span of main_agent
def execute_plan(plan: dict):
results = []
for step in plan["steps"]:
results.append(execute_step(step))
return results
@observe() # Grandchild span (child of execute_plan)
def execute_step(step: str):
return f"completed {step}"
@observe() # Child span of main_agent
def summarize(results: list):
return ", ".join(results)
# Trace hierarchy:
# main_agent
# ├── create_plan
# ├── execute_plan
# │ ├── execute_step (step1)
# │ └── execute_step (step2)
# └── summarize

Depth limits

Traccia limits span nesting to 10 levels by default to prevent runaway recursion. You can adjust this with the max_span_depth config option.

What Gets Captured Automatically

The decorator automatically captures:

Function arguments

All args/kwargs (except those in skip_args)

Return value

Unless skip_result=True

Execution time

Start time, end time, duration

Exceptions

Error type, message, stack trace

Function metadata

Module name, function name

LLM parameters

Auto-detected model, temperature, max_tokens

Type conversion

Complex types (objects, dataframes, etc.) are automatically converted to strings and truncated to 1000 characters to keep spans manageable.

Error Handling

When a decorated function raises an exception, Traccia automatically:

  • Records the exception as a span event
  • Sets span status to ERROR
  • Captures error type, message, and stack trace
  • Re-raises the exception (doesn't suppress it)
errors.py
python
from traccia import observe
@observe()
def risky_function(data):
if not data:
raise ValueError("Data cannot be empty")
return process(data)
try:
result = risky_function(None)
except ValueError as e:
# Exception is recorded in span and re-raised
print(f"Caught: {e}")
# Span will include:
# - span.status = ERROR
# - error.type = "ValueError"
# - error.message = "Data cannot be empty"
# - error.stack_trace = "Traceback (most recent call last)..."

Advanced Examples

LLM Function with Auto-Detection

python
@observe()
def call_gpt(prompt: str, model: str = "gpt-4", temperature: float = 0.7):
# Traccia auto-detects this is an LLM call from the 'model' parameter
# and extracts model, temperature, and prompt as LLM attributes
return llm_client.complete(model=model, prompt=prompt, temperature=temperature)
# Span will include:
# - span.type = "llm" (auto-detected)
# - llm.model = "gpt-4"
# - llm.temperature = 0.7
# - llm.prompt = "..." (truncated)

Class Methods

python
class Agent:
def __init__(self, name: str):
self.name = name
@observe(attributes={"agent_type": "research"})
def research(self, topic: str):
# 'self' is automatically skipped from capture
return self._process(topic)
@observe()
def _process(self, topic: str):
return f"Researching {topic}"
agent = Agent("ResearchBot")
result = agent.research("quantum computing")

Combining Multiple Parameters

python
@observe(
name="premium-search",
attributes={
"team": "search",
"tier": "premium",
"cache_enabled": True
},
tags=["production", "monitored"],
as_type="tool",
skip_args=["api_key"],
skip_result=False
)
def premium_search(query: str, api_key: str, limit: int = 10):
# Comprehensive tracing with all options
results = search_api.query(query, api_key=api_key, limit=limit)
return results[:limit]

Best Practices

✅ Do

  • Use on high-level functions that represent meaningful operations
  • Skip sensitive arguments like passwords and API keys
  • Use descriptive custom names for important spans
  • Add attributes for important context (environment, version, etc.)
  • Skip large results to keep spans manageable

❌ Don't

  • Over-instrument every tiny helper function
  • Capture sensitive data without skip_args
  • Capture huge objects or dataframes without skip_result
  • Create deeply nested spans (> 10 levels) unless necessary
  • Use in extremely high-frequency functions (> 1000 calls/sec)

Next Steps

© 2026 Traccia.