跳到主要内容

Eval-Prod 漂移:测试中的智能体并不等同于生产环境中的智能体

· 阅读需 13 分钟
Tian Pan
Software Engineer

评估套件显示绿色(通过)。仪表盘显示绿色。一周后,支持团队淹没在同样的投诉中:“助手一直拒绝预订会议。”你打开评估工具(eval harness),重放失败的追踪记录,结果它运行正常。非常完美。每一次都成功。Bug 不在你的评估中,也不在你的模型中。Bug 在于:你的评估所测量的 Agent 和你的客户正在交谈的 Agent 已经不再是同一个系统了,而目前还没有人意识到这一点。

评估与生产环境偏移(Eval-prod drift)是指评估工具加载到 Agent 中的内容与推理栈在请求时实际组装的内容之间,发生的缓慢且难以归因的发散。提示词(Prompts)、固定的模型版本(model pins)、工具架构(tool schemas)、护栏配置(guardrail configs)和功能标志(feature flags)分别通过不同的部署路径流入 Agent —— 代码合并、配置推送、提示词注册中心的回调、实验平台、运行时上线 —— 几乎没有团队拥有一个能够协调这些内容的单一事实来源。因此,评估工具最终测量的是存在于某人 PR 分支中的 Agent 版本,而生产环境运行的则是昨日热修复、上周的功能标志变体,以及工具团队在没告知任何人的情况下推送的任何内容的集合。

这不是一种理论上的失效模式。它是任何运行超过三个月、且配置分布在多个代码库中的 Agent 系统的默认状态。

Agent 并非单一产物

如果你问工程师“什么是 Agent?”,他们通常会指向一个 Python 类、一个提示词模板或一个编排图。而诚实的答案是:Agent 是大约七种产物的运行时组合,而且其中大多数是独立发布的。

典型分解:

  • 系统提示词(System prompt)。 存在于提示词注册中心或配置文件中。通常产品经理可以通过 UI 进行编辑。
  • 模型版本锁定和解码参数(Model pin and decoding params)。 存在于环境变量、运行时配置或功能标志中。
  • 工具架构(Tool schemas)。 从工具所属服务的代码中生成,在请求时按名称解析,有时会被缓存。
  • 工具描述(Tool descriptions)。 通常与架构分开编辑,有时在 Agent 代码库中,有时在工具代码库中。
  • 检索配置(Retrieval config)。 索引名称、嵌入模型、分块器版本、重排序器权重。每一个都有自己的部署节奏。
  • 护栏和后处理器(Guardrails and post-processors)。 正则过滤器、分类器网关、拒绝策略、输出验证器。
  • 功能标志和实验变体(Feature flags and experiment variants)。 按客群、用户、地区锁定,支持实时更新。

评估工具加载这七个产物的一个快照。生产环境则在每次请求时重新组装另一个全新的快照。在任何给定的日子里,这两个快照达到字节级一致的概率都很低,而且没有人去检查。

更糟糕的是,这种偏移是不对称的。评估通常针对主分支上的内容以及评估团队整理的数据集运行。而生产环境运行的是跨五个代码库的前四次部署中幸存下来的内容,加上功能标志系统当前为该用户群体提供的任何内容。评估是一个静态构建,而生产环境是一个动态合并。

悄悄撕裂配置的五条路径

评估与生产环境的偏移并不是由一个糟糕的流水线造成的。它是由于多个流水线共同作用产生的,其中每一个流水线在孤立状态下都是正常的。

热修复路径。 凌晨 2 点的一次事故:Agent 正在泄露一个禁用词。有人通过提示词管理 UI 编辑了系统提示词并点击部署。生产环境恢复了。而锁定在主分支提示词文件的评估工具永远看不到这次编辑。三周后,一个无关的评估退化问题在一段生产环境并不存在的提示词上进行调试。

评估 PR 路径。 一名评估工程师添加了新的测试用例,并在同一个 PR 中调整了系统提示词以使其通过。CI 显示绿色。PR 合并。但实际为用户提供服务的提示词是从提示词注册中心运行时获取的,它完全忽略了代码库。评估提升了;生产环境毫无变化。

