- State-driven routing using conditional edges
- Composable nodes that read/write shared typed state
- Converging branches into a single terminal formatter
- Integrating external APIs (OpenAI Responses API and Tavily search)
Setup and imports
Install dependencies if needed (uncomment the pip line in a fresh environment), then initialize clients and environment variables.Make sure
OPENAI_API_KEY and TAVILY_API_KEY are available in your environment before running the examples. Leaving keys in source code is not recommended for production.- OpenAI Responses API client for LLM calls.
- Tavily client for web search; the exact method name for searching may vary by SDK version—adapt as needed.
Typed shared state
We define a typedAgentState that nodes will read from and write to. LangGraph’s nodes interact through this shared state rather than direct node-to-node parameter passing.
question and accumulates intent, search_results, draft_answer, and final_answer as the graph runs.
Node overview
We implement four nodes:
Use the table above to quickly see responsibilities and expected state writes.
classify_intent
This node returns a single label —"search" or "answer" — which controls conditional routing.
Use a terse classifier prompt to minimize hallucination and to make the decision deterministic. If you want higher fidelity, consider a small validation step after classification.
search_web
This node fetches results from Tavily and normalizes them into a simple list of dictionaries withtitle, url, and content.
- SDKs differ: if
tavily_client.searchreturns a paged object orresp.results, adapt the extraction accordingly. - Keep the normalized output small and consistent to simplify downstream formatting.
answer_direct
Ask the LLM to answer concisely without performing a web lookup. The draft is stored indraft_answer.
format_output
The final node handles both branches:- If
intent == "search", it formats Tavily results into a readable summary. - If
intent == "answer", it returns the LLM’s draft answer.
Wiring the graph
We wire the graph to start atclassify_intent, branch conditionally to search_web or answer_direct, and then converge at format_output before transitioning to END.
Conditional edges let the graph decide the next node dynamically based on the current
state. This makes branching explicit, easier to reason about, and straightforward to visualize.Run examples
Run two sample invocations to exercise both routes:- The first question should choose
answer, producedraft_answervia the LLM, and return it asfinal_answer. - The second should choose
search, fetch results with Tavily, and return a formatted list of top matches.
Visualize the graph
To inspect control flow and conditional edges, export the graph as Mermaid source and render it in any Mermaid-compatible tool (for example, mermaid.live or the VS Code Mermaid preview). Many graph implementations expose a method such asto_mermaid() or get_mermaid()—check your graph object’s API.
Recommended rendering steps:
- Get the Mermaid source string from your graph object.
- Paste the Mermaid code into an external renderer (e.g., https://mermaid.live/).
- Inspect branching points and convergence to verify the routing.
Extending this pattern
This pattern scales well:- Add more classifier labels and map them to additional tool nodes via
add_conditional_edges. - Insert validation or hallucination-checking nodes before the formatter.
- Persist important facts into memory nodes that future queries can read.