想象你是一家繁忙餐厅的排菜总管。墙上贴着二十张订单,每张都写着忌口、辣度、上菜顺序;后厨里有十几个工作台——备料、掌勺、装盘、质检——你得盯着每道菜走到哪一步、哪个台子空着、哪桌在催菜。这就是 LangGraph 在做的事:它用一张“状态机流程图”管住复杂多步流程的每一步。而每位厨师手边那张标准菜谱卡:“洗净 → 切丝 → 爆炒 → 出锅”,就是 LCEL——一条把单步动作串起来的声明式流水线。排菜总管不替厨师颠勺,厨师也不负责全局调度;餐厅能高效运转,靠的是“总管管流程、菜谱管执行”的默契。这篇文章,就带你亲手搭好这对“双人搭档”:让 LangGraph 调度、LCEL 执行,联手端出一盘盘复杂的 AI Agent 大菜。
一句话价值:掌握 LangGraph 与 LCEL 的组合打法,你就能从“调 API 的脚本工”升级为“设计生产级 Agent 流程”的架构师,这也是高级智能体岗位的核心分水岭。
可视化演示
展示LangGraph状态机多步流程编排、LCEL声明式链路单步执行、两者组合的状态传递与人机交互节点。切换模式查看Agent不同构建机制。
本节配有交互动效(进入视口后自动加载,约 3KB)。
LangGraph状态机编排 节点=计算步骤,边=转移关系 条件边支持分支与循环逻辑 StateGraph(Nodes,Edges) 有向图状态机管理多步流程
状态机原理
后厨的墙上挂着一块巨大的排菜板,每一道菜是一张卡片,卡片上记录着“配菜 + 主料 + 辣度 + 特殊备注”。排菜总管只看这块板,就知道哪道菜在备料、哪道在掌勺、哪道待质检。LangGraph 就是这块排菜板背后的调度系统——它把整个工作流画成一张图来驱动。
图由两类元素构成:节点(node)和边(edge)。节点是“工作台”,执行具体的处理函数;边是“传菜路线”,决定 A 台处理完后去 B 台还是 C 台。整张图运行时会维护一份所有节点共享的状态(state):每个节点读取状态、加工、再写回,就像每个台位在订单卡上做批注。
在代码里,先用 StateGraph(StateSchema) 声明状态的“数据结构”,再用 add_node("备料", func) 注册工作台,用 add_edge / add_conditional_edge 定义路线,最后 compile() 把图“点火”成可执行对象。LangGraph 的核心循环就是:当前状态 S 沿着边找到下一节点,执行节点函数,更新状态,再找下一条边。
有了这张“排菜板”,再复杂的流程都被拆成了清晰的台位与传菜路线。但请注意:总管只负责“谁在哪儿做”,至于每个台位具体怎么把菜做好,那是菜谱卡的事——这就是接下来要讲的 LCEL。
- S_t(当前状态)此刻这道菜在排菜板上的完整信息,包括已完成的工序和待办事项。
- E_t(触发边)总管根据当前情况选择的下一条路线,例如“辣度超标 → 退回重做”。
- Node(节点函数)某个工作台的处理动作,输入状态,输出增量的结果。
- S_{t+1}(新状态)把增量合并回订单卡,得到下一回合的完整快照。
LCEL声明式链路
每位厨师台前都贴着一张菜谱卡:“洗净 → 切丝 → 大火爆炒 → 出锅装盘”。它用一条直线把工序串起来,厨师不用想下一步干嘛,照着卡走就行。LCEL(LangChain Expression Language)就是这张菜谱卡——它把“提示词整理、模型调用、结果解析”串成一条声明式的流水线。
LCEL 的核心是竖线管道运算符 |:左边产出的东西自动变成右边的输入,像传送带一样一环扣一环。任何实现了 Runnable 接口的对象——提示模板、大模型、输出解析器——都可以被 | 串起来。这就是“可运行对象”(Runnable)的统一契约:有输入、有输出、能调用。
一条典型的链长这样:prompt_template | llm | output_parser。它最终是一个 RunnableSequence,支持 invoke(同步执行)、stream(流式输出)、batch(批量处理)。声明式的妙处在于:代码读起来就是流程本身,不需要手写任何胶水逻辑。
菜谱卡让每个台位的“单步动作”标准化、可复用。但光有菜谱卡,餐厅还是转不起来——必须由排菜总管把卡发到对应工作台,并按订单顺序调度。下一节,我们就正式把 LCEL 链“递”给 LangGraph 的每个节点。
- Prompt(提示模板)把用户的自然语言问题,套进固定的提示词模板,得到完整提问文本。
- Model(大模型)接收提问文本,返回模型生成的原始回答字符串。
- Parser(输出解析器)把原始回答解析成结构化结果,例如去掉杂质、转成 JSON。
- Chain(整条链路)三者用管道串联,输入 q 直接得到最终结果。
组合集成方式
排菜总管把菜谱卡钉在每个工作台正上方:“备料台请按《凉菜卡01》执行,掌勺台请按《热菜卡07》执行”。工作台换了人,流程却不变——因为“台位职责”和“具体怎么做”被拆开了。LangGraph 与 LCEL 的组合,拆的正是这两件事。
组合的方法非常简单:把一个 LCEL 链“包”进一个普通函数,这个函数接收状态、调用链、返回增量字段,再把这个函数注册成 LangGraph 的节点。LangGraph 根本不关心节点内部是什么,它只认“输入状态 → 输出状态”这个契约。
典型的节点函数长这样:
然后 graph.add_node("node_name", node) 把它挂上图,定义好边,最后 graph.compile()。运行时,LangGraph 负责挑节点、传状态、收结果,LCEL 负责在节点内部完成“提示 → 生成 → 解析”的精细活。
菜谱卡钉上了墙,总管一声令下,台位就按卡开工。但这里有个关键问题:台位之间怎么把订单信息完整传下去?辣度、忌口、加急备注,如何在十几个台位间准确流转?这就是状态传递机制的活。
- Chain_i(第 i 条链)为某个职责预制的 LCEL 流水线,例如“总结链”“翻译链”。
- wrap(包装动作)写一个函数,把链的调用封装在“读状态 → 调链 → 写回增量”的夹层里。
- Node_i(图节点)包装后的函数符合 LangGraph 的节点契约,可被
add_node注册。 - 整体执行图调度到节点 i 时,等效于把状态 S 交给链处理,再把结果合并回去。
def node(state): result = chain.invoke({"question": state["question"]}) return {"answer": result}
状态传递机制
订单卡从进厨房的第一秒起,就在被各个台位批注:备料台写上“鱼已切好”,掌勺台写上“辣度减半已执行”,装盘台写上“少放葱”。没人重抄整张卡,每个人只在自己负责的格子里写几笔——但任何台位拿到卡,都能看到完整信息。这就是 LangGraph 状态传递的本质。
状态的结构由 StateSchema(通常是一个 TypedDict)提前定义好,例如 {"question": str, "draft": str, "messages": list}。每个节点执行完只返回自己负责的那部分字段,即增量;LangGraph 会用“归约器”(reducer)把增量合并回总状态,节点之间永远共享同一份最新快照。
对于普通字段,默认的归约规则是“覆盖”:新值替换旧值。对于需要累积的字段(比如多轮对话记录),要显式声明归约器,最常用的是 add_messages——它把新消息追加到消息列表末尾,而不是覆盖。这样状态既全局可见,又避免了每个节点重复携带全部数据。
订单卡就这样“越写越厚”地穿过整条后厨流水线——这正是复杂 Agent 的血液系统。有了血液(状态),还得有大脑(编排逻辑):什么时候该走哪条路、要不要调用外部工具,这就是 Agent 的编排能力。
- S(当前状态)此刻订单卡上的全部信息,所有节点共享同一份。
- ΔS(节点增量)某个节点只返回它改动的那几个字段,不必返回全部。
- ⊕(归约合并)根据每个字段的 reducer 类型,把增量合并回总状态 S。
- S'(新状态)下一个节点看到的完整订单卡,包含此前所有台位的批注。
Agent编排能力
有经验的排菜总管不会傻走一条线。看到订单备注“客人对花生过敏”,他立刻把菜从“常规炒台”改派到“无过敏原专台”;发现冰箱缺了鲈鱼,他马上派小工去市场采购。复杂 Agent 也是这样:根据当前状态,动态决定下一步走哪条边。
LangGraph 提供了条件边(conditional edge):一个路由函数接收状态,返回“下一个节点的名字”。这把 if-else 从代码里搬进了流程图——流程走向由状态数据驱动,而不是写死的。这是 Agent 区别于“固定流水线”的核心能力。
再加上大模型本身的推理能力,一个 Agent 循环就成型了:模型判断“需要查资料” → 路由到工具节点 → 工具返回结果写回状态 → 模型再判断“可以回答了” → 路由到输出节点。多步推理(multi-step reasoning)和工具调用(tool calling)就是这样被编排成一张动态图。
有了会看单改路线的总管,餐厅能接住各种突发状况。但有一类情况连总管都不敢拍板——比如贵宾宴席的主菜,他必须请主厨亲自尝一口再决定上不上。这种“流程停下来等人”的机制,就是人机交互节点。
- S(当前状态)包含用户问题、中间推理结果、工具返回的数据。
- Tools(工具集合)Agent 可调用的外部能力,如搜索引擎、计算器、数据库。
- Router(路由函数)读取状态 S,返回下一个节点的名字。
- next(下一节点)LangGraph 据此沿对应边把流程推向下一个工作台。
人工介入控制
国宴级别的订单,排菜总管会设定一个“主厨试菜点”:菜做完先不上桌,端到主厨面前尝一口,要么盖章放行,要么打回重做。在这个点上,整条流水线是暂停的——所有人等着主厨的裁决。这就是 human-in-the-loop(人在回路)。
LangGraph 提供了中断机制:在某个节点执行前(interrupt_before)或执行后(interrupt_after)设置断点。图运行到断点时,会保存当前状态并挂起,等待外部(人类)输入,之后从断点继续,而不是从头执行。
配合持久化(checkpointer)功能,中断后的状态可以安全保存和恢复。常见用法:Agent 生成草稿 → 中断 → 人审阅并修改 → 恢复执行后续节点。这个能力把 Agent 从“全自动工具”升级为“可控的生产流程”——就像主厨的章,让每一道大菜都有人把关。
主厨尝完菜,流程继续。但人不可能永远盯着机器——如果某个台位总是把菜做坏,总管就得定规矩:最多重做几次、重做失败怎么办。这就是容错重试策略。
- Interrupt(中断原语)把当前状态挂起,并把预设问题抛给人类。
- ask(待确认问题)例如“这份草稿是否批准发布?”。
- S_halt(挂起状态)断点时刻的完整快照,被 checkpointer 持久化保存。
- 恢复执行拿到人类回答后,合并进状态并继续后续的边。
容错重试策略
掌勺台偶尔会糊锅。总管定的规矩是:糊了,重新炒一次;又糊,再重炒一次;连炒三次还糊,立刻上报主厨换菜,绝不让客人干等。谁都不想上菜出错,但一家成熟的餐厅,靠的不是“永不犯错”,而是“错了知道怎么补救”。
复杂 Agent 的节点里,大模型调用、工具调用都可能抛出异常:网络抖动、API 限流、返回格式不对。所以每个关键节点要做两层防护:一是捕获异常(try / except),二是按策略重试(retry)。
重试策略要控制三个参数:最大重试次数 N_max、退避时长(如指数退避 1s → 2s → 4s)、重试条件(只在可恢复异常时重试)。超过 N_max 就进入兜底分支(Fallback):返回预设文案、降级模型,或转人工。LangGraph 把“重试逻辑”封装在节点内部,配合外层编排实现更精细的重试路由。
到这里,后厨的整套管理体系就齐了:排菜板管全流程、菜谱卡管单步、订单卡传递状态、总管动态派单、主厨试菜把关、糊锅重炒兜底。这就是 LangGraph 与 LCEL 组合构建复杂 Agent 的完整图景——一张自带秩序的生产流水线。
- n(已重试次数)记录这道菜已经重做几次,每次失败自增。
- N_max(上限)预先约定的最大重试次数,防止无限循环。
- Reexec(重试执行)回到节点开头,重新执行一次 LCEL 链。
- Fallback(兜底分支)重试耗尽后走降级路径,保证整体流程不崩。
AI 应用场景
智能客服工单Agent
阶段:复杂多步骤智能体(Agent)
解决的问题:传统客服脚本只能一问一答,遇到“退款 + 改地址 + 查物流”的复合需求就乱套。用 LangGraph 把工单处理拆成多步——意图识别 → 查询订单 → 生成回复 → 人工复核,再用 LCEL 实现每一步的模型调用,错误自动重试,重大投诉转人工。
具体案例:某电商平台售后 Agent,用户说“我要退款但别退到原卡”。Agent 先走“意图分类”节点,再调“退款规则检查”工具,生成草稿后中断,由客服主管确认退款方式,恢复后执行退款并发送通知。
数据分析Agent
阶段:多步推理与工具调用
解决的问题:传统 BI 工具要人写 SQL、人看报表。数据分析 Agent 把“提问 → 查数 → 分析 → 出图”串成图流程:LLM 生成 SQL → 工具执行查询 → 校验结果 → 生成结论,任一步失败按重试策略重跑或降级为规则引擎兜底。
具体案例:某 SaaS 公司的数据助手,销售问“华东区 Q2 环比增长多少”。Agent 走“语义转 SQL”LCEL 链,调用数据库工具,结果写回状态,再走“结论生成”链输出带数字的答案。全程通过 LangGraph 状态机保证每个环节可追溯、可回放。
人工审批流Agent
阶段:人机交互(human-in-the-loop)
解决的问题:纯自动化的合同生成风险高,纯人工的流程效率低。用 LangGraph 的 interrupt 把流程设为:自动起草(LCEL 链)→ 暂停 → 法务审批 → 恢复 → 自动发送。状态和审批意见在断点间无损传递。
具体案例:某公司的合同助手:业务员提交需求,Agent 调“合同模板链”生成草稿,走到“法务审核”节点自动暂停;法务在线批注点通过后,流程从断点恢复,后续节点自动完成用印申请与归档。
Python 演示
from typing import TypedDict from langchain_core.output_parsers import StrOutputParserfrom langchain_core.prompts import ChatPromptTemplatefrom langchain_openai import ChatOpenAIfrom langgraph.graph import END, StateGraph class AgentState(TypedDict): question: str draft: str answer: str llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) summarize_chain = ( ChatPromptTemplate.from_template("请先用一句话提炼问题的核心:{question}") | llm | StrOutputParser()) answer_chain = ( ChatPromptTemplate.from_template( "请结合要点【{draft}】完整回答用户问题:{question}" ) | llm | StrOutputParser()) def node_summarize(state: AgentState) -> dict: return {"draft": summarize_chain.invoke({"question": state["question"]})} def node_answer(state: AgentState) -> dict: return { "answer": answer_chain.invoke( {"question": state["question"], "draft": state["draft"]} ) } graph = StateGraph(AgentState)graph.add_node("summarize", node_summarize)graph.add_node("answer", node_answer)graph.set_entry_point("summarize")graph.add_edge("summarize", "answer")graph.add_edge("answer", END) app = graph.compile() if __name__ == "__main__": result = app.invoke({"question": "LangGraph和LCEL如何搭配使用?"}) print(result["answer"])
class AgentState(TypedDict):用类型字典定义状态的键与类型,即“订单卡模板”。summarize_chain = (LCEL 竖线管道把提示模板、模型、解析器串成一条链。| llm管道运算符把上一步输出自动送进大模型。def node_summarize(state: AgentState) -> dict:节点函数接收状态,是 LangGraph 与 LCEL 之间的适配器。graph = StateGraph(AgentState)用状态结构创建状态机图。graph.add_node("summarize", node_summarize)把 LCEL 包装节点注册到图上。graph.set_entry_point("summarize")指定流程入口节点。app = graph.compile()编译为可执行应用。result = app.invoke({"question": "LangGraph和LCEL如何搭配使用?"})喂入初始状态,启动整条流水线。
常见误区
误区用了 LangGraph 就不需要 LCEL 了,状态机把提示、模型、解析全包了,链式语法是多余的历史包袱。
正解两者职责完全不同。LangGraph 管“流程编排”——谁在哪儿做、做完去哪;LCEL 管“单步执行”——一个台位具体怎么把活干完。排菜总管再厉害也不会颠勺;LCEL 是节点内部那把“勺”。实践中几乎每个节点内部都要靠 LCEL 链来调用模型。
为何常见:因为 LangGraph 也自带调用模型的语法糖,初学者容易误以为链式 API 被淘汰了,实际上两者是互补关系而非替代关系。
误区以为在一条 LCEL 链里,前一次 `invoke` 的结果会自动保存,下一次调用还能“记得”上次的输入。
正解LCEL 是无状态的管道:输入进去、结果出来,管道本身不存任何历史。要“记住”中间结果,必须把它写进 LangGraph 的 State,由状态机在下一次节点调用时传回。菜谱卡不会自己记住上次炒的菜,记菜的是排菜板。
为何常见:因为链式调用看起来像“连续对话”,初学者容易把“流水线传参”误解为“自带记忆”。
误区写的节点函数把整个 state 抄一遍再返回,生怕漏了某个字段导致后面的节点拿不到数据。
正解节点只需要返回自己改动的那部分增量字段,LangGraph 用归约器自动合并。全量返回既啰嗦又容易覆盖别人写好的字段。台位只在自己负责的格子里写字,不用重抄整张订单。
为何常见:受普通函数“输入完整、输出完整”习惯的影响,没意识到 LangGraph 提供了声明式的状态合并机制。
误区以为 `interrupt` 就像程序崩溃,恢复后要重新调用整个图,所有节点重跑一遍。
正解interrupt 配合 checkpointer 会保存挂起时的状态快照;恢复时从断点所在的节点继续执行后续边,前面的节点不会重跑。主厨尝完菜,这道菜还在质检台上,不用重新备料。
为何常见:把“中断”理解成了“异常终止”,混淆了暂停(可续跑)与崩溃(需重启)两个概念。
面试题
Q1. LangGraph的核心抽象是什么?
- A.基于状态迁移的图结构← 正确答案
- B.基于数据表的存储结构
- C.基于消息队列的流式结构
- D.基于文件系统的存储结构
答案:A。排菜总管的核心不是某道菜怎么做,而是“菜在哪个台位、下一步去哪”。LangGraph 正是用“节点 + 边 + 状态”的图结构来编排多步流程,而不是存储、消息中间件或文件系统。
Q2. LCEL声明式链路的管道运算符是?
- A.使用加号连接组件
- B.使用竖线连接组件← 正确答案
- C.使用箭头连接组件
- D.使用冒号连接组件
答案:B。菜谱卡上的"→"在 LCEL 里写作 |。竖线把提示模板、模型、解析器串成一条链,前一步的输出自动成为后一步的输入,所以答案是竖线。
Q3. LangGraph中边(edge)的作用是什么?
- A.定义节点间流转方向← 正确答案
- B.存储大模型权重参数
- C.缓存历史对话消息
- D.加密节点间的传输
答案:A。边是“传菜路线”,决定一个节点执行完后下一个该进哪个节点。权重存储、对话缓存、传输加密都不是 LangGraph 中“边”的职责。
Q4. 将LCEL链接入LangGraph节点的正确做法是?
- A.把链直接传给add_edge调用
- B.在节点函数内调用链invoke← 正确答案
- C.用链做StateSchema结构
- D.编译后再挂载到图上面
答案:B。节点函数是 LangGraph 与 LCEL 之间的“适配器”:函数接收状态、调用链的 invoke、返回增量字段。add_edge 管的是节点间的路线,不接收业务链;用链做 StateSchema 或编译后挂载都不是标准做法。
Q5. TypedDict在LangGraph中的作用是?
- A.存储模型训练的参数
- B.定义状态的键与类型← 正确答案
- C.解析大模型的输出
- D.配置重试的次数
答案:B。TypedDict 就是“订单卡模板”,提前规定状态里有哪些字段、各是什么类型。它不是训练参数、不是解析器、也不是重试配置,而是状态结构的静态声明。
Q6. add_messages归约器的主要行为是?
- A.覆盖旧的消息列表
- B.把新消息追加到列表← 正确答案
- C.删除超过长度的消息
- D.压缩消息以减少体积
答案:B。订单卡上的“多轮对话”格需要不断追加而不是覆盖。add_messages 把新消息追加到消息列表末尾,适合累积对话历史;覆盖、删除、压缩都不是它的默认行为。
Q7. 实现人工介入(human-in-the-loop)的常用手段?
- A.节点前后设置中断点← 正确答案
- B.等待外部输入再恢复← 正确答案
- C.用LCEL并行替代暂停
- D.删除状态强制重启
答案:AB。主厨试菜 = 在节点前后设 interrupt 断点,流程挂起等待人类输入,拿到输入后从断点恢复。LCEL 并行与人工介入无关;删除状态强制重启会丢失上下文,不是中断的正规做法。
Q8. 条件路由(conditional edge)的职责是?
- A.强制全部节点串行执行
- B.把状态复制到每个分支
- C.根据状态选择下一节点← 正确答案
- D.自动回滚到上一节点
答案:C。条件边就是总管“看单派台”:路由函数读状态,返回下一个节点的名字,流程再沿对应边走。它不强制串行、不复制状态、也不负责回滚,核心就是“按状态选路”。
Q9. 节点容错重试策略通常包含哪些?
- A.捕获节点内的运行异常← 正确答案
- B.设定最大重试的次数← 正确答案
- C.重试前等待退避时长← 正确答案
- D.把失败状态永久删除
答案:ABC。糊锅重炒的完整规矩:捕获异常(知道糊了)、设上限(最多三次)、退避等待(等油温恢复)。把失败状态永久删除会让流程丢失上下文,反而是要避免的错误做法。
Q10. 人工中断恢复后,图从哪里继续执行?
- A.从中断节点继续执行← 正确答案
- B.从入口节点重新开始
- C.从末尾节点倒序执行
- D.从随机节点跳跃执行
答案:A。interrupt 配合 checkpointer 保存状态快照,恢复时从断点继续,而不是重跑整张图。就像主厨尝完菜,菜还在质检台接着走,不用重新备料、重新炒。