LangGraph Basics
- LangGraph models a workflow as a graph: a shared State, nodes (functions), and edges (flow).
- Conditional edges branch β that's how you build routers and agent loops.
- A tool-calling chatbot is just
LLM node β ToolNode, withtools_conditiondeciding when to stop.
Chains run in a straight line; LangGraph lets flow branch, loop, and hold state β which is what agents need. This page builds it up from a toy graph to a tool-using chatbot, one submodule per idea, ending with a cheat sheet.
Reading the diagram: top β a simple graph where a conditional edge randomly routes to
cricket or badminton. Bottom β a chatbot where tools_condition routes to a ToolNode
when the LLM asks for a tool, then loops the result back. Both share one State object.
State β the shared dataβ
State is a TypedDict that every node reads and updates. It's the graph's memory.
from typing_extensions import TypedDict
class State(TypedDict):
graph_info: str
Each node receives the state and returns a partial update; by default the returned value overrides that key.
Nodes β just Python functionsβ
A node takes the state and returns a dict updating one or more keys.
def start_play(state: State):
return {"graph_info": state["graph_info"] + " I am planning to play"}
def cricket(state: State):
return {"graph_info": state["graph_info"] + " Cricket"}
def badminton(state: State):
return {"graph_info": state["graph_info"] + " Badminton"}
Edges & conditional edges (routing)β
Normal edges connect nodes in order. A conditional edge calls a function that returns the name of the next node β this is branching / routing.
import random
from typing import Literal
def random_play(state: State) -> Literal["cricket", "badminton"]:
return "cricket" if random.random() > 0.5 else "badminton"
The router function decides the path at runtime based on the state.
Build, compile, invokeβ
Wire nodes and edges onto a StateGraph, mark START and END, then compile().
from langgraph.graph import StateGraph, START, END
graph = StateGraph(State)
graph.add_node("start_play", start_play)
graph.add_node("cricket", cricket)
graph.add_node("badminton", badminton)
graph.add_edge(START, "start_play")
graph.add_conditional_edges("start_play", random_play) # branch here
graph.add_edge("cricket", END)
graph.add_edge("badminton", END)
app = graph.compile()
app.invoke({"graph_info": "Hey My name is Krish"})
START feeds input in, END terminates, and compile() validates the structure. You can
render it as a Mermaid diagram with app.get_graph().draw_mermaid_png().
The add_messages reducerβ
For chat, state holds a growing list of messages β you want new messages appended, not
overwritten. The add_messages reducer does exactly that.
from typing import Annotated
from typing_extensions import TypedDict
from langchain_core.messages import AnyMessage
from langgraph.graph.message import add_messages
class State(TypedDict):
messages: Annotated[list[AnyMessage], add_messages] # append, don't replace
The Annotated[..., add_messages] part is the key difference from the toy graph β it tells
LangGraph to merge updates into the list.
A tool-calling chatbotβ
Put it together: an LLM bound to tools (arxiv, wikipedia, Tavily), a ToolNode to run them,
and tools_condition to route β call tools when the LLM requests them, otherwise finish.
from langgraph.graph import StateGraph, START, END
from langgraph.prebuilt import ToolNode, tools_condition
from langchain_community.tools import ArxivQueryRun, WikipediaQueryRun
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_groq import ChatGroq
tools = [ArxivQueryRun(...), WikipediaQueryRun(...), TavilySearchResults()]
llm_with_tools = ChatGroq(model="qwen-qwq-32b").bind_tools(tools)
def tool_calling_llm(state: State):
return {"messages": [llm_with_tools.invoke(state["messages"])]}
builder = StateGraph(State)
builder.add_node("tool_calling_llm", tool_calling_llm)
builder.add_node("tools", ToolNode(tools))
builder.add_edge(START, "tool_calling_llm")
builder.add_conditional_edges("tool_calling_llm", tools_condition) # tool call? β tools : END
builder.add_edge("tools", "tool_calling_llm") # loop the tool result back to the LLM
graph = builder.compile()
graph.invoke({"messages": HumanMessage(content="Recent AI news for March 3rd 2025")})
tools_condition is the prebuilt router: if the last AI message contains a tool call it goes
to the ToolNode, otherwise to END. The edge from tools back to the LLM is what makes it
a loop β the model sees each tool result and decides what to do next.
Cheat sheetβ
| Concept | Code |
|---|---|
| State | class State(TypedDict): ... |
| Message state | messages: Annotated[list[AnyMessage], add_messages] |
| Add node | graph.add_node("name", fn) |
| Straight edge | graph.add_edge("a", "b") |
| Branch / router | graph.add_conditional_edges("node", router_fn) |
| Tool loop | ToolNode(tools) + tools_condition + edge back to the LLM |
| Run | graph.compile().invoke({...}) |
- Forgetting
add_messageson the messages key β new messages overwrite the list instead of appending, and the conversation resets every step. - No edge from
toolsback to the LLM β the tool result never reaches the model, so it can't answer. - A conditional-edge function that returns something other than a valid node name β the graph won't know where to go.
- Forgetting to
compile()beforeinvokeβ you run the compiled app, not the builder.
Quick self-check
What are the three building blocks of a LangGraph?
State (shared TypedDict), nodes (functions that update state), and edges (flow between nodes, including conditional edges for branching).
Why use add_messages on the messages key?
It's a reducer that appends new messages to the list instead of overwriting it β so conversation history accumulates across steps.
What does tools_condition do?
It's a prebuilt router: if the latest AI message has a tool call it routes to the ToolNode, otherwise to END.
What makes the chatbot a loop rather than a straight line?
The edge from the tools node back to the LLM node β the model sees each tool result and can decide to call another tool or finish.
Related: Updated LangChain (v1) Β· Hybrid Search Β· Glossary