Skip to content
agent2agent
Agent Frameworks & Tools

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.

By Nora LinJune 1, 20258 min read

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.

A two-node LangGraph graph: the `reason` node calls the LLM; the `act` node executes the chosen tool; conditional edges route back to reason or to END.

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?
LangGraph is a Python framework for building stateful, multi-step AI agents as directed graphs. It's used to build agents that need to persist state across many tool calls, support human-in-the-loop interrupts, or coordinate multiple specialized sub-agents. It's built by the LangChain team and integrates closely with LangSmith for tracing and evaluation.
Do I need to know LangChain to use LangGraph?
No, but it helps. LangGraph can be used with any LLM provider and tool ecosystem. You do need basic LangChain concepts like `BaseMessage`, `HumanMessage`, and `AIMessage` to work with the messages list. The core LangGraph concepts — StateGraph, nodes, edges — are independent of LangChain.
How does LangGraph handle persistent state across sessions?
LangGraph supports checkpointing via a `checkpointer` argument to `compile()`. The built-in `SqliteSaver` persists state to a SQLite database keyed by `thread_id`. Pass the same `thread_id` on resumption and the graph picks up where it left off. `PostgresSaver` is available for production deployments with concurrent access requirements.
Can LangGraph handle parallel tool calls?
Yes. If the LLM outputs multiple tool calls in a single response (parallel function calling), LangGraph's ToolNode executes them concurrently and returns all results before the next reasoning step. You can also create explicit fan-out edges that send state to multiple nodes in parallel and fan back in with a merge reducer.
How do I debug a LangGraph agent that isn't working correctly?
First, add `verbose=True` or stream the graph invocation to see each step. Second, inspect `result['messages']` after invocation — the full trace of every LLM call and tool result is in there. Third, use LangSmith (free tier available) for a visual trace that shows exactly which node ran, what state it received, and what it returned. Unit-test individual node functions in isolation to rule out node-level bugs before debugging the full graph.
Nora Lin

Written by

Nora Lin

Senior 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.

Related articles

Agent Frameworks & Tools

Best AI Agent Frameworks in 2025: LangGraph, CrewAI, AutoGen Compared

LangGraph wins on control and debuggability. CrewAI wins on team abstractions. AutoGen wins on conversational multi-agent patterns. No single framework is best — the right choice depends on your task structure, team size, and tolerance for complexity.

Nora Lin·10 min read
Agent Frameworks & Tools

CrewAI vs AutoGen: Which Multi-Agent Framework Should You Use?

CrewAI wins when your multi-agent workflow maps naturally onto human team roles. AutoGen wins when agents need to deliberate, debate, and build on each other's work conversationally. Both are mature, well-supported, and genuinely different in how they model coordination.

Marcus Reid·8 min read
Understanding AI Agents

What Is an AI Agent? The Complete Guide

AI agents are programs that perceive their environment, plan a sequence of steps, use tools to act, and loop back until a goal is achieved — unlike a one-shot LLM call that just predicts the next token.

Marcus Reid·9 min read