@observe() Decorator
SDKInstrument 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:
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.
@observe(name="research-query")def process_query(query: str): # Span will be named "research-query" instead of "process_query" passattributesOptional[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).
@observe(attributes={ "team": "ml-research", "environment": "production", "version": "2.1.0", "priority": 1})def research_agent(topic: str): passtagsOptional[Iterable[str]]List of string tags for categorizing the span. Stored as span.tags attribute.
@observe(tags=["experimental", "ml", "v2"])def new_feature(data): passas_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).
@observe(as_type="tool")def database_query(sql: str): pass
@observe(as_type="llm")def call_model(prompt: str): passskip_argsOptional[Iterable[str]]List of argument names to exclude from automatic capture. Useful for sensitive data or large objects. (Note: TypeScript SDK uses skipArgs).
@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 passskip_resultbool = FalseIf 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).
@observe(skip_result=True)def fetch_large_dataset(): # Result won't be added to span attributes return huge_dataframeAsync Functions
The @observe() decorator automatically detects and handles async functions:
from traccia import observeimport 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"
# Usageresult = await async_agent("What is AI?")Async support
Nested Spans & Hierarchy
When decorated functions call other decorated functions, Traccia automatically creates parent-child span relationships:
from traccia import observe
@observe() # Root spandef 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_agentdef create_plan(query: str): return {"steps": ["step1", "step2"]}
@observe() # Child span of main_agentdef 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_agentdef summarize(results: list): return ", ".join(results)
# Trace hierarchy:# main_agent# ├── create_plan# ├── execute_plan# │ ├── execute_step (step1)# │ └── execute_step (step2)# └── summarizeDepth limits
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
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)
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
@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
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
@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.