今天要讲的核心:Conditional Edge(条件路由)

你有没有遇到过这样的情况:写了一个 Agent,它每次都把请求丢给工具,哪怕用户只是问了句"你好",它也去调一圈 API,浪费时间又浪费钱?

这就是缺少条件路由的典型症状。


01 什么是 Conditional Edge,为什么需要它

普通 Edge(固定边)只做一件事:执行完 A 节点,无条件跳 B 节点。

Conditional Edge(条件边)做的是:执行完 A 节点之后,先问一句"下一步去哪",然后根据回答动态决定跳 B、C 还是直接结束。

普通边:  NodeA ──────────────> NodeB条件边:  NodeA ──> router() ─┬─> NodeB                      ├─> NodeC                      └─> END

两种模式的本质区别:

┌─────────────────────────────────────────────────────────────┐│                 Fixed Edge  vs  Conditional Edge             │├──────────────────────┬──────────────────────────────────────┤│   固定边              │   条件边                              ││   执行路径:静态       │   执行路径:运行时动态确定             ││   分支:无             │   分支:1~N 个                       ││   决策逻辑:没有       │   决策逻辑:你的函数                  ││   适合:线性流程       │   适合:有判断的工作流                 │└──────────────────────┴──────────────────────────────────────┘

一句话总结:Conditional Edge 是 LangGraph 里的 if/switch,但它活在运行时,基于实际状态做判断


02 add_conditional_edges:三个参数搞清楚

LangGraph 里添加条件边就一个方法:addConditionalEdges(JS 版)或 add_conditional_edges(Python 版)。

