第 02 章

工作流模式(上)

链式调用、并行执行、智能路由 —— 三种基础编排模式

当一次 LLM 调用不够用的时候,你需要的是模式,而不是更多的提示词

一个不够用

你有没有遇到过这样的场景?

单次调用搞不定

想让 LLM 先生成内容,再改进内容,最后润色成稿——一步到位?结果往往是每一步都做不好。

串行太慢

明明三个子任务之间毫无依赖,但你还是一个个串着跑。等三分钟,其实十秒就够了。

不知道该走哪条路

用户可能要故事、要笑话、要诗歌——你不想把三个 prompt 塞进一次调用,质量会崩。

这些问题,本质上都是编排问题:不是 LLM 不够聪明,而是你没有给它安排好工作流。

本章核心:学会三种最基础的工作流模式——链式调用、并行执行、智能路由。它们是构建复杂智能体系统的积木。

开始之前

需要具备

  • 已经读完第 01 章,了解 LangGraph 的核心概念(节点、边、状态)
  • 熟悉 TypeScript 基本语法
  • 有一个可用的 Anthropic API Key
  • Node.js 18+ 环境

本章结束后你能

  • 区分工作流(Workflow)和智能体(Agent)
  • 写出链式调用、并行执行、路由三种模式的代码
  • 理解 addConditionalEdges 和质量门控
  • 独立完成一个内容处理管道练习

本章不会涉及

  • 编排者-工作者模式(Orchestrator-Worker)
  • 评估者-优化者模式(Evaluator-Optimizer)
  • 持久化、流式处理、人机交互
  • 生产环境部署细节

基础设置

安装依赖

npm install @langchain/langgraph @langchain/core @langchain/anthropic

初始化 LLM

// 导入 DeepSeek 聊天模型
import { ChatDeepSeek } from "@langchain/deepseek";

const llm = new ChatDeepSeek({
  model: "deepseek-chat",
  apiKey: "<your_deepseek_key>"
});

LLM 增强能力

import * as z from "zod";                          // Zod:运行时类型校验库,用来定义 Schema
import { tool } from "@langchain/core/tools";      // LangChain 工具定义函数

// ========== 1. 结构化输出 ==========
// 场景:你希望 LLM 不返回自由文本,而是返回严格符合 Schema 的 JSON 对象
// 原理:withStructuredOutput 内部会把 Zod Schema 转成 JSON Schema 发给模型,
//        模型会被强制按格式输出,返回结果会经过 Zod 校验
const SearchQuery = z.object({
  search_query: z.string().describe("Optimized web search query."),    // .describe() 的文字会作为字段说明传给模型
  justification: z.string().describe("Why this query is relevant."),
});
const structuredLlm = llm.withStructuredOutput(SearchQuery);
// 用法:const result = await structuredLlm.invoke("...");
// result = { search_query: "...", justification: "..." }  ← 保证是合法 JSON

// ========== 2. 工具调用 ==========
// 场景:你希望 LLM 能"调用"外部函数(如计算器、搜索、数据库查询)
// 原理:bindTools 把工具定义发给模型,模型决定是否需要调用工具、传什么参数;
//        返回的 aiMsg.tool_calls 里包含工具名和参数,由你来实际执行
const multiply = tool(
  ({ a, b }) => a * b,                             // 第 1 个参数:工具的实际执行函数
  {
    name: "multiply",                               // 工具名称(模型通过这个名字调用)
    description: "Multiply two numbers",            // 工具描述(模型根据描述判断何时使用)
    schema: z.object({                              // 参数 Schema(告诉模型需要传哪些参数)
      a: z.number(),
      b: z.number(),
    }),
  }
);
const llmWithTools = llm.bindTools([multiply]);
// 用法:const aiMsg = await llmWithTools.invoke("3乘以5等于几?");
// aiMsg.tool_calls = [{ name: "multiply", args: { a: 3, b: 5 } }]
三个增强:withStructuredOutput 控制输出格式、bindTools 添加工具调用能力、MemorySaver 提供短期记忆。本章主要用到前两个。

