深入浅出 LangGraph:从单 Agent 痛点走向多 Agent 复杂协作编排实战

一、 为什么我们需要多 Agent?

随着大模型应用的深入,复杂的 Agent 产品基本都是多 Agent 架构。我们在开发中经常会遇到单 Agent 带来的诸多痛点:

  1. 上下文开销巨大:单 Agent 架构下,所有 Tool 的描述、每个功能的 Prompt 都堆砌到 System Prompt 里。
  2. 信息干扰严重 :实际执行每个具体功能只需一部分 Prompt,但单 Agent 每次都要带上全部上下文。这不仅导致 Token 消耗更高 ,更致命的是大量的无关信息会导致模型思考效率降低且容易出错。
  3. 并发困难:单 LLM 只有一个"大脑",只能一步步思考,无法真正并行。

如何拆分成多 Agent?

每个子 Agent 只保留需要的 Prompt。执行具体功能时,消耗的 Token 更少,没有无关信息干扰,准确率自然更高。

多 Agent 的三大核心优势

  • 决策准确率高,Token 消耗更低:调用 LLM 次数虽然可能增多,但每次只带必要的最小 Prompt,没有冗余信息干扰,综合成本反而下降。
  • 并行思考与任务处理:主 Agent 下发任务,子 Agent 并行处理(如编程 Agent 写代码,测试 Agent 同步写测试代码 TDD),整体效率更高。
  • 多角色互相讨论,纠错能力更强:借鉴 AutoGen 的"法庭"模式,通过多角色辩论大幅提升结果可靠性。

Agent 公式 :Agent = LLM + Harness (tool + mcp + rag + skill + ...)。

二、 LangChain vs LangGraph:从线性到网状

在多 Agent 编排领域,我们的工具也在演进:

  • LangChain :擅长线性工作流编排(如基础模块:llm api、document loaders、splitter、embedding、vector store、output parser、memory)。
  • LangGraph :专为网状 工作流编排设计。它引入了 State(状态图),由工作节点 + 组织方式(API) 构成,完美支持复杂 Agent 协作。

LangGraph 核心概念

在 LangGraph 中,一切皆图(Graph):

  • 开始节点 (START) :初始化状态。
  • 工作节点 (Node) :职责是接收 State 并返回新的 State。
  • 连接工作节点 (Edge) :定义节点间的流转方向。
  • 分支与循环 (Conditional Edges) :根据 State 动态决定下一步走向。
  • 最终状态 (END) :工作流终止。

此外,持久化(Persistence)至关重要。为了应对 Agent 执行中断、失败、暂停或需要授权的情况,我们需要将 State 保存到内存(MemoryServer / MemorySaver)或数据库(SQLite, Redis)中,下次继续执行,避免从头重跑。


三、 LangGraph 实战演练

下面我们从基础到进阶,逐步实现 LangGraph 的四大核心能力。

1. 基础图构建与可视化 (basic-graph.mjs)

一切从最基础的 Graph 开始。我们需要定义状态(State)和节点(Node)。

javascript

javascript 复制代码
import {
  Annotation, // 注释 工作流的状态值的描述 数据 state
  END,        // 结束节点
  START,      // 开始节点
  StateGraph, // 状态图 流程编排器 节点的组织
} from '@langchain/langgraph';

// 状态声明,使用 Annotation 声明一个字段
const StateAnnotation = Annotation.Root({
  text: Annotation({ // 只有 text 字段
    // _prev 来到当前节点之前的状态,next 当前节点得到的状态
    // reducer 怎么处理状态的改变
    reducer: (_prev, next) => next, // js 数组reduce 消消乐 text状态如何变
    default: () => "", // 默认值
  })
});

// 定义节点
// 函数就是节点
// 返回值 是下一个节点的状态
// reducer 处理状态的改变
const step1 = (state) => ({text: `${state.text} -> step1`});
const step2 = (state) => ({text: `${state.text} -> step2`});

// 实例化图工作流编排器
const graph = new StateGraph(StateAnnotation)
  // 声明所有节点
  .addNode("step1", step1)
  .addNode("step2", step2)
  .addEdge(START, "step1")
  .addEdge("step1", "step2")
  .addEdge("step2", END)
  .compile(); // 编译工作流 执行

// 可视化的图
// mermaid 文本画图工具,简单的markdown,自动生成流程图
// 可视化整个节点流转关系。
const drawable = await graph.getGraphAsync();
const mermaid = drawable.drawMermaid({ withState: true });
console.log(mermaid);

2. 条件分支与路由 (conditional-routing.mjs)

在实际业务中,我们需要根据用户的输入分发给不同的节点处理(例如:数学计算 vs 日常闲聊)。

javascript

