工作流模式(下)
编排者-工作者、评估优化、智能体 —— 三种高级编排模式
当任务的数量和路径都不可预知时,静态模式就不够用了更复杂的场景
第 02 章的三种模式遇到了新问题
任务数量不确定
你想写一份报告,但不知道要分几个章节。用户说"写五节"就五节,说"三节"就三节。并行模式要求你在编码时就确定有几个并行节点,做不到动态分派。
质量不达标怎么办
链式调用有质量门控,但门控只能"通过/不通过"。如果 LLM 第一次生成的内容不好,你更想要的是带着反馈重试,而不是直接放弃。
流程本身就不可预知
有些任务需要根据中间结果自主决定下一步做什么——可能调工具,可能直接回复,可能需要多轮操作。这就是智能体(Agent)的场景。
这三个问题对应三种新模式:编排模式解决动态分派、评估优化解决反馈循环、智能体解决自主决策。
模式四:编排模式
编排者(Orchestrator)把大任务拆成若干子任务,分派给工作者(Worker),等所有工作者完成后综合结果。关键区别:子任务的数量在运行时才确定。

图:编排者拆分任务 → 动态工作者 → 综合报告
【AI 生图提示词】请生成一张中文技术流程图,风格简洁、配色柔和、适合教学: - 画面主体:一个编排-工作者-综合三层结构 - 顶部 START(绿色圆角矩形)→ 编排者(橙色圆角矩形,标注"LLM 拆分任务") - 编排者向下扇出到 N 个工作者(蓝色圆角矩形,并排排列,标注"工作者 1""工作者 2""…""工作者 N"),用虚线边框和省略号表示数量不固定 - 所有工作者向下汇聚到综合者(橙色圆角矩形,标注"LLM 合并结果")→ END(红色圆角矩形) - 各元素关系:编排者到工作者是动态一对多,用 Send 类在运行时创建;工作者到综合者是多对一汇总 - 关键标注:编排者旁注明"运行时决定工作者数量",工作者之间用虚线框括起来表示"数量动态" - 配色建议:浅米色背景,编排者/综合者用橙色,工作者用蓝色,START/END 用绿/红 - 画面比例:16:9 - 避免:过度装饰、3D 效果、杂乱的颜色
Send 类在运行时动态创建任意数量的工作者节点,编排者 LLM 说几个就几个。
Send 动态分发
核心 API:Send 类
import { StateGraph, StateSchema, ReducedValue, GraphNode, Send, START, END } from "@langchain/langgraph";
Send 让你从条件边函数里动态地把任务分发到指定节点。每次 new Send("节点名", { 数据 }) 就创建一个独立的工作者调用。
// 条件边路由函数 —— 返回 Send 数组,每个 Send 创建一个工作者
const assignWorkers: ConditionalEdgeRouter<typeof State, "llmCall"> = (state) => {
// state.sections 是编排者 LLM 拆分出的子任务列表
// 有几个 section,就创建几个工作者
return state.sections.map(
(section) => new Send("llmCall", { sections: [section] })
);
};
为什么需要 ReducedValue?
多个工作者并行运行,每个都往同一个 state 字段写结果。如果用普通字段,后者会覆盖前者。ReducedValue 用 reducer 函数把所有工作者的结果合并到一个数组里。
// 用 ReducedValue 定义一个会"累积"的字段
const State = new StateSchema({
topic: z.string(),
sections: z.array(z.object({
title: z.string(),
description: z.string(),
})),
completedSections: new ReducedValue(
z.array(z.object({ title: z.string(), content: z.string() })),
// reducer: 把新结果追加到已有数组
(existing, update) => [...(existing ?? []), ...update],
),
finalReport: z.string(),
});
Send 只能在条件边函数中使用,不能在普通节点里创建。ReducedValue 的 reducer 函数第一个参数可能是 null(第一次调用时),别忘了处理。
编排模式代码
import { StateGraph, StateSchema, ReducedValue, GraphNode, Send, START, END } from "@langchain/langgraph";
import * as z from "zod";
const State = new StateSchema({
topic: z.string(),
sections: z.array(z.object({ title: z.string(), description: z.string() })),
completedSections: new ReducedValue(
z.array(z.object({ title: z.string(), content: z.string() })),
(existing, update) => [...(existing ?? []), ...update],
),
finalReport: z.string(),
});
// 编排者:分析任务,拆分成子任务列表
const orchestrator: GraphNode<typeof State> = async (state) => {
const structLlm = llm.withStructuredOutput(
z.object({ sections: z.array(z.object({
title: z.string().describe("Section title"),
description: z.string().describe("What to cover"),
})) })
);
const result = await structLlm.invoke([
{ role: "system", content: "Plan a report sections." },
{ role: "user", content: state.topic },
]);
return { sections: result.sections };
};
// 工作者:根据子任务描述生成内容(会被 Send 动态创建多次)
const llmCall: GraphNode<typeof State> = async (state) => {
const section = state.sections[0]; // Send 传入 { sections: [section] },这里取第 0 个
const msg = await llm.invoke(
`Write a section: "${section.title}" covering: ${section.description}`
);
return { completedSections: [{ title: section.title, content: msg.content }] };
};
编排图构建
// 综合者:把所有工作者生成的内容合成最终报告
const synthesizer: GraphNode<typeof State> = async (state) => {
const parts = state.completedSections
.map((s) => `## ${s.title}\n${s.content}`)
.join("\n\n");
const msg = await llm.invoke(
`Format into a final report:\n\n${parts}`
);
return { finalReport: msg.content as string };
};
// 条件边:编排者 → 动态分派工作者
const assignWorkers = (state) => {
return state.sections.map(
(section) => new Send("llmCall", { sections: [section] })
);
};
// 构建图
const orchestratorWorkflow = new StateGraph(State)
.addNode("orchestrator", orchestrator)
.addNode("llmCall", llmCall)
.addNode("synthesizer", synthesizer)
.addEdge(START, "orchestrator")
// 关键:条件边用 Send 动态创建多个 llmCall 实例
.addConditionalEdges("orchestrator", assignWorkers, ["llmCall"])
// 所有工作者完成后汇入综合者
.addEdge("llmCall", "synthesizer")
.addEdge("synthesizer", END)
.compile();
const result = await orchestratorWorkflow.invoke({
topic: "Create a report on the history of AI"
});
Send 创建 N 个 llmCall 实例并行运行 → 每个实例的结果通过 ReducedValue 累积 → 全部完成后综合者读取累积结果并生成报告。
模式五:评估优化
一个 LLM 负责生成,另一个 LLM 负责评估。评估不合格就带着反馈回到生成者重新来过,直到合格为止。