工作流与智能体

工作流 Workflow

  • 路径预定义——代码决定下一步去哪个节点
  • 适合流程清晰、步骤确定的场景
  • 可预测、可调试、可测试
  • 本章的链式、并行、路由都是工作流

智能体 Agent

  • 路径自主决定——LLM 自己判断下一步做什么
  • 适合开放性、需要灵活判断的场景
  • 更强大但更难调试
  • 后续章节会深入讲解
简单记:工作流是"按图纸施工",智能体是"让工人自己决定怎么干"。大多数生产系统都是两者的混合——主体用工作流保证确定性,局部用智能体处理灵活需求。

LangGraph 的优势在于:工作流和智能体用同一套 API(StateGraph、addNode、addEdge),你随时可以把工作流的某个节点替换成智能体决策。

模式一:链式调用

Prompt Chaining

每个 LLM 调用处理前一个调用的输出,像流水线一样一步步推进。适合可以分解为多个可验证子步骤的任务。

图:链式调用 —— 生成 → 改进 → 润色
关键设计:"质量门控"——在链的中间加一个检查点,不符合质量标准的输出直接跳到终点,避免在低质量内容上浪费时间。

链式调用代码

思考题:你想让 AI 写一个笑话,但要求质量过关:先写初稿,检查质量——太烂就短路结束,过关才继续改进和抛光。中间有一步不通过就"短路",怎么实现?
import { StateGraph, StateSchema, GraphNode, ConditionalEdgeRouter, START, END } from "@langchain/langgraph";
import * as z from "zod";

// 定义图状态 —— 每个字段对应流水线的一个阶段
const State = new StateSchema({
  topic: z.string(),          // 输入主题
  joke: z.string(),           // 原始笑话
  improvedJoke: z.string(),   // 改进后的笑话
  finalJoke: z.string(),      // 最终润色
});

// 节点 1:生成初始笑话
const generateJoke: GraphNode<typeof State> = async (state) => {
  const msg = await llm.invoke(`Write a short joke about ${state.topic}`);
  return { joke: msg.content };
};

// 节点 2:改进笑话(添加双关语)
const improveJoke: GraphNode<typeof State> = async (state) => {
  const msg = await llm.invoke(
    `Make this joke funnier by adding wordplay: ${state.joke}`
  );
  return { improvedJoke: msg.content };
};

// 节点 3:最终润色(添加反转)
const polishJoke: GraphNode<typeof State> = async (state) => {
  const msg = await llm.invoke(
    `Add a surprising twist to this joke: ${state.improvedJoke}`
  );
  return { finalJoke: msg.content };
};

📄 完整代码:02_prompt_chaining.ts

质量门控

核心机制:addConditionalEdges + ConditionalEdgeRouter

思考题:Prompt Chaining 模式中,如何用条件边实现"质量门控"——初稿质量不通过时直接跳到 END,跳过后续改进步骤?
// 质量门函数 —— 检查笑话是否包含 ? 或 !(简单判断是否有笑点)
const checkPunchline: ConditionalEdgeRouter<typeof State, "improveJoke"> = (state) => {
  if (state.joke?.includes("?") || state.joke?.includes("!")) {
    return "Pass";   // 有笑点 → 继续改进
  }
  return "Fail";     // 没笑点 → 直接结束
};

// 构建完整工作流
const chain = new StateGraph(State)
  .addNode("generateJoke", generateJoke)
  .addNode("improveJoke", improveJoke)
  .addNode("polishJoke", polishJoke)
  // 入口
  .addEdge(START, "generateJoke")
  // 条件边 —— generateJoke 之后根据质量门控决定去向
  .addConditionalEdges("generateJoke", checkPunchline, {
    Pass: "improveJoke",    // 通过 → 改进
    Fail: END         // 不通过 → 直接结束
  })
  .addEdge("improveJoke", "polishJoke")
  .addEdge("polishJoke", END)
  .compile();

