文章目录
- 【73.Python+AI】用LangGraph实现多Agent状态机:Agent间的握手、交接与故障恢复
-
- 导入语
- [1 ~> 多Agent状态机总览](#1 ~> 多Agent状态机总览)
-
- [1.1 三大工程难题](#1.1 三大工程难题)
- [2 ~> State设计:多Agent共享的"交接单"](#2 ~> State设计:多Agent共享的"交接单")
-
- [2.1 字段按Agent归属分区](#2.1 字段按Agent归属分区)
- [2.2 Agent节点:只负责自己的一亩三分地](#2.2 Agent节点:只负责自己的一亩三分地)
- [3 ~> 并行执行与汇聚](#3 ~> 并行执行与汇聚)
-
- [3.1 fan-out / fan-in 的实现](#3.1 fan-out / fan-in 的实现)
- [3.2 并行vs串行的收益](#3.2 并行vs串行的收益)
- [4 ~> 故障恢复机制](#4 ~> 故障恢复机制)
-
- [4.1 三级降级策略](#4.1 三级降级策略)
- [4.2 用条件边做降级路由](#4.2 用条件边做降级路由)
- [4.3 故障恢复决策表](#4.3 故障恢复决策表)
- [5 ~> 多Agent状态机的调试](#5 ~> 多Agent状态机的调试)
- [思考 && 总结](#思考 && 总结)
- 结尾
【73.Python+AI】用LangGraph实现多Agent状态机:Agent间的握手、交接与故障恢复
📖 文章简介: 本文讲解如何用LangGraph构建生产级的多Agent状态机。在第62篇单Agent状态图的基础上,本文聚焦多Agent场景特有的三个工程难题:Agent间的状态传递(一个Agent的产物如何干净地交给下一个)、并行执行与汇聚(多个Agent同时干活再合并结果)、以及故障恢复机制(某个Agent执行失败后如何重试/降级/人工接管)。通过一个"调研+写作+翻译"三Agent并行协作的完整实现,配上Mermaid有向图展示状态流转与汇聚节点设计,适合已经用过LangGraph单Agent流程、需要向多Agent编排进阶的开发者。

🎬 个人主页: 源码骑士
❄ 专栏传送门: 《Android开发基础》《python基础课程》
⭐️热衷从源码视角拆解技术底层原理,将复杂架构讲得通俗易懂
🎬 源码骑士的简介:
5年Android Framework系统开发经验,曾主导多项系统级性能优化专项
技术栈覆盖Android系统全链路(Binder/Handler/AMS/WMS/启动流程)及Java后端全家桶(Spring + MyBatis + Redis + Oracle)
累计产出原创技术文章100+篇,文章以流程图为特色,被读者评价为"看一篇胜过啃一周源码"
导入语
用LangGraph跑通一个单Agent审批流之后,你自然会想:能不能让两个Agent并行干活?比如一个Agent在调研资料的同时,另一个Agent在写报告框架,最后第三个Agent把两者合并?
想法很美好,实操全是坑:并行的结果怎么合并?一个Agent挂了整个流程怎么办?状态在Agent之间传来传去,怎么保证不丢字段?这篇文章就用一个完整的案例把多Agent状态机的三大难题一次解决。
1 ~> 多Agent状态机总览
#mermaid-svg-a0kh5hS4gpNQFppf{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-a0kh5hS4gpNQFppf .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-a0kh5hS4gpNQFppf .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-a0kh5hS4gpNQFppf .error-icon{fill:#552222;}#mermaid-svg-a0kh5hS4gpNQFppf .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-a0kh5hS4gpNQFppf .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-a0kh5hS4gpNQFppf .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-a0kh5hS4gpNQFppf .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-a0kh5hS4gpNQFppf .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-a0kh5hS4gpNQFppf .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-a0kh5hS4gpNQFppf .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-a0kh5hS4gpNQFppf .marker{fill:#333333;stroke:#333333;}#mermaid-svg-a0kh5hS4gpNQFppf .marker.cross{stroke:#333333;}#mermaid-svg-a0kh5hS4gpNQFppf svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-a0kh5hS4gpNQFppf p{margin:0;}#mermaid-svg-a0kh5hS4gpNQFppf .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-a0kh5hS4gpNQFppf .cluster-label text{fill:#333;}#mermaid-svg-a0kh5hS4gpNQFppf .cluster-label span{color:#333;}#mermaid-svg-a0kh5hS4gpNQFppf .cluster-label span p{background-color:transparent;}#mermaid-svg-a0kh5hS4gpNQFppf .label text,#mermaid-svg-a0kh5hS4gpNQFppf span{fill:#333;color:#333;}#mermaid-svg-a0kh5hS4gpNQFppf .node rect,#mermaid-svg-a0kh5hS4gpNQFppf .node circle,#mermaid-svg-a0kh5hS4gpNQFppf .node ellipse,#mermaid-svg-a0kh5hS4gpNQFppf .node polygon,#mermaid-svg-a0kh5hS4gpNQFppf .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-a0kh5hS4gpNQFppf .rough-node .label text,#mermaid-svg-a0kh5hS4gpNQFppf .node .label text,#mermaid-svg-a0kh5hS4gpNQFppf .image-shape .label,#mermaid-svg-a0kh5hS4gpNQFppf .icon-shape .label{text-anchor:middle;}#mermaid-svg-a0kh5hS4gpNQFppf .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-a0kh5hS4gpNQFppf .rough-node .label,#mermaid-svg-a0kh5hS4gpNQFppf .node .label,#mermaid-svg-a0kh5hS4gpNQFppf .image-shape .label,#mermaid-svg-a0kh5hS4gpNQFppf .icon-shape .label{text-align:center;}#mermaid-svg-a0kh5hS4gpNQFppf .node.clickable{cursor:pointer;}#mermaid-svg-a0kh5hS4gpNQFppf .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-a0kh5hS4gpNQFppf .arrowheadPath{fill:#333333;}#mermaid-svg-a0kh5hS4gpNQFppf .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-a0kh5hS4gpNQFppf .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-a0kh5hS4gpNQFppf .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-a0kh5hS4gpNQFppf .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-a0kh5hS4gpNQFppf .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-a0kh5hS4gpNQFppf .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-a0kh5hS4gpNQFppf .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-a0kh5hS4gpNQFppf .cluster text{fill:#333;}#mermaid-svg-a0kh5hS4gpNQFppf .cluster span{color:#333;}#mermaid-svg-a0kh5hS4gpNQFppf div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-a0kh5hS4gpNQFppf .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-a0kh5hS4gpNQFppf rect.text{fill:none;stroke-width:0;}#mermaid-svg-a0kh5hS4gpNQFppf .icon-shape,#mermaid-svg-a0kh5hS4gpNQFppf .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-a0kh5hS4gpNQFppf .icon-shape p,#mermaid-svg-a0kh5hS4gpNQFppf .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-a0kh5hS4gpNQFppf .icon-shape .label rect,#mermaid-svg-a0kh5hS4gpNQFppf .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-a0kh5hS4gpNQFppf .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-a0kh5hS4gpNQFppf .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-a0kh5hS4gpNQFppf :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 失败
重试成功
重试失败
开始
任务分发节点
调研Agent
写作Agent
汇聚节点
等待两个分支完成
翻译Agent
结束
重试/降级
降级路径
跳过调研直接写
1.1 三大工程难题
| 难题 | 本质问题 | 本文方案 |
|---|---|---|
| 状态传递 | 下游Agent需要上游的哪些字段? | 显式State schema + 增量更新 |
| 并行汇聚 | 并行分支的结果怎么合并? | LangGraph的fan-out/fan-in |
| 故障恢复 | 单点失败拖垮整个流程 | try-except包装 + 条件边降级 |
2 ~> State设计:多Agent共享的"交接单"
2.1 字段按Agent归属分区
python
from typing import TypedDict, Optional
class TeamState(TypedDict):
# 公共输入
topic: str
# 调研Agent的产物
research_notes: Optional[str]
research_status: str # pending/done/failed
# 写作Agent的产物
draft: Optional[str]
draft_status: str
# 翻译Agent的产物
translated: Optional[str]
# 全局控制
error: Optional[str]
设计要点:每个Agent只写自己的字段,读别人的字段 。这比所有Agent共用一个result字段清晰得多------出了问题一眼就能看出是哪个Agent的产物缺失。
2.2 Agent节点:只负责自己的一亩三分地
python
def researcher_node(state: TeamState) -> dict:
"""调研Agent:只更新自己的字段"""
try:
notes = search_and_summarize(state["topic"])
return {"research_notes": notes, "research_status": "done"}
except Exception as e:
return {"research_status": "failed", "error": str(e)}
def writer_node(state: TeamState) -> dict:
"""写作Agent:可以先写框架,不强依赖调研"""
draft = llm.invoke(f"为主题'{state['topic']}'写一份报告初稿").content
return {"draft": draft, "draft_status": "done"}
def merge_node(state: TeamState) -> dict:
"""汇聚节点:把调研结果融入初稿"""
if state["research_status"] == "done":
final = llm.invoke(
f"初稿:{state['draft']}\n调研资料:{state['research_notes']}\n"
f"请用调研资料充实初稿"
).content
else:
# 调研失败的降级:直接用初稿
final = state["draft"]
return {"draft": final}
3 ~> 并行执行与汇聚
3.1 fan-out / fan-in 的实现
LangGraph中,多条出边指向不同节点就是并行分发,多条入边汇入同一节点就是汇聚:
python
from langgraph.graph import StateGraph, END
workflow = StateGraph(TeamState)
workflow.add_node("dispatch", lambda s: {}) # 分发节点
workflow.add_node("researcher", researcher_node)
workflow.add_node("writer", writer_node)
workflow.add_node("merge", merge_node)
workflow.add_node("translator", translator_node)
workflow.set_entry_point("dispatch")
# fan-out:dispatch同时触发两个并行分支
workflow.add_edge("dispatch", "researcher")
workflow.add_edge("dispatch", "writer")
# fan-in:两个分支都完成后才进入merge
workflow.add_edge("researcher", "merge")
workflow.add_edge("writer", "merge")
workflow.add_edge("merge", "translator")
workflow.add_edge("translator", END)
app = workflow.compile()
关键认知: LangGraph的汇聚是自动的------
merge节点会等所有指向它的上游分支都完成才执行。你不需要自己写计数器或锁。
3.2 并行vs串行的收益
bash
串行:调研(8s) → 写作(6s) → 翻译(4s) = 18秒
并行:max(调研8s, 写作6s) → 汇聚(2s) → 翻译(4s) = 14秒
↓ 节省22%
任务越独立、单个任务越耗时,并行收益越大。
4 ~> 故障恢复机制
4.1 三级降级策略
python
def resilient_researcher(state: TeamState, max_retry=2) -> dict:
for attempt in range(max_retry + 1):
try:
notes = search_and_summarize(state["topic"])
return {"research_notes": notes, "research_status": "done"}
except RateLimitError:
if attempt < max_retry:
time.sleep(2 ** attempt) # 指数退避
continue
return {"research_status": "failed",
"error": "API限流,重试耗尽"}
except Exception as e:
return {"research_status": "failed", "error": str(e)}
4.2 用条件边做降级路由
python
def route_after_research(state: TeamState) -> str:
"""调研后决定走哪条路"""
return "merge" # 无论成败都进merge,由merge内部决定降级策略
# 更激进的方案:失败直接走fallback节点
workflow.add_conditional_edges(
"researcher",
lambda s: "merge" if s["research_status"] == "done" else "fallback"
)
4.3 故障恢复决策表
| 故障类型 | 策略 | 实现 |
|---|---|---|
| API限流/超时 | 重试 | 指数退避,最多2~3次 |
| 工具不可用 | 降级 | 跳过该Agent,下游用默认输入 |
| LLM输出格式错误 | 重试+兜底 | 重新生成,仍失败则返回占位文本 |
| 状态字段缺失 | 校验 | merge节点检查上游status字段 |
5 ~> 多Agent状态机的调试
python
# 开启流式输出,观察每个节点的执行
for event in app.stream({"topic": "异步框架对比",
"research_status": "pending",
"draft_status": "pending"}):
for node_name, output in event.items():
print(f"[{node_name}] 输出字段: {list(output.keys())}")
多Agent流程的调试口诀:盯状态字段,不盯打印日志。每个节点只更新自己负责的字段,任何字段异常都能直接定位到责任Agent。
思考 && 总结
- 多Agent状态机的核心是"分区状态"设计: 每个Agent一个专属字段区,读公共区、写私有区------职责边界清晰了,协作才不会乱。
- LangGraph的fan-in是自动汇聚: 多条入边的节点天然等待所有上游完成,这是它比手写asyncio编排优雅的地方。
- 故障恢复要分层: 节点内重试(抗抖动)→ 条件边降级(抗单点失败)→ merge内兜底(保证最终有输出),三层缺一不可。
- 并行的前提是任务独立: 如果写作Agent必须等调研结果才能动笔,强行并行只会得到两个互相等待的节点。
从单Agent流程到多Agent状态机,本质是从"程序设计"走向"系统设计"------你考虑的不再只是逻辑对不对,而是分工、容错和协作效率。
结尾
各位小伙伴,本文的内容到这里就全部结束了,源码骑士在这里再次感谢您的阅读!
源码骑士 --- Android Framework & 全栈开发
👀 关注:跟博主一起从源码视角深耕底层原理,见证每一次成长
❤️ 点赞:让优质内容被更多人看见,让知识传递更有力量
⭐ 收藏:把核心知识点存好,在需要时随时查、随时用
💬 评论:分享你的经验或疑问,评论区一起交流避坑
🔄 一键四连:不要忘记给博主"一键四连"哦!
🗡️ 寄语:技术之路难免有困惑,但同行的人会让前进更有方向
结语:多Agent协作不是把Agent数量堆上去,而是把分工、交接、容错这三件事设计明白。画好状态图再写代码,你的多Agent系统就成功了一半。不要忘记给博主"一键四连"哦!