☰
Agent Harness稳定性优化:从99%到99.9%的可用性实践
2026/10/19 6:07:44 网站建设 项目流程

最近一年,我在负责某个跨平台 AI Agent 系统的生产稳定性优化。这个系统对外提供的不只是单个模型调用,而是一整套具备规划、记忆、工具调用能力的智能体服务。最初的可用性目标是 99%,但业务方很快发现,Agent 这个东西一旦真正被用到核心流程里,用户的耐心阈值就变得极低——一个超 30 秒的响应或者一次工具调用卡死,用户直接放弃整个需求。于是目标被拉到了 99.9%,对应全年的不可用时间从 87.6 小时压缩到 8.76 小时,对 Infra 和 Harness 层的设计要求完全上了一个台阶。

这篇文章把我们从 99% 走向 99.9% 的整个过程拆开讲:先是梳理了 Agent Harness 稳定性的难点到底在哪,然后会讲落地时真正有效的几个抓手——超时治理、幂等重试、多级降级、状态快照、可观测性、灰度发布,最后是一份高频故障的排查实录。内容以实操为主,适合正在做 Agent 平台化、智能工作流引擎、或者打算把 Agent 接入生产系统的团队参考。

1. 先拆清楚:Agent Harness 的稳定性难在哪

1.1 不确定性不是借口,是设计输入

在做传统后端服务时,下游接口的行为虽然也有波动,但整体是可预期的——数据库要么通要么超时,消息队列要么消费要么积压。但 Agent 不一样,Agent 的每一步都由大模型驱动,模型输出本身是概率性的,同一个 Prompt 换个采样参数就可能是完全不同的决策路径。

我常和别人说,Agent 服务本质上是一个“在不可控的模型行为之上构建可控业务流程”的系统。Harness 层要处理的不是某一个调用,而是整个 Agent 生命周期——从请求进来、规划拆解、工具调用、结果反馈、多轮对话,直到任务完成。这中间任何一环都可能因为模型幻觉、工具不稳定、上下文超限等原因失败,而且往往是组合式失败,比如模型决定调用工具 A,工具 A 超时,模型重试又调用了工具 B,B 返回格式异常,整个链路的耗时和错误被无限放大。

1.2 SLA 99% 到 99.9% 意味着什么

从 99% 到 99.9%,可用性提高了 10 倍,但这并不是靠“把服务部署得更稳一点”就能做到的。我复盘时画过一张可用性构成公式:

可用性 = (总时长 - 不可用时长) / 总时长

不可用时长 = 故障发生频率 × 单次故障恢复时长。要提升可用性,要么减少故障发生次数,要么缩短恢复时间。99% 阶段,我们的做法偏“事后兜底”,告警响了再处理,恢复到正常可能要 20-40 分钟。到了 99.9% 阶段,这个模式行不通,必须把故障处理前置到设计阶段:所有外部依赖都要有降级预案,所有 Agent 执行过程都要有检查点,所有容量变更都要有回滚通道。

另外有一点很多人会忽略:SLA 的统计口径。我们最初的 SLA 是“网关层成功率”,500 错误和超时算不可用。但业务方真正感知到的是“任务级成功率”——即 Agent 完整完成一个用户请求的比例。网关成功率再高,如果 Agent 经常在中间步骤挂掉,业务体验还是崩的。所以第一件事就是把度量口径对齐,后面会详细讲。

2. 动手前必须做的三件事:度量、分解、定基线

2.1 把“系统可用”拆成可量化指标

如果不去拆指标,优化工作就很容易变成“到处都加固一下”的盲目操作。我们最终把整个 Agent 执行链路拆成了四个可量化的层级:

层级核心指标说明
接入层网关可用性、P95/P99 延迟请求整体的响应表现
执行层任务完成率、平均完成时长Agent 从开始到结束是否成功
依赖层模型调用成功率、工具调用成功率外部依赖的健康度
资源层队列积压、并发水位、Token 消耗系统内部容量是否够用

每个层级的指标单独观测,问题发生时就能快速定位是网关、Harness 还是下游模型/工具的问题。如果某个时间段任务完成率下降,但从接入层看延迟正常,那问题多半在 Agent 执行逻辑或工具调用上,而不是入口堵了。

2.2 找到真正的薄弱环节

指标拆完以后,我们拉了一个月的线上数据做根因归类。结果和直觉判断差异很大:最主要的不可用来源并不是模型服务本身的故障,而是工具调用的长尾超时和Agent 状态丢失导致的重复执行。工具调用一次超时可能只有几秒,但模型拿到超时错误后会重新规划,继续调用下一个工具,整个任务的耗时指数级上升,加上状态丢失后需要重跑整个任务,故障在业务侧体现得极其明显。

