BUILD · Jun 1, 2026

LangGraph tutorial for beginners: build your first workflow

A hands-on tutorial for building your first LangGraph agent. state graphs, nodes, edges, and how to deploy it. No prior LangChain experience required.

Agent-ready: drop this post into Claude Code or Codex

The LangGraph documentation defines state graphs as the core abstraction: nodes represent LLM calls or tool executions, edges define control flow. This architecture enables both simple sequences and complex agent loops.

TL;DR: LangGraph uses directed graphs (nodes + edges) instead of linear chains, making it ideal for agent workflows with branching and cycles. This tutorial builds a customer query agent with conditional routing and human-in-the-loop: all from scratch without LangChain experience.

LangGraph keeps getting recommended everywhere. Every AI engineer I follow on X talks about it. But every tutorial I found assumed I already knew LangChain.

I didn’t. And you probably don’t need to either.

Here’s the thing about LangGraph: its core idea, state graphs, is simpler than LangChain’s chain-of-thought abstraction. A graph with nodes and edges is easier to reason about than a pipeline that chains abstractions on top of abstractions.

This tutorial walks through building a LangGraph agent from scratch. No LangChain experience required. We’ll build a customer query agent that classifies intent, retrieves information, and formulates a response: with conditional branching and human-in-the-loop.

Key takeaways:

  • LangGraph uses directed graphs (nodes + edges), not linear chains: better for agent workflows with branching and cycles
  • The state schema is the contract for your graph: define it carefully
  • Conditional routing lets your agent make decisions about which path to follow
  • Human-in-the-loop is a first-class concept, not an afterthought

What is LangGraph?

LangGraph is a framework for building stateful, multi-step agent workflows using directed graphs.

Think of it as a state machine for LLMs. You define:

  • State: the data that flows through your workflow (messages, tool results, decisions)
  • Nodes: functions that process the state (LLM calls, tool execution, data transformation)
  • Edges: connections between nodes that define the flow
  • Conditional edges: functions that decide which node to go to next based on state

The key insight: agent workflows aren’t linear. An LLM calls a tool, gets a result, decides what to do next, possibly calls another tool, possibly returns a final answer. You can model this as a chain, but a graph is more natural.

Why graphs over chains?

Before LangGraph, most agent frameworks used chains: linear sequences of operations. A chain looks like this:

User Input → Classify Intent → Retrieve Info → Generate Response

This works for simple flows. But agents need branching:

User Input → Classify Intent → Needs more info? → Retrieve Info → Generate Response
 → Simple question? → Generate Response
 → Escalate? → Transfer to Human

A chain handles this poorly, you end up with nested conditionals, complex routing logic, and a system that’s hard to debug. A graph handles it naturally, each branch is just a different path through the nodes.

How do I build my first LangGraph state graph?

Let’s build a customer query agent step by step.

Step 1: Install LangGraph

pip install langgraph langchain-anthropic

That’s it. Two dependencies. You don’t need the full LangChain suite.

Step 2: Define your state schema

The state is a typed dictionary that defines what flows through your graph:

from typing import TypedDict, List, Optional, Literal
from langgraph.graph import StateGraph, END

class AgentState(TypedDict):
 messages: List[dict] # The conversation so far
 intent: Optional[str] # Classified intent: "billing", "technical", "general"
 retrieved_info: Optional[str] # Retrieved information from knowledge base
 needs_escalation: bool # Whether to escalate to a human
 final_response: Optional[str] # The final response to the user

This is the contract for your graph. Every node reads from and writes to this state.

Step 3: Add nodes

Nodes are functions that take the state and return updates:

from langchain_anthropic import ChatAnthropic

llm = ChatAnthropic(model="claude-sonnet-4-20250514")

def classify_intent(state: AgentState) -> dict:
 """Classify the user's intent based on their message."""
 response = llm.invoke([
 {"role": "system", "content": "Classify the intent as: billing, technical, or general. Return just the label."},
 {"role": "user", "content": state["messages"][-1]["content"]}
 ])
 return {"intent": response.content.strip().lower()}

def retrieve_information(state: AgentState) -> dict:
 """Retrieve information from the knowledge base based on intent."""
 # Simple mock retrieval: in production, use vector search
 knowledge_base = {
 "billing": "Our billing cycle is monthly. Payments are processed on the 1st.",
 "technical": "API documentation is at docs.example.com. Rate limit: 100/min.",
 "general": "We're available 24/7 via email or chat. Typical response time: 2hrs."
 }
 info = knowledge_base.get(state["intent"], "No specific information found.")
 return {"retrieved_info": info}

