LangGraph in Depth: From Single-Agent Pain Points to Multi-Agent Orchestration in Practice
The article starts from the pain points of single agents—high context overhead, information interference, and the inability to run in parallel—and explains how splitting work across multiple agents can cut token consumption and improve accuracy. It also gives the formula: Agent = LLM + Harness.
Starting from the context and concurrency pain points of single agents, it walks through five runnable examples to explain LangGraph's state, routing, loops, persistence, and human-in-the-loop interrupts.
I. Why Do We Need Multiple Agents?
As large model applications go deeper, complex agent products are almost always built on multi-agent architectures. In development, we keep running into the pain points of a single agent:
- Huge context overhead: In a single-agent architecture, all tool descriptions and every feature's prompt get crammed into the system prompt.
- Severe information interference: Executing any specific feature actually needs only part of the prompt, but a single agent has to carry the entire context every time. This not only means higher token consumption; worse, a large amount of irrelevant information slows down the model's reasoning and makes it more error-prone.
- Hard to run concurrently: A single LLM has only one "brain" and can only think step by step, with no real parallelism.
How do we split it into multiple agents?
Each sub-agent keeps only the prompts it needs. When executing a specific feature, it consumes fewer tokens, with no irrelevant information getting in the way, so accuracy naturally goes up.
The three core advantages of multi-agent systems
- Higher decision accuracy, lower token consumption: The number of LLM calls may increase, but each call carries only the minimal necessary prompt, with no redundant information interfering, so the overall cost actually goes down.
- Parallel thinking and task processing: The main agent assigns tasks and sub-agents process them in parallel (e.g., a coding agent writes code while a testing agent writes test code in parallel, TDD-style), making the whole process more efficient.
- Multiple roles discuss with each other, improving error correction: Drawing on AutoGen's "courtroom" model, multi-role debate greatly improves the reliability of results.
Agent formula: Agent = LLM + Harness (tool + mcp + rag + skill + ...).
II. LangChain vs LangGraph: From Linear to Networked
In the field of multi-agent orchestration, our tools are evolving too:
- LangChain: Good at linear workflow orchestration (e.g., the basic modules: LLM API, document loaders, splitter, embedding, vector store, output parser, memory).
- LangGraph: Designed specifically for networked workflow orchestration. It introduces
State(state graph), made up of work nodes + an organizational structure (API), and fully supports complex agent collaboration.
LangGraph core concepts
In LangGraph, everything is a graph:
- Start node (START): Initializes the state.
- Work node (Node): Its job is to receive State and return a new State.
- Connecting work nodes (Edge): Defines the direction of flow between nodes.
- Branches and loops (Conditional Edges): Dynamically decides the next step based on State.
- Final state (END): The workflow terminates.
In addition, persistence is crucial. To handle cases where agent execution is interrupted, fails, pauses, or needs authorization, we need to save State to memory (MemoryServer / MemorySaver) or a database (SQLite, Redis), so execution can continue next time instead of rerunning from scratch.
III. LangGraph in Practice
Below, we'll go from basics to advanced and implement LangGraph's four core capabilities step by step.
1. Basic graph construction and visualization (basic-graph.mjs)
It all starts with the most basic Graph. We need to define the state (State) and nodes (Node).
javascript
import {
Annotation, // 注释 工作流的状态值的描述 数据 state
END, // 结束节点
START, // 开始节点
StateGraph, // 状态图 流程编排器 节点的组织
} from '@langchain/langgraph';
// State declaration: use Annotation to declare a field
const StateAnnotation = Annotation.Root({
text: Annotation({ // 只有 text 字段
// _prev is the state before arriving at the current node, next is the state the current node produces
// How the reducer handles state changes
reducer: (_prev, next) => next, // js 数组reduce 消消乐 text状态如何变
default: () => "", // 默认值
})
});
// Define nodes
// A function is a node
// The return value is the state of the next node
// The reducer handles state changes
const step1 = (state) => ({text: `${state.text} -> step1`});
const step2 = (state) => ({text: `${state.text} -> step2`});
// Instantiate the graph workflow orchestrator
const graph = new StateGraph(StateAnnotation)
// Declare all nodes
.addNode("step1", step1)
.addNode("step2", step2)
.addEdge(START, "step1")
.addEdge("step1", "step2")
.addEdge("step2", END)
.compile(); // 编译工作流 执行
// Visualize the graph
// Mermaid is a text-based diagramming tool—simple Markdown that auto-generates flowcharts
// Visualize the entire node transition flow
const drawable = await graph.getGraphAsync();
const mermaid = drawable.drawMermaid({ withState: true });
console.log(mermaid);
2. Conditional branching and routing (conditional-routing.mjs)
In real business scenarios, we need to dispatch user input to different nodes for handling (e.g., math calculations vs. casual chat).
javascript
import {
Annotation, END, START, StateGraph
} from '@langchain/langgraph';
// State declaration
const StateAnnotation = Annotation.Root({
query: Annotation({ reducer: (_prev, next) => next, default: () => "" }),
route: Annotation({ reducer: (_prev, next) => next, default: () => "chat" }),
answer: Annotation({ reducer: (_prev, next) => next, default: () => "" }),
});
// Node declaration with selectable paths
const router = (state) => {
const isMath = /[+-*/]/.test(state.query); // 匹配加减乘除任意一个运算符
// How do we decide the next step?
// If it’s a math problem, go to the math node; otherwise go to the chat node
return {
route: isMath ? "math" : "chat",
}
}
const mathNode = (state) => {
try {
return {
answer: String(eval(state.query)) // 匹配
}
} catch {
return {
answer: "表达式无法计算"
}
}
}
const chatNode = (state) => ({
answer: `你说的是:${state.query}`,
})
const graph = new StateGraph(StateAnnotation)
// Declare routing nodes, with name as the node’s name
.addNode("router", router)
.addNode("math", mathNode)
.addNode("chat", chatNode)
.addEdge(START, "router") // 走向固定
// Conditional jump based on the state.route value—math or chat
// Branch conditions
.addConditionalEdges(
"router",
(state) => state.route,
{
math: "math",
chat: "chat",
},
)
.addEdge("math", END)
.addEdge("chat", END)
.compile(); // 编译状态图 执行
// Run a test call
console.log("result:", await graph.invoke({query: "你好"}));
console.log("result:", await graph.invoke({query: "1+2"}));
3. Loops and retry mechanisms (loop-retry.mjs)
Agents often run into failure-and-retry situations. With conditional edges, we can easily implement loops.
javascript
import {
Annotation, END, START, StateGraph
} from '@langchain/langgraph';
const StateAnnotation = Annotation.Root({
tries: Annotation({ reducer: (_prev, next) => next, defaultValue: 0 }),
ok: Annotation({ reducer: (_prev, next) => next, default: () => false }),
message: Annotation({ reducer: (_prev, next) => next, default: () => "" }),
})
const attempt = (state) => {
const tries = state.tries + 1;
const ok = tries >= 3;
return {
tries,
ok,
message: ok ? `第${tries}次成功尝试` : `第${tries}次失败`,
}
}
const graph = new StateGraph(StateAnnotation)
.addNode("attempt", attempt)
.addEdge(START, "attempt")
.addConditionalEdges("attempt", (state) => state.ok ? "done" : "retry", {
retry: "attempt", // 循环回到 attempt 节点
done: END,
})
.compile();
console.log("result:", await graph.invoke({tries: 0}));
4. State persistence and multi-user isolation (checkpointer-memory.mjs)
How do we make an agent remember context? How do we distinguish between different users' sessions? LangGraph provides MemorySaver.
javascript
import {
Annotation, END, START, MemorySaver, StateGraph
} from '@langchain/langgraph';
const StateAnnotation = Annotation.Root({
// Session-related data—a given user’s visit count
visitCount: Annotation({ reducer: (_prev, next) => next, default: () => 0 }),
message: Annotation({ reducer: (_prev, next) => next, default: () => "" }),
})
// The counting node
function recordVist(state) {
const visitCount = state.visitCount + 1;
const message = visitCount === 1
? "这是你在本会话里第1次进入。"
: `这是你在本会话里第${visitCount}次进入。`;
return {
visitCount,
message,
}
}
const graph = new StateGraph(StateAnnotation)
.addNode("recordVist", recordVist)
.addEdge(START, "recordVist")
.addEdge("recordVist", END)
const checkpointer = new MemorySaver(); // 实例化一个内存保存器
const app = graph.compile({
checkpointer // 挂载持久化
})
// Multi-user isolation (distinguished by thread_id)
const user1 = { configurable: { thread_id: "用户-小张" } };
const user2 = { configurable: { thread_id: "用户-小李" } };
const res1 = await app.invoke({}, user1); // 小张第一次
console.log(res1);
const res2 = await app.invoke({}, user1); // 小张第二次
console.log(res2);
const res3 = await app.invoke({}, user1); // 小张第三次
console.log(res3);
const res4 = await app.invoke({}, user2); // 小李第一次,独立计数
console.log(res4);
5. Interrupts and Resumption: Human-in-the-loop (graph-interrupt.mjs)
For sensitive operations like money transfers, we need human confirmation. LangGraph lets us interrupt graph execution and resume it after getting user input.
javascript
import {
Annotation, END, START, StateGraph, Command, interrupt, MemorySaver
} from '@langchain/langgraph';
import { createInterface } from 'node:readline/promises';
// Graph design diagram
const StateAnnotation = Annotation.Root({
actionSummary: Annotation({ reducer: (_prev, next) => next, default: () => "" }),
userInput: Annotation({ reducer: (_prev, next) => next, default: () => "" }),
})
const showTransfer = () => ({
actionSummary: "向张三转账 $100"
})
const waitConfirm = (state) => {
const text = interrupt({ // 中断
hint: "终端里输入[确认]或者备注后回车,图才会继续",
actionSummary: state.actionSummary
});
return { userInput: String(text) }
}
const graph = new StateGraph(StateAnnotation)
.addNode("showTransfer", showTransfer)
.addNode("waitConfirm", waitConfirm)
.addEdge(START, "showTransfer")
.addEdge("showTransfer", "waitConfirm")
.addEdge("waitConfirm", END)
.compile({ checkpointer: new MemorySaver() })
const config = { configurable: { thread_id: "interrupt-demo" } };
const paused = await graph.invoke({}, config);
console.log("待你确认", paused.__interrupt__?.[0]?.value);
// Node resumes business execution
const rl = createInterface({
input: process.stdin,
output: process.stdout
});
const line = (await rl.question("> ")).trim();
console.log("你输入了", line);
await rl.close();
// Use Command to resume execution and pass in user input
const done = await graph.invoke(new Command({resume: line}), config);
console.log("done:", done);
IV. Summary
Going from the linear orchestration of LangChain to the mesh-like orchestration of LangGraph is the path AI agents must take to move from “solo operations” to “team collaboration.”
With LangGraph, we can easily pull off:
- Shared state: Use
Annotation.Rootandreducerto control state transitions with fine granularity. - Flexible control flow: Use conditional edges (
addConditionalEdges) to implement complex routing (Router) and loops (Loop/Retry). - Production-grade persistence: Combine
MemorySaver/SqliteSaverwiththread_idto isolate sessions across users and resume from checkpoints. - Human-in-the-loop: Use
interrupt()andCommand(resume)to bring human review into critical business nodes.
The door to multi-agent collaboration is open—go try LangGraph in your own workflows!
Source: Juejin · AI coding · juejin.cn