图:生成 → 评估 → 循环直到合格
【AI 生图提示词】请生成一张中文技术流程图,风格简洁、配色柔和、适合教学: - 画面主体:一个生成-评估的循环迭代结构 - 顶部 START(绿色圆角矩形)→ 生成者(蓝色圆角矩形)→ 评估者(紫色圆角矩形) - 评估者分两条路径:accepted(合格)→ END(红色圆角矩形);rejected(不合格)→ 携带 feedback 回到生成者形成循环 - 循环箭头从评估者左侧回到生成者,箭头旁标注"rejected + feedback" - 各元素关系:生成者与评估者是线性串联,但评估者有条件分支——合格则结束,不合格则循环回生成者 - 关键标注:生成者旁注明"LLM 生成/改进",评估者旁注明"LLM withStructuredOutput 评估",循环箭头旁注明"rejected → feedback" - 配色建议:浅米色背景,生成者蓝色,评估者紫色,START/END 用绿/红,循环箭头用橙色 - 画面比例:16:9 - 避免:过度装饰、3D 效果、杂乱的颜色
与链式调用的区别
链式调用是线性前进(A → B → C),质量门控只能"过/不过"。评估优化是循环迭代,不合格就修改再提交,直到满意。
核心设计
- 评估者用
withStructuredOutput返回结构化评价 - 条件边根据评价结果决定是结束还是循环
- 反馈信息写回 state,供下一轮生成参考
评估优化代码
import { StateGraph, StateSchema, GraphNode, ConditionalEdgeRouter, START, END } from "@langchain/langgraph";
import * as z from "zod";
// 评估结果 Schema
const FeedbackSchema = z.object({
grade: z.enum(["funny", "not funny"]).describe("Evaluation grade"),
feedback: z.string().describe("Feedback for improvement"),
});
const State = new StateSchema({
joke: z.string(),
topic: z.string(),
feedback: z.string(),
funnyOrNot: z.string(),
});
// 生成者:写笑话(如果之前有反馈,就参考反馈改进)
const llmCallGenerator: GraphNode<typeof State> = async (state) => {
const prompt = state.feedback
? `Write a joke about ${state.topic}. Improve based on feedback: ${state.feedback}`
: `Write a joke about ${state.topic}`;
const msg = await llm.invoke(prompt);
return { joke: msg.content as string };
};
// 评估者:判断笑话好不好笑
const llmCallEvaluator: GraphNode<typeof State> = async (state) => {
const evaluator = llm.withStructuredOutput(FeedbackSchema);
const result = await evaluator.invoke(
`Evaluate this joke: "${state.joke}". Is it funny? Give feedback.`
);
return {
funnyOrNot: result.grade,
feedback: result.feedback,
};
};
// 条件边:合格 → 结束,不合格 → 带反馈重来
const routeJoke: ConditionalEdgeRouter<typeof State, "llmCallGenerator"> = (state) => {
return state.funnyOrNot === "funny" ? "Accepted" : "Rejected";
};
评估优化图构建
// 构建评估优化工作流
const optimizerWorkflow = new StateGraph(State)
.addNode("llmCallGenerator", llmCallGenerator)
.addNode("llmCallEvaluator", llmCallEvaluator)
.addEdge(START, "llmCallGenerator")
.addEdge("llmCallGenerator", "llmCallEvaluator")
// 评估者之后:合格结束,不合格循环
.addConditionalEdges("llmCallEvaluator", routeJoke, {
Accepted: END,
Rejected: "llmCallGenerator", // 带反馈回到生成者
})
.compile();
// 测试
const result = await optimizerWorkflow.invoke({
topic: "Cats",
joke: "",
feedback: "",
funnyOrNot: "",
});
console.log(result.joke);
console.log("Feedback received:", result.feedback);
attempts 计数器,条件边里检查是否超过阈值。
// 安全改进版:限制最大重试 3 次
const MAX_ATTEMPTS = 3;
const routeJokeSafe: ConditionalEdgeRouter<typeof State, "llmCallGenerator"> = (state) => {
if (state.funnyOrNot === "funny" || (state.attempts ?? 0) >= MAX_ATTEMPTS) {
return "Accepted"; // 合格或超限 → 结束
}
return "Rejected"; // 不合格 → 重试
};
模式六:智能体
智能体(Agent)是一种自主决策的循环结构:LLM 自己决定要调用什么工具、调几次、什么时候结束。不需要你预先定义流程。

图:工具调用循环 —— LLM 自主决定下一步
【AI 生图提示词】请生成一张中文技术流程图,风格简洁、配色柔和、适合教学: - 画面主体:一个 LLM 自主决策的工具调用循环结构 - 顶部 START(绿色圆角矩形)→ LLM(蓝色圆角矩形,较大突出) - LLM 下方有条件判断菱形"有 tool_calls?",分两条路径: - 左侧"是"→ ToolNode(橙色圆角矩形)→ 循环箭头回到 LLM(箭头旁标注"继续判断") - 右侧"否"→ END(红色圆角矩形,旁标注"直接输出回答") - 各元素关系:LLM 与 ToolNode 构成循环——LLM 决定是否调用工具,ToolNode 执行工具后将结果送回 LLM,直到 LLM 不再调用工具 - 关键标注:LLM 旁注明"自主决策",ToolNode 旁注明"自动执行工具并返回结果",条件判断旁标注"shouldContinue 函数" - 配色建议:浅米色背景,LLM 蓝色,ToolNode 橙色,循环箭头用橙色高亮,START/END 用绿/红 - 画面比例:16:9 - 避免:过度装饰、3D 效果、杂乱的颜色
工具调用循环
核心机制:shouldContinue + ToolNode
ToolNode
LangGraph 预置的工具执行节点。它自动解析 LLM 输出中的 tool_calls,调用对应工具,把结果追加到 messages。
import { ToolNode } from "@langchain/langgraph/prebuilt";
shouldContinue
条件边函数,检查 LLM 最后一条消息是否包含 tool_calls。有就执行工具,没有就结束。
// 条件边:检查是否需要继续调用工具
const shouldContinue: ConditionalEdgeRouter<typeof State, "toolNode"> = (state) => {
const lastMessage = state.messages.at(-1);
// 最后一条消息有 tool_calls → 执行工具
if (lastMessage?.tool_calls?.length) return "toolNode";
// 没有 tool_calls → LLM 已经给出最终回答,结束
return END;
};
messages 数组?智能体的 state 通常只有一个 messages 字段。LLM 的输出、工具的返回值、用户的输入都是 message,追加到这个数组里。LangGraph 的 ToolNode 和 shouldContinue 都基于这个约定工作。
智能体代码
import { StateGraph, StateSchema, GraphNode, ConditionalEdgeRouter, Messages, START, END } from "@langchain/langgraph";
import { ToolNode } from "@langchain/langgraph/prebuilt";
import { tool } from "@langchain/core/tools";
import * as z from "zod";
// 定义工具
const multiply = tool(({ a, b }) => a * b, {
name: "multiply", description: "Multiply two numbers",
schema: z.object({ a: z.number(), b: z.number() }),
});
const add = tool(({ a, b }) => a + b, {
name: "add", description: "Add two numbers",
schema: z.object({ a: z.number(), b: z.number() }),
});
// LLM 绑定工具
const llmWithTools = llm.bindTools([multiply, add]);
// State 只需要一个 messages 数组
const State = new StateSchema({
messages: Messages({}), // 内置 Messages 注解
});
// LLM 节点:调用带工具的 LLM
const llmCall: GraphNode<typeof State> = async (state) => {
const response = await llmWithTools.invoke(state.messages);
return { messages: [response] };
};
// 条件边:检查是否继续调用工具
const shouldContinue: ConditionalEdgeRouter<typeof State, "toolNode"> = (state) => {
const lastMessage = state.messages.at(-1);
if (lastMessage?.tool_calls?.length) return "toolNode";
return END;
};
智能体图构建
// 工具执行节点
const toolNode = new ToolNode([multiply, add]);
// 构建智能体工作流
const agentWorkflow = new StateGraph(State)
.addNode("llmCall", llmCall)
.addNode("toolNode", toolNode)
.addEdge(START, "llmCall")
// LLM 之后检查是否有工具调用
.addConditionalEdges("llmCall", shouldContinue, ["toolNode", END])
// 工具执行完后回到 LLM 继续判断
.addEdge("toolNode", "llmCall")
.compile();
// 测试:让智能体计算 (3 + 5) * 2
const result = await agentWorkflow.invoke({
messages: [{ role: "user", content: "What is (3 + 5) * 2?" }],
});
console.log(result.messages.at(-1).content);
// 智能体会自动:调用 add(3,5) → 得到 8 → 调用 multiply(8,2) → 得到 16 → 输出结果
执行过程
- 用户发问 "(3+5)*2=?"
- LLM 决定先算 3+5,调用
add - 工具返回 8,LLM 继续判断
- LLM 决定再算 8*2,调用
multiply - 工具返回 16,LLM 继续判断
- LLM 给出最终回答,无工具调用 → 结束
智能体的"智能"
你没有写"先加后乘"的逻辑。LLM 自己读懂了算式,自己决定调哪个工具、调几次。这就是自主决策——流程路径完全由 LLM 运行时决定。
六种模式对比
| 模式 | 核心思想 | 关键 API | 流程类型 |
|---|---|---|---|
| 链式调用 | 串行加工,每步处理上一步输出 | addEdge + addConditionalEdges |
线性 / 带跳过 |
| 并行执行 | 多个任务同时运行,互不等待 | START 连多条边 |
扇出 → 扇入 |
| 智能路由 | 先分类输入,再分发到专门节点 | withStructuredOutput |
分类 → 分发 |
| 编排模式 | 动态拆分任务,按需创建工作者 | Send + ReducedValue |
动态扇出 → 聚合 |
| 评估优化 | 生成 + 评估循环,直到质量达标 | withStructuredOutput + 循环边 |
循环迭代 |
| 智能体 | LLM 自主决定调用工具和流程 | ToolNode + bindTools |
自主循环 |
排错指南
编排模式
Send 传入的 state 不匹配:new Send("llmCall", { section }) 传入的对象会作为该工作者节点的 state。如果节点里访问 state.sections[0],但 Send 传的是 { section },就会读到 undefined。
console.log(state),确认 Send 传入的字段名和节点访问的字段名一致。
评估优化
循环停不下来:评估者 LLM 总是给出"不合格",生成者永远在重试。最常见的原因是评估标准太严,或者 withStructuredOutput 的 Schema 描述不够清晰。
attempts 计数器限制最大重试次数。调整评估 Schema 的描述文字,降低评判标准。
智能体
工具没被调用:LLM 直接回答而不是调用工具。通常是工具描述不够清晰,或者 LLM 觉得自己就能回答。
bindTools 是否正确传入。在 system prompt 里明确要求"必须使用工具来计算"。检查 tool_calls 字段是否存在。
console.log 可能打印一大堆。推荐使用 LangSmith(设置 LANGSMITH_TRACING=true),可以图形化查看每一步的输入输出和路由决策。
动手练习
构建一个研究助手
需求:用户输入一个研究主题,助手自动搜索、评估搜索质量、综合成报告。
Step 1:搜索
节点 search:根据主题生成搜索关键词。用 withStructuredOutput 返回关键词列表。
Step 2:评估
节点 evaluate:评估搜索结果是否充分。返回 sufficient: boolean 和 feedback: string。
Step 3:综合
节点 synthesize:把搜索结果综合成一份简短的研究摘要。
- 使用评估优化模式:评估不通过时带反馈回到搜索节点重新搜索
- 加上最大重试 3 次的限制
- State 包含:
topic、searchResults、feedback、sufficient、attempts、finalReport
提示
- 搜索节点不需要真的调用搜索引擎——模拟一下,用 LLM 生成搜索结果即可
- 参考评估优化模式的
addConditionalEdges写法 - 在条件边里检查
attempts是否超过 3
参考答案
import { StateGraph, StateSchema, GraphNode, ConditionalEdgeRouter, START, END } from "@langchain/langgraph";
import * as z from "zod";
const MAX_ATTEMPTS = 3;
const EvalResult = z.object({
sufficient: z.boolean().describe("Are the search results sufficient?"),
feedback: z.string().describe("Feedback for improvement if insufficient."),
});
const State = new StateSchema({
topic: z.string(),
searchResults: z.string(),
feedback: z.string(),
sufficient: z.boolean(),
attempts: z.number(),
finalReport: z.string(),
});
// 搜索节点:模拟搜索(实际项目中替换为真实搜索 API)
const search: GraphNode<typeof State> = async (state) => {
const prompt = state.feedback
? `Research "${state.topic}" more thoroughly. Previous feedback: ${state.feedback}`
: `Research "${state.topic}" and summarize key findings.`;
const msg = await llm.invoke(prompt);
return {
searchResults: msg.content as string,
attempts: (state.attempts ?? 0) + 1,
};
};
// 评估节点:判断搜索结果是否充分
const evaluate: GraphNode<typeof State> = async (state) => {
const evaluator = llm.withStructuredOutput(EvalResult);
const result = await evaluator.invoke(
`Evaluate these research results for "${state.topic}":\n${state.searchResults}`
);
return { sufficient: result.sufficient, feedback: result.feedback };
};
// 综合节点:生成最终报告
const synthesize: GraphNode<typeof State> = async (state) => {
const msg = await llm.invoke(
`Write a research summary based on:\n${state.searchResults}`
);
return { finalReport: msg.content as string };
};
// 条件边:充分或超限 → 综合,否则 → 重试
const routeEvaluation: ConditionalEdgeRouter<typeof State, "search"> = (state) => {
if (state.sufficient || (state.attempts ?? 0) >= MAX_ATTEMPTS) return "Pass";
return "Fail";
};
const workflow = new StateGraph(State)
.addNode("search", search)
.addNode("evaluate", evaluate)
.addNode("synthesize", synthesize)
.addEdge(START, "search")
.addEdge("search", "evaluate")
.addConditionalEdges("evaluate", routeEvaluation, {
Pass: "synthesize",
Fail: "search",
})
.addEdge("synthesize", END)
.compile();
本章回顾
| 模式 | 特点 | 关键 API | 适用场景 |
|---|---|---|---|
| 编排模式 | 运行时动态创建工作者,结果累积合并 | Send + ReducedValue |
动态子任务、报告生成 |
| 评估优化 | 生成 → 评估循环,带反馈重试 | withStructuredOutput + 循环条件边 |
质量迭代、内容改进 |
| 智能体 | LLM 自主决策工具调用,循环直到完成 | ToolNode + shouldContinue |
工具调用、自主推理 |
核心 API 回顾
Send:在条件边中动态创建节点实例ReducedValue:用 reducer 累积多个工作者的输出ToolNode:预置的工具执行节点shouldContinue:检查tool_calls决定是否继续
安全意识
- 循环模式一定要加最大重试次数
- 智能体的工具调用也可能无限循环——限制最大步数
ReducedValue的 reducer 首次参数可能是null