LangGraph 条件路由:用 Conditional Edge 实现动态分支决策
今天要讲的核心: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%免费】

更多推荐


所有评论(0)