73-LangGraph多Agent状态机-Agent握手交接-并行汇聚与故障恢复

文章目录

  • 【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。


思考 && 总结

  1. 多Agent状态机的核心是"分区状态"设计: 每个Agent一个专属字段区,读公共区、写私有区------职责边界清晰了,协作才不会乱。
  2. LangGraph的fan-in是自动汇聚: 多条入边的节点天然等待所有上游完成,这是它比手写asyncio编排优雅的地方。
  3. 故障恢复要分层: 节点内重试(抗抖动)→ 条件边降级(抗单点失败)→ merge内兜底(保证最终有输出),三层缺一不可。
  4. 并行的前提是任务独立: 如果写作Agent必须等调研结果才能动笔,强行并行只会得到两个互相等待的节点。

从单Agent流程到多Agent状态机,本质是从"程序设计"走向"系统设计"------你考虑的不再只是逻辑对不对,而是分工、容错和协作效率。


结尾

各位小伙伴,本文的内容到这里就全部结束了,源码骑士在这里再次感谢您的阅读!

源码骑士 --- Android Framework & 全栈开发

👀 关注:跟博主一起从源码视角深耕底层原理,见证每一次成长

❤️ 点赞:让优质内容被更多人看见,让知识传递更有力量

收藏:把核心知识点存好,在需要时随时查、随时用

💬 评论:分享你的经验或疑问,评论区一起交流避坑

🔄 一键四连:不要忘记给博主"一键四连"哦!

🗡️ 寄语:技术之路难免有困惑,但同行的人会让前进更有方向

结语:多Agent协作不是把Agent数量堆上去,而是把分工、交接、容错这三件事设计明白。画好状态图再写代码,你的多Agent系统就成功了一半。不要忘记给博主"一键四连"哦!

相关推荐
DogDaoDao27 分钟前
【第10篇】Python 异常处理与调试入门
python·深度学习·ai·程序员·大模型·异常处理与调试
程序员雷欧40 分钟前
Java 反射深度解析:从原理到源码的全面剖析
java·开发语言·python
我的xiaodoujiao1 小时前
快速学习Python基础知识详细图文教程18--多线程
开发语言·python·学习·测试工具
❀搜不到2 小时前
isat-sam分割导出掩码图
开发语言·python
Zane19942 小时前
ClassName() 只是一步?拆开看 __new__ 和 __init__ 各自在干什么
后端·python
曾阿伦2 小时前
Trae CN Python环境调试debug指南
开发语言·python
渣男教父3 小时前
Python游戏自动化实战案例
python·编程语言
天天爱吃肉82183 小时前
【工程师笔记|新能源整车电控一次过CISPR25/BCI,汽车EMC/EMI落地十大核心设计技巧】
大数据·人工智能·笔记·python·汽车
m0_617493943 小时前
Python 使用 cryptography 实现数据加密与解密
开发语言·python
FriendshipT3 小时前
ComfyUI 使用 MiniMax H3 进行文生视频
人工智能·pytorch·python·深度学习·音视频