def generate_response(state: AgentState) -> dict:
 """Generate a final response based on intent and retrieved info."""
 response = llm.invoke([
 {"role": "system", "content": f"You are a support agent. Use this info: {state['retrieved_info']}"},
 {"role": "user", "content": state["messages"][-1]["content"]}
 ])
 return {"final_response": response.content}

Each node returns a dictionary of state updates. LangGraph merges these into the main state.

Step 4: Define edges

Now connect the nodes. In LangGraph, you add nodes, then add edges between them:

# Create the graph
workflow = StateGraph(AgentState)

# Add nodes
workflow.add_node("classify", classify_intent)
workflow.add_node("retrieve", retrieve_information)
workflow.add_node("generate", generate_response)

# Add edges
workflow.set_entry_point("classify")
workflow.add_edge("classify", "retrieve")
workflow.add_edge("retrieve", "generate")
workflow.add_edge("generate", END)

This gives you a linear graph: classify → retrieve → generate → end.

Step 5: Compile and run

app = workflow.compile()

result = app.invoke({
 "messages": [{"role": "user", "content": "My API key stopped working suddenly."}],
 "intent": None,
 "retrieved_info": None,
 "needs_escalation": False,
 "final_response": None
})

print(result["final_response"])

When you run this, the graph executes: classify intent → retrieve info → generate response. Each node updates the state, and the final state contains the result.

How do I add conditional branching to LangGraph?

Linear graphs are fine, but the real power is conditional edges: edges that decide which node to go to next based on the state.

Let’s add escalation logic:

def should_escalate(state: AgentState) -> Literal["escalate", "retrieve"]:
 """Decide whether to escalate or retrieve info."""
 # Escalate if the user sounds frustrated or the issue is critical
 response = llm.invoke([
 {"role": "system", "content": "Does this message require escalation? Keywords: 'frustrated', 'cancel', 'refund', 'manager'. Answer 'yes' or 'no'."},
 {"role": "user", "content": state["messages"][-1]["content"]}
 ])
 if "yes" in response.content.lower():
 return "escalate"
 return "retrieve"

def escalate_to_human(state: AgentState) -> dict:
 """Transfer the conversation to a human agent."""
 return {
 "final_response": "I understand you need additional help. I'm transferring you to a human agent who can resolve this.",
 "needs_escalation": True
 }

# Add the escalation node
workflow.add_node("escalate", escalate_to_human)

# Replace the simple edge with a conditional edge
workflow.add_conditional_edges(
 "classify",
 should_escalate,
 {"escalate": "escalate", "retrieve": "retrieve"}
)

workflow.add_edge("escalate", END)

Now the graph decides: after classification, should it escalate or retrieve? The conditional function should_escalate examines the state and returns the next node name.

How do I add human-in-the-loop to LangGraph?

One of LangGraph’s best features is first-class human-in-the-loop support. You can mark a node as interrupt_before, which pauses execution before that node runs:

# Compile with interrupt before the escalate node
app = workflow.compile(interrupt_before=["escalate"])

When the graph reaches the escalate node, it pauses and returns control:

# Run the graph
thread = {"configurable": {"thread_id": "1"}}

# First run: might hit the interrupt
result = app.invoke({
 "messages": [{"role": "user", "content": "I want a refund. This is ridiculous."}],
 # .. other initial state
}, thread)

# Check if interrupted
if app.get_state(thread).next:
 # Prompt the human agent for approval
 print(f"Awaiting human approval for: {result.get('intent')}")
 approval = input("Approve escalation? (y/n): ")

 if approval.lower() == "y":
 # Resume the graph
 result = app.invoke(None, thread)
 else:
 # Update state and continue without escalation
 app.update_state(thread, {"intent": "general"})
 result = app.invoke(None, thread)

This pattern is powerful. Your agent runs autonomously for standard flows, pauses for approval on sensitive actions, and continues once the human approves or modifies the state.

How do I add tools to my LangGraph agent?

Let’s add real tool execution. Here’s a weather agent with a search tool:

from langgraph.prebuilt import ToolNode
from langchain_core.tools import tool

@tool
def get_weather(location: str) -> str:
 """Get the current weather for a location."""
 # In production, call a weather API
 return f"25°C, partly cloudy in {location}"

