The which tool did what problem
Welcome! I'm Param. In this course we are going to take a real LangGraph routing agent, the one that picks between SQL, RAG, and web search, and give it the kind of observability your backend services already have. JSON logs with a request ID, OpenTelemetry spans for every tool call, and a Phoenix UI where you click a trace and see exactly what happened.
Picture a user asking, "What were total sales last month for electronics?" Your agent has to pick a route (SQL, RAG, or web search), run the tool, synthesize an answer, and return it. Three LLM calls, one tool call, and a handful of attributes all matter for the final answer.
The agent we are going to instrument
A router classifies the question, one tool runs, a synthesiser writes the final answer. Every arrow is a place observability matters.
For a single request on your laptop, yes. In production with concurrent users, retries, and background tasks, those print lines interleave across users, drop context when a tool fires a subprocess, and give you no way to measure latency or cost per step. You end up asking questions the logs cannot answer, like which router prompt version ran or how long the SQL tool blocked the event loop.
def router_node(state: dict) -> dict:
routing = route_question(
user_message=state["user_message"],
conversation_history=state["conversation_history"],
)
print(f"\n[router] → {routing['route'].upper()} | {routing['reason']}")
return {
"route": routing["route"],
"route_reason": routing["reason"],
}Two print calls. No timestamp, no request ID, no tokens, no latency. When the agent picks the wrong route, this is all you see.
Quiz: Quiz
Loading practice…
AI prompt: Try it: your agent as a span tree
Loading practice…