后来我用了一个简单的数据透视方法:把每次任务失败按“第一个触发失败的环节”归类,再按影响时长排列。排在前面的永远是那几个固定的薄弱点——外部工具不稳定、模型偶发拒绝服务、Harness 侧的内存/连接资源不足。定位到具体环节后,优化目标就很清晰了。

2.3 优化顺序:先止血、再根治、后加固

这个阶段最容易犯的错误是上来就搞大架构改造。我们当时的经验是分三步走:

  • 先止血:把超时控制、重试、熔断这些基础能力补齐,解决“故障发生后影响面太大”的问题。
  • 再根治:针对具体根因做治理,比如工具超时重试策略优化、模型路由策略调整、上下文压缩。
  • 后加固:用混沌演练、容量规划、灰度发布把系统推向更高可用性目标。

止血阶段的收益往往是最快的,一周内的 SLA 就能恢复不少,但到了后期必须靠根治和加固才能继续往上走。

3. 稳定性保障的四个抓手

3.1 超时控制:给 Agent 的每一步都上“倒计时”

Agent 执行链路里最危险的就是“没有超时的等待”。模型推理慢一点可能靠重试解决,但如果工具调用没有超时上限,一个 HTTP 连接可能被对端半开状态挂很久。我们最开始出过一次事故:某个外部工具服务无响应,Agent 调用它的请求在底层 TCP 层面悬挂了 70 多秒,而任务层面又没有兜底时间,导致该实例的线程被耗尽,连带其他正常请求全部排队超时。

超时控制的落点要分布在每一层:

  • 模型调用超时:首次调用建议 30-60 秒,推理类模型可视情况放宽,但必须有硬上限。
  • 工具调用超时:单个工具执行建议 10-15 秒,长任务类工具需要用异步任务模式,而不是同步等待。
  • 单步执行超时:Agent 单次思考加执行的总时长要有上限,超过就取消并降级。
  • 整体任务超时:端到端最长容忍时间,比如 3 分钟,超时后进入兜底流程。

这里面最重要的设计是“倒计时联动”——每一层的超时时间必须逐级递减,保证外层能兜住内层。如果模型调用需要 40 秒,工具调用需要 15 秒,但整轮执行只给了 30 秒,那就会出现外层超时了,内部任务还在跑的情况,资源释放逻辑全部失效。我们最终设置的参照系是:整体 180 秒 > 单步 45 秒 > 模型 35 秒 > 工具 12 秒,逐级覆盖。

提示:超时不光是“到了时间就放弃”,还要考虑放弃后如何回收资源。每次超时触发时,要确保相关联的 goroutine/线程/数据库连接/外部调用上下文都被真正取消,而不是只向上层报一个超时错误。

3.2 重试与幂等:失败不可怕,可怕的是重复执行副作用

重试是提高成功率最直接的手段,但 Agent 场景下重试有很强的副作用风险。传统接口重试顶多多耗一点资源,Agent 重试可能会把同一个“建单”操作执行两次,或者在用户已经取消任务后继续跑后面的步骤。

我们最终形成的重试规范是三层:

  1. 第一层是网络级重试:只针对连接错误、瞬时 5xx、限流错误,使用指数退避加抖动(full jitter),初始间隔 500ms,最大重试 3 次。
  2. 第二层是任务级重试:Agent 执行到某个步骤失败后,允许回到上一个稳定状态重新规划,但必须有次数上限(比如 2 次),避免模型在一个错误路径上反复横跳。
  3. 第三层是业务级幂等:所有具有副作用的工具调用必须携带幂等键。Harness 在执行前生成 request_id + tool_operation_id,工具侧根据这个幂等键去重,保证即使重试也不会重复扣款、重复发单。

幂等键的生成逻辑要注意:不能每次重试都重新生成,否则就失去幂等意义了。我们通常把幂等键绑定到 Agent 的步骤 ID 上,比如 step_id + attempt_id 的 format 就可以了。另外,对于无法支撑幂等的第三方工具,只能在策略上用“短重试 + 人工补偿”的方式兜底,别硬重试。

3.3 多级降级与多模型路由

靠一家模型供应商支撑核心 SLA 是很脆弱的。我们经历过主模型服务突降可用性,所有 Agent 请求的响应时间直接翻三倍,完任务成功率掉到 95% 以下。后来做了多模型路由,主备切换的颗粒度从“整个供应商”细化到了“具体模型场景”。

