🎯 By the end of this module, you will:
Debugging Agents by Analyzing State Transitions
When an agent produces a wrong answer, staring at the final output tells you nothing. Agent behavior emerges from state mutations across nodes. Inspecting transitions frame-by-frame reveals the exact node where data was dropped or corrupted.
# stream_mode="values": Full state snapshot after each node
# Shows ALL state fields after every node completes
for state_snapshot in app.stream(
{"messages": [HumanMessage("What is the Q3 revenue?")]},
stream_mode="values"
):
# state_snapshot = entire state dict at this point in time
print("\n=== FULL STATE SNAPSHOT ===")
print(f"messages: {len(state_snapshot['messages'])} messages")
print(f"query_result: {state_snapshot.get('query_result', 'NOT SET YET')}")
print(f"generated_sql: {state_snapshot.get('generated_sql', 'NOT SET YET')}")
# Example output showing accumulated state across nodes:
# === After sql_planner ===
# messages: 2, generated_sql: "SELECT SUM(amount)...", query_result: NOT SET YET
# === After sql_executor ===
# messages: 2, generated_sql: "SELECT SUM...", query_result: [{'total': 4820000}]
# === After formatter ===
# messages: 3, generated_sql: "SELECT...", query_result: [...]🔬 State Time-Travel Studio
State Transition Time-Travel Debugger
Rewind and inspect state snapshots step-by-step between stream modes ('values' vs 'updates')
input_guardrail
Executed at: 12:00:01.050🔹 'updates' mode shows ONLY the fresh keys written by this exact node. Ideal for zeroing in on which node modified what.
{
"safety_status": "PASSED",
"sanitized_query": "Find total revenue for Q3 2024 in EU region."
}Debugging Traps
A node returns {"messages": [ai_msg]} instead of using the add_messages reducer. This replaces the entire message history with a single message. Every prior turn vanishes silently. Use Annotated[list, add_messages] in your TypedDict, not plain list.
stream_mode="values" shows accumulated state — but if Node B overwrites Node A's output, you only see Node B's version. Use stream_mode="updates" instead to see what EACH individual node returned separately. Values hides overwrites; updates reveals them.
Key Takeaways
- 1.updates > values for Debugging: stream_mode='updates' shows exactly what each node returned. When data is missing, the first node with a wrong delta is the culprit — fix the reducer or the node logic, not the prompt.
- 2.Breakpoints for Mid-Run Inspection: interrupt_before=[node] is the graph-equivalent of Python's breakpoint(). Use it to pause before expensive or destructive nodes (DB writes, email sends) to verify state before committing the action.
- 3.Null Returns are Silent Killers: A node that returns {} or {key: None} corrupts downstream state silently without raising an exception. Always validate node return values in tests — check that every expected key has a non-None value.
Enabling Tool Interoperability with MCP
The Model Context Protocol is the USB-C for AI — learn to connect any LangGraph agent to databases, file systems, and developer tools without writing custom glue code.