【LangGraph 学习】下:Agent 与多智能体 — 从工具调用到调度员模式

2039 字
10 分钟
【LangGraph 学习】下:Agent 与多智能体 — 从工具调用到调度员模式

📌 系列简介:「LangGraph 学习路径」共三篇。上篇搭图、中篇加记忆和暂停,下篇引入 LLM 做决策——从单个 Agent 调用工具到多个 Agent 协同工作。 ⏱️ 预计阅读时间:20 分钟 💻 代码仓库AI-Journey-Fighting/examples/langgraph-test


Agent 与多智能体:从工具调用到调度员模式#

🗺️ 系列导航#

主题核心能力
上篇State · Node · Edge + 条件路由 + 回边从零搭图
中篇Checkpointer · interrupt · Human-in-the-loop记忆与暂停
下篇(本篇)ToolNode · createReactAgent · SupervisorAgent 与多智能体

📖 读这篇,你可以带走什么#

#你会学到对应概念
1Agent 的本质:LLM + 工具 + 图引擎的循环prebuilt-tool-node
2白盒 vs 黑盒两种 Agent 写法createReactAgent
3一条消息如何从”用户问题”变成”工具调用”再变成”回答”消息流追踪
4Supervisor 模式:一个调度员 + 多个专业子 Agentmulti-agent-supervisor
5接入 DeepSeek 的 4 个坑踩坑合集

写在前面#

上篇和中篇搭了图、加了记忆——但所有的判断逻辑都是硬编码的:正则、if/else、amount > 0

这篇引入 LLM,让它来决定什么时候调工具、什么时候够了。再往上,多个 Agent 各自带自己的 tools,由一个 Supervisor 统一调度。这才是”Agent”这个词真正的含义。


一、Agent 的本质:LLM + 工具 + 图引擎#

先跑起来#

import { tool } from "@langchain/core/tools";
import { z } from "zod";
import { ChatOpenAI } from "@langchain/openai";
const getProductStock = tool(
async ({ sku }) => getProductBySku(sku), // ① 实际执行函数
{
name: "get_product_stock", // ② AI 用的名字
description: "按 SKU 查商品名与库存", // ③ AI 读这个决定要不要调
schema: z.object({ sku: z.string() }), // ④ 参数类型
}
);

Tool = 给 AI 的一个带说明书的函数。 AI 读 description 自己决定要不要调、传什么参数。你的核心工作是写好 description——在生成式 AI 时代,注释比代码更重要

图结构:LLM ⇄ 工具循环#

START
[agent](LLM 思考:要不要调工具?)
◇ toolsCondition
├── 有 tool_calls → [tools](执行工具)→ 回 [agent] 🔄
└── 无 tool_calls → END ✅

完整消息流追踪#

以”SKU-003 还剩多少库存?“为例:

① [HumanMessage("SKU-003 还剩多少库存?")]
↓ agent 节点(LLM 分析:我需要查库存)
② [HumanMessage, AIMessage(tool_calls: get_product_stock("SKU-003"))]
↓ toolsCondition → 有 tool_calls → 去 tools
③ [HumanMessage, AIMessage, ToolMessage("USB-C 线缆, 库存 120")]
↓ 回到 agent(LLM 拿到数据,组织回答)
④ [HumanMessage, AIMessage, ToolMessage, AIMessage("USB-C 线缆库存120件")]
↓ toolsCondition → 无 tool_calls → END ✅

messages 是一个不断追加的数组。 这和上篇 checkpointer 的 visitCount 累加是一个模式——只是 reducer 从 (_prev, next) => next(覆盖)换成了 concat(追加数组)。


二、两种写法:白盒 vs 黑盒#

白盒 prebuilt-tool-node黑盒 prebuilt-agent
APIStateGraph + addNode + addEdgecreateReactAgent
图结构自己搭框架内部建好
可控度高(可插自定义节点)低(开箱即用)

白盒:手动搭 ReAct 图#