javascript 复制代码
import {
  Annotation, END, START, StateGraph
} from '@langchain/langgraph';

// 状态声明
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: () => "" }),
});

// 节点声明 走向可以选择的
const router = (state) => {
  const isMath = /[+-*/]/.test(state.query); // 匹配加减乘除任意一个运算符
  // 下一步怎么走?
  // 如果是数学问题,走math 节点,否则走chat 节点
  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)
  // 声明路由节点, name 节点的名称
  .addNode("router", router)
  .addNode("math", mathNode)
  .addNode("chat", chatNode)
  .addEdge(START, "router") // 走向固定
  // 条件跳转 state.route 值判断,math, chat
  // 分支条件
  .addConditionalEdges(
    "router",
    (state) => state.route,
    {
      math: "math",
      chat: "chat",
    },
  )
  .addEdge("math", END)
  .addEdge("chat", END)
  .compile(); // 编译状态图 执行

// 调用测试
console.log("result:", await graph.invoke({query: "你好"}));
console.log("result:", await graph.invoke({query: "1+2"}));

3. 循环与重试机制 (loop-retry.mjs)

Agent 经常会遇到失败重试的场景。利用条件边,我们可以轻松实现循环。

javascript

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. 状态持久化与多用户隔离 (checkpointer-memory.mjs)

如何让 Agent 记住上下文?如何区分不同用户的会话?LangGraph 提供了 MemorySaver。

javascript

javascript 复制代码
import {
  Annotation, END, START, MemorySaver, StateGraph
} from '@langchain/langgraph';

const StateAnnotation = Annotation.Root({
  // session 相关会话 某人 访问次数
  visitCount: Annotation({ reducer: (_prev, next) => next, default: () => 0 }),
  message: Annotation({ reducer: (_prev, next) => next, default: () => "" }),
})

// 计数的节点
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 // 挂载持久化
})

// 多用户隔离(通过 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. 中断与恢复:Human-in-the-loop (graph-interrupt.mjs)

对于敏感操作(如转账),我们需要人工确认。LangGraph 允许我们中断图执行,并在获取用户输入后恢复。

javascript

javascript 复制代码
import {
  Annotation, END, START, StateGraph, Command, interrupt, MemorySaver
} from '@langchain/langgraph';
import { createInterface } from 'node:readline/promises';

// graph 设计图
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 业务恢复执行
const rl = createInterface({
  input: process.stdin,
  output: process.stdout
});
const line = (await rl.question("> ")).trim();
console.log("你输入了", line);
await rl.close();

// 使用 Command 恢复执行,并传入用户输入
const done = await graph.invoke(new Command({resume: line}), config);
console.log("done:", done);

四、 总结

从 LangChain 的线性编排到 LangGraph 的网状编排,是 AI Agent 从"单兵作战"走向"团队协作"的必经之路。

通过 LangGraph,我们可以轻松实现:

  1. State 状态共享 :利用 Annotation.Root 和 reducer 精细化控制状态流转。
  2. 灵活的控制流 :通过条件边(addConditionalEdges)实现复杂的路由(Router)与循环(Loop/Retry)。
  3. 生产级的持久化 :结合 MemorySaver / SqliteSaver 和 thread_id,实现多用户会话隔离与断点续传。
  4. Human-in-the-loop :通过 interrupt() 和 Command(resume),在关键业务节点引入人工审核。

多 Agent 协作的大门已经打开,赶紧在你的业务中试试 LangGraph 吧!

相关推荐
ZzT2 小时前
OpenRig 拆解:用 YAML 把 Claude Code 和 Codex 编成一支团队
ai编程
MacroZheng2 小时前
这可能是Windows下最适合Claude Code/Codex的终端了,真的夯!
人工智能·后端·ai编程
郑州光合科技余经理3 小时前
出海外卖平台:支付对接为什么要和下单收款分开
android·java·开发语言·前端·后端·php·ai编程
星核0penstarry3 小时前
企业接入多家 AI API,聚合平台能解决什么、不能解决什么
人工智能·大模型·ai编程·合规·api聚合平台·大模型网关·接入层
乘风gg4 小时前
Spec Kit vs OpenSpec vs Superpowers:三大 AI Coding 框架深度对比
前端·ai编程·claude
To_OC14 小时前
被 Vibe Coding 反噬之后,我老老实实地回去写文档了
aigc·ai编程·vibecoding
李福春16 小时前
技术思考问题2:AI 编程高效的关键,是更强模型还是更清晰的问题定义?
ai编程·工程效能·vibe coding·深圳同盟·腾讯云架构师技术同盟·能力圈·检查清单
小虎AI生活16 小时前
AI 替掉重复劳动后,把人转向获客侧的实操方法(附提示词模板)
aigc·ai编程