ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

LangGraph 入门实战(上):为什么需要它?网状工作流从节点、边到分支循环

LangGraph 入门实战(上):为什么需要它?网状工作流从节点、边到分支循环 文章目录一、为什么需要多 Agent1.1 先理解 Agent 是什么1.2 单 Agent 的痛点上下文开销1.3 多 Agent 的三个核心优势1.4 从 LangChain 到 LangGraph二、网状工作流编排的核心 API2.1 第一个图Hello LangGraph2.2 代码思路拆解三、分支让图学会看情况走哪条路四、循环让图学会失败了重来五、全文总结六、核心知识点复盘七、常见问题 / 避坑指南用 LangChain 写 Agent 时流程是一根直线提示词 → 模型 → 输出。但真实的 Agent 产品往往需要判断走哪条路、失败了重试、多步循环、多人协作——这是一张网不是一条线。LangGraph 就是用来编排这种网状工作流的框架。本文从为什么需要多 Agent讲起带你用代码搭出第一张图并实现条件分支和循环重试。基础薄弱也能跟着读下来。配套代码依赖pnpmaddlangchain/core langchain/langgraph langchain/openai一、为什么需要多 Agent1.1 先理解 Agent 是什么一个 Agent 可以用一个公式理解Agent LLM大脑 Harness装备tool 工具、MCP、RAG、skill 等LLM 是大脑负责理解、规划、决策Harness 是装备工具调用、检索增强、记忆能力都属于这一层。单 Agent只有一个大脑它需要一步步思考、逐个调用工具所有事情自己扛。多 Agent有多个大脑可以并行思考主 Agent主管下发任务多个子 Agent专员并行处理后把结果交回。1.2 单 Agent 的痛点上下文开销单 Agent 架构下所有工具的描述、每个功能的 prompt 都堆在同一个 system prompt里。问题在于执行某个功能时其实只需要其中一部分 prompt但每次调用都把全部内容带上token 消耗更高更致命的是大量无关信息会干扰模型思考效率低、容易出错。多 Agent 的拆法就是每个 Agent 只保留完成自己职责所需的最少 prompt。调用 LLM 的次数虽然变多了但每次消耗的 token 更少、没有冗余信息干扰准确率反而更高。而且 Agent 是组合式的可以按需加载、动态加载。1.3 多 Agent 的三个核心优势优势说明决策准确率高、token 消耗更低每个 Agent 只带必要 prompt无冗余干扰调用次数多但单次更省、更准并行思考、任务处理主管分派子任务子 Agent 并行执行整体效率更高多角色互相讨论、纠错能力强比如编程 Agent 写代码、测试 Agent 按 TDD 写测试、验证 Agent 验收多角色互相检查类似 AutoGen 的法庭模式一个典型的分工例子编程 Agent 负责写代码 → 测试 Agent 编写测试代码 → 验证 Agent 验证结果是否符合预期 → 告诉主 Agent 是否通过。每个大脑还可以选择适合自己任务的模型写代码用强模型、简单分类用小模型。1.4 从 LangChain 到 LangGraphLangChain 提供的是基础积木LLM API 封装、document loaders文档加载、splitter切分、embedding、vector store向量库、output parser、memory 等。在工作流编排层面两者的区别是LangChain线性工作流编排——链条式一步接一步适合简单流水线LangGraph网状工作流编排——支持分支、循环、回退、并行适合复杂多 Agent 协作。一句话工作流 工作节点 组织节点的方式API。LangGraph 提供的就是把节点组织成网的能力让你从简单 Agent 走向复杂的多 Agent 协作。而且 LangChain 和 LangGraph 共享同一套底层基础设施模型、消息格式都通用上手成本很低。二、网状工作流编排的核心 API先建立一张图的心智模型LangGraph 的所有 API 都围绕这几个概念概念含义对应 API开始节点图的入口携带初始状态START工作节点干活的节点职责是读状态、算结果、更新状态addNode(名字, 函数)边连接节点决定执行完往哪走addEdge(起点, 终点)条件边按状态动态决定走向分支addConditionalEdges(...)结束节点图的出口产出最终状态END状态 state在节点之间流动的数据是图的血液Annotation.Root({...})2.1 第一个图Hello LangGraph完整代码对应src/basic-graph.mjsimport{Annotation,// 用来描述/声明工作流的状态数据部分END,// 特殊节点结束START,// 特殊节点开始StateGraph// 状态图流程编排器负责组织节点}fromlangchain/langgraph;// ① 声明状态图里流动的数据长什么样// Annotation.Root 里声明一个或多个字段constStateAnnotationAnnotation.Root({text:Annotation({// reducer状态怎么更新。_prev 是进入节点前的旧值next 是节点返回的新值// 类比 Array.reduce消消乐每经过一个节点旧状态和新值如何合并reducer:(_prev,next)next,// 这里直接用新值覆盖旧值default:(),// 默认值必须写成工厂函数})});// ② 定义节点节点就是一个普通函数// 规则节点函数接收 stateLangGraph 自动把当前状态传进来// 返回一个状态更新片段reducer 会拿它去合并出新状态conststep1(state)({text:${state.text}- step1});conststep2(state)({text:${state.text}- step2});// ③ 建图加节点、连边、编译constgraphnewStateGraph(StateAnnotation)// 实例化编排器告诉它状态结构.addNode(step1,step1)// 注册节点节点名 节点函数.addNode(step2,step2).addEdge(START,step1)// 开始 → step1.addEdge(step1,step2)// step1 → step2.addEdge(step2,END)// step2 → 结束.compile();// 编译得到可执行的图// ④可选生成 Mermaid 流程图可视化节点流转关系constdrawableawaitgraph.getGraphAsync();constmermaiddrawable.drawMermaid({withStyles:true});console.log(mermaid);// 把输出贴到 mermaid.live 即可看到流程图// ⑤ 执行invoke 传入初始状态返回最终状态constresultawaitgraph.invoke({text:hello});console.log(result);// { text: hello - step1 - step2 }2.2 代码思路拆解1状态State是核心。LangGraph 的图本质上是状态在节点间流动、被不断更新的过程。Annotation.Root就是声明这份数据有哪些字段。每个字段两个关键配置reducer: (_prev, next) next决定新值怎么覆盖旧值。这里next直接覆盖是最简单的策略default: () 初始默认值。注意必须是函数工厂模式不能直接写default: 否则多个图实例之间可能共享同一份数据。2节点 函数。节点函数不用关心图怎么运转它只做两件事读state里自己需要的字段 → 返回要更新的字段片段不用返回整个 state只返回变化的部分。3边 执行顺序。addEdge用节点名字符串连线START和END是两个特殊的虚拟节点分别表示入口和出口。4compile invoke。compile()把声明好的图编译成可执行对象invoke(初始状态)跑完整张图返回最终状态。执行过程数据变化invoke({ text: hello }) → step1: { text: hello - step1 } → step2: { text: hello - step1 - step2 } → END返回 { text: hello - step1 - step2 }三、分支让图学会看情况走哪条路直线流程用addEdge就够了但 Agent 经常需要判断数学题走计算节点聊天走对话节点。这就要用到条件边addConditionalEdges。完整代码对应src/conditional-routing.mjsimport{Annotation,END,START,StateGraph}fromlangchain/langgraph;// ① 状态query 用户输入、route 路由决策结果、answer 最终回答constStateAnnotationAnnotation.Root({query:Annotation({reducer:(_p,n)n,default:()}),route:Annotation({reducer:(_p,n)n,default:()chat}),answer:Annotation({reducer:(_p,n)n,default:()})});// ② 路由节点只做决策不产出答案// 判断 query 里是否包含 - * / 符号决定下一步走 math 还是 chatconstrouter(state){constisMath/[\-*/]/.test(state.query);return{route:isMath?math:chat};};// ③ 数学节点用 eval 计算表达式constmathNode(state){try{// eval 会把传入的字符串当做 JS 代码执行返回执行结果// 12 被当成 12 执行 → 3 → String(3) → 3return{answer:String(eval(state.query))};}catch(error){return{answer:表达式无法计算};}};// ④ 聊天节点普通回声constchatNode(state){return{answer:你说的是${state.query}};};// ⑤ 建图constgraphnewStateGraph(StateAnnotation).addNode(router,router).addNode(math,mathNode).addNode(chat,chatNode).addEdge(START,router)// 固定走向入口 → router// 条件边router 执行完后调用第二个参数这个路径函数// 路径函数返回 state.route 的值math 或 chat// 第三个参数是映射表返回值 → 实际要去的节点名.addConditionalEdges(router,(state)state.route,{math:math,chat:chat}).addEdge(math,END)// 两个分支最后都结束.addEdge(chat,END).compile();// ⑥ 测试两种输入console.log(result:,awaitgraph.invoke({query:你好}));// routechat → { answer: 你说的是你好 }console.log(result:,awaitgraph.invoke({query:12}));// routemath → { answer: 3 }条件边的工作原理重点理解addConditionalEdges(当前节点, 路径函数, 映射表)分三步当前节点执行完后LangGraph 调用路径函数把最新 state 传给它路径函数返回一个字符串这里是state.route值为math或chatLangGraph 拿这个字符串去映射表里查找到下一个节点。所以分支判断的结果是存放在 state 里的route字段路由节点负责做决定条件边负责按决定跳转。职责分离得很干净。关于eval它的特性是把字符串当 JS 代码直接执行demo 里用它做计算器很方便。但生产环境绝对不能 eval 用户输入——用户输入恶意代码比如删除文件的代码会被直接执行这是严重安全漏洞。真实项目里数学计算应换成安全的表达式解析库或干脆交给 LLM/代码解释器。四、循环让图学会失败了重来网状流程和线性流程最大的区别之一是可以兜圈子条件边的目标节点可以是之前执行过的节点于是就形成了循环。典型场景失败重试、反思修正、Agent 多轮思考直到满意。完整代码对应src/loop-retry.mjsimport{Annotation,END,START,StateGraph}fromlangchain/langgraph;// ① 状态tries 已尝试次数、ok 是否成功、message 结果信息constStateAnnotationAnnotation.Root({tries:Annotation({reducer:(_p,n)n,default:()0}),ok:Annotation({reducer:(_p,n)n,default:()false}),message:Annotation({reducer:(_p,n)n,default:()})});// ② 尝试节点每次执行 tries1第 3 次才算成功// 真实场景里这里可能是调用 LLM/接口检查结果是否合格constattempt(state){consttriesstate.tries1;constoktries3;return{tries,ok,message:ok?第${tries}次成功:第${tries}次失败};};// ③ 建图attempt → 条件判断 → 成功去 END失败回到 attempt 自己constgraphnewStateGraph(StateAnnotation).addNode(attempt,attempt).addEdge(START,attempt).addConditionalEdges(attempt,(state)state.ok?done:retry,{done:END,// 成功结束retry:attempt// 失败回到 attempt形成循环}).compile();// ④ 执行attempt 会被执行 3 次console.log(result:,awaitgraph.invoke({tries:0}));// { tries: 3, ok: true, message: 第3次成功 }循环的关键点秘诀就在映射表里retry: attempt——条件边的目标是当前节点自己流程就绕回来了每次循环节点拿到的是更新后的 statetries已经 1所以循环不会原地踏步而是带着新数据重新执行这就是 Agent 里自我反思 / 重试到成功模式的骨架把ok的判断换成LLM 输出是否通过校验立刻就是一个实用的 retry 循环。避坑循环一定要有退出条件这里是tries 3。真实项目里建议同时加一个最大次数兜底防止模型一直不满意导致死循环、token 无限消耗。五、全文总结本篇解决了两个问题为什么要用 LangGraph以及它的网状流程怎么搭。为什么多 Agent单 Agent 把所有 prompt 堆在一起token 浪费且无关信息干扰决策拆成多个专职 Agent 后每个只带最少 prompt准确率更高、token 更省还能并行处理、多角色互相纠错。LangChain vs LangGraphLangChain 提供基础积木和线性编排LangGraph 专注网状编排分支、循环、回退两者共享底层基础设施。核心 APIAnnotation.Root声明状态字段 reducer default 工厂函数节点就是收 state、返回状态片段的函数addEdge连直线addConditionalEdges做分支START/END是入口出口compile()后用invoke(初始状态)执行。分支路由节点把决策写进 state条件边用路径函数 映射表决定去向。循环条件边指回前面的节点即形成循环每次带着更新后的 state 重新执行务必设计退出条件。至于图跑一半需要暂停等人确认、之后接着跑以及状态不丢失的能力属于状态持久化与中断恢复在下篇展开。六、核心知识点复盘知识点一句话记忆Agent 公式Agent LLM大脑 Harnesstool/MCP/RAG/skill 等装备多 Agent 优势准确率高省 token、并行处理、多角色互相纠错单 Agent 痛点所有 prompt 堆一起无关信息干扰、token 浪费LangChain vs LangGraphLangChain 线性编排 基础积木LangGraph 网状编排分支/循环/多 AgentStateGraph状态图编排器加节点、连边、编译、执行Annotation.Root声明状态字段每个字段配 reducer怎么合并和 default必须是工厂函数节点函数接收 state返回状态更新片段不用返回整个 statereducer(prev, next) next决定节点返回值如何合并进状态类似 reduceaddEdge固定连线addEdge(START, node)、addEdge(node, END)addConditionalEdges(节点, 路径函数, 映射表)路径函数返回字符串映射表查下一个节点分支原理决策结果存 state如 route条件边按值跳转循环原理条件边指回已执行过的节点每次带新 state 重跑必须有退出条件START / END特殊虚拟节点分别表示图的入口和出口compile / invokecompile()得到可执行图invoke(初始状态)返回最终状态drawMermaidgraph.getGraphAsync()后drawMermaid()生成流程图文本eval字符串当 JS 执行demo 可用生产环境禁止 eval 用户输入七、常见问题 / 避坑指南1.default直接写字符串报错或状态串了default必须是工厂函数default: () 不能写成default: 。对象/数组类型尤其要注意直接写值会导致多次调用共享同一份引用。2. 节点函数里能直接修改 state 吗不要直接改。节点应该通过返回新对象来更新状态return { text: ... }LangGraph 用 reducer 合并。直接 mutate state 是不可控的副作用写法。3. 条件边不跳转 / 走了错误的分支检查三处① 路径函数返回的字符串是否和映射表的key 完全一致大小写敏感② 路由节点是否真的把决策写进了 state③ 映射表的 value 是否是已用addNode注册过的节点名。4. 循环停不下来条件边形成循环时必须保证 state 里的判断字段会朝退出方向变化如 tries 递增并设置最大重试次数兜底否则会无限循环烧 token。5. invoke 传了初始字段但节点里读不到初始状态里的字段必须先在Annotation.Root里声明过图只认声明过的字段多余字段不会进入 state。6. 想用 LLM 做路由判断而不是正则怎么做把router节点换成调用模型配合结构化输出让模型返回{ route: math | chat }其余建图代码完全不变。这正是 LangGraph 和 LangChain 共享底层的好处——节点里可以直接用 LangChain 的模型、工具、解析器。7. 节点之间可以并行执行吗可以。LangGraph 支持把多个节点扇出并行执行再汇聚fan-out/fan-in状态字段配合不同 reducer 分别收集结果。多 Agent 并行处理用的就是这套机制属于图编排的进阶用法。8.eval计算用户输入安全吗不安全。eval(恶意代码)会直接执行任意 JSdemo 仅为演示分支逻辑。生产环境请用安全的表达式求值库或让 LLM 工具调用完成计算。
返回列表