Introduction
Building AI agents that can reason through multi-step problems, maintain state, and recover from errors has become one of the most important challenges in modern application development. LangGraph, an extension of the LangChain ecosystem, provides a powerful framework for creating stateful, cyclical agent workflows using graph-based abstractions.
Unlike traditional linear chains, LangGraph treats agent workflows as directed graphs where nodes represent computation steps and edges define the flow of control. This approach makes it possible to build agents that loop, branch, and maintain complex state across interactions.
What Makes LangGraph Different
LangGraph was built to address limitations in traditional chain-based approaches. The core concepts include:
- Stateful graphs: State is passed between nodes and can be modified at each step
- Cyclical workflows: Agents can loop back to previous steps, enabling iterative reasoning
- Conditional edges: Control flow can branch based on the current state
- Persistence: State can be checkpointed, enabling human-in-the-loop interactions and recovery
The graph-based model maps naturally to how agents actually work. An agent receives input, reasons about it, decides on an action, observes the result, and loops until it reaches a conclusion.
Building a Research Agent
Let’s build a practical research agent that can search the web, summarize findings, and decide whether it has enough information to answer a question.
Setting Up the Graph
First, install the required packages:
pip install langgraph langchain-openai langchain-community
Now let’s define the state and build the graph:
from typing import TypedDict, Annotated, List
from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI
from langchain_community.tools import DuckDuckGoSearchRun
import operator
class AgentState(TypedDict):
question: str
search_results: Annotated[List[str], operator.add]
summary: str
iterations: int
final_answer: str
llm = ChatOpenAI(model="gpt-4o", temperature=0)
search = DuckDuckGoSearchRun()
Defining the Nodes
Each node is a function that takes the current state and returns a partial state update:
def search_node(state: AgentState) -> dict:
"""Perform a web search based on the question."""
query = state["question"]
results = search.run(query)
return {
"search_results": [results],
"iterations": state["iterations"] + 1
}
def summarize_node(state: AgentState) -> dict:
"""Summarize the search results."""
combined = "\n".join(state["search_results"])
prompt = f"Summarize these search results for the question: {state['question']}\n\nResults:\n{combined}"
summary = llm.invoke(prompt).content
return {"summary": summary}
def evaluate_node(state: AgentState) -> dict:
"""Decide if we have enough information to answer."""
prompt = f"""Based on this summary, can you fully answer the question: {state['question']}?
Summary: {state['summary']}
If yes, provide the final answer. If no, say 'NEED_MORE_INFO'."""
response = llm.invoke(prompt).content
if "NEED_MORE_INFO" in response:
return {"final_answer": ""}
return {"final_answer": response}
Adding Conditional Edges
The key to iterative agent behavior is conditional routing:
def should_continue(state: AgentState) -> str:
"""Decide whether to search again or finish."""
if state["final_answer"] and "NEED_MORE_INFO" not in state["final_answer"]:
return "end"
if state["iterations"] >= 3:
return "end"
return "search"
# Build the graph
workflow = StateGraph(AgentState)
workflow.add_node("search", search_node)
workflow.add_node("summarize", summarize_node)
workflow.add_node("evaluate", evaluate_node)
workflow.set_entry_point("search")
workflow.add_edge("search", "summarize")
workflow.add_edge("summarize", "evaluate")
workflow.add_conditional_edges("evaluate", should_continue, {
"search": "search",
"end": END
})
app = workflow.compile()
Running the Agent
result = app.invoke({
"question": "What are the latest developments in quantum computing?",
"search_results": [],
"summary": "",
"iterations": 0,
"final_answer": ""
})
print(result["final_answer"])
State Persistence and Human-in-the-Loop
One of LangGraph’s most powerful features is state persistence. You can checkpoint state at any node, enabling:
- Human-in-the-loop: Pause execution and ask for human approval before continuing
- Time travel: Replay from any checkpoint with modified state
- Error recovery: Resume from the last successful checkpoint after a failure
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
app = workflow.compile(checkpointer=checkpointer)
# Run with a thread ID for state tracking
config = {"configurable": {"thread_id": "thread-1"}}
result = app.invoke(initial_state, config)
When to Use LangGraph vs LangChain
The choice between LangGraph and LangChain depends on your use case:
Use LangChain when:
- You need simple, linear chains of operations
- Building straightforward RAG pipelines
- Quick prototyping with standard patterns
- The workflow doesn’t require loops or complex branching
Use LangGraph when:
- Building agents that need to reason iteratively
- Workflows require conditional branching and loops
- You need state persistence and checkpointing
- Human-in-the-loop interactions are required
- Multiple agents need to coordinate and share state
Best Practices
-
Keep nodes focused: Each node should do one thing well. This makes graphs easier to debug and modify.
-
Design state carefully: Your state schema defines what information flows through the graph. Include only what’s necessary to avoid bloat.
-
Add iteration limits: Always cap the number of iterations to prevent infinite loops, especially in cyclical graphs.
-
Log state transitions: Instrument your nodes to log state changes for debugging and observability.
-
Test nodes independently: Since nodes are plain functions, you can unit test each one in isolation before composing the graph.
Conclusion
LangGraph provides a robust foundation for building production-grade AI agents that go beyond simple prompt-response patterns. By modeling workflows as stateful graphs, you can create agents that reason iteratively, handle errors gracefully, and integrate human oversight when needed. The graph-based approach requires more upfront design than simple chains, but the payoff in flexibility and reliability makes it worthwhile for any serious agent application.