模型路由的决策依据包括:供应商健康度(错误率、延迟滑动窗口)、模型能力匹配度、成本预算、上下文长度约束。每个 Agent 任务定义了一个“模型链”:首选模型 -> 备选模型 -> 兜底模型。首选模型连续失败超过阈值后,Harness 自动把流量切到备选模型,切换时对所有进行中的任务做了平滑过渡——已发出去的首选模型请求等待超时结果,新请求直接走备选。

再往下还有一层“规则兜底”。某些场景(比如简单的信息查询、单轮问答)其实不一定需要大模型复杂的推理能力,降级后可以直接走一个预置的规则引擎,或者返回一个“能力暂时不可用”的友好提示。虽然这些降级方案的用户体验不如完整 Agent,但至少保住了可用性。

3.4 资源治理:并发、Token 配额、任务队列

Agent 服务和普通 API 最大的不同是:每次请求都会消耗不可控的算力资源。两个并发请求可能只有两条 Prompt,但模型推理开销、工具链调用概率、上下文累积长度都可能完全不同,所以 Harness 层必须具备资源治理能力。

我们当时的做法有几个关键点:

  • 并发信号量:对活动中的 Agent 任务数量做全局上限控制,超过阈值的新任务进入等待队列,而不是直接打爆后端。
  • 工具调用并发独立的“子并发桶”,避免一个 Agent 任务内部并行调多个工具时挤占其他任务的资源。
  • Token 配额:LLM 供应商通常有 TPM(每分钟 Token)限制,Harness 要实时统计当前窗口消耗量,在接近上限时提前排队或降级到别的模型。
  • 任务队列持久化:新任务先写队列,再异步执行,避免突发流量把进程内存打满。

关于队列有两点经验:第一,队列长度一定要设上限并给满时的拒绝策略,否则“排队等待”会变成隐性的不可用,用户看到转圈半天没反应,比直接报错还糟糕。第二,队列本身要可持久化,否则服务重启后积压任务全部丢失,这在 Agent 场景里很容易被忽略——任务重启恢复这种机制,很多团队会忘记做,这也是我们后面专门上了快照机制的原因。

4. 从 99% 到 99.9% 的关键跨越:故障恢复与自愈

4.1 会话快照与检查点

Agent 任务是最长的单次事务之一,一个复杂的任务可能跑几十秒甚至几分钟,包含多次模型调用和工具执行。传统的“请求超时就失败”模式在这种场景下不可接受,因为重跑整个任务的成本太高了。我们的解法是引入检查点机制。

具体实现:Harness 为每个 Agent 任务维护一个状态机,状态包含了当前意图、已完成的步骤列表、每步的输入输出摘要、中间变量以及下一步的计划。在每个步骤完成之后,把状态序列化存到外部存储。这样即使执行节点崩溃,新节点也可以从最近一个检查点恢复,而不是从头开始。

检查点写入本身不能阻塞主流程,异步写是首选,但在关键步骤(比如有业务副作用之后的步骤)必须同步确认写入完成。我们用的存储是 Redis 加上对象存储双层结构,热数据在 Redis,冷数据落到对象存储。恢复时按任务 ID 找回快照,如果快照缺失就回到任务首次保存的位置。

注意:不是所有状态都适合存快照。有些上下文里包含敏感信息,落地前要做脱敏或加密。我们自己就因为审计需求把完整上下文库写过一段时间,快照存储成本直接涨了三倍。

4.2 冷启动优化

新启动的 Agent 实例在流量进来前往往没有预热好,首次请求的延迟比热身后的实例高出非常多,极端情况下甚至超时。这个现象在模型推理类服务上尤其明显——模型权重加载、显存分配、缓存初始化都需要时间。

我们把冷启动分成了两级来优化:

  • Pod/容器层面:使用预置镜像,把模型权重或推理缓存打进镜像,避免启动时去远程拉取大文件;启动脚本里做自检,确认模型加载完成、依赖连接建立后才注册到服务发现。
  • Harness 层:灰度流量从零开始逐步放大,让新实例先承接小流量,观察延迟和错误率正常后再全量放量。

还有一个小技巧:对 Agent 执行节点做固定数量的“热备份”,流量高峰前提前扩容并预热,配合定时的健康探针,避免扩容即雪崩。

4.3 混沌演练与故障注入

99.9% 的目标不是靠上线前测试测出来的,必须通过持续演练来验证系统在各类故障下的行为。我们在稳定运行一段时间后,开始周期性做故障注入演练,一个个场景来:模拟工具服务整体不可用、模型限流错误率升高、Redis 故障、Pod 被强制杀死、正常流量突然翻倍。