// 调用
const result = await chain.invoke({ topic: "cats" });
注意:ConditionalEdgeRouter<typeof State, "improveJoke"> 末尾的泛型参数(此处为 "improveJoke")用于声明路由函数可能返回的节点名。返回字符串必须和 addConditionalEdges 的映射对象的 key 完全一致,否则运行时报错。

📄 完整代码:02_prompt_chaining.ts

模式二:并行执行

Parallelization

多个 LLM 调用同时运行,互不等待。适合子任务之间没有依赖关系的场景。也可以对同一任务运行多次,取最优结果。

图:并行执行 —— 同一主题,三路并行
核心技巧:从 START 到多个节点各加一条边,LangGraph 就会自动并行执行这些节点。所有并行节点完成后,再汇入下游节点。

并行执行代码

思考题:你要给用户展示一个话题的三种创作风格:笑话、小故事、打油诗。串行写太慢了,能不能三个同时写,最后汇总?
import { StateGraph, StateSchema, GraphNode, START, END } from "@langchain/langgraph";
import * as z from "zod";

const State = new StateSchema({
  topic: z.string(),
  joke: z.string(),
  story: z.string(),
  poem: z.string(),
  combinedOutput: z.string(),
});

// 三个并行的 LLM 节点 —— 互不依赖,同时运行
const callLlm1: GraphNode<typeof State> = async (state) => {
  const msg = await llm.invoke(`Write a joke about ${state.topic}`);
  return { joke: msg.content };
};

const callLlm2: GraphNode<typeof State> = async (state) => {
  const msg = await llm.invoke(`Write a story about ${state.topic}`);
  return { story: msg.content };
};

const callLlm3: GraphNode<typeof State> = async (state) => {
  const msg = await llm.invoke(`Write a poem about ${state.topic}`);
  return { poem: msg.content };
};

// 聚合节点 —— 等所有并行节点完成后执行
const aggregator: GraphNode<typeof State> = async (state) => {
  const combined = `Story:\n${state.story}\n\nJoke:\n${state.joke}\n\nPoem:\n${state.poem}`;
  return { combinedOutput: combined };
};

// 关键:START 连接到三个节点 → 自动并行
const parallelWorkflow = new StateGraph(State)
  .addNode("callLlm1", callLlm1)
  .addNode("callLlm2", callLlm2)
  .addNode("callLlm3", callLlm3)
  .addNode("aggregator", aggregator)
  .addEdge(START, "callLlm1")    // 并行入口
  .addEdge(START, "callLlm2")    // 并行入口
  .addEdge(START, "callLlm3")    // 并行入口
  .addEdge("callLlm1", "aggregator")   // 汇入聚合
  .addEdge("callLlm2", "aggregator")
  .addEdge("callLlm3", "aggregator")
  .addEdge("aggregator", END)
  .compile();

const result = await parallelWorkflow.invoke({ topic: "cats" });

📄 完整代码:02_parallelization.ts

模式三:智能路由

Routing

先分类输入,再路由到专门的处理节点。每个处理节点只做自己擅长的事,输出质量更高。

图:智能路由 —— 先分类,再分发
与并行化的区别:并行化是"全都要",路由是"只选一个"。路由用 withStructuredOutput 让 LLM 做出二选一/三选一的决策,然后用 addConditionalEdges 把请求送到正确的节点。

路由代码实现

思考题:你在做一个智能客服,用户的问题有不同类型。你需要先判断类型,再把问题转给对应的专家处理。怎么实现"一个入口,多条出路"?
import { StateGraph, StateSchema, GraphNode, ConditionalEdgeRouter, START, END } from "@langchain/langgraph";
import * as z from "zod";

