跳到主要内容

生产环境中的自愈智能体:如何构建自我修复系统

· 阅读需 8 分钟
Tian Pan
Software Engineer

大多数智能体故障不会自行报告。没有崩溃,没有警报,没有堆栈跟踪。你的智能体只是默默地返回错误答案,跳过工具调用,或在任务中途停滞——而你直到三小时后用户投诉时才发现。从“在开发环境中正常运行”到“在生产环境中可靠”的差距,并非仅仅增加重试次数就能弥补。它关乎构建一个能够检测自身故障、对故障进行分类并在不半夜两点把你吵醒的情况下恢复的系统。

以下是自修复智能体管道在实践中的实际面貌。

隐性故障的剖析

在你修复故障之前,你需要了解它们是如何表现的。智能体故障主要分为三大类:

瞬时错误 — API 超时、速率限制、网络瞬时故障。这些错误在重试后就会消失。它们最容易处理。

逻辑错误 — 智能体从自身角度看成功完成任务,但产生了不正确的输出:错误的工具参数、幻觉值、未满足的约束。这些是危险的,因为它们不会引发异常。

回归错误 — 部署前正常运行的行为,在部署后停止工作。你的代码、提示或数据中发生了某些变化,破坏了之前稳定的路径。

大多数团队只处理第一类错误。他们的智能体在超时时重试,并认为问题已解决。第二类和第三类则需要完全不同的方法。

阶段 1:在检测漂移之前建立基线

生产环境自修复背后的核心洞察是:不了解“正常”是什么样子,你就无法检测到异常。

对于瞬时错误,这很简单——任何异常都是异常。但对于回归检测,你需要一个统计基线。一种实用方法是:

  1. 在每次部署前,收集滚动 7 天窗口内的错误日志
  2. 通过去除可变内容(如 UUID、时间戳、数字 ID、请求哈希)来标准化日志消息
  3. 计算每个标准化错误签名的预期错误率

新部署后,监控错误率 60 分钟。使用泊松概率模型将观测到的计数与你的基线进行比较。如果某个特定错误签名在预期分布下以 p < 0.05 的速率出现,则表明你有一个潜在的回归。

这比简单的阈值(“如果错误 > 10/分钟则发出警报”)更为严格,因为它考虑了自然方差。周一早上的峰值与部署后的峰值看起来不同。

from scipy.stats import poisson

def is_regression(baseline_rate: float, observed_count: int, window_minutes: int = 60) -> bool:
expected = baseline_rate * window_minutes
# One-tailed test: is observed count significantly higher than expected?
p_value = 1 - poisson.cdf(observed_count - 1, expected)
return p_value < 0.05

阶段 2:行动前进行分流

原始统计信号会产生误报。并非每一次部署后的错误激增都由该部署引起。在升级到自动化修复之前,运行一个分流智能体来:

  1. 分类更改的文件 — 本次部署是修改了运行时代码,还是仅修改了测试、文档和配置?
  2. 将错误链接到更改 — 分流智能体能否在特定更改的文件与观测到的错误签名之间建立因果关系?
  3. 返回结构化裁决,包含置信水平和支持证据

分流步骤是区分一个有用的自修复系统和一台昂贵噪声机器的关键。没有它,你的修复智能体将花费大量 token 去调查由不稳定的第三方 API 引起的、与你的代码无关的错误。

在实践中效果良好的分流智能体提示:

给定以下 git diff 和错误日志样本,请确定:
1. 更改的文件是否可能影响运行时行为(是/否 + 理由)
2. 这些更改与观测到的错误之间是否存在合理的因果路径
3. 置信水平:高 / 中 / 低

返回一个 JSON 对象,包含字段:is_runtime_change, causal_link, confidence, evidence_summary

仅当 causal_link == trueconfidence != "low" 时,才升级到自动化修复生成。

阶段 3:自动化修复生成

一旦你确认了真实的回归,问题是:回滚还是向前修复?

回滚更安全,但并非总是可行。如果部署包含数据库迁移或更改了外部 API 契约,回滚代码并不能撤销其副作用。

向前修复风险更大,但通常是必要的。这就是编码智能体发挥其价值的地方。将分流智能体的裁决——具体的错误签名、相关的 diff、因果证据——作为一个重点修复任务发送给编码智能体。

关键在于上下文范围界定。不要将整个代码库倾倒到提示中。分流裁决已经识别出相关文件。给编码智能体提供:

  • 具体的错误消息和堆栈跟踪
  • 最可能导致问题的文件的 diff
  • 预期行为(来自你的测试或文档)
  • 一个约束:“只修改此 diff 中涉及的文件”

这使得修复精准且可审核。编码智能体生成补丁;人工进行审核并合并。你消除了手动调试步骤——这是耗费成本的部分——同时在实际部署决策中保持了人类在环。

阶段 4:构建时与运行时故障

自修复管道需要在两个不同的层面运行:

构建时:如果你的部署管道失败(Docker 构建错误、依赖冲突、CI 失败),立即捕获构建日志和 git diff。在失败的部署到达预生产环境之前,将其路由到一个编码代理。这创建了一个快速反馈循环:提交 → 构建失败 → 建议补丁 → 开发者审查 → 重新部署。周期时间从数小时缩短到数分钟。

运行时:上述统计基线 + 分诊 + 前向修复管道。在生产环境中持续运行,部署后监控实时环境 60 分钟。

这两个层面捕捉根本不同的故障模式,应独立实施。

良好的可观测性使得什么成为可能

没有结构化日志,这一切都无法奏效。在你构建自修复能力之前,你需要:

  • 标准化错误签名 — 剥离变量令牌的日志消息,以便相同的错误总是哈希到相同的签名
  • 部署标记 — 日志中的时间戳,用于识别新版本何时上线
  • 代理特定遥测 — 调用了哪些工具,使用了哪些参数,以及它们返回了什么

最后一点是最常被跳过的。如果你的代理框架没有记录带完整输入和输出的工具调用,你将是盲目飞行。当分诊代理需要建立代码更改和错误之间的因果关系时,它需要知道是哪个工具调用失败以及为什么失败——而不仅仅是代理返回了意想不到的结果。

自修复的局限性

自修复代理不能替代优秀的工程实践。它们适用于:

  • 你自己的代码更改引入的回归
  • 瞬时基础设施故障
  • 配置漂移

它们不适用于:

  • 基本模型能力差距(模型无法完成你要求它做的事情)
  • 提示注入或对抗性输入(自动化修复可能会使情况变得更糟)
  • 需要领域专业知识才能正确诊断的故障

目标是自动处理机械的、可模式匹配的故障,这样你的工程师就可以将注意力集中在那些确实需要人工判断的故障上。

从何开始

如果你正在朝这个方向努力,循序渐进的路径是:

  1. 首先添加结构化日志。 规范化你的错误消息。标记部署。检测工具调用。
  2. 建立基线。 即使是一个简单的 7 天滚动平均值也能捕捉到明显的回归。
  3. 添加分诊步骤。 在任何自动化之前,一个仅仅分类错误的分诊代理就能节省大量的无用功。
  4. 自动化回滚。 在你自动化修复之前,先自动化回滚。回滚到已知良好状态的风险较低,并且仍然可以消除大部分手动事件响应。
  5. 最后添加修复生成。 一旦早期层级稳固并产生可靠信号,就用自动化补丁生成来扩展它们。

生产代理的可靠性是一个系统问题,而不是模型问题。模型总会犯错。问题在于你的基础设施能否在用户注意到之前捕获并从中恢复。

Let's stay in touch and Follow me for more thoughts and updates