演练过程中我们确实发现过不少“想当然”的问题。比如有一次模拟模型供应商返回 429 限流时,我们的模型路由虽然切到了备用模型,但 Token 配额的统计没有跟着切换,导致备用模型用了两秒钟就被 quota 挡住,形成了“假切换”。这种问题只有真正在故障状态下跑一遍才能暴露。

混沌演练的节奏也很重要,先做可控时段的演练,再做不定时演练。演练前要有“停止按钮”,事故扩大时立即回切。每一轮演练必须输出行动项,否则就是走过场。我们后来把演练平台做成了自动化:定期随机模拟某个下游故障,然后自动回放流量检查任务完成率是否符合预期。

5. 可观测性建设:没有数据,一切优化都是空谈

5.1 全链路追踪的关键字段

Agent 的调用链路比普通微服务要深得多,一次用户请求可能产生几十条 Span,光是模型推理的 request/response 就可能因为流式输出而拆成多个事件。建设 tRacing 时最核心的不是技术选型,而是定义好每个跨组件调用的关键字段。我们的执行节点统一生成 trace context,并在每条 Span 上强制带上 session_id、agent_id、task_id、tool_name、model_name 这些字段,保证后续能按“一个用户任务”维度聚合追踪。

其中 tool_name 和 model_name 是排查问题时最有用的两个维度。比如我们发现某个 Agent 场景的任务完成率偏低,用 tool_name 分组一看,基本都定格在某个外部工具上——这是一个非常重要的观测特征。

经验:不要把所有 Agent 内部细节都打成 Span,否则噪声太大。只对“外部依赖调用”“模型调用”“关键状态节点”三种事件做细粒度记录,内部普通逻辑靠日志即可。

5.2 监控指标清单

可观测性指标要能支撑前文提到的四个层级。我们在 Prometheus 里建了这样一组核心指标:

  • 网关层:请求总数、成功率、P50/P95/P99 延迟、限流次数
  • 执行层:Agent 任务启动数、任务完成数、任务失败数、平均执行时长、检查点写入耗时
  • 依赖层:模型调用成功率、模型平均首 token 延迟、工具调用成功率、工具调用耗时、工具错误码分布
  • 资源层:并发任务数、队列长度、Token 消耗速率、线程池活跃度

对于每个指标都明确告警阈值和持续时长。比如“工具调用成功率低于 98% 持续 2 分钟”和“队列长度大于 500 持续 1 分钟”,这类组合条件能减少不少误报。

5.3 告警策略:分级、收敛、值班

告警不是越响越好。一开始我们的告警铺得很全,结果值班同学被高频告警轰炸以后,真正严重的故障反而被淹没。后来做了三层收敛:

  • P0 级:SLA 直接受影响,比如任务成功率低于 99%、核心模型全挂,电话通知。
  • P1 级:局部问题,比如单个工具服务错误率上升,钉钉/企微通知。
  • P2 级:容量趋势预警,比如并发趋势预测将在 30 分钟后达到配额上限,合并进日报。

告警分组上按业务线收敛,同一个任务维度的告警合并成一条,避免同一个故障触发十几个告警。其他系统可以把告警打通到同一个事件中心,值班同学只要看事件中心一个页面就够了。

6. 灰度发布与容量规划

6.1 金丝雀发布

Agent Harness 和普通后端服务发布有一个显著区别:它的行为高度依赖于模型和外部工具,同一段代码在不同模型版本下可能表现出完全不同的稳定性。所以我们的发布流程升级成了三层灰度:

第一层是内部验证:用预置的回归用例集跑一遍,检查任务完成率、工具调用正确性、上下文格式。 第二层是小流量金丝雀:选一个低流量业务线,放 5%-10% 流量,观察任务成功率、延迟、错误率和人工抽检结果。这一步极其重要,因为模型输出是概率性的,有些问题只在特定 Prompt 分布下才会暴露。 第三层是逐步放量:20% -> 50% -> 100%,每步至少观察 30 分钟到 1 小时。

踩坑记录:我们小流量金丝雀做得好的时候,发布过多个 Harness 版本都没事,某次跳过金丝雀直接发了全量,结果一个工具调用参数格式的变更在线上爆了——新版本虽然通过了内部自测,但线上某个工具的真实响应格式和 mock 的不一样。从那以后,再紧急的修复也必须走小流量。

6.2 容量预估

Agent 服务的容量模型需要兼顾“外部不确定性和并发放大效应”。提升容量不能只看请求量的线性增长,还要考虑 Agent 规划后对工具和模型调用次数的放大。

我们基于历史数据建立了一个简化模型:

并发容量 = 单实例每秒可处理的 Agent 步骤数 × 实例数 / 单个任务的平均步骤数

