LangGraph Essentials
> LangGraph is a framework for building AI applications as explicit, stateful graphs instead of linear prompt chains. This notebook covers what problem it solves, its core building blocks, and a compact example of wiring up an agent that can loop, branch, and pause.
What is LangGraph?
LangGraph is a low-level orchestration library (built by the LangChain team) for building applications where an LLM's control flow needs to be more than "prompt in, answer out." Instead of chaining steps in a straight line, you model the application as a graph: nodes are units of work, edges define how control moves between them, and a shared state object flows through the whole run.
This matters because real agents rarely execute in a straight line. They call a tool, look at the result, decide whether to try again, ask a human for approval, or hand off to a different specialist. That kind of control flow is naturally cyclic — something a simple chain can't express, but a graph can.
Core characteristics:
Why Not Just a Simple Chain?
A chain (prompt → LLM → parse → done) is fine for single-shot tasks. It breaks down once you need:
LangGraph exists specifically for this class of problem — it's the layer above "just call the LLM API," used once an application's control flow becomes a graph rather than a line.
Core Concepts
TypedDict or Pydantic model) that represents everything the graph carries between steps: conversation history, tool results, counters, flags.Common Use Cases
Practical Example: A Tool-Calling Agent Loop
This sketch shows the shape of a minimal agent graph: an LLM node that may request a tool, a tool-execution node, and a conditional edge that loops back until the model is done calling tools.
from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, END
from langgraph.graph.message import add_messages
from langchain_core.messages import AnyMessage
class AgentState(TypedDict):
messages: Annotated[list[AnyMessage], add_messages]
def call_model(state: AgentState) -> dict:
response = llm_with_tools.invoke(state["messages"])
return {"messages": [response]}
def call_tools(state: AgentState) -> dict:
last_message = state["messages"][-1]
results = [run_tool(call) for call in last_message.tool_calls]
return {"messages": results}
def should_continue(state: AgentState) -> str:
last_message = state["messages"][-1]
return "tools" if last_message.tool_calls else END
graph = StateGraph(AgentState)
graph.add_node("agent", call_model)
graph.add_node("tools", call_tools)
graph.set_entry_point("agent")
graph.add_conditional_edges("agent", should_continue, {"tools": "tools", END: END})
graph.add_edge("tools", "agent") # loop back after tool execution
app = graph.compile(checkpointer=my_checkpointer)
result = app.invoke(
{"messages": [("user", "What's the weather in Jakarta, then convert it to Fahrenheit?")]},
config={"configurable": {"thread_id": "user-123"}},
)
The thread_id in the config is what makes this resumable — the checkpointer keys state by thread, so calling invoke again with the same thread_id continues the same conversation from where it left off.
Best Practices
When to Reach for LangGraph (and When Not To)
Good fit:
Overkill for: