资讯动态

Agentic AI 可靠性实战:从状态机到分布式容错

发布时间:2026/10/17 7:40:42 来源:尧图企业网站定制
1. 从一次线上事故说起为什么 Agent 的可靠性问题被严重低估了去年冬天我负责的一个多 Agent 协作系统在凌晨两点崩了。不是那种干脆利落的崩溃而是半死不活——任务队列里堆着三百多个待处理请求每个 Agent 都显示运行中但没有任何一个真正在推进工作。日志里翻来覆去就是那几行agent execution terminated due to error、the agent execution provider did not respond in time。重启服务之后问题暂时消失但三天后又卷土重来。那次事故让我彻底意识到一件事我们一直在用请求-响应的思维去构建 Agent 系统但 Agent 的本质根本不是一次 API 调用而是一个长期运行的分布式状态机。这个认知转变非常关键。当你把 Agent 当成一个函数——输入问题、输出答案——你会觉得它跟普通的微服务没什么区别加个重试、加个超时、加个熔断就完事了。但当你把 Agent 当成一个可能运行几分钟、几小时甚至几天的状态机它要维护记忆、要调用工具、要跟其他 Agent 协作、要在失败后恢复现场问题的性质就完全变了。这篇文章我想聊的就是这个Agentic AI 基础设施的可靠性到底难在哪里以及我们该怎么系统性地解决它。不管你是正在搭建第一个 AI Agent 的开发者还是已经在维护多 Agent 协作平台的工程师这些从实战里踩出来的经验应该都能帮到你。我会从架构设计、状态管理、故障恢复、可观测性几个维度展开尽量把每个为什么讲透而不是只丢一堆结论。2. 重新理解 Agent它不是函数是状态机2.1 函数式思维带来的三个致命误判大部分 Agent 框架的入门文档都在教你写这样的代码给 Agent 一个任务它思考、调用工具、再思考最后返回结果。这个心智模型太像普通的 API 调用了于是我们自然而然地套用了微服务的那套可靠性方案。但这里藏着三个致命误判。第一个误判是执行时间是秒级的。普通 API 调用通常几十到几百毫秒超时设个 5 秒绰绰有余。但一个复杂的 Agent 任务比如帮我调研这个行业的竞争格局并生成报告可能要调用十几次工具、做几十轮推理执行时间轻松突破几分钟。你按秒级超时去设计Agent 会在正常工作时被误杀。第二个误判是执行是无状态的。函数调用完了就完了下次调用从零开始。但 Agent 有记忆——短期记忆是当前对话上下文长期记忆是向量库里的历史经验永久记忆可能是写进数据库的用户偏好。这些状态在 Agent 运行过程中不断变化一旦进程崩溃状态就丢了一半。第三个误判是失败可以简单重试。普通接口失败了重试一次没副作用。但 Agent 重试可能意味着重复调用了一个有副作用的工具比如已经发了邮件、已经扣了款、重复消耗了大量 token、或者在一个已经部分完成的任务上从头再来。重试的代价完全不是一个量级。2.2 状态机视角下的 Agent 生命周期换个视角把 Agent 看成一个状态机它的生命周期大致是这样的初始化 → 规划 → 执行循环思考-调用工具-观察结果→ 收敛 → 完成/失败。每个状态之间的转换都可能出问题而且很多问题是卡在中间——既没成功也没失败就那么挂着。我习惯把 Agent 的状态分成三类来管理状态类型具体内容存储位置丢失后果瞬时状态当前推理步骤、临时变量内存可重建影响小会话状态对话历史、工具调用记录Redis/内存需恢复影响中持久状态长期记忆、任务进度、副作用记录数据库不可丢影响大这个分类直接决定了你的持久化策略。瞬时状态丢了无所谓会话状态要能快速恢复持久状态必须保证不丢。很多团队出问题就是把该持久化的东西放在了内存里进程一重启全没了。2.3 为什么分布式是绕不开的单机 Agent 已经够复杂了为什么还要分布式因为现实需求逼着你这么做。一个 Agent 要处理的任务可能超出单机算力需要多个 Agent 并行协作Agent 要调用的工具可能分布在不同的服务里为了高可用你不能让所有 Agent 跑在一台机器上。但分布式一上来问题就指数级放大。网络分区、时钟漂移、消息乱序、部分失败——这些分布式系统的经典难题在 Agent 场景下全部重现而且因为 Agent 执行时间长、状态复杂处理起来更棘手。比如两个 Agent 协作完成一个任务A 把子任务派给 BB 执行到一半网络断了A 不知道 B 到底做没做这时候 A 该重试还是该等待这就是典型的分布式状态机一致性问题。3. 核心难点拆解Agent 可靠性到底难在哪3.1 长时运行带来的状态一致性挑战Agent 运行时间长意味着它跨越的时间窗口里外部环境可能已经变了。举个具体的例子Agent 在规划阶段读取了库存数据决定下单 100 件商品然后它花了 3 分钟做其他推理等真正执行下单时库存可能已经被别人抢光了。这就是典型的读-改-写竞态在长时运行的 Agent 里特别常见。解决思路有两种。一种是乐观锁执行前再校验一次状态不一致就重新规划。另一种是预留机制规划阶段就把资源锁定执行阶段直接用。前者实现简单但可能频繁重规划后者一致性强但会降低并发。我的经验是对于副作用大的操作下单、支付、发消息用预留机制对于只读操作或幂等操作可以用乐观锁。还有一个更隐蔽的问题状态版本漂移。Agent A 和 Agent B 共享一份任务状态A 更新了状态但 B 还在用旧版本两边基于不同版本做决策最后冲突。这个在分布式系统里靠版本号version或向量时钟vector clock解决但 Agent 场景下很多人压根没考虑这层。3.2 工具调用的不确定性超时、幂等与副作用Agent 调用工具是整个系统里最脆弱的一环。工具可能是外部 API、可能是数据库、可能是另一个 Agent任何一个环节出问题都会传导上来。我整理过我们系统里工具调用失败的分布大致是这样的网络超时约 40%工具本身报错约 30%参数错误约 15%权限/配额问题约 10%其他约 5%超时是最常见的但超时最麻烦的地方在于你不知道对方到底执行了没有。请求发出去了超时了可能对方根本没收到也可能对方收到了正在执行也可能对方执行完了但响应丢了。这三种情况处理方式完全不同。所以工具调用必须区分幂等和非幂等。幂等的工具查询、计算超时了直接重试没问题。非幂等的工具下单、发消息必须配合去重键idempotency key让工具方能够识别重复请求。我们给每个非幂等调用生成一个全局唯一的 request_id工具方收到后先查这个 id 有没有处理过处理过就直接返回上次结果。提示设计工具接口时把是否幂等作为必填元数据。Agent 在决定重试策略时先看这个标记非幂等的绝不盲目重试。3.3 多 Agent 协作中的死锁与活锁多 Agent 协作最容易出的问题是死锁和活锁。死锁是大家互相等谁也不动活锁是大家都在动但一直在做无用功任务永远推进不了。我遇到过一个典型的死锁场景Agent A 等 Agent B 的输出才能继续Agent B 等 Agent A 释放某个资源才能开始两边就这么僵住了。这种在传统并发编程里靠资源排序解决在 Agent 协作里则需要明确的依赖图和超时机制。每个 Agent 等待其他 Agent 时必须设一个最大等待时间超时后要么降级处理要么上报给协调者。活锁更隐蔽。比如两个 Agent 都在尝试解决同一个冲突A 改一下、B 改回来、A 再改、B 再改回来来回拉锯。这种要靠冲突解决策略——要么指定优先级要么引入随机退避要么让一个中立的协调 Agent 来裁决。3.4 记忆系统的读写竞争与污染Agent 的记忆系统是可靠性的重灾区。短期记忆上下文还好主要是长度管理问题。长期记忆向量库就麻烦了多个 Agent 同时读写同一个记忆库可能出现写覆盖、读到脏数据、记忆污染。记忆污染特别值得说。如果 Agent A 往记忆库里写了一条错误信息Agent B 读到之后基于它做了错误决策又把新的错误信息写回去错误就会像病毒一样扩散。我们后来加了一个记忆写入审核机制重要的记忆写入前要经过校验比如跟已有记忆做一致性检查冲突的记忆标记为待人工确认而不是直接覆盖。4. 可靠性基础设施的搭建从持久化到可观测4.1 状态持久化选对存储设计对模型状态持久化的第一原则是按状态类型选存储不要一刀切。我们的实践是这样的瞬时状态放内存进程内变量不持久化会话状态放 Redis设 TTL支持快速读写和过期清理持久状态放关系型数据库PostgreSQL强一致支持事务向量记忆放专用向量库配合元数据过滤关键设计点是状态快照snapshot。Agent 每完成一个关键步骤就把当前完整状态序列化存一份。这样进程崩溃后可以从最近的快照恢复而不是从头再来。快照的粒度要权衡太频繁影响性能太稀疏恢复代价大。我们的经验是每个工具调用前后各存一次因为工具调用是状态变化最剧烈的点。序列化格式我推荐用 JSON 或 MessagePack别用 pickle 这类跟语言绑定的格式。原因很简单Agent 系统往往是多语言混布的Python 写的 Agent 可能要把状态交给 Go 写的协调器格式必须跨语言。4.2 幂等设计让重试变得安全幂等是可靠性的基石。我前面提了去重键这里展开讲讲怎么落地。核心思路是每个会产生副作用的操作都要有一个全局唯一的标识并且操作方要记录这个标识的处理状态。具体实现上我们建了一张operation_log表字段包括request_id、operation_type、statuspending/success/failed、result、created_at。工具方收到请求后用request_id查表如果记录存在且 statussuccess直接返回缓存的 result如果记录存在且 statuspending说明有并发请求返回处理中如果不存在插入一条 pending 记录执行业务逻辑完成后更新为 success这套机制配合数据库的唯一索引能保证即使请求重复到达副作用也只发生一次。代价是每次操作多一次数据库读写但对于非幂等操作来说这个代价完全值得。4.3 超时、重试与熔断的协同策略超时、重试、熔断这三个机制必须协同设计单独用任何一个都会出问题。我见过太多团队只设了重试没设熔断结果下游服务挂了上游疯狂重试把下游彻底压垮。我们的策略是这样的机制触发条件动作参数建议超时单次调用超过阈值中断调用按 P99 延迟的 2-3 倍设重试超时或可重试错误重新调用指数退避最多 3 次熔断错误率超过阈值快速失败错误率50% 持续 30s 触发重试一定要用指数退避加抖动exponential backoff with jitter。固定间隔重试会让所有失败的请求在同一时刻再次涌向下游形成重试风暴。加抖动就是让每个请求的退避时间随机化错开重试高峰。熔断的状态机是关闭→打开→半开三态。关闭时正常放行错误率超标后打开直接拒绝过一段时间进入半开放少量请求试探试探成功就关闭失败就继续打开。这个在 Agent 场景下特别重要因为 Agent 的重试逻辑往往比普通服务更激进。4.4 可观测性让 Agent 的黑盒变透明Agent 最难调试的地方就是它像个黑盒——你只看到输入和输出中间发生了什么全靠猜。可观测性就是要把这个黑盒打开。三个支柱日志、指标、追踪。日志要结构化每条日志带上trace_id、agent_id、step、tool_name这些字段方便串联。指标要覆盖几个关键维度任务成功率、平均执行时长、工具调用失败率、token 消耗、重试次数。追踪tracing最关键用 OpenTelemetry 这类标准把一次 Agent 执行的完整链路串起来每个工具调用是一个 span能清楚看到时间花在哪。我特别想强调**执行轨迹execution trace**的价值。把 Agent 的每一步思考、每次工具调用、每个观察结果都记录下来出问题时可以完整回放。我们系统里有个事故回放功能输入 trace_id 就能看到那次失败执行的完整过程定位问题效率提升了好几倍。5. 实操落地一套可复现的 Agent 可靠性方案5.1 环境与依赖准备假设你要搭建一套带可靠性保障的 Agent 系统我按我们的技术栈给个参考配置。语言用 Python生态最全框架可以用 LangGraph 或自己写状态机存储用 PostgreSQL Redis向量库用 pgvector省得再维护一套可观测性用 OpenTelemetry Prometheus Grafana。依赖清单大致是pip install langgraph psycopg2-binary redis pgvector opentelemetry-api \ opentelemetry-sdk opentelemetry-instrumentation prometheus-client \ tenacity pydantictenacity用来做重试pydantic用来做状态模型校验这两个是可靠性方案里的关键工具。别小看 pydantic状态序列化/反序列化时它能帮你挡住大量脏数据。5.2 状态机骨架的实现Agent 状态机的核心是一个显式的状态定义和转换逻辑。我用 pydantic 定义状态模型from pydantic import BaseModel from enum import Enum from typing import Optional, List class AgentStatus(str, Enum): INIT init PLANNING planning EXECUTING executing WAITING_TOOL waiting_tool CONVERGING converging DONE done FAILED failed class AgentState(BaseModel): agent_id: str task_id: str status: AgentStatus step: int 0 context: List[dict] [] pending_tool_call: Optional[dict] None version: int 0 last_snapshot_at: Optional[str] None注意version字段这是乐观锁用的。每次状态更新前先读当前 version更新时带上WHERE version ?更新成功 version 加一失败说明有并发冲突需要重新读取再试。状态转换函数要保证幂等同样的输入状态执行多次结果一致。这样即使恢复时重复执行某个转换也不会出问题。5.3 工具调用的幂等封装工具调用的封装是可靠性的关键。我写了一个装饰器自动处理幂等、重试、超时import uuid from tenacity import retry, stop_after_attempt, wait_exponential_jitter def reliable_tool(idempotent: bool False, timeout: int 30): def decorator(func): retry( stopstop_after_attempt(3), waitwait_exponential_jitter(initial1, max10), reraiseTrue ) def wrapper(*args, **kwargs): request_id kwargs.pop(request_id, None) or str(uuid.uuid4()) if not idempotent: # 非幂等操作先查 operation_log existing query_operation_log(request_id) if existing and existing[status] success: return existing[result] result func(*args, **kwargs, timeouttimeout) if not idempotent: save_operation_log(request_id, success, result) return result return wrapper return decorator这个装饰器把幂等检查、重试、超时都包进去了业务代码只管写逻辑。注意wait_exponential_jitter就是指数退避加抖动比固定间隔重试稳得多。5.4 故障恢复的完整流程故障恢复的流程我画不出图这里不用图表用文字描述清楚。假设 Agent 在WAITING_TOOL状态时进程崩溃了恢复流程是加载最近快照从数据库读出该 agent_id 的最新快照恢复状态检查 pending_tool_call如果快照里有未完成的工具调用先查 operation_log 确认它到底执行了没有决定恢复动作如果工具已成功把结果填回 context状态推进到 EXECUTING如果工具未执行或失败重新发起调用版本校验恢复时检查 version 是否跟数据库一致不一致说明有其他进程改过需要合并或放弃继续执行从恢复的状态继续跑而不是从头开始这套流程的关键是快照 operation_log 双保险。快照告诉你崩溃前在干什么operation_log 告诉你那个操作到底成没成。两者结合才能准确恢复。5.5 多 Agent 协作的协调机制多 Agent 协作我用一个**协调者orchestrator**模式。协调者不干具体活只负责分配任务、监控进度、处理超时、解决冲突。每个工作 Agent 定期向协调者心跳协调者维护一张全局的任务状态表。协调者的核心逻辑是超时重分配如果某个 Agent 超过预期时间没汇报进度协调者把它标记为可疑派另一个 Agent 去查证或接管。这里要注意避免重复执行——接管前必须确认原 Agent 真的挂了而不是只是慢。我们的做法是让原 Agent 和接管 Agent 都往 operation_log 写靠幂等机制保证副作用只发生一次。6. 常见问题与排查技巧实录6.1 典型故障速查表我把我们踩过的坑整理成一张速查表遇到问题先对号入座现象可能原因排查方向解决手段Agent 卡在运行中不动工具调用超时未处理查 trace 看卡在哪个 span加超时熔断任务重复执行重试无幂等保护查 operation_log 有无重复加去重键状态丢失该持久化的放内存了查快照是否完整补持久化多 Agent 互相等待依赖环画依赖图找环加超时协调者记忆读到脏数据并发写覆盖查记忆写入时序加版本审核token 消耗异常重试风暴查重试次数指标加退避熔断6.2 三个最容易踩的坑第一个坑把超时设得太短。我们一开始按普通 API 的标准设了 5 秒超时结果 Agent 调用大模型推理经常超时触发大量无谓重试。后来改成按 P99 延迟的 2-3 倍设误杀率大幅下降。教训是Agent 的超时阈值必须基于实际延迟分布来定不能拍脑袋。第二个坑重试没有区分错误类型。不是所有错误都值得重试。参数错误、权限错误重试一万次也没用只会浪费资源。我们后来给错误分了类只有网络类、超时类、限流类错误才重试其他直接失败上报。第三个坑可观测性做得太晚。我们前两个月基本靠 print 调试出了线上问题两眼一抹黑。后来补上 tracing 才发现很多问题早就该被发现。可观测性不是锦上添花是可靠性的基础设施应该第一天就搭。6.3 独家避坑技巧分享几个文档里不会写、但实战中特别有用的技巧。技巧一给 Agent 加心跳和看门狗。Agent 每执行一步就更新一次心跳时间戳看门狗进程定期扫描发现心跳超时的 Agent 就标记为可疑并触发恢复。这个机制能自动发现卡死的 Agent比等用户报障快得多。技巧二用影子执行验证恢复逻辑。恢复逻辑最怕的是以为恢复了其实没恢复对。我们的做法是定期做故障演练随机 kill 一个 Agent 进程看它能不能正确恢复。这种混沌工程chaos engineering的思路能提前暴露恢复逻辑的 bug。技巧三状态快照加校验和。快照序列化时算一个 checksum 一起存恢复时先校验 checksum不一致说明快照损坏直接放弃用更早的快照。这个能防止恢复出一个损坏状态这种更隐蔽的问题。技巧四给记忆写入加冷却期。重要记忆写入后不立即生效而是进入一个短暂的冷却期期间可以被修正或撤回。这能有效防止错误记忆快速扩散。7. 关于 Agent 可靠性我的一些个人体会做 Agent 基础设施这两年我最大的体会是可靠性不是加出来的是设计出来的。你不可能在一个没考虑幂等的系统上事后补一个重试机制就变可靠也不可能在一个状态管理混乱的系统上事后加个监控就万事大吉。可靠性必须从架构设计的第一天就融进去。另一个体会是Agent 的可靠性问题本质上是分布式系统问题的一个新变种。那些经典的分布式难题——一致性、幂等、故障恢复、可观测性——在 Agent 场景下全部重现只是披了一层AI的外衣。所以别被Agentic AI这个词唬住把分布式系统那套成熟的方法论搬过来能解决 80% 的问题。最后说个具体的别追求 100% 可靠追求可恢复。任何分布式系统都不可能不出故障关键是出了故障能快速恢复、不丢数据、不产生错误副作用。把精力放在恢复机制上比追求永不失败务实得多。我们系统现在的目标不是零故障而是任何单点故障都能在 30 秒内自动恢复这个目标实际得多也达成了。如果你正在搭建 Agent 系统我的建议是从第一天就把状态持久化、幂等设计、可观测性这三件事做扎实。这三件事做对了后面加什么功能都不会太慌这三件事没做后面每加一个功能都是在给自己挖坑。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