graph.addConditionalEdges(  sourceNode,      // 从哪个节点出发  routerFn,        // 路由函数:输入 state,输出下一个节点名  edgeMapping      // 可选:把路由函数返回值映射到节点名)

三个参数的关系:

┌─────────────────────────────────────────────────┐│              add_conditional_edges               ││                                                  ││  sourceNode ──> 执行完这个节点后触发路由          ││                       │                          ││                  routerFn(state)                 ││                  ┌────┴────┐                     ││                  │ 返回字符串│                    ││                  └────┬────┘                     ││                       │                          ││              edgeMapping(可选)                  ││          "tool_call" → "tool_node"               ││          "search"    → "search_node"             ││          "__end__"   → END                       │└─────────────────────────────────────────────────┘

关于 edgeMapping:如果路由函数直接返回节点名称字符串,这个参数可以省略。如果你想用语义化的标签(如 "tool_call")来代替节点名,就需要显式传这个映射表。


03 最简单的条件路由:规则型 Router

先写最直接的场景——用固定规则路由,不涉及 LLM 判断。

场景:用户发消息,如果 Agent 要调用工具,就跳转到工具节点;否则直接结束。

import { StateGraph, END } from"@langchain/langgraph";import { Annotation } from"@langchain/langgraph";import { BaseMessage, AIMessage } from"@langchain/core/messages";import { ChatOpenAI } from"@langchain/openai";import { TavilySearchResults } from"@langchain/community/tools/tavily_search";// 定义状态constAgentState = Annotation.Root({messages: Annotation<BaseMessage[]>({    reducer: (x, y) => x.concat(y),    default: () => [],  }),});const tools = [newTavilySearchResults({ maxResults: 3 })];const model = newChatOpenAI({ model: "gpt-4o" }).bindTools(tools);// Agent 节点:调用 LLMasyncfunctioncallAgent(state: typeof AgentState.State) {const response = await model.invoke(state.messages);return { messages: [response] };}// 工具节点:执行工具调用asyncfunctioncallTools(state: typeof AgentState.State) {const lastMessage = state.messages[state.messages.length - 1] asAIMessage;const toolCalls = lastMessage.tool_calls ?? [];const results = awaitPromise.all(    toolCalls.map(async (tc) => {      const tool = tools.find((t) => t.name === tc.name);      if (!tool) thrownewError(`Tool ${tc.name} not found`);      const result = await tool.invoke(tc.args);      return { role: "tool"asconst, content: String(result), tool_call_id: tc.id! };    })  );return { messages: results };}// ✅ 路由函数:检查最后一条消息是否包含工具调用functionshouldContinue(state: typeof AgentState.State): "tools" | "__end__" {const lastMessage = state.messages[state.messages.length - 1] asAIMessage;if (lastMessage.tool_calls && lastMessage.tool_calls.length > 0) {    return"tools";  }return"__end__";}// 构建 Graphconst workflow = newStateGraph(AgentState)  .addNode("agent", callAgent)  .addNode("tools", callTools)  .addEdge("__start__", "agent")  .addConditionalEdges("agent", shouldContinue)  // ← 条件边在这里  .addEdge("tools", "agent");                     // ← 工具执行完,回到 agentconst app = workflow.compile();

这段代码里有个关键细节:.addConditionalEdges("agent", shouldContinue) 没有传第三个参数 edgeMapping,因为 shouldContinue 直接返回节点名 "tools""__end__",LangGraph 能自动解析。


04 多路分支:一个节点分叉出三条路

实际项目里,一个 Agent 往往需要根据用户意图,分发给不同的专家节点。这就是多路条件路由。

场景:用户可能问天气、查代码文档、或随便聊天,根据意图分别路由到不同节点。

import { StateGraph, END } from"@langchain/langgraph";import { Annotation } from"@langchain/langgraph";import { BaseMessage, HumanMessage } from"@langchain/core/messages";import { ChatOpenAI } from"@langchain/openai";import { z } from"zod";// 扩展状态,加入 intent 字段constRouterState = Annotation.Root({messages: Annotation<BaseMessage[]>({    reducer: (x, y) => x.concat(y),    default: () => [],  }),intent: Annotation<string>({    reducer: (_, y) => y,    default: () =>"",  }),});const llm = newChatOpenAI({ model: "gpt-4o" });// 意图识别节点:让 LLM 判断用户想做什么asyncfunctionclassifyIntent(state: typeof RouterState.State) {const lastMsg = state.messages[state.messages.length - 1];const schema = z.object({    intent: z.enum(["weather", "code", "chat"]).describe(      "用户意图:weather=查天气,code=查代码文档,chat=随意聊天"    ),  });const structured = llm.withStructuredOutput(schema);const result = await structured.invoke([    { role: "system", content: "判断用户意图,只返回 weather/code/chat 三者之一" },    lastMsg,  ]);return { intent: result.intent };}// 三个专家节点asyncfunctionhandleWeather(state: typeof RouterState.State) {// 接入天气 APIreturn { messages: [{ role: "assistant", content: "当前北京天气:晴,25°C" }] };}asyncfunctionhandleCode(state: typeof RouterState.State) {// 搜索技术文档return { messages: [{ role: "assistant", content: "找到相关文档:..." }] };}asyncfunctionhandleChat(state: typeof RouterState.State) {const response = await llm.invoke(state.messages);return { messages: [response] };}// ✅ 多路路由函数functionrouteByIntent(  state: typeof RouterState.State): "weather_node" | "code_node" | "chat_node" {const intentMap = {    weather: "weather_node"asconst,    code: "code_node"asconst,    chat: "chat_node"asconst,  };return intentMap[state.intentas keyof typeof intentMap] ?? "chat_node";}const workflow = newStateGraph(RouterState)  .addNode("classify", classifyIntent)  .addNode("weather_node", handleWeather)  .addNode("code_node", handleCode)  .addNode("chat_node", handleChat)  .addEdge("__start__", "classify")  .addConditionalEdges("classify", routeByIntent, {    // ✅ 显式传 edgeMapping,语义更清晰    weather_node: "weather_node",    code_node: "code_node",    chat_node: "chat_node",  })  .addEdge("weather_node", "__end__")  .addEdge("code_node", "__end__")  .addEdge("chat_node", "__end__");const app = workflow.compile();

多路路由的关键是:路由函数的返回值必须与 edgeMapping 或节点名一一对应,少一个分支,LangGraph 在图校验阶段就会报错。


05 循环路由:让 Agent 不断尝试直到满足条件

条件路由有个非常重要的用法:构造 Agentic Loop(智能体循环)。Agent 调用工具 → 看结果 → 决定是否继续调用 or 结束。这个模式几乎在所有 ReAct Agent 里都有。

┌─────────────────────────────────────────────────────────┐│                    Agentic Loop                          ││                                                          ││   START ──> [agent] ──> router ──> "tools" ──> [tools] ││                  ▲                                  │    ││                  └──────────────────────────────────┘    ││                                                          ││             router ──> "__end__" ──> END                 │└─────────────────────────────────────────────────────────┘

关键点:.addEdge("tools", "agent") 把工具节点的输出重新接回 agent 节点,条件路由控制循环的退出时机。

// 带最大循环次数保护的 RouterconstSafeLoopState = Annotation.Root({messages: Annotation<BaseMessage[]>({    reducer: (x, y) => x.concat(y),    default: () => [],  }),iterationCount: Annotation<number>({    reducer: (_, y) => y,    default: () =>0,  }),});constMAX_ITERATIONS = 10;functionsafeRouter(  state: typeof SafeLoopState.State): "tools" | "__end__" {// ✅ 安全退出:超过最大循环次数强制结束if (state.iterationCount >= MAX_ITERATIONS) {    console.warn(`达到最大迭代次数 ${MAX_ITERATIONS},强制结束`);    return"__end__";  }const lastMessage = state.messages[state.messages.length - 1] asAIMessage;if (lastMessage.tool_calls && lastMessage.tool_calls.length > 0) {    return"tools";  }return"__end__";}asyncfunctioncallAgentWithCounter(state: typeof SafeLoopState.State) {const response = await model.invoke(state.messages);return {    messages: [response],    iterationCount: state.iterationCount + 1,  // ✅ 每次循环计数 +1  };}

加循环次数保护是生产环境必须做的事。没有这个保护,一旦 LLM 出现幻觉或工具返回异常,Agent 会无限循环,把你的 API 额度烧光。


06 LLM 作为路由函数:语义驱动的动态分支

规则型路由适合条件明确的场景。当判断逻辑很复杂,或者需要理解自然语言时,用 LLM 来做路由会更合适。

import { z } from"zod";import { ChatOpenAI } from"@langchain/openai";const routerLLM = newChatOpenAI({ model: "gpt-4o-mini", temperature: 0 });// 定义路由决策的结构化输出constRouterDecision = z.object({next: z.enum(["researcher", "writer", "tools", "__end__"]),reason: z.string().describe("路由理由,一句话说明"),});asyncfunctionllmRouter(  state: typeof AgentState.State): Promise<"researcher" | "writer" | "tools" | "__end__"> {const structured = routerLLM.withStructuredOutput(RouterDecision);const result = await structured.invoke([    {      role: "system",      content: `你是一个工作流路由器。根据当前状态决定下一步:- researcher:需要搜集外部信息- writer:已有足够信息,可以开始写作- tools:需要调用工具(计算、查询等)- __end__:任务已完成`,    },    {      role: "user",      content: `当前消息历史:\n${state.messages.slice(-3).map((m) => m.content).join("\n")}`,    },  ]);console.log(`路由决策:${result.next},理由:${result.reason}`);return result.next;}

LLM 路由和规则路由的对比:

┌─────────────────────────────────────────────────────────────┐│              规则路由  vs  LLM 路由                          │├────────────────────────┬────────────────────────────────────┤│   规则路由              │   LLM 路由                         ││   速度:快(无 LLM 调用)│   速度:慢(需要 LLM 推理)        ││   成本:低               │   成本:高                        ││   准确性:规则范围内 100%│   准确性:90%+(视 prompt 质量)   ││   灵活性:低(硬编码)   │   灵活性:高(理解语义)            ││   适合场景:工具调用判断 │   适合场景:意图分类、复杂判断      │└────────────────────────┴────────────────────────────────────┘

实际项目里,两者经常混用:主干用 LLM 路由做意图分类,关键退出点用规则路由做保护


07 完整实战:客服 Agent 多轮分支决策

把前面几个概念整合起来,写一个真实可跑的客服 Agent,支持:查询订单、技术支持、通用问答三个分支,并有循环保护。

import { StateGraph, END, START } from"@langchain/langgraph";import { Annotation, messagesStateReducer } from"@langchain/langgraph";import { BaseMessage, AIMessage, HumanMessage } from"@langchain/core/messages";import { ChatOpenAI } from"@langchain/openai";import { tool } from"@langchain/core/tools";import { z } from"zod";import { ToolNode } from"@langchain/langgraph/prebuilt";// ── 状态定义 ──constCustomerServiceState = Annotation.Root({messages: Annotation<BaseMessage[]>({    reducer: messagesStateReducer,    default: () => [],  }),category: Annotation<string>({    reducer: (_, y) => y,    default: () =>"",  }),resolved: Annotation<boolean>({    reducer: (_, y) => y,    default: () =>false,  }),turnCount: Annotation<number>({    reducer: (_, y) => y,    default: () =>0,  }),});// ── 工具定义 ──const queryOrderTool = tool(async ({ orderId }: { orderId: string }) => {    // 模拟订单查询    returnJSON.stringify({      orderId,      status: "已发货",      estimatedDelivery: "2026-04-28",      carrier: "顺丰",      trackingNo: "SF1234567890",    });  },  {    name: "query_order",    description: "查询订单状态和物流信息",    schema: z.object({ orderId: z.string().describe("订单ID") }),  });const allTools = [queryOrderTool];const toolNode = newToolNode(allTools);const llm = newChatOpenAI({ model: "gpt-4o" }).bindTools(allTools);const classifierLLM = newChatOpenAI({ model: "gpt-4o-mini", temperature: 0 });// ── 分类节点 ──asyncfunctionclassifyMessage(  state: typeof CustomerServiceState.State) {const schema = z.object({    category: z.enum(["order", "tech", "general"]),  });const structured = classifierLLM.withStructuredOutput(schema);const lastMsg = state.messages[state.messages.length - 1];const result = await structured.invoke([    { role: "system", content: "分类用户问题:order=订单/物流,tech=技术支持,general=通用问答" },    lastMsg,  ]);return { category: result.category };}// ── 各专家节点 ──asyncfunctionorderAgent(state: typeof CustomerServiceState.State) {const systemPrompt = `你是订单客服专员。帮助用户查询订单状态、处理物流问题。可以调用 query_order 工具查询实时订单信息。`;const response = await llm.invoke([    { role: "system", content: systemPrompt },    ...state.messages,  ]);return {    messages: [response],    turnCount: state.turnCount + 1,  };}asyncfunctiontechAgent(state: typeof CustomerServiceState.State) {const baseModel = newChatOpenAI({ model: "gpt-4o" });const systemPrompt = `你是技术支持工程师。帮助用户解决产品使用问题。回答要简洁、可操作。`;const response = await baseModel.invoke([    { role: "system", content: systemPrompt },    ...state.messages,  ]);return {    messages: [response],    resolved: true,    turnCount: state.turnCount + 1,  };}asyncfunctiongeneralAgent(state: typeof CustomerServiceState.State) {const baseModel = newChatOpenAI({ model: "gpt-4o" });const response = await baseModel.invoke(state.messages);return {    messages: [response],    resolved: true,    turnCount: state.turnCount + 1,  };}// ── 路由函数 ──functionrouteByCategory(  state: typeof CustomerServiceState.State): "order_agent" | "tech_agent" | "general_agent" {const map = {    order: "order_agent"asconst,    tech: "tech_agent"asconst,    general: "general_agent"asconst,  };return map[state.categoryas keyof typeof map] ?? "general_agent";}functionshouldOrderContinue(  state: typeof CustomerServiceState.State): "tools" | "__end__" {if (state.turnCount >= 5) return"__end__";const last = state.messages[state.messages.length - 1] asAIMessage;return last.tool_calls?.length ? "tools" : "__end__";}// ── 构建 Graph ──const workflow = newStateGraph(CustomerServiceState)  .addNode("classify", classifyMessage)  .addNode("order_agent", orderAgent)  .addNode("tech_agent", techAgent)  .addNode("general_agent", generalAgent)  .addNode("tools", toolNode)  .addEdge(START, "classify")  .addConditionalEdges("classify", routeByCategory)  .addConditionalEdges("order_agent", shouldOrderContinue)  .addEdge("tools", "order_agent")  .addEdge("tech_agent", END)  .addEdge("general_agent", END);const app = workflow.compile();// ── 测试运行 ──asyncfunctionmain() {const result = await app.invoke({    messages: [newHumanMessage("我的订单 ORDER-2026-001 现在在哪了?")],  });console.log("最终回复:", result.messages[result.messages.length - 1].content);}main().catch(console.error);

08 常见坑与自查清单

实际用 Conditional Edge 时,这几个坑踩的人最多:

坑1:路由函数返回值与节点名对不上

// ❌ 错误:edgeMapping 里没有 "finish"functionrouter(state) { return"finish"; }graph.addConditionalEdges("node_a", router, {"done": "end_node",  // "finish" 找不到对应,图校验直接报错});// ✅ 正确:返回值与 edgeMapping key 完全匹配functionrouter(state) { return"done"; }graph.addConditionalEdges("node_a", router, {"done": "end_node",});

坑2:忘记处理所有可能的返回值分支

LangGraph 在 compile() 时会校验所有声明的分支目标是否存在。漏写一个节点定义,直接编译报错。

坑3:循环路由没有退出条件

如果 Agent 节点和工具节点互相跳转,但忘记加"返回 __end__"的分支,会无限循环。每个循环路由都必须有能到达 END 的路径

坑4:路由函数是 async 的但没有 await

LangGraph 的路由函数支持异步(用于 LLM 路由),但必须显式 await,否则返回的是 Promise 对象,不是节点名字符串。

// ✅ 异步路由函数正确写法.addConditionalEdges("classify", async (state) => {  const result = await llmRouter(state);  // ← await 不能少  return result;})

坑5:多个条件边共享同一节点时,只有最后一个生效

同一个 sourceNode 只能调用一次 addConditionalEdges。如果多次调用,后面的会覆盖前面的。把所有分支逻辑写进同一个路由函数里。


总结

这篇我们从头到尾拆解了 LangGraph 的 Conditional Edge:

  • addConditionalEdges 三个参数:sourceNode、路由函数、可选的 edgeMapping,路由函数返回值决定跳转目标
  • 两种路由策略:规则路由速度快、成本低,适合固定判断;LLM 路由理解语义,适合复杂意图分类
  • 循环路由必须加保护:用计数器或 resolved 标志控制退出,生产环境 MAX_ITERATIONS 不能省
  • edgeMapping 决定分支完整性:路由函数的所有返回值都必须在映射表里有对应,否则 compile() 报错
  • 异步路由函数要显式 await:LLM 路由用 async 函数时,内部调用必须加 await
  • 一个节点只能有一组 Conditional Edge:多路分支写在同一个路由函数里,不要重复调用 addConditionalEdges

学AI大模型的正确顺序,千万不要搞错了

🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!

有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!

就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋

在这里插入图片描述

📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇

学习路线:

✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经

以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!

我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

在这里插入图片描述

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