功能标志路径。 增长团队发布了一个 Agent 变体,对免费用户更简洁,对付费用户更详尽。功能标志系统按请求解析变体。评估工具在功能标志关闭的情况下运行,因为这是确定性的选择。免费用户运行的是一个从未被评估过的 Agent。

A/B 实验路径。 通过一个存在于不同服务中的实验框架,将一个提示词优化推送到 10% 的流量中。评估工具看到的是对照组。这 10% 的流量仅通过实验自身的成功指标(通常是单一的点赞率)进行衡量,而不是完整的评估指标。未衡量维度(拒绝率、工具调用准确性、尾部延迟)的退化在隐形地积累。

工具架构路径。 日历团队将一个参数从 attendee_emails 重命名为 invitees 并更新了他们的服务。在请求时拉取到 Agent 上下文中的工具描述现在提到了一个字段,该字段与模型仍然训练生成的参数名称不匹配。评估针对冻结的架构快照运行,永远看不到这种不匹配。生产环境的工具调用成功率悄然下降了 8%。

其中每一种都是合理甚至是理想的部署实践。热修复挽救了事故。功能标志实现了渐进式发布。实验是学习的方式。问题在于它们的并集:五个独立的决策者推送了五个独立的变更,而评估工具一个都没观察到。

单一指纹规约

第一种缓解措施并非增加更多评估(evals),而是一个单一的加密指纹。它能在请求时唯一标识 agent 的完整构成,并出现在每一行日志、每一次评估追踪和每一个仪表板中。

具体来说,每个推理请求都应携带一个结构化的标头或元数据字段,列出:

  • 解析后的系统提示词哈希(模板替换后)。
  • 精确的模型 ID 和版本锁定,包括供应商。
  • 模型看到的完整工具清单(schema 加描述)的哈希。
  • 激活的功能标志(feature-flag)和实验变体 ID。
  • 检索配置哈希(索引、嵌入器、重排序器)。
  • 护栏规则集(guardrail bundle)版本。

将这些内容拼接并进行哈希处理。这个数值就是该请求中 agent 的身份。如果两个请求的指纹不同,那么它们就是由不同的 agent 提供的服务,仅此而已。如果你的评估框架(eval harness)不输出指纹,那么它评估的就不是“某个” agent —— 它评估的是一个在你的技术栈中根本不存在的柏拉图式理想态。

一旦指纹存在,核心的漂移检测 CI 环节就变得容易描述了:获取过去 24 小时生产流量中的主导指纹,强制评估框架加载完全相同的构成,并运行你的黄金数据集(golden set)。任何“刚刚评估得分”与“昨天在主分支构成上的评估得分”之间的不一致,都是漂移警报。任何在生产环境中出现但从未被评估过的指纹,则是更高级别的警报。

这也是你捕获那些静态评估永远无法发现的问题的方法:仅存在于生产环境的指纹。当生产环境正在运行一种从未流经评估流水线的功能标志状态和提示词版本的组合时,正确的警报应该是“你正在向 N% 的用户提供未经评估的 agent 服务”,而不是“评估结果为绿色”。

金丝雀套件作为收敛性测试

一旦你能识别哪个 agent 在哪里运行,你就需要一种廉价的方法来测试这些 agent 是否达成一致。金丝雀套件就是这种测试。

金丝雀套件由 20–50 个手动挑选、具有明确预期行为的案例组成。它涵盖了 agent 绝不应出错的简单案例:一个简单的工具调用、一个明确的拒绝、一个标准的检索查询、一个基础的多轮澄清。它刻意保持小巧。它刻意保持枯燥。它刻意设计为能在不到一分钟内完成端到端的完整回放。

这项规约是:金丝雀套件会持续针对生产环境运行 —— 不是通过阴影流量,也不是通过单独的 staging agent,而是通过向生产端点发出合成请求,并使用评估框架所用的相同评分器对响应进行评分。它也会在每次 CI 运行期间针对评估框架的构成运行。你关注的信号是差异(diff)。当金丝雀套件在评估中通过但在生产中失败时,评估与生产环境的漂移就暴露了。当两者都失败时,说明你遇到了真正的回归。当两者都通过时,agent 大概没问题。