import { ToolNode, toolsCondition } from "@langchain/langgraph/prebuilt";
const toolNode = new ToolNode(tools);
const graph = new StateGraph(MessagesAnnotation)
.addNode("agent", agentFn)
.addNode("tools", toolNode)
.addEdge(START, "agent")
.addConditionalEdges("agent", toolsCondition, ["tools", END])
.addEdge("tools", "agent")
.compile();

三个内置组件:

组件做什么逻辑
ToolNode自动读取 tool_calls、执行工具、写回 ToolMessage95% 的场景通用
toolsCondition判断”最后一条消息有没有 tool_calls?“有 → tools,无 → END
createReactAgent上面所有组装代码一封封装一行搞定

前两篇学到的概念,全部在这里出场:

概念在这里
StateGraph + addNode注册 agent/tools 节点
addEdge(START, "agent")入口
addConditionalEdgestoolsCondition 条件路由
"tools" → "agent" 回边工具执行完回到 LLM 再思考
compile() + checkpointer可选传 checkpointer

黑盒:一行创建#

import { createReactAgent } from "@langchain/langgraph/prebuilt";
const agent = createReactAgent({
llm: model, // ★ 参数名 llm,不是 model
tools: [getProductStock],
systemPrompt: "你是仓库助手...",
checkpointer: new MemorySaver(),
});
const result = await agent.invoke({
messages: [new HumanMessage("SKU-002 还剩多少?")],
});

agent.invoke 背后做了什么? LLM 收到问题 → 判断需要调工具 → 返回 tool_call → ToolNode 执行 → 拿到真实数据 → LLM 再组织回答 → 结束。这和 model.invoke 的区别是质的:

方式问”SKU-003 还剩多少?“结果
model.invokeLLM 瞎猜或拒绝❌ “我没有实时数据”
agent.invokeLLM → 调工具 → 查真实数据 → 回复✅ “USB-C 线缆,库存 120 件”

三、Supervisor 模式:一个调度员 + 多个专业子 Agent#

前两篇的所有图都是”一个脑子”。真实业务需要多个专业 Agent 协作——天气 Agent 查天气,小知识 Agent 查百科。

┌── weather_agent(天气)
用户 → supervisor ─┼── trivia_agent(小知识)
└── ...

子 Agent 定义#

const weatherAgent = createReactAgent({
name: "weather_agent", // ★ 必须,supervisor 用它识别
llm: model,
tools: [lookupWeatherTool],
systemPrompt: "你只处理天气...",
});
const triviaAgent = createReactAgent({
name: "trivia_agent",
llm: model,
tools: [lookupCityTriviaTool],
systemPrompt: "你只讲城市小知识...",
});

Supervisor 定义#

const workflow = createSupervisor({
agents: [weatherAgent, triviaAgent], // 直接传实例
llm: model,
prompt: `你是调度员,只负责选人。
- 问天气 → 用 weather_agent
- 问小知识 → 用 trivia_agent`,
addHandoffBackMessages: false,
});

Supervisor 也是一个 LLM。 但它不做业务——它只通过 tool_calls 调用 handoff 工具(transfer_to_weather_agent / transfer_to_trivia_agent),把任务分给专业子 Agent。

执行过程#

👤 用户:成都今天天气怎么样?
🔧 supervisor 调用: transfer_to_weather_agent({}) ← 调度
📦 工具返回: Successfully transferred ← handoff 完成
🔧 weather_agent 调用: lookup_weather("成都") ← 子 Agent 开工
📦 工具返回: {"summary":"多云","tempHighC":27,...} ← 真实数据
💬 weather_agent 回复: 成都今天多云,22~27°C,空气质量中高度污染...

用 stream() 做步骤化展示#

const stream = await app.stream(
{ messages: [new HumanMessage(QUERY)] },
{ streamMode: "values" }
);
let prevMsgCount = 0;
for await (const event of stream) {
const msgs = event?.messages ?? [];
for (let i = prevMsgCount; i < msgs.length; i++) {
const m = msgs[i];
if (m.tool_calls?.length) console.log(` 🔧 调用: ${...}`);
else if (m._getType?.() === "tool") console.log(` 📦 工具返回: ${...}`);
else if (m._getType?.() === "ai") console.log(` 💬 回答: ${...}`);
}
prevMsgCount = msgs.length;
}

