n4nAI

Getting started with LangGraph: a beginner's tutorial

A hands-on LangGraph tutorial for beginners: build a stateful LLM loop with nodes, edges, and conditional logic using runnable Python code.

n4n Team3 min read764 words

Audio narration

Coming soon — every post will get a voice note here.

This LangGraph tutorial for beginners skips the hype and builds a working stateful agent loop from scratch. You will define a graph with explicit nodes and edges, run a generate-critique cycle, and see how LangGraph manages state across iterations. By the end you’ll have a debuggable pattern you can extend into a real agent.

Prerequisites

  • Python 3.10 or newer (LangGraph uses modern typing features).
  • Comfort with Python functions, dicts, and basic LLM API calls.
  • A virtual environment to avoid polluting global packages.
  • An API key from any OpenAI-compatible provider. If you don’t have one, use OpenAI directly or a gateway.

You should already know what an AIMessage is from LangChain core. If not, read the LangChain messaging docs first; LangGraph assumes that vocabulary.

Install and configure

Pin versions to avoid surprise breakages. LangGraph moves fast.

pip install langgraph==0.2.0 langchain-openai==0.1.0 langchain-core==0.2.0 python-dotenv

Create a .env file in your project root:

OPENAI_API_KEY=sk-your-key-here
# N4N_API_KEY=your-key-here

Load it and instantiate the model. If you want a single OpenAI-compatible endpoint that fronts 240+ models with automatic fallback when a provider is rate-limited, point ChatOpenAI at n4n.ai and pick any supported model name.

import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI

load_dotenv()

llm = ChatOpenAI(
    model="gpt-4o-mini",
    # base_url="https://api.n4n.ai/v1",  # uncomment to route via n4n.ai
    # api_key=os.getenv("N4N_API_KEY"),
    temperature=0,
)

Keep the model call at module level so nodes can reuse it. Don’t instantiate a new client per node; that wastes connections.

Define your graph state

LangGraph persists state between node runs. The state is a TypedDict; fields with Annotated get reducers.

from typing import TypedDict, Annotated
from langgraph.graph.message import add_messages

class AgentState(TypedDict):
    messages: Annotated[list, add_messages]
    iterations: int

Why a reducer matters

The add_messages reducer appends returned messages to the existing list instead of replacing it. Without it, every node must echo the full history back. The iterations field is a plain integer; returning it overwrites the prior value, which is what we want for a counter.

Write the node functions

Nodes are callables that accept the current state and return a partial update. They should be side-effect free aside from the LLM call.

from langchain_core.messages import HumanMessage, AIMessage

def generate(state: AgentState):
    response = llm.invoke(state["messages"])
    return {"messages": [response], "iterations": state["iterations"] + 1}

def critique(state: AgentState):
    crit_instruction = HumanMessage(
        content="Review the last AI response. Reply with 'PASS' if it is correct and concise, otherwise 'REVISE: <problem>'."
    )
    response = llm.invoke(state["messages"] + [crit_instruction])
    return {"messages": [response]}

Calling the LLM without inventing abstractions

Notice generate returns a list containing one AIMessage. The reducer extends the timeline. critique does not modify iterations; omitting a field leaves it unchanged. This explicitness is the point of LangGraph.

Debugging with a print node

Drop a tiny node to log state without altering flow.

def debug(state: AgentState):
    print("ITER", state["iterations"], "MSGS", len(state["messages"]))
    return {}

workflow.add_node("debug", debug)
workflow.add_edge("generate", "debug")
workflow.add_edge("debug", "critique")

In production replace print with structured logging, but the pattern of inserting an observability node is idiomatic.

Connect nodes with edges

A StateGraph declares topology. START and END are sentinels.

from langgraph.graph import StateGraph, START, END

workflow = StateGraph(AgentState)
workflow.add_node("generate", generate)
workflow.add_node("critique", critique)
workflow.add_node("debug", debug)

workflow.add_edge(START, "generate")
workflow.add_edge("generate", "debug")
workflow.add_edge("debug", "critique")

Conditional edges are where LangGraph earns its keep

After critique, branch. Stop if the critique passed or we hit a max loop count.

def should_continue(state: AgentState) -> str:
    last = state["messages"][-1]
    if isinstance(last, AIMessage) and last.content.strip().upper().startswith("PASS"):
        return END
    if state["iterations"] >= 3:
        return END
    return "generate"

workflow.add_conditional_edges(
    "critique",
    should_continue,
    {"generate": "generate", END: END}
)

app = workflow.compile()

The mapping dict must cover every string should_continue can return. Missing keys raise at compile time, not runtime—a good guardrail.

Compile and run

Invoke with an initial human message and a starting counter.

result = app.invoke({
    "messages": [HumanMessage(content="Explain what a mutex is in one sentence.")],
    "iterations": 0,
})

for msg in result["messages"]:
    print(f"{type(msg).__name__}: {msg.content[:80]}")

Expected output from the first run

A solid model usually passes on the first try:

HumanMessage: Explain what a mutex is in one sentence.
AIMessage: A mutex is a locking mechanism that ensures only one thread accesses a resource at a time.
AIMessage: PASS

If the answer is vague, critique returns REVISE: ... and the graph loops. The third message (the critique) stays in the list, so generate sees it and can self-correct. After three failed attempts, the iterations guard forces END.

Run asynchronously

In a server you shouldn’t block the event loop. LangGraph mirrors the invoke/ainvoke split from LangChain.

import asyncio

async def main():
    res = await app.ainvoke({
        "messages": [HumanMessage(content="What is RAII in C++?")],
        "iterations": 0,
    })
    print("final iterations:", res["iterations"])

asyncio.run(main())

Use ainvoke inside FastAPI or any async worker. The graph logic is identical; only the entrypoint changes.

Inspect the graph

LangGraph compiles to a runnable object with introspection.

print(app.get_graph().draw_mermaid())

You get a Mermaid snippet: START --> generate --> debug --> critique --> (generate|END). Paste it into any Mermaid viewer to confirm control flow. Do this before adding complexity.

Common pitfalls

  • Forgetting the reducer: returning {"messages": response} (not a list) throws or overwrites. Always wrap in a list.
  • Synchronous blocking: invoke blocks. In a web app use await app.ainvoke(...).
  • Case sensitivity: models emit pass, Pass, PASS. Normalize with .upper().
  • Uncovered branches: a conditional edge missing a mapping key fails at compile, but a function returning an unexpected string crashes at runtime. Add a default.
  • State drift: if you later add a Pydantic state, remember reducers still need Annotated. TypedDict is simpler for prototypes.

When not to use LangGraph

If your task is a single LLM call with no branching, LangGraph is overhead. Use llm.invoke directly. The graph pays off when you have loops, human-in-the-loop checkpoints, or multiple specialized nodes. Don’t reach for it to feel fashionable.

Extend the pattern

Replace critique with a tool-calling node: if the LLM requests a tool, execute it and append the result, then loop to generate. Or add a route node that sends coding questions to a code_gen node and factual ones to answer. LangGraph’s value is that every transition is visible in your code, not buried in a framework’s agent loop.

This LangGraph tutorial for beginners gave you the skeleton: state, nodes, conditional edges, and a compile step. Build from here with real tools and you have an agent you can actually debug in production.

Tagslanggraphai-agentstutoriallangchain

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All langgraph for agent workflows posts →