这与大多数团队已经在做的监控生产模型质量不同。生产质量监控告诉你“本周用户满意度下降了”。而“金丝雀套件作为收敛性测试”则告诉你“评估框架和生产服务器在评估框架曾判定为已锁定的行为上产生了分歧”。前者是一个滞后指标,后者才是上游根因。

一个实际的改进方案是:按子系统对金丝雀套件进行分层。某些案例仅测试提示词。某些测试工具 schema 路径。某些测试检索配置。当套件失败时,失败的层级会告诉你七个工件中哪一个发生了漂移,从而将事后复盘(post-mortem)的时间从几小时缩短到几分钟。

配置收敛是组织问题,而非工具问题

在成熟的组织中,评估与生产环境漂移之所以持续存在,并不是因为团队无法构建指纹识别或金丝雀套件,而是因为做出部署决策的人向不同的副总裁(VP)汇报。

提示词的修改通常由 PM 或 AI 应用团队负责。模型锁定通常由平台工程负责。工具 schema 属于拥有该功能的后端团队。功能标志(Feature flags)是增长或产品部门的职能。实验由数据科学团队运行。评估框架通常由机器学习工程或专门的评估团队负责。六个负责人,六种节奏,六种对“发布”的定义。

除非有人对作为单元的“构成”负责,否则任何指纹识别方案都无法在这样的组织架构中生存。具体来说,这个角色看起来像:

  • 一个负责生产指纹的值班轮岗,当未经评估的指纹触达超过 5% 的流量时,该轮岗人员会被传呼(paged)。
  • 一个变更管理评审,任何对七个工件中任何一个的修改,都必须连同其预期影响和金丝雀套件结果一起发布。
  • 一个仪表板,在评估分数的旁边显示每个指纹在生产流量中的占比,以及与评估框架所评分指纹的重合度。

如果没有这些,漂移会在值班工程师发布热修复(hotfix)而不更新评估仓库的那一刻重新出现 —— 这种情况总会发生,因为热修复正是值班的全部意义所在。

将上线视为原子化操作,而非渐进式过程

通常的发布模式——调整一下提示词、切换一个开关、缓慢扩容——正是“漂移”产生的原因,而非避免它的方法。从构建逻辑上看,每一次渐进式发布都是评测结果与生产环境产生分歧的时间窗口。如果这个窗口很小且监控完善,那倒无妨。但如果两者皆无,它就是所有评测与生产环境不一致事故的根源。

更清晰的纪律是:对智能体构成的每一次变更都是一次上线。一次上线会将所有七个产物打包成一个新的指纹,让它们一起通过评测,一起在影子模式下运行金丝雀测试集,然后原子化地切换流量。实验和发布依然存在,但它们发生在不同指纹之间,而非指纹内部。A/B 测试应该是两个完整智能体之间的对比,每个智能体都作为一个整体经过了评测,而不是在一个不断变化的背景下对比单一提示词变体。

大多数团队会抵制这种做法,因为与可热重载的提示词相比,原子化上线显得过于沉重。接受这种负担的原因在于,替代方案(即现状)是一个评测通过了但用户在投诉的系统,而且没人能告诉你这到底是测量问题还是产品问题,其调查成本远超原子化上线流程的维护成本。

明天即可采取的姿态

你不需要在这个季度重构你的部署流水线。你需要先按顺序做三件低成本的事情。

  • 输出指纹。 在每一条推理日志中添加一个结构化字段,记录完整构成的哈希值。你可以把评测端的集成推迟几周;服务端哈希本身就很有价值,因为你首先会发现,你已经在同时提供多少种不同的指纹服务了。
  • 建立金丝雀测试集。 20 个案例,10 分钟写完,每小时运行一次 cron 任务。如果在其中任何一个案例上生产环境与评测结果不符,就呼叫相关人员。这个测试集的全部职责是充当“绊网”,而不是基准测试。
  • 明确负责人。 挑选一名工程师或一个团队,在指纹分布出现异常时负责响应。如果没有这一点,前两件事只是摆设。

你试图防止的核心退化并不是评测结果变差。而是在评测通过后的那几个静默周里,用户真正对话的智能体已经与你声称发布的版本发生了漂移。只有当系统无法再否认这种差异存在时,这种鸿沟才会消失。

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