streamMode: "messages" 能做到更细粒度的打字机效果——每个 token 逐个吐。但 values 模式适合结构化展示,每一步的完整状态都在。


四、前端对照表(完整版)#

LangGraph前端
Annotation.Root()Redux Store Schema
NodeRedux Reducer
addEdge路由配置
addConditionalEdgesswitch-case
回边while 循环
compile({ checkpointer })configureStore({ middleware })
thread_idsessionStorage key
interrupt()async Generatoryield
Command({ resume })generator.next(value)
createReactAgent封装好的 SDK
agent ⇄ tools 循环LLM + Action + Reducer 的闭环
createSupervisorAPI Gateway / 微服务网关
streamMode: "values"WebSocket 推送完整状态

五、踩坑合辑#

本次学习从 8 个文件中踩了不少坑,最有价值的几个:

createAgent vs createReactAgent#

createAgent 来自 langchain(旧 API),参数名是 modelcreateReactAgent 来自 @langchain/langgraph/prebuilt(新 API),参数名是 llm。传给 createSupervisor 时,旧版用 .graph 属性,新版直接传实例。

AI 框架半年迭代 API 可能大变样。保留原始文件 + 创建 fix 版是一个好策略。

thinking 模型的 reasoning_content#

DeepSeek 的 thinking 模型(如 deepseek-v4-flash)会额外返回 reasoning_content 字段。LangGraph 内部传递消息时不保留这个字段,DeepSeek API 后续调用要求原样传回 → 400 错误。

修复:通过 modelKwargs: { thinking: { type: "disabled" } } 关闭思考模式,或用 deepseek-chat(非 thinking 模型)。

不同 Agent 实例不能共享 LLM#

Supervisor 和子 Agent 如果共享同一个 ChatOpenAI 实例,bindTools 会相互影响——Supervisor 的 handoff 工具泄漏到子 Agent 中,导致子 Agent 调用 transfer_to_weather_agent 而不是 lookup_weather

stream 事件格式随版本变化#

LangGraph v1.3+ 的 stream() 事件格式从数组 [mode, payload] 变成了对象 { nodeName: output }。踩这个坑时最快的方式是 console.log(Object.keys(event)) 直接看。


六、三篇总结#

核心能力从…到…
State/Node/Edge + 条件路由 + 回边从零到有图
Checkpointer + interrupt + 转账实战从无状态到有记忆
ToolNode + createReactAgent + Supervisor从硬编码到 LLM 决策

LangGraph 本质上就是一个声明式的图编排框架。 你定义节点(Node)和连线(Edge),它负责按拓扑顺序调度执行。

basic-graph.mjs(一条线)到 multi-agent-supervisor.mjs(调度员 + 多个 Agent),8 个文件完成了一次从”调一个 LLM”到”编排多个 LLM + 工具 + 人工”的进阶。这个进阶不是 API 的堆叠,而是思维模型的三次跃迁:函数调用 → 图编排 → Agent 团队。


下一步#

系列的下一步是 Multi-Agent 的进阶编排(串行/并行混合)和 SQLite 持久化。但在此之前,这 8 个文件已经覆盖了 LangGraph 的全部核心原语——掌握了这些,你就有了自己动手搭 Agent 的底气。

💡 所有示例代码在 AI-Journey-Fighting/examples/langgraph-test,每个文件都有对应的 learning 注释版。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

【LangGraph 学习】下:Agent 与多智能体 — 从工具调用到调度员模式
https://blog.fridolph.top/posts/2026-05-30__langgraph_3/
作者
Fridolph
发布于
2026-05-30
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
Fridolph
热爱 Coding、音乐和羽毛球的 90 后全栈工程师
公告
欢迎访问我的小站 ^_^ 我是昇哥,热爱Coding,喜爱音乐、羽毛球和摄影的 90后全栈工程师
分类
标签

文章目录