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

*Source: https://www.infowok.com/langgraph-tutorial-build-first-agent-2026/ · Sukhveer Kaur · Published June 20, 2026 · Updated July 6, 2026*

---

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.

<Prerequisites>

- 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?](/what-are-ai-agents-complete-guide-2026/) first
- No LangGraph experience needed — we build from zero

</Prerequisites>

<KeyTakeaways>

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

</KeyTakeaways>

## 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](./langgraph-tutorial-build-first-agent-2026-architecture.svg)

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](./langgraph-tutorial-build-first-agent-2026-flow.svg)

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.

<Callout type="note" title="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.
</Callout>

## 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`.

<Callout type="warning" title="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.
</Callout>

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](/langgraph-vs-crewai-vs-autogen-2026/) — 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](/ai-agents-from-scratch-python-part-5/) builds the agent loop by hand before the framework, and [what an AI agent really is](/what-are-ai-agents-complete-guide-2026/) 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)](/langgraph-vs-crewai-vs-autogen-2026/)** — once you can build one agent, this is how you pick the right framework for the next one.

<NextSteps>

- **New to agents?** Start with [What are AI agents?](/what-are-ai-agents-complete-guide-2026/) for the core idea, then [build the agent loop by hand](/ai-agents-from-scratch-python-part-5/).
- **Comfortable here?** Pick a framework with [LangGraph vs CrewAI vs AutoGen](/langgraph-vs-crewai-vs-autogen-2026/).
- **Going to production?** Learn to [deploy your agent to Cloud Run or Fly.io](/deploy-ai-agent-cloud-run-flyio/).

</NextSteps>
