跳到主要内容

CI 流水线中的 AI 智能体:如何为无法单元测试的部署设置质量关口

· 阅读需 11 分钟
Tian Pan
Software Engineer

发布一个调用 LLM 的功能很容易。但要判断该功能的下一个版本是否优于生产环境中的当前版本,却相当困难。传统 CI/CD 对确定性行为提供通过/失败信号:函数要么返回正确值,要么不返回。但当函数封装了一个语言模型时,输出是概率性的——相同的输入在不同运行、不同模型版本和不同时间会产生不同输出。

大多数团队的应对方式是绕过这个问题。他们运行单元测试,对几个提示词做快速的人工检查,然后发布。这种方式在出问题之前都还能用——直到某个模型提供商悄悄更新了底层权重,或者一个看似没问题的提示词改动在孤立测试中没有异常,却在凌晨三点以生产流量规模改变了输出分布。

更好的答案并非假装 LLM 输出是确定性的,而是构建基于分布、阈值和评分标准的 CI 质量关口,而不是精确匹配。

为什么现有 CI 无法捕获 LLM 回归

单元测试的陷阱根深蒂固。团队编写评估来检查给定提示词产生的响应是否在某个指标上超过某个阈值——有用性、忠实度、相关性——并在 CI 中运行这些评估。如果分数高于阈值,构建就通过了。这看起来很严格,但实际上大多不是。

第一个问题是单个评估是点估计,而不是分布。LLM 评估的单次运行是有噪声的——相同的提示词每次产生不同的输出,而对这些输出进行评分的评审模型又增加了另一层噪声。对单个样本的单次通过/失败几乎无法告诉你行为是否在真实输入群体中退化了。

第二个问题是大多数评估套件测试的是快乐路径。工程师根据他们期望模型能处理好的内容编写测试用例。他们真正关心的失败模式——格式错误的输入、对抗性查询、训练数据未预期到的边缘情况——被系统性地低估了。评估套件自信地通过,而真正会在生产中出问题的边缘情况仍未经测试。

第三个问题是集成覆盖率。一个通过所有单元评估的智能体,在与真实外部系统交互时仍可能灾难性地失败:返回意外模式的 API、充满脏数据的数据库、导致智能体陷入循环的超时。模拟这些依赖项会给你一个干净的绿色构建和虚假的安全感。数据即控制流——当你模拟完美响应时,你只测试了快乐路径。

最小可行部署关口

在添加智能体 CI 步骤之前,你需要一个能真正捕获回归的基线。该基线有三个组成部分:

从生产流量中精心整理的黄金数据集。 不是你凭空想出来的合成示例——而是你的系统已经处理过的真实输入,并标注了"良好"的样子。样本应包括边缘情况和接近失败的输入,而不仅仅是你的模型表现良好的输入。精心整理的 50 个真实示例胜过从提示词生成的 500 个合成示例。

评分标准,而不是模糊的问题。 "这是个好答案吗?"这样的评估标准会产生嘈杂、不可靠的分数。有用的标准会明确维度(准确性、完整性、语气符合度、引用正确性),并具体定义每个分数级别的含义。评分标准是产品决策,而不是工程决策——让定义"完成"的团队签字确认。

比较基线,而不是绝对阈值。 与其问"这个输出的分数是否高于 0.8?",不如问"这个输出的分数是否明显低于当前生产环境的提示词?"相对于你正在发布的内容来定义回归,比用数据集和评分标准都已更新后校准的绝对阈值要稳定得多。

有了这三样东西,你就可以设置部署关口:对候选版本和当前生产版本都运行评估,计算差值,如果新版本在分布上的统计得分明显更差,则让构建失败。

添加智能体步骤:它们真正有价值的地方

一旦基线评估关口正常运行,为特定问题类别添加智能体 CI 步骤就有了真正的价值。关键是要明确智能体应该做什么——"让 AI 看一眼"不是一个 CI 步骤。

探索性回归搜索。 编写测试数据集的人受到想象力的限制。智能体可以针对特定行为生成对抗性输入:边界条件、提示词注入尝试、旨在触发拒绝的输入、历史上导致错误的输入。智能体不是孤立运行的——它生成测试用例,这些用例会进入你已有的评估流水线。价值在于将覆盖范围扩展到人类不会想到要测试的领域。

提示词变更差异分析。 当开发者修改系统提示词时,这个变更在结构上通常很小,但在语义上却很重要——重新表述一个约束、添加一条指令、更改一个示例。智能体步骤可以分析提示词版本之间的差异,识别哪些行为可能会改变,并建议针对变更区域的测试输入。这将通用评估运行变成了一个专注于实际变更的运行。

输出分布偏移检测。 除了对单个测试用例的通过/失败,你还可以运行一个智能体步骤,对当前版本和候选版本的输出进行聚类,并标记分布显著分歧的情况——即使两个版本在标准评分标准上的得分都可以接受。一个保持平均质量但增加语气或长度方差的提示词变更,是聚合分数捕获不到的问题。