再乘以一个安全系数(比如 1.5),得出目标实例数。另一个角度是从 Token 消耗看容量:调研一下供应商的 TPM 配额上限,反推系统最大可支撑的任务并发量,控制在 60%-70% 水位。这样既避免因为流量尖峰打爆配额,也留了降级切换的余量。

容量预测要有滚动更新的机制,每周根据实际水位校准模型系数。刚开始我们按 80% 阈值扩容,后来发现某些场景下工具调用放大倍数忽高忽低,只按请求量扩容会漏掉真正的瓶颈。把 Token 消耗和工具调用次数纳入扩容依据以后,容量判断才准了不少。

7. 高频问题现场实录与排查手册

7.1 工具调用“假死”

现象:Agent 卡在“正在调用工具”状态,不报错也不超时,用户侧表现为长时间无响应。

排查思路:先看工具调用的 Span 处于什么阶段。可能是工具已经返回了,但 Agent 的回调解析卡住了;也可能是工具请求压根没发出去,卡在 Harness 的队列里。最常用的定位手段是看 span 的时间戳和当前线程堆栈,两者配合基本能判断卡点。

根因通常有三个:底层 HTTP 客户端没有设置连接超时和读超时;工具 SDK 内部有异步任务没有正确等待;或者是信号量耗尽,工具调用一直在排队但没有反馈给用户。对应解法:全链路强制超时配置;对第三方 SDK 做隔离包装,避免原生异步逻辑绕过控制;信号量排队超过阈值时直接上报“系统繁忙”并进入降级流程。

7.2 上下文爆炸

现象:任务消耗的 Token 量异常增长,模型调用延迟上升,甚至导致模型因为超出上下文长度而报错。

根因分析:Agent 每一步都会把工具返回结果塞回对话历史,工具返回的内容如果又长又冗余,几次迭代后上下文就膨胀了。遇到那种工具返回一大段 JSON 的情况,上下文爆炸速度会非常快。

我们的解法是上下文管理器:每步模型输入前,把历史对话和工具结果做压缩。基本原则是——已经完成且不再需要的原始工具结果,替换成一段“已调用工具 X,成功获取到摘要 Y”的描述;长文本做截断或摘要;历史步骤只保留语义摘要。压缩逻辑要写成可测试的组件,因为它直接影响 Agent 推理质量,压缩太狠会把关键信息丢掉。

7.3 限流抖动

现象:整体任务成功率没降,但 P99 延迟在某些时段突然飙升,观察日志发现大量 429 限流响应。

这种问题最迷惑人,因为表面看成功率正常,实际上每次限流后 Harness 自动重试,最终成功率被“掩盖”了,但延迟被拉高。

排查时不能只看平均延迟,要看限流事件数和模型调用重试次数的分布。如果是模型供应商的 TPM 限额被打满,需要在 Harness 层做更前置的 Token 配额预算,在接近上限时提前切换模型或降级,而不是等到 429 后才被动重试。

另一个容易引发限流抖动的场景是模型路由切换后的配额不足,前文也提到了。处理方式是每一条模型链都预留独立的 Token 配额,切换前检查目标模型的剩余额度,不够就继续往下一个兜底模型切。

7.4 快速排查清单

问题现象第一检查点常见根因快速缓解
任务成功率下降执行层任务失败数、失败阶段模型、工具、上下文降级路由或限流保护
P99 延迟陡增模型调用耗时、工具调用耗时供应商限流、长尾工具调整模型路由、压缩上下文
实例 CPU/内存高并发任务数、队列长度并发失控、任务堆积限制并发、拒绝新任务
任务恢复后重复执行快照写入时间、幂等键检查点丢失、幂等失效同步快照、校验幂等键
某个工具全体失败工具错误码、上游状态外部服务故障熔断降级、切备用工具

8. 写在最后:稳定性的底色是系统性治理

沿着 99% 到 99.9% 这条路走到现在,我最大的体会是:稳定性从来不是某一个妙招的功劳,而是把超时、重试、降级、状态、观测、容量这些基础能力打磨到极致之后系统性的体现。每次优化完一个指标,我都要问自己:如果明天模型供应商挂了、后天工具服务挂了、大后天流量翻倍,系统能不能不靠“人肉救火”维持住可用性?答案如果不够坚定,说明还有功课没做。

最后分享一个小技巧:每次优化落地后,把线上的真实监控数据截图保存下来,对应到每次变更记录里。这个习惯帮了大忙——几个月后复盘时,翻当时的性能曲线,你能一眼看出哪个参数调整产生了真实收益,哪个只是“看起来有效”。做稳定性优化最怕的就是凭感觉改参数,数据和记录的积累才是把经验变成资产的关键。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询