InfoWok
Intermediate

LangGraph Tutorial: Build Your First AI Agent in Python (2026)

A LangGraph tutorial for 2026: build your first AI agent in Python with create_agent, then rebuild it as a raw StateGraph so you actually see what's underneath.

SK
Sukhveer Kaur
Published June 20, 2026 · Updated July 6, 2026
7 min read
LangGraph tutorial banner showing a state, model, and tools graph with the labels create_agent and StateGraph, on a dark indigo backgroundAI Engineering
LANGGRAPH FIRST AGENT
On this page +

Your plain LLM script works great — right up to the moment the agent needs to pick a tool, retry, or branch. Then it collapses into nested if-statements and state you patch by hand. That’s the exact problem LangGraph was built to delete.

This LangGraph tutorial takes you both ways through that door. First you’ll get a running agent in about fifteen lines with the prebuilt create_agent. Then you’ll rebuild the same agent as a raw StateGraph so you can see the state, the nodes, and the edges the shortcut hides. By the end you’ll know not just how to ship an agent, but which of the two paths to reach for next time.

🟡 Intermediate⏱️ 25 minStack: Python 3.10+, an LLM API key
Before you start
  • Comfortable writing and running Python functions and installing packages with pip
  • You’ve called an LLM API at least once (any provider) — new to that? Read What are AI agents? first
  • No LangGraph experience needed — we build from zero
🎯 Key takeaways
  • A LangGraph agent is a graph: one shared state object flows through nodes (steps) and edges (decisions), and LangGraph runs the loop for you.
  • create_agent is the fast path: a real tool-calling agent in ~15 lines, built on the same graph runtime underneath.
  • A checkpointer plus a thread_id is what turns a one-shot call into an agent that remembers — the piece most beginners miss.

What LangGraph Actually Is (and Why 1.0 Is the Safe Default)#

Before any code, the mental model — because LangGraph makes a lot more sense once you see the shape of it. A LangGraph agent is a graph: a shared state, a set of nodes, and the edges between them. State is one object that every step reads and updates (usually the running list of messages). A node is a single step — call the model, run a tool. An edge decides where control goes next, and an edge can be conditional: “if the model asked for a tool, go to the tools node; otherwise finish.”

That loop in the diagram — model decides, tools run, result flows back to the model — is the whole agent. A plain LLM call is just the top box on its own. The reason your hand-rolled version turns into spaghetti is that you end up writing the arrows by hand. LangGraph makes the arrows a first-class thing.

The 2026 reason to commit to it: LangGraph hit 1.0 in late 2025, with no breaking changes promised until 2.0. It’s now the runtime that LangChain’s own agents are built on. So learning it isn’t betting on a side project — it’s learning the default. You also get persistence and resumable runs for free, which we’ll use shortly.

Your First Agent in ~15 Lines with create_agent#

Here’s the fast path. Install the two packages and set one API key, and you have everything you need.

bash
pip install -U langgraph "langchain[openai]"

Put your model key in a .env file (or export it in your shell). I’m using OpenAI here because it’s the quickest to get a key for, but create_agent is model-agnostic — swap in Anthropic or Google and the rest of the code is identical.

bash
# .env
OPENAI_API_KEY=sk-your-key-here

Now the agent itself. A tool in LangChain is a normal Python function with the @tool decorator — the docstring tells the model what the tool does, so write it like a hint to a colleague.

python
from langchain.agents import create_agent
from langchain.tools import tool
 
@tool
def get_weather(city: str) -> str:
    """Return a short weather report for a city."""
    return f"It's 31°C and sunny in {city}."
 
agent = create_agent(
    model="openai:gpt-5.4-mini",
    tools=[get_weather],
    system_prompt="You are a concise weather assistant. Use the tool; never guess.",
)
 
result = agent.invoke(
    {"messages": [{"role": "user", "content": "What's the weather in Pune?"}]}
)
print(result["messages"][-1].content)

Run it and the model calls get_weather("Pune"), reads the result, and answers in plain language. That create_agent call already built the full graph from the diagram — the model node, the tools node, the conditional edge, and the loop back. You just didn’t have to draw it. The first time I used it I genuinely didn’t believe a real tool-calling agent fit in that little code, so I added a print inside the tool to confirm it was actually being called. It was.

📌 Model strings are swappable

