A @trace decorator for every tool
You could write with tracer.start_as_current_span at the top of every tool, but you will forget one, and the code gets noisy. A decorator centralises the pattern. One @trace, every tool is instrumented the same way, every new tool is traced on day one.
import functools, time, json
from opentelemetry import trace
tracer = trace.get_tracer("agent")
def trace(name=None, span_type="CHAIN", attributes=None, model=None):
static_attrs = attributes or {}
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
span_name = name or func.__name__
start = time.perf_counter()
with tracer.start_as_current_span(span_name) as span:
span.set_attribute("openinference.span.kind", span_type)
span.set_attribute("func.name", func.__name__)
for key, value in static_attrs.items():
span.set_attribute(key, value)
if model:
span.set_attribute("llm.model_name", model)
try:
result = func(*args, **kwargs)
elapsed = round((time.perf_counter() - start) * 1000, 2)
span.set_attribute("duration_ms", elapsed)
return result
except Exception as e:
span.set_attribute("error", True)
span.set_attribute("error.message", str(e))
raise
return wrapper
return decoratorA compact version of the real decorator. It sets the span kind attribute Phoenix uses, records duration, and captures errors.
from observability import trace
@trace(span_type="PARSER", model="gpt-4o-mini")
def route_question(user_message: str, conversation_history: list[dict]) -> dict:
# ... existing router code
return {"route": route, "reason": parsed.get("reason", "")}One line above the function and the router is instrumented. The decorator also works on synthesis_node, sql_node, and every other step.
from observability import trace
from config import DB_PATH
@trace(span_type="TOOL", model="gpt-4o-mini", attributes={
"db.type": "sqlite",
"db.path": DB_PATH,
})
def run_sql_tool(user_question: str, conversation_history: list[dict]) -> dict:
# ... generate and execute SQL
return {"sql": sql, "rows": rows, "table_md": table_md, "error": error}Static attributes like db.type and db.path describe the tool once. The decorator adds duration on every call.
Fill in the blanks: Complete the RAG tool decoration
Loading practice…