@tool
def search_docs(query: str) -> str:
 """Search the documentation for information."""
 # In production, use vector search
 return f"Found relevant docs for: {query}"

# Add tools to the LLM
llm_with_tools = llm.bind_tools([get_weather, search_docs])

def agent_node(state: AgentState) -> dict:
 """The main agent node. LLM decides to use tools or respond."""
 response = llm_with_tools.invoke(state["messages"])
 return {"messages": state["messages"] + [response]}

# Tool node executes any tool calls the LLM generated
tool_node = ToolNode([get_weather, search_docs])

# Define routing: after agent, check if tools were called
def should_continue(state: AgentState) -> Literal["tools", "generate"]:
 messages = state["messages"]
 last_message = messages[-1]
 if last_message.tool_calls:
 return "tools"
 return "generate"

# Build the graph
workflow = StateGraph(AgentState)
workflow.add_node("agent", agent_node)
workflow.add_node("tools", tool_node)
workflow.add_node("generate", generate_response)

workflow.set_entry_point("agent")
workflow.add_conditional_edges("agent", should_continue, {
 "tools": "tools",
 "generate": "generate"
})
workflow.add_edge("tools", "agent") # Loop back to agent after tools
workflow.add_edge("generate", END)

The graph now loops: agent calls LLM → if tools are called, execute them and loop back → if no tools called, generate final response.

This is the pattern for most production agents: an agent node that can use tools, a tool execution node, and conditional routing that loops until the task is complete.

How do I deploy a LangGraph agent?

LangGraph compiles to a callable, so you can deploy it with any web framework:

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()
graph = workflow.compile()

class Query(BaseModel):
 message: str
 thread_id: str

@app.post("/agent")
def run_agent(query: Query):
 result = graph.invoke(
 {"messages": [{"role": "user", "content": query.message}]},
 {"configurable": {"thread_id": query.thread_id}}
 )
 return {"response": result.get("final_response")}

Deploy this on Railway, Fly.io, or any cloud provider. Cost: about ₹500–₹1000/month for a low-traffic agent.

When should I use LangGraph vs plain LangChain?

If you’ve used LangChain, here’s the key difference: LangChain forces a linear pipeline. You use Chain objects that pass through a sequence of operations. Adding branching means nested chains or custom RunnableLambda logic.

LangGraph lets you express the workflow as a graph. Branching is just a conditional edge. Loops are just edges back to a previous node. State is explicit: you define exactly what flows through the graph.

The result: LangGraph agents are easier to debug. When something goes wrong, you inspect the state at each node. You can see exactly what happened, what the LLM returned, and which branch the graph took.


Related: Best AI agent frameworks for 2026: how LangGraph compares to CrewAI, AutoGen, and custom builds.

Also: How to build your first AI agent from scratch: the fundamentals before frameworks.

Related: Building an AI code review agent: lessons from production: how to build a production code review agent with LangGraph, including architecture and failure modes.

Start small

Don't build a complex graph on your first try. Start with 3 nodes and a single conditional edge. Get that working. Then add the human-in-the-loop. Then add tools. Each layer adds complexity: make sure the base is solid before you stack more on top.

FAQ

What is LangGraph and how is it different from LangChain? LangGraph is a framework for building stateful, multi-step agent workflows using directed graphs. LangChain is a linear chain-of-thought framework. LangGraph lets you build conditional branching, cycles, and human-in-the-loop patterns that LangChain can’t express naturally.

Do I need to know LangChain before learning LangGraph? No. LangGraph works with any LLM provider directly through their API. You don’t need LangChain’s chain abstractions. LangGraph uses its own state graph model that’s closer to a state machine than LangChain’s sequential pipeline.

What’s the simplest LangGraph agent I can build? A basic weather agent: define a state schema, add a tool-calling node, add a routing node that checks for tool calls, compile the graph, and run it. That’s about 30 lines of code.

How does human-in-the-loop work in LangGraph? LangGraph supports interrupt nodes: when the graph reaches a node marked with interrupt_before, it pauses execution and returns control to your application. You can resume with user input, approval, or modified state.


This article was published on Agentic Up (https://agenticup.dev): practical guides for developers and founders building with AI agents. Reach me at hello@agenticup.dev.

Newsletter

Get the brief on AI agents

Practical posts on shipping agents, automating work, and building in public. No hype, no fluff.

Contact: hello@agenticup.dev