// 路由决策 Schema —— 用结构化输出让 LLM 做选择题
const routeSchema = z.object({
  step: z.enum(["poem", "story", "joke"]).describe("The next step in the routing process"),
});
const router = llm.withStructuredOutput(routeSchema);

const State = new StateSchema({
  input: z.string(),
  decision: z.string(),
  output: z.string(),
});

// 三个专门的处理节点
const llmCall1: GraphNode<typeof State> = async (state) => {
  const result = await llm.invoke([
    { role: "system", content: "You are an expert storyteller." },
    { role: "user", content: state.input },
  ]);
  return { output: result.content };
};
// llmCall2(喜剧专家)、llmCall3(诗歌专家)结构相同,省略……

// 路由节点 —— 调用结构化输出做分类
const llmCallRouter: GraphNode<typeof State> = async (state) => {
  const decision = await router.invoke([
    { role: "system", content: "Route to story, joke, or poem based on the user's request." },
    { role: "user", content: state.input },
  ]);
  return { decision: decision.step };
};

// 条件边 —— 根据路由决策分发
const routeDecision: ConditionalEdgeRouter<typeof State, "llmCall1" | "llmCall2" | "llmCall3"> = (state) => {
  if (state.decision === "story") return "llmCall1";
  else if (state.decision === "joke") return "llmCall2";
  else return "llmCall3";
};

📄 完整代码:02_routing.ts

路由图构建

思考题:路由工作流中,addConditionalEdges 的对象映射和数组列表两种写法有什么区别?分别在什么场景下使用?
// 构建路由工作流
const routerWorkflow = new StateGraph(State)
  .addNode("llmCall1", llmCall1)
  .addNode("llmCall2", llmCall2)
  .addNode("llmCall3", llmCall3)
  .addNode("llmCallRouter", llmCallRouter)
  // 入口 → 路由器
  .addEdge(START, "llmCallRouter")
  // 路由器 → 条件分发(三选一)
  .addConditionalEdges("llmCallRouter", routeDecision, ["llmCall1", "llmCall2", "llmCall3"])
  // 三个处理节点各自通向终点
  .addEdge("llmCall1", END)
  .addEdge("llmCall2", END)
  .addEdge("llmCall3", END)
  .compile();

// 测试路由 —— 输入 "写个关于猫的笑话" 会走 llmCall2
const result = await routerWorkflow.invoke({
  input: "Write me a joke about cats"
});
console.log(result.output);

addConditionalEdges 两种写法

对象映射:第三个参数传对象 { Pass: "nodeA", Fail: END }

数组列表:第三个参数传数组 ["llmCall1", "llmCall2", "llmCall3"],路由函数直接返回节点名

选择哪种?

返回值是固定枚举(Pass/Fail)→ 用对象映射,更安全

返回值本身就是节点名 → 用数组列表,更简洁

📄 完整代码:02_routing.ts

排错指南

常见错误 1

边名拼错:addConditionalEdges 里的映射 key 和 ConditionalEdgeRouter 返回值不一致。

比如 Router 返回 "pass" 但映射写的是 Pass: "improveJoke"——大小写不匹配直接报错。

排查方法:在路由函数里加 console.log("routing to:", result) 确认返回值。

常见错误 2

忘了 START 或 END:每个图必须有至少一个 START 入口和 END 出口。

缺少出口会导致图编译失败:"No path to END node found"。

排查方法:检查每个分支路径是否最终都能到达 END。

常见错误 3

并行节点写同一个字段:两个并行节点返回相同的 state key,后到的会覆盖先到的。

比如两个节点都 return { result: ... },最终只剩一个。

排查方法:并行节点的输出字段要分开命名,或使用 ReducedValue 做合并。
思考题:两个节点并行执行,都要往同一个字段写结果,最终只保留了一个节点的输出。怎么让两个节点的结果都保留?

📄 完整代码:02_reduced_value.ts

调试利器:设置环境变量 LANGSMITH_TRACING=true 和 LANGSMITH_API_KEY=...,可以在 LangSmith 里看到每一步的输入输出和路由决策,比 console.log 高效得多。