与真实依赖项的集成冒烟测试。 对于调用外部 API 或查询数据库的智能体功能,让 CI 步骤在真实基础设施的沙箱副本上运行智能体,可以捕获单元评估完全无法发现的整类失败。沙箱不需要是完整的生产副本——它需要足够真实,让智能体遇到真实的响应结构、真实的模式变化和真实的错误条件。

不可跳过的成本控制

智能体 CI 步骤有一种传统流水线中不存在的失败模式:运行起来可能很昂贵,而且"昂贵"随着每次合并而扩大。每次运行成本 0.50CI步骤听起来微不足道,直到你的团队每天合并二十次。这是每月0.50 的 CI 步骤听起来微不足道,直到你的团队每天合并二十次。这是每月 3,000 的 CI 成本,仅仅是一个评估步骤。

实用的控制措施:

采样,而不是穷举。 你的黄金数据集可能有五百个示例,但你不需要在每次提交时运行所有五百个。在定时夜间构建和合并到主干时运行完整套件;在每个 PR 上运行 50 个分层样本。分层样本应包括你最难的用例——如果回归是真实的,它会首先在难用例中出现。

分层评审模型。 第一轮使用廉价、快速的模型,只有当廉价评审标记出潜在问题时才升级到昂贵的评审。GPT-3.5 级别的模型可以以前沿模型成本的一小部分筛查明显的回归——连贯性失败、格式违规、长度异常。只对标记的子集运行昂贵的评审。

在流水线级别设置硬性预算限制。 CI 中的每个 LLM API 调用都应该通过一个强制每次运行成本上限的层。如果 CI 步骤达到上限,它会以成本超支错误使步骤失败,而不是静默地继续。这防止了来自积极生成输入或无限期重试失败调用的智能体的失控评估循环。

积极缓存。 评估步骤反复针对同一个黄金数据集运行。对于未更改的提示词版本的结果可以被缓存。如果自上次运行以来模型、提示词和测试输入都没有变化,那么上次运行的评估结果仍然有效。即使是以(提示词版本、模型 ID、输入)的哈希为键的简单文件缓存,也能显著降低成本。

失败模式护栏

智能体 CI 中的失败模式与传统测试失败不同,需要不同的处理方式。

不稳定的评估不是不稳定的测试。 偶尔失败的传统测试是需要修复的 bug。偶尔失败的 LLM 评估可能是信号——模型接近阈值,输出质量不一致,你应该调查而不是重试直到通过。不要将评估方差视为要抑制的不稳定性。将其视为信息。

评审可能出错。 LLM 作为评审的评估比没有要好,比人工审核要差。评审有自己的偏见——它倾向于更长的答案、与自身同一模型系列的输出,以及听起来自信的响应,而不管准确性如何。在流水线中构建健全性检查:如果你的评审给每个输出打分都高于 0.9,那么评分标准太容易了,或者评审被错误校准了。

对你能衡量的东西设置关口,而不是你希望能衡量的东西。 尝试对"推理质量"或"用户满意度"等抽象质量设置关口很诱人。这些无法转化为可靠的 CI 指标。对你能一致衡量的东西设置关口:格式符合性、对对抗性输入的拒绝率、已知基准答案问题的事实准确性、响应延迟分布。抽象质量属于定期人工审查周期,而不是自动部署关口。

对新信号使用软阻断而不是硬阻断。 当你在流水线中引入新的评估指标时,在将其设为硬关口之前,先以观察模式运行几周。这让你可以根据真实世界的合并模式校准阈值,并捕获指标在合法变更上触发的情况。将新指标直接设为硬关口通常会导致团队在第一次误报后将其禁用。

实际可行的集成模式

在实践中经得起考验的架构如下:标准的代码检查和类型检查像往常一样首先运行。然后在小样本上运行轻量级 LLM 冒烟测试——来自黄金数据集的 10 到 20 个输入——以较低成本捕获明显的灾难性失败。如果冒烟测试通过,则在带预算上限的分层样本上运行更全面的评估。全面评估计算与生产基线的差值,如果差值超过阈值则使构建失败。最后,对于更改系统提示词的 PR,智能体步骤运行提示词差异的针对性分析,并生成少量进入评估的针对性测试输入。

这个流水线的总成本,配合分层评审和采样,对于中等规模的生产 LLM 功能,每次合并大约运行 $0.15–0.30。这与运行中等复杂的传统集成测试套件相当。

这个模式没有解决的问题:它不能替代对生产流量的可观测性,它无法捕获同等影响所有版本的模型提供商漂移,也不能替代对重大提示词变更的人工审查。它捕获的是你自己造成的回归,而这是大多数回归的来源。

构建这个流水线所需的纪律,比从没有任何检查就进入生产的回归中恢复所需的纪律要少得多。

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