HomeAboutOur TeamContact
HomeArtificial Intelligence
LangGraph Tutorial: Build Your First AI Agent in Python (2026)

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

Artificial Intelligence
June 20, 2026
6 min read
Intermediate
LangGraph tutorial banner showing a state, model, and tools graph with the labels create_agent and StateGraph, on a dark indigo background
Table of Contents
01
What LangGraph Actually Is (and Why 1.0 Is the Safe Default)
02
Your First Agent in ~15 Lines with create_agent
03
Open the Hood: the Same Agent as a Raw StateGraph
04
Make It Resumable: a Checkpointer and thread_id
05
When to Stay on create_agent vs Hand-Build a Graph
06
Quick Recap
07
Conclusion

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

A LangGraph agent drawn as a graph: START flows into the model node, which either ends with a final answer or routes to the tools node and loops the result back to the model, while a checkpointer saves the state per thread_id so runs are resumable

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

Five-step flow to a running LangGraph agent: install langgraph and langchain, set the model API key, write the agent with create_agent, run it with invoke, then add a checkpointer and thread_id so it remembers

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-4o-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-4o-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-4o-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-4o-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

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

Tags

#LangGraph#LangChain#AIAgents#Python#AgenticAI#StateGraph#AIForDevelopers

Share

Previous Article
AI Agent Loop in Python: Build a ReAct Agent From Scratch

Read next

AI agent versus workflow comparison banner showing a fixed Step 1 to Step 2 workflow path beside an agent where the model decides the path, on a dark background
AI Agent vs Workflow: What's the Actual Difference? (2026)
June 22, 2026
5 min
Beginner
ASYNC & AWAIT
See all by Sukhveer Kaur

Get new guides in your inbox

Practical AI, software engineering, and cloud articles — straight to your inbox. No spam, unsubscribe anytime.
LangGraph Tutorial: Build Your First AI Agent in Python (2026)
6 min left
Sukhveer Kaur

Sukhveer Kaur

Software Developer & AI Engineer

Table Of Contents

1
What LangGraph Actually Is (and Why 1.0 Is the Safe Default)
2
Your First Agent in ~15 Lines with create_agent
3
Open the Hood: the Same Agent as a Raw StateGraph
4
Make It Resumable: a Checkpointer and thread_id
5
When to Stay on create_agent vs Hand-Build a Graph
6
Quick Recap
7
Conclusion
© 2026, All Rights Reserved.

Quick Links

Advertise with usOur TeamAbout UsEditorial StandardsContact Us

Social Media