"openai:gpt-5.4-mini" is a provider-prefixed model string. Change the prefix to anthropic: or google_genai: (and install that provider’s package) and nothing else in the code moves. Pin the model explicitly so a changed default never silently alters your agent.

Open the Hood: the Same Agent as a Raw StateGraph#

create_agent is great, but it hides exactly the parts you need to understand before you build anything non-trivial. So let’s rebuild the identical agent by hand. This is the version that makes the diagram click.

python
from typing import Annotated, TypedDict
from langchain.chat_models import init_chat_model
from langchain.tools import tool
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode, tools_condition
 
@tool
def get_weather(city: str) -> str:
    """Return a short weather report for a city."""
    return f"It's 31°C and sunny in {city}."
 
tools = [get_weather]
model = init_chat_model("openai:gpt-5.4-mini").bind_tools(tools)
 
class State(TypedDict):
    messages: Annotated[list, add_messages]
 
def call_model(state: State) -> dict:
    return {"messages": [model.invoke(state["messages"])]}
 
builder = StateGraph(State)
builder.add_node("model", call_model)
builder.add_node("tools", ToolNode(tools))
builder.add_edge(START, "model")
builder.add_conditional_edges("model", tools_condition)
builder.add_edge("tools", "model")
graph = builder.compile()

Read it against the diagram and every line maps to something. State is the shared object, and add_messages is the reducer. Instead of overwriting the message list on each step, it appends — which is why the conversation accumulates. call_model is the model node. ToolNode(tools) is the tools node, ready-made. The three add_edge lines wire START into the model and loop tools back into the model. The interesting line is add_conditional_edges("model", tools_condition). That’s the branch: tools_condition reads the model’s last message, routes to the tools node if it asked for a tool, and goes to END if it gave a final answer.

Common mistake: forgetting .bind_tools(tools) on the model. Without it the model literally doesn’t know the tools exist, so it never calls them and you’re left wondering why your tools node never fires. create_agent does this binding for you — which is half of what it’s saving you.

Invoke it the same way: graph.invoke({"messages": [{"role": "user", "content": "Weather in Pune?"}]}). Same result, but now you own every node and edge — which matters the moment you want to add a step that isn’t just the model or a tool.

Make It Resumable: a Checkpointer and thread_id#

Both agents so far have amnesia. Ask a follow-up and they’ve forgotten the last turn, because each invoke starts from an empty state. The fix is a checkpointer plus a thread_id — and this is the single feature that most “first agent” tutorials skip, even though it’s what makes an agent feel like an agent.

A checkpointer saves the graph state after every step. The thread_id is the key it saves under, so passing the same one continues a conversation and a new one starts fresh. Going back to the prebuilt agent, it’s two extra lines:

python
from langgraph.checkpoint.memory import InMemorySaver
 
checkpointer = InMemorySaver()
agent = create_agent(
    model="openai:gpt-5.4-mini",
    tools=[get_weather],
    checkpointer=checkpointer,
)
 
config = {"configurable": {"thread_id": "pune-chat-1"}}
agent.invoke({"messages": [{"role": "user", "content": "Weather in Pune?"}]}, config)
 
follow_up = agent.invoke(
    {"messages": [{"role": "user", "content": "And tomorrow?"}]}, config
)
print(follow_up["messages"][-1].content)

Because both calls pass the same thread_id, the second one understands “tomorrow” refers to Pune — the first turn is still in state. Change the thread_id and you get a clean conversation with no memory of Pune at all. For the raw graph, it’s the same idea: builder.compile(checkpointer=checkpointer) and pass the same config.

⚠️ Pick the right saver for the job

InMemorySaver keeps state in RAM and vanishes when your process exits — perfect for testing, useless in production. Use SqliteSaver for a single-process prototype and PostgresSaver for a real multi-worker service. Swapping them is a one-line change; the rest of your agent doesn’t care.

The mistake I see most often here is omitting the config on the second call. No thread_id, no memory — and with some setups, an outright error. If a “stateful” agent keeps forgetting, check that every invoke carries the same config before you suspect anything else.

When to Stay on create_agent vs Hand-Build a Graph#

This is the decision the docs are quiet about, so here’s my honest line. Stay on create_agent for any single agent whose job is “reason, call tools in a loop, answer.” That’s most agents, and writing a graph by hand for it is just ceremony. The prebuilt path also keeps pace with LangChain’s middleware, structured output, and guardrails without you rewiring anything.

