LangGraph Tutorial: Build a Stateful Agent in 30 Minutes
LangGraph makes agents explicit: nodes are functions, edges are routing logic, and state is a typed dict that persists across every step. In 30 minutes you can build a stateful research agent that searches the web and synthesizes a summary.
Most agent frameworks hide the loop from you. LangGraph shows you every step: you define exactly which functions run, in which order, and what data flows between them. This **langgraph tutorial** builds a complete stateful research agent from scratch — from installation to a working agent that searches, reasons, and stops cleanly.
Quick answer
LangGraph agents are directed graphs: nodes are Python functions that transform state, edges define routing between nodes, and a TypedDict state object carries information across the loop. Install with `pip install langgraph langchain-openai`, define your state, write nodes, add edges, compile, and run. The whole scaffold is about 60-80 lines of Python.What makes LangGraph different from other agent frameworks?
Most agent frameworks abstract the loop as a black box. LangGraph exposes it as a state machine: a directed graph where you control exactly which node executes next based on the current state. This makes the agent's logic debuggable, testable, and modifiable in ways that while-loop agents are not.
According to LangGraph's official documentation, the framework was built specifically to address the pain of debugging non-deterministic agent loops — a complaint that drove the team to make state transitions explicit and inspectable. LangGraph 0.2 (released in 2024) added first-class support for subgraphs, streaming, and persistent checkpointing, making it the framework of choice for production-grade agentic systems.
How do you install and set up LangGraph?
Install the core packages:
- `pip install langgraph langchain-openai langchain-community tavily-python python-dotenv`
- Create a `.env` file with `OPENAI_API_KEY=sk-...` and `TAVILY_API_KEY=tvly-...`
- Add `from dotenv import load_dotenv; load_dotenv()` at the top of your script
You can substitute `langchain-anthropic` for `langchain-openai` if you prefer Claude. The graph structure is identical regardless of model provider.
How do you define a state schema in LangGraph?
The state schema is the most important design decision in a LangGraph agent. It's a `TypedDict` that defines every piece of information the agent carries through its loop. Every node receives the full state and returns a partial update.
For a research agent that searches and summarizes:
- `from typing import TypedDict, Annotated`
- `from langchain_core.messages import BaseMessage`
- `import operator`
- Define: `class AgentState(TypedDict): messages: Annotated[list[BaseMessage], operator.add]; goal: str; steps_taken: int`
- The `Annotated[list, operator.add]` tells LangGraph to append new messages rather than replace the list — a critical detail for multi-step agents.
Keep the state schema lean. Only include fields the agent actively reads or writes. A bloated state schema with 20 fields makes nodes hard to reason about and increases the probability of stale data bugs.
How do you create nodes and add edges?
Nodes are ordinary Python functions that accept a state dict and return a partial state update. Here's the complete two-node agent skeleton:
The reason node
- Import: `from langchain_openai import ChatOpenAI`
- Bind tools: `llm = ChatOpenAI(model="gpt-4o").bind_tools([search_tool])`
- Node function: `def reason(state): response = llm.invoke(state["messages"]); return {"messages": [response], "steps_taken": state["steps_taken"] + 1}`
- This node calls the LLM with the current messages and appends the response.
The act node (ToolNode)
- Import: `from langgraph.prebuilt import ToolNode`
- Instantiate: `act_node = ToolNode([search_tool])`
- ToolNode automatically reads the last AI message for tool calls, executes them, and returns tool result messages.
- No manual function needed — this is a LangGraph built-in.
Wiring the graph
- `from langgraph.graph import StateGraph, END`
- `builder = StateGraph(AgentState)`
- `builder.add_node("reason", reason)`
- `builder.add_node("act", act_node)`
- `builder.set_entry_point("reason")`
- `builder.add_conditional_edges("reason", should_continue)` — where `should_continue` returns `"act"` if there's a tool call and `END` otherwise or if steps >= 15
- `builder.add_edge("act", "reason")` — always return to reasoning after acting
- `graph = builder.compile()`
What does a complete working example look like?
Here's the complete routing function and invocation for the research agent:
- `def should_continue(state):`
- `last_message = state["messages"][-1]`
- `if state["steps_taken"] >= 15: return END`
- `if hasattr(last_message, "tool_calls") and last_message.tool_calls: return "act"`
- `return END`
- Run: `result = graph.invoke({"messages": [HumanMessage(content="What are the top 3 AI agent frameworks in 2025?")], "goal": "...", "steps_taken": 0})`
- The final answer is the last AIMessage in `result["messages"]`.
The agent will search the web, read the results, reason about what it found, and synthesize a final answer — all in the same graph loop, with every step visible in the message list.
What are the most common LangGraph gotchas?
Even experienced developers hit these when learning LangGraph:
- Forgetting `operator.add` on the messages field — without it, each node replaces the messages list instead of appending, and the agent loses its conversation history.
- Setting entry_point after adding edges — set `set_entry_point` before calling `add_conditional_edges`. Graph compilation order matters.
- Returning the full state from nodes — nodes should return only the fields they modify. Returning the full state dict works but causes subtle bugs when reducers are involved.
- Not handling the case where ToolNode gets no tool calls — if `act` is called with a message that has no tool calls, ToolNode raises an error. The conditional edge should never route to `act` unless tool calls are present.
- Missing `add_edge` from `act` back to `reason` — without this edge, the graph stops after the first tool call. Always add the back-edge.
For the broader framework landscape and how LangGraph compares to CrewAI and AutoGen, see Best AI Agent Frameworks. For the foundations of what you're building, How to Build Your First AI Agent covers goal definition, tool design, and testing strategy.
Frequently asked questions
What is LangGraph and what is it used for?
Do I need to know LangChain to use LangGraph?
How does LangGraph handle persistent state across sessions?
Can LangGraph handle parallel tool calls?
How do I debug a LangGraph agent that isn't working correctly?
Written by
Nora LinSenior AI Research Analyst & Technical Reviewer
Nora researches AI agent capabilities, safety, and practical deployment patterns. She reviews every guide on agent2agent to ensure technical accuracy and current best practices.
This article is for educational purposes only. It does not constitute professional software, legal, or financial advice. Read our full disclaimer.