模块 05 - Agent 架构 | 前置知识:createAgent 入门、LangGraph 入门
单 Agent 撑不住的时候
我做过一个客服 Agent,刚开始只挂了 5 个工具:查订单、查物流、申请退款、查 FAQ、转人工。System prompt 短小精悍,效果不错。
三个月后业务方提了一堆新需求:技术支持、营销活动、会员权益、售后保修。我把工具加到 20 个,prompt 写到 800 字。模型开始犯各种错——把营销活动当成会员权益、技术支持的步骤跟售后保修混在一起,整体准确率下降 15%。
这就是单 Agent 的天花板:prompt 越长指令越冲突,工具越多选错越多。继续往里塞越塞越坏。
Multi-Agent 把这套东西拆开:每个 Agent 只懂一个领域、只挂 3-5 个工具、prompt 在 200 字以内。然后用一个调度层把请求路由到对的专家那儿。这跟微服务把单体应用拆开是一个道理。
三种协作模式
LangGraph 1.x 里多 Agent 落地有三种典型拓扑:
Supervisor 模式
中心化调度——所有请求先到 Supervisor,由它决定派给哪个专家,专家干完回到 Supervisor,由它决定是再派一个还是给用户回复。
优点:流程清晰、容易加监控、专家之间不互相打架。 缺点:Supervisor 是单点瓶颈。
Hierarchical 模式
Supervisor 之上还有 Supervisor。适合规模再上一个量级的场景——一个 Supervisor 协调不过来 20 个 Agent,就拆成多个领域 Supervisor,再上面挂个顶层调度。
优点:扩展性好。 缺点:层级越多延迟越高,调试越累。
Swarm 模式
去中心化——Agent 之间直接 handoff,没有 Supervisor。每个 Agent 都知道”什么情况下该把控制权交给谁”。
优点:无单点,路径灵活。 缺点:决策路径难以追踪,全局最优很难保证。
实战建议:从 Supervisor 起步,规模上来再拆成 Hierarchical,Swarm 慎用——除非你真的需要”任意两个 Agent 都能直接转交”的灵活度,否则它会让系统的可预测性大幅下降。
用 createAgent + Command 实现路由
LangGraph 1.x 的 Command 是多 Agent 路由的核心原语。一个节点返回 new Command({ goto: "其他节点名", update: { ... } }) 就能跳转——比传统的”返回 state + 用条件边判断”更直接。
下面是 Supervisor 模式的最小实现。场景:一个客服分诊 Agent,根据用户问题路由到三个专科 Agent。
各专科 Agent
每个专科 Agent 都是一个独立的 createAgent,挂自己领域的工具:
// multi-agent.ts
import { createAgent } from "langchain";
import { ChatAnthropic } from "@langchain/anthropic";
import { tool } from "@langchain/core/tools";
import { z } from "zod";
// 工具:订单查询
const queryOrder = tool(
async ({ orderId }) => `订单 ${orderId} 状态:已发货,预计明天送达`,
{
name: "query_order",
description: "根据订单号查询订单状态、物流",
schema: z.object({ orderId: z.string() }),
}
);
// 工具:退款发起
const initiateRefund = tool(
async ({ orderId, reason }) =>
`订单 ${orderId} 退款已发起(原因:${reason}),3-5 个工作日到账`,
{
name: "initiate_refund",
description: "为指定订单发起退款",
schema: z.object({ orderId: z.string(), reason: z.string() }),
}
);
// 工具:技术故障排查
const diagnose = tool(
async ({ symptom }) =>
`针对症状"${symptom}"的建议:1) 重启设备 2) 检查网络 3) 重置应用缓存`,
{
name: "diagnose",
description: "诊断常见技术故障并给出排查步骤",
schema: z.object({ symptom: z.string() }),
}
);
// 三个专科 Agent
const orderAgent = createAgent({
model: new ChatAnthropic({ model: "claude-haiku-4-5", temperature: 0 }),
tools: [queryOrder],
systemPrompt: "你是订单专员。只回答订单查询、物流追踪相关问题。",
name: "order_agent",
});
const refundAgent = createAgent({
model: new ChatAnthropic({ model: "claude-sonnet-4-6", temperature: 0 }),
tools: [initiateRefund],
systemPrompt:
"你是退款专员。处理退款申请前,先确认订单号和退款原因,然后调用工具发起。",
name: "refund_agent",
});
const techAgent = createAgent({
model: new ChatAnthropic({ model: "claude-sonnet-4-6", temperature: 0 }),
tools: [diagnose],
systemPrompt:
"你是技术支持专员。先询问具体故障现象,再调用 diagnose 给出排查步骤。",
name: "tech_agent",
});注意几点:
- 每个 Agent 都用了
name字段——多 Agent 协作时必填,否则 LangSmith trace 里分不清谁是谁 - 简单查询(订单)用 Haiku 4.5 省钱,复杂决策(退款、技术)用 Sonnet 4.6
- 每个 Agent 的 prompt 只描述自己领域,不超过 50 字
Supervisor 节点
Supervisor 本身是一个用 withStructuredOutput 的路由模型,不是 createAgent——它不需要工具,只需要从用户消息里挑一个目标 Agent:
import {
StateGraph,
START,
END,
MessagesAnnotation,
Command,
} from "@langchain/langgraph";
const routeSchema = z.object({
next: z
.enum(["order_agent", "refund_agent", "tech_agent", "FINISH"])
.describe("路由目标,FINISH 表示直接给用户回复"),
reason: z.string().describe("路由理由(用于日志)"),
});
const supervisorModel = new ChatAnthropic({
model: "claude-sonnet-4-6",
temperature: 0,
}).withStructuredOutput(routeSchema);
async function supervisorNode(state: typeof MessagesAnnotation.State) {
const decision = await supervisorModel.invoke([
{
role: "system",
content: `你是客服分诊主管。根据用户的最新问题,路由到合适的专员:
- order_agent:订单查询、物流追踪
- refund_agent:退款、退货
- tech_agent:技术故障、使用问题
- FINISH:问题已解决,可以给用户最终回复
如果同一问题已经被某个专员回答过且用户没有追问,选 FINISH。`,
},
...state.messages.map((m) => ({
role: m.getType() === "human" ? ("user" as const) : ("assistant" as const),
content: typeof m.content === "string" ? m.content : "",
})),
]);
console.log(`[supervisor] → ${decision.next} (${decision.reason})`);
if (decision.next === "FINISH") {
return new Command({ goto: END });
}
return new Command({ goto: decision.next });
}Command({ goto: ... }) 是 LangGraph 1.x 多 Agent 路由的标准写法——节点直接告诉 graph 下一步去哪,比”返回 state + 在条件边里判断”少写一层。
专科 Agent 节点 wrapper
每个 createAgent 都是独立的子图,要塞进 supervisor graph 时包一层——调子 Agent、把结果合并回主 state、跳回 supervisor:
async function runOrderAgent(state: typeof MessagesAnnotation.State) {
const result = await orderAgent.invoke({ messages: state.messages });
// 只把新产生的消息追加回主 state,不重复已有消息
const newMessages = result.messages.slice(state.messages.length);
return new Command({
goto: "supervisor",
update: { messages: newMessages },
});
}
async function runRefundAgent(state: typeof MessagesAnnotation.State) {
const result = await refundAgent.invoke({ messages: state.messages });
const newMessages = result.messages.slice(state.messages.length);
return new Command({
goto: "supervisor",
update: { messages: newMessages },
});
}
async function runTechAgent(state: typeof MessagesAnnotation.State) {
const result = await techAgent.invoke({ messages: state.messages });
const newMessages = result.messages.slice(state.messages.length);
return new Command({
goto: "supervisor",
update: { messages: newMessages },
});
}组图
import { MemorySaver } from "@langchain/langgraph";
const graph = new StateGraph(MessagesAnnotation)
.addNode("supervisor", supervisorNode, {
ends: ["order_agent", "refund_agent", "tech_agent", END],
})
.addNode("order_agent", runOrderAgent, { ends: ["supervisor"] })
.addNode("refund_agent", runRefundAgent, { ends: ["supervisor"] })
.addNode("tech_agent", runTechAgent, { ends: ["supervisor"] })
.addEdge(START, "supervisor");
const app = graph.compile({ checkpointer: new MemorySaver() });注意 addNode 第三个参数 { ends: [...] }——告诉 LangGraph 这个节点可能跳到哪些其他节点。这是 1.x 用 Command 路由时必须显式声明的,否则 graph 静态检查通不过。
跑一次
const cfg = { configurable: { thread_id: "customer-001" } };
const result = await app.invoke(
{
messages: [
{
role: "user",
content: "我订单 ORD-1234 还没收到,帮我查一下",
},
],
},
cfg
);
// 看完整对话
for (const m of result.messages) {
const tag = m.getType() === "human" ? "用户" : `AI (${m.name ?? "?"})`;
const text = typeof m.content === "string" ? m.content : "";
console.log(`[${tag}] ${text}`);
}输出大致这样:
[supervisor] → order_agent (查询订单物流)
[用户] 我订单 ORD-1234 还没收到,帮我查一下
[AI (order_agent)] 订单 ORD-1234 状态:已发货,预计明天送达
[supervisor] → FINISH (订单状态已告知)继续追问退款:
const r2 = await app.invoke(
{ messages: [{ role: "user", content: "那我不要了,能退款吗" }] },
cfg
);
// supervisor 会路由到 refund_agent完整路径:supervisor → refund_agent → supervisor → FINISH。
Hierarchical:多层 Supervisor
业务再大就分领域。下面只给骨架——每个领域 supervisor 内部就是上面那套:
// 售前 supervisor 子图
const presaleGraph = new StateGraph(MessagesAnnotation)
.addNode("supervisor", presaleSupervisorNode, { ends: ["guide", "config", END] })
.addNode("guide", runGuideAgent, { ends: ["supervisor"] })
.addNode("config", runConfigAgent, { ends: ["supervisor"] })
.addEdge(START, "supervisor");
const presale = presaleGraph.compile();
// 售后 supervisor 子图
const aftersaleGraph = new StateGraph(MessagesAnnotation)
.addNode("supervisor", aftersaleSupervisorNode, { ends: ["refund", "warranty", END] })
.addNode("refund", runRefundAgent, { ends: ["supervisor"] })
.addNode("warranty", runWarrantyAgent, { ends: ["supervisor"] })
.addEdge(START, "supervisor");
const aftersale = aftersaleGraph.compile();
// 顶层 supervisor 把请求路由到子图
const topGraph = new StateGraph(MessagesAnnotation)
.addNode("top", topSupervisorNode, { ends: ["presale", "aftersale", END] })
.addNode("presale", async (state) => {
const r = await presale.invoke({ messages: state.messages });
return new Command({ goto: "top", update: { messages: r.messages.slice(state.messages.length) } });
}, { ends: ["top"] })
.addNode("aftersale", async (state) => {
const r = await aftersale.invoke({ messages: state.messages });
return new Command({ goto: "top", update: { messages: r.messages.slice(state.messages.length) } });
}, { ends: ["top"] })
.addEdge(START, "top");子图作为节点嵌进主图——这是 LangGraph 最强大的能力之一。每个子图可以独立测试、独立部署、独立调优。
Swarm:handoff 自由传递
Swarm 模式取消 Supervisor,每个 Agent 自己决定 handoff 给谁。实现思路是给每个 Agent 提供一组 “handoff 工具”——这些工具的作用就是返回一个 Command 跳到目标 Agent:
function createHandoffTool(targetAgent: string, description: string) {
return tool(
async (_input, config) => {
// 工具返回值不重要,关键是抛出 Command 让 graph 跳转
// 1.x 的 ToolNode 会识别返回的 Command 并执行路由
return new Command({
goto: targetAgent,
update: {
messages: [
{
role: "tool",
content: `已转接到 ${targetAgent}`,
// 此字段来自 LangGraph 工具调用上下文,如遇 undefined 检查 LangGraph 版本
tool_call_id: config?.toolCall?.id,
},
],
},
});
},
{
name: `handoff_to_${targetAgent}`,
description,
schema: z.object({}),
}
);
}
const handoffToRefund = createHandoffTool("refund_agent", "把对话转交给退款专员");
const handoffToTech = createHandoffTool("tech_agent", "把对话转交给技术专员");
const orderAgent = createAgent({
model: new ChatAnthropic({ model: "claude-haiku-4-5" }),
tools: [queryOrder, handoffToRefund, handoffToTech],
systemPrompt:
"你是订单专员。如果用户问的是退款,调 handoff_to_refund;如果问技术问题,调 handoff_to_tech;否则用 query_order 自己处理。",
name: "order_agent",
});Swarm 的隐患:模型可能”踢皮球”——A 转给 B,B 又转回 A。生产用 Swarm 必须监控 handoff 链长度,超过 3 次强制结束。
实战建议
| 场景 | 推荐模式 |
|---|---|
| < 5 个工具、单一领域 | 不上多 Agent,单个 createAgent 够用 |
| 5-15 个工具、2-4 个领域 | Supervisor |
| 多领域、每领域有多专家 | Hierarchical |
| 工作流灵活多变、专家之间频繁互转 | Swarm(慎用) |
几条硬规则:
- 每个专科 Agent 工具数控制在 3-5 个。超过就该再拆
- Agent 之间通过 messages 通信,不要直接共享 state——业务字段(订单 ID、用户身份)放在 message content 或单独 state 字段,不要塞 prompt
- 加
recursionLimit——多 Agent 嵌套时 25 步上限很容易撞到,按需提到 50-100 - 每个 Agent 必填
name,否则 LangSmith trace 看不清是谁在说话 - 从最简单的 Supervisor 起步,业务跑通再考虑 Hierarchical
小结
Multi-Agent 把单 Agent prompt 过载、工具过多的问题拆解掉——每个专家 Agent 只做一件事,由一个 Supervisor 路由。LangGraph 1.x 用 Command({ goto, update }) 实现节点级路由,比”返回 state + 条件边”更直接。Supervisor 起步、Hierarchical 扩展、Swarm 慎用。每个 Agent 工具数控制在 3-5 个,name 必填,recursionLimit 调高。
下一节 Human-in-the-Loop 与 typed interrupt 给多 Agent 加上人工审批断点——在高风险操作(退款、删数据)执行前必须经过人审。
本文摘自《LangChain.js Agent 开发权威指南》,作者递归客。
本书资源
- 源码仓库 · github.com/diguike/book-langchain-agent
- 在线阅读 · inferloop.dev/langchain-agent
- 所有书目 · inferloop.dev
继续阅读 · 同作者其他书
- 《Transformer 工程实战》从注意力机制到生产部署
- 《自己动手写 AI Agent》从 Claude Code 开源架构到你的第一个编程助手
- 《AI 时代的 CLI 工具开发实战》用 TypeScript 构建现代 CLI 工具
- 《LLM Infra 工程实战》从入门到实践
- 《Hermes Agent 实战》构建会成长的个人 AI Agent
- 《OpenClaw 源码解析》现代 Agent 系统的架构设计与工程实践
- 《Agent Memory 工程实战》从 claude-mem 源码到企业级记忆平台
- 《AI Token 中转站实战》从 0 搭建企业级 LLM 网关
- 《百万级 AI Agent 平台架构》智能客服 SaaS 实战
- 《Claude Code Skill 指南》
- 《Claude 插件官方指南》