动手练习

构建一个内容处理管道

需求:对一篇英文文章做三步处理。

Step 1:摘要

节点 summarize:输入文章原文,输出 50 词以内的摘要。

Step 2:翻译

节点 translate:把摘要翻译成中文。

Step 3:格式化

节点 format:加上标题和分隔线,输出最终结果。

额外要求:在 summarize 和 translate 之间加一个质量门控——如果摘要超过 80 个词,直接跳到 END 并返回原始摘要(说明摘要太长,翻译没意义)。

提示

  • State 需要:article、summary、translation、finalOutput
  • 质量门控检查 summary.split(" ").length
  • 参考链式调用的 addConditionalEdges 写法

参考答案

import { StateGraph, StateSchema, GraphNode, ConditionalEdgeRouter, START, END } from "@langchain/langgraph";
import * as z from "zod";

const State = new StateSchema({
  article: z.string(),
  summary: z.string(),
  translation: z.string(),
  finalOutput: z.string(),
});

// Step 1:生成摘要
const summarize: GraphNode<typeof State> = async (state) => {
  const msg = await llm.invoke(
    `Summarize this article in under 50 words:\n\n${state.article}`
  );
  return { summary: msg.content as string };
};

// Step 2:翻译为中文
const translate: GraphNode<typeof State> = async (state) => {
  const msg = await llm.invoke(
    `Translate the following summary to Chinese:\n\n${state.summary}`
  );
  return { translation: msg.content as string };
};

// Step 3:格式化输出
const format: GraphNode<typeof State> = async (state) => {
  const output = `=== 文章摘要 ===\n${state.translation}\n${"—".repeat(20)}`;
  return { finalOutput: output };
};

// 质量门控:摘要太长(超过 80 词)则跳过翻译
const checkSummaryLength: ConditionalEdgeRouter<typeof State, "translate"> = (state) => {
  const wordCount = state.summary.split(" ").length;
  return wordCount <= 80 ? "Pass" : "Fail";
};

// 构建图
const pipeline = new StateGraph(State)
  .addNode("summarize", summarize)
  .addNode("translate", translate)
  .addNode("format", format)
  .addEdge(START, "summarize")
  .addConditionalEdges("summarize", checkSummaryLength, {
    Pass: "translate",
    Fail: END,
  })
  .addEdge("translate", "format")
  .addEdge("format", END)
  .compile();

本章回顾

模式 特点 关键 API 适用场景
链式调用 串行处理,每步处理上一步输出 addEdge + addConditionalEdges 多步加工、质量门控
并行执行 同时运行,互不等待 START 连多条边 独立子任务、多角度生成
智能路由 先分类,再分发到专门节点 withStructuredOutput + addConditionalEdges 多类型请求、专门化处理

核心概念回顾

  • GraphNode:节点就是一个 async 函数,接收 state,返回 state 更新
  • ConditionalEdgeRouter:条件边函数,决定下一步去哪个节点
  • withStructuredOutput:让 LLM 输出符合 Schema 的结构化数据

模式选择指南

  • 任务有先后顺序 → 链式调用
  • 任务之间无依赖 → 并行执行
  • 需要根据输入选不同处理 → 智能路由

延伸阅读

下一章

第 03 章:工作流模式(下)

编排者-工作者模式(Orchestrator-Worker)、评估者-优化者模式(Evaluator-Optimizer)—— 用 Send 动态创建并行任务,用循环反馈不断改进输出。

回顾上一章

第 01 章:LangGraph 概述

如果你对 StateSchema、StateGraph、addNode 这些基础 API 还不熟悉,建议先回到第一章补课。

恭喜完成第 02 章!你已经掌握了三种最基础的工作流模式。它们是构建复杂 LLM 应用的基石——几乎所有高级模式都可以看作这三者的组合和扩展。
1 / 17