Graduate to a raw StateGraph when you need something the loop can’t express on its own:

  • Custom branching — route to different nodes based on your own logic, not just “tool or finish.”
  • Human-in-the-loop — pause the graph, wait for approval, then resume (the checkpointer makes this clean).
  • Multiple agents — several nodes that each do real work and hand off to each other.
  • A non-model, non-tool step — say, a validation or formatting node that sits between others.

If you’re weighing LangGraph against other frameworks entirely, I compared the main options in LangGraph vs CrewAI vs AutoGen — but for a single tool-using agent, the answer is almost always “start prebuilt.”

Quick Recap#

Everything you just built, in five lines:

  • A LangGraph agent is a graph: shared state, nodes for steps, edges for decisions.
  • create_agent gives you a working tool-calling agent in ~15 lines.
  • The raw StateGraph version shows what the shortcut hides: add_messages, ToolNode, and tools_condition.
  • A checkpointer + thread_id is what makes the agent remember.
  • Prebuilt for single agents; a hand-built graph for branching, pauses, and multi-agent work.

Conclusion#

You built the same agent twice — once the fast way with create_agent, once by hand so the graph holds no mysteries — and then gave it a memory with a checkpointer. That’s the honest path through LangGraph 1.0: ship on the prebuilt agent, and reach for a raw graph only when you actually need the control. Most of the time, you won’t yet — and knowing when you will is the real skill.

If you want the conceptual on-ramp behind all this, From Scratch to LangGraph builds the agent loop by hand before the framework, and what an AI agent really is strips the idea down to its core. The natural next step from here is a long-running agent that plans and delegates — LangChain’s Deep Agents — which runs on the exact graph runtime you just learned.

Which path did you reach for first — the prebuilt create_agent, or the raw StateGraph? Tell me in the comments, especially if a graph let you do something the prebuilt agent couldn’t.

Read next: LangGraph vs CrewAI vs AutoGen (2026) — once you can build one agent, this is how you pick the right framework for the next one.

🧭 Where to go from here

Frequently asked questions

What is LangGraph in simple terms? +
LangGraph is a Python framework for building AI agents as a graph of nodes and edges that share one state object. A node is a step, such as calling the model or running a tool, and an edge decides where to go next. LangGraph runs the loop, keeps the state, and can save it so a run is resumable. It reached version 1.0 in late 2025 and is the runtime behind LangChain's agents.
Should I use create_agent or a raw StateGraph? +
Start with create_agent. It gives you a working tool-calling agent in about fifteen lines and covers most single-agent use cases. Reach for a raw StateGraph when you need custom branching, a human-in-the-loop pause, multiple cooperating agents, or a node that is not just the model or a tool. The honest rule: prototype on create_agent and graduate to a graph only when you hit a wall it cannot express.
Which import do I use for create_agent in 2026? +
Use from langchain.agents import create_agent. The older langgraph.prebuilt.create_react_agent path still works but is the legacy entry point, and its deprecation message points you to create_agent. If a 2024 tutorial tells you to import create_react_agent, swap it for create_agent.
How does a LangGraph agent remember a conversation? +
Attach a checkpointer and pass a thread_id. The checkpointer saves the graph state after every step, and the thread_id tells LangGraph which conversation to load. Use the same thread_id to continue a chat and a new one to start fresh. InMemorySaver is fine for testing, SqliteSaver for a single-process prototype, and PostgresSaver for production.

References

  1. Agents — Docs by LangChain (create_agent)
  2. Persistence — LangGraph docs (checkpointers and threads)
  3. LangChain and LangGraph Agent Frameworks Reach v1.0 Milestones
  4. langgraph — PyPI
Written by
Sukhveer Kaur
Sukhveer KaurSoftware Developer & AI Engineer

Sukhveer is a software developer specialising in AI systems and backend engineering. She has hands-on experience designing agentic AI applications, working with large language model pipelines, autonomous agent frameworks, and cloud-native services in Java and Python. At InfoWok, she bridges the gap between cutting-edge AI research and practical implementation — helping developers understand and apply emerging technologies through clear, experience-backed writing.

New AI engineering guides, the day they ship

Real Python, production depth. No digest spam.

Comments