发布周期笔记 — 第 8 部分(完结篇)
你交付了第 7 篇中的回归套件。它工作得很好。切片感知的门禁抓住了真正的 bug。校准过的评审器稳如磐石。
然后你的工程负责人问你,这套东西在每个 PR 上跑要花多少钱。你算了一笔账:每个 PR 大约 12 分钟的评审器推理,一天 60 个 PR,四个维度 × 十七个切片,账单是真金白银。更糟的是,现在每个开发者都要为一行 prompt 拼写错误等 12 分钟才能拿到绿勾。速度下降[1],团队怨声载道,有人提议“干脆每晚跑一次门禁吧“——而这恰恰就是放弃门禁本该做到的一切。
修复之道不是减少测试。修复之道是**分层测试,大部分信号在前九十秒内就抵达。**这篇文章讲的是门禁套件下面跑的东西:亚秒级契约测试、紧凑的冒烟层、成本感知的车队,以及任何新门禁开始拦截任何人之前 2 周的影子窗口。
这是本系列的第 8 篇,也是最后一篇。读完之后你就拥有了完整的图景——从四阶段流水线一直到在每次提交时运行的契约测试夹具。
对于定制语言模型来说 CI 意味着什么?
定制 LLM 的 CI 就是门禁套件不必重复做的工作。门禁评分语义质量;CI 则在门禁花掉哪怕一个评审器 token 之前,抓住一切会让门禁评分变得毫无意义的问题。
契约测试以毫秒计运行,验证 prompt 模板是否仍能渲染、工具调用 schema 是否仍能解析、检索索引是否仍有响应、清单(manifest)是否仍引用着实际存在的哈希。它们是确定性的、免费的,也是流水线其余部分能够存在的唯一原因。一个把 prompt 模板搞坏的 PR 应该在 200 毫秒内失败,而不是在 12 分钟的评审器推理评估出胡话之后才失败。
契约层的存在与否,决定了你的 CI 账单是随 PR 量线性扩张,还是不。Divinci 的 CI 运行器把超过 90% 的评审器预算花在真正的语义评估上,而不是花在本来就会通不过 schema 检查的 PR 上。这个比例,就是头条数字。
为什么传统 CI 对 LLM 失效——从成本视角看
第 1 篇和第 7 篇讲过确定性 CI 为何对生成式模型失效。本篇要讲的版本,不是这四种属性存不存在,而是它们的成本形态。
| LLM 的属性 | 传统 CI 的失败方式 | 成本形态 |
|---|---|---|
| 非确定性输出 | 精确匹配断言会不稳定 | 重跑会随抖动率线性放大成本 |
| 多维度质量 | 单一布尔值信息量太低 | 每个维度都是一次单独(付费)的评审器调用 |
| 厂商漂移 | 钉住的 gpt-4-2024-01-01 悄无声息下线 | 厂商日落某个检查点时,会爆发出一波重新校准成本 |
| 非局部 prompt 效应 | 本地单元测试无法捕捉这种效应 | 分布形态在 PR 之间发生变化,而非在 PR 内部——需要对整套套件重跑,而非增量跑 |
CI 架构必须让上述每一项都负担得起。契约测试以低成本处理属性 1 和 3。冒烟测试部分处理属性 4。只有完整套件能处理属性 2——而且只在真正需要的 PR 上运行。
CI 分层蛋糕——从亚秒到二十五分钟
我们交付的架构是四层,每一层都通过抓住下面更便宜的层抓不到的东西来挣回自己的算力。每一层的切片感知框架遵循天盘 Semver 谎言事后复盘说得很清楚[4]的同一个教训:聚合信号会撒谎;按切片信号能抓住聚合所掩盖的东西。
成本形态就是设计。~74% 的 PR 从不花一分评审器 token——契约或冒烟就够了。真正抵达完整套件的 PR,是那些改动了 prompt、模型配置、检索索引或评估代码的——也正是这些改动,门禁套件是唯一值得信任的信号。候选发布则是抵达第 4 层的少数。
契约测试——不公平的优势
契约测试是第一道防线、最便宜的一道防线,也是大多数团队会跳过的一道防线,因为他们觉得这层东西配不上“AI 评估流水线“的身份。然而,在我们客户的套件中,有 30–40% 的本可成为回归的问题,实际上正是在这一层失败——在任何评审器被调用之前。
契约层只断言以下五件事,别无其他:
- **Prompt 模板渲染。**每个模板都能在一份规范化夹具上完成渲染,不存在未绑定变量、失控循环或损坏的类 Jinja include。
- **工具调用 schema。**每个声明工具的参数 schema 能解析,JSONSchema 有效,渲染后的 prompt 真的引用了所有必填槽位。
- **清单完整性。**发布清单中的每个 SHA——模型、prompt、检索索引、评审器、数据集——都对应注册表中实际存在的工件。悬挂指针在这里失败,而不是在更下游的三层之后。
- **索引可用性。**检索索引能在预算内回应一个已知查询。被悄悄破坏检索能力的重建索引,会在这里浮现,而不是在生产中。
- **禁用词列表与 token 预算。**任何引入了禁止 token、超过单次调用 token 预算或渲染超出上下文窗口的 prompt 模板,都在这里失败。启发式语义相似度评分[6]也足够便宜,可以在契约层运行,以覆盖字面字符串匹配不足以覆盖的模糊匹配禁用词。
# 一次有代表性的契约测试调用 — 大约 600 毫秒完成
divinci ci contract \
--manifest release/staging.yaml \
--check schema,template,manifest,index,denylist \
--fail-fast \
--json-out /tmp/contract-report.json这些调用没有一个会触及评审器。没有一个是非确定性的。没有一个会花掉可测量的钱。而它们中的每一个,都能排除掉一整类“门禁套件说医疗切片回归了“的告警——而那些告警本应浪费 12 分钟的评审器推理,去给一个模型从一开始就不可能正确生成的输出打分。
冒烟层 — 90 秒、每 PR ~$0.05
如果说契约层是廉价的不公平优势,冒烟层就是真正能以一杯咖啡的价格抓住回归的那一层。从最高流量的切片里抽出 20 到 30 个用例,只对任务完成度和安全性打分,不对忠实度、延迟或检索锚定做检查。每个 PR 都跑这一层。它大约耗时 90 秒,因为这些用例被批处理进一次评审器调用,并使用结构化输出 schema,而评审器用的是廉价的校准评审器——不是发布候选所用的高质量版本。
我们在回归日志里追踪每个已交付修复是被哪一层抓到的,在过去六个月的客户部署中,直方图一直稳定:
漏出的那 3% 就是第 5 篇的即时回滚存在的原因。门禁不承诺零漏出;它们承诺一个紧致的上界,以及对于漏出者的快速恢复。
CI 车队规模 — 12 分钟套件如何保持便宜
完整套件这一层就是数学必须自洽的地方。一个朴素实现会按 用例 × 维度 各调用一次评审器、顺序运行,账单随用例数线性扩张。三项优化几乎承担了让其保持可控的全部工作:
**嵌入缓存。**每个黄金数据集用例的检索上下文指纹都会被哈希;如果用例没有变化、检索索引也没有变化,缓存的嵌入即视为有效,检索步骤被跳过。在我们客户部署中,第一个稳定周之后的命中率一直稳定在 90% 以上。
**评审器批处理。**校准过的评审器使用结构化输出调用,每次调用批量处理 8–16 个用例。评审器的按 token 单价不变;按用例摊销的开销下降,因为系统 prompt 在整批中被摊销。安全批处理的阈值由评审器自身在该批大小下的校准一致性决定[2]——我们在每周的评审器校准流程中实测这一指标(第 7 篇)。
**跨用例的 KV 缓存复用。**对于每次调用都以相同系统 prompt 和工具定义开头的模型,该前缀的 KV 缓存按整套套件运行一次,而不是按用例每次重算[3]。在开放权重部署上这很直接;在闭源 API 模型上则取决于厂商对前缀缓存的支持。
合并后的效果让完整套件落在上文分层蛋糕图所示的成本数字附近。具体数字是内部数据,但比例才是公开主张:~74% 的 PR 花零评审器美元;~22% 花几分钱;剩下的 4% 为我们已知能产出的最高置信度上线前信号,花掉几美元。
影子 CI — 开启它而不破坏团队
我们最常看到团队犯的一个错误,就是在第一天就把一个新门禁从“关闭“直接翻到“拦截“。阈值是用昨天的数据调出来的,假阳性率未知,而当门禁第一次触发时,团队没有任何校准可以判断这是真的还是误报。值班的评估工程师被叫起,门禁被关掉,信任崩塌,项目就此死去。
修复之道是 影子 CI:让新门禁以非拦截方式跑两周,在每个 PR 上以机器人评论的形式发布结果,在翻为拦截之前每周复核假阳性率。Divinci CI 运行器有一个 --shadow 标志正是为此而设。PR 评论看起来和最终的拦截版本一模一样——同样的 diff 显示、同样的按切片分解——只是它不会拦截合并。
divinci ci run --layer=full --shadow --duration=14d --report-as=bot-comment当假阳性率在窗口内持续低于 5%,我们就翻开关。当达不到时,我们收紧按切片阈值、重新校准评审器、再次影子运行。无论哪种情况,团队都不会被一个第一天就触发的新门禁伏击。
一个真正能组合的 GitHub Actions 工作流
把分层蛋糕接入你现有 CI 的那一块,运行在 .github/workflows/llm-ci.yaml 里。各层被串接,使得便宜的快速失败,昂贵的只在必要时运行——needs: 链和路径过滤触发器承担了这一工作[5]。
name: LLM CI
on:
pull_request:
paths:
- 'prompts/**'
- 'config/models.yaml'
- 'eval/**'
- 'retrieval/**'
- 'manifests/**'
jobs:
contract:
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
- uses: actions/checkout@v4
- run: divinci ci contract --manifest manifests/staging.yaml --fail-fast
smoke:
needs: contract
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- run: divinci ci run --layer=smoke --post-pr-comment
env:
DIVINCI_API_KEY: ${{ secrets.DIVINCI_API_KEY }}
full:
needs: smoke
if: contains(steps.changes.outputs.paths, 'prompts/') || contains(steps.changes.outputs.paths, 'config/models.yaml')
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- run: divinci ci run --layer=full --post-pr-comment --gate
env:
DIVINCI_API_KEY: ${{ secrets.DIVINCI_API_KEY }}注意三件事。各层通过 needs: 串接,所以契约失败时冒烟不会跑,冒烟失败时完整套件也不会跑。full 任务通过路径过滤限定到真正值得跑 12 分钟的变更——README 上的拼写修复不会触发门禁套件。--post-pr-comment 标志让按切片的 diff 不必离开 GitHub 就能看见。
PR 失败的调试回路
“门禁触发了“的另一半,就是“告诉我为什么”。回归套件输出一句 medical 切片任务完成度下降 0.04,如果没有引发它的用例,是无法行动的。我们在 PR 评论里展示最差的 5 个按切片差异,附带原始输入、基线输出、候选输出,以及评审器的推理轨迹。调试回路被设计为耗时秒级,而不是分钟级:
# 拉取在本 PR 上触发 medical 切片门禁的 5 个最差用例
divinci ci diffs --pr 1247 --slice medical --dimension task_completion --top 5这与第 6 篇的 7 步树是同一个诊断面,只是接入了 CI 反馈回路。提交 PR 的工程师在 PR 上就能看到用例级证据;不必去打开一个单独的评估仪表盘。
版本控制纪律 — prompt、数据集、评审器即代码
Prompt 模板、黄金数据集和评审器 prompt 全部存活在仓库里,在发布清单中以哈希钉死。清单是把整套套件绑定到某个可复现状态的单一对象:
# manifests/staging.yaml — 每次 CI 运行都对它进行哈希
release_id: rel-staging
model: { sha: 0c1f9…, weights: r2://models/custom-v7.2, open_weights: true }
prompt: { sha: c4a8e…, template: prompts/support/v3.4.j2 }
retrieval: { sha: b21f0…, index: r2://indices/kb-2026-04 }
judge: { sha: d8e21…, rubric: eval/rubrics/v7.yaml }
dataset: { sha: a90b1…, file: eval/datasets/golden-2026-04.jsonl }当一次 CI 运行发布一个分数时,该分数会被打上对应的清单哈希。当分数发生位移时,“是哪个输入挪动了“有了直接答案:对清单做 diff,触发的那一层告诉你应该先看哪个维度。这就是第 1 篇四阶段流水线和第 4 篇的 vindex 回执合起来要闭合的回路:清单就是这八篇文章在不同视角下,一直在构建的那个审计原语。
这套方法没解决什么
依然是我们在本系列每篇里都坦诚写下的三条限制。
- **CI 不会测试不在套件里的东西。**无论分层蛋糕多巧妙,它能抓到的回归只有黄金数据集中某个用例本会标记出来的那些。回放层缓解了行为漂移问题,但从未见过的新颖查询,仍然会一直漏到生产中才被发现。这套系统必须与生产监控配对。
- **成本数字会随模型定价变化。**本文中的每个成本数字都依赖于评审器 token 价格、嵌入价格和推理价格,而这些每季度都会漂移。比率——74% / 22% / 4%、31% / 27% / 28% / 11% / 3%——才是承重的主张;美元数字仅是某一时刻的示意。
- **厂商侧的检查点变更仍然很难处理。**当闭源 API 厂商悄悄更新了某个稳定名称背后的模型,契约层抓不到;只有门禁套件能抓到,而且只能事后抓到。我们的缓解办法是:在厂商支持的地方钉住显式检查点标识符,并把检查点被宣布的那一天视为对完整套件重做基线的触发事件。我们无法预防根因。
系列收尾
这是 8 篇中的第 8 篇。完整弧线:
- 如何用 Divinci AI 构建 LLM CI/CD 流水线 — 四阶段流水线(Register / Gate / Roll / Observe),其后一切都生活在它之内。
- 定制语言模型中 10 种 CI/CD 发布失败 — 2026 年具名失败模式,每一种都映射到本应抓住它的阶段。
- 面向 LLM 的 12 项 QA 与发布管理能力 — 能力矩阵与三阵营维恩图,将 Divinci 置于与替代方案的对照中。
- 在受监管领域验证与发布定制 LM — 合规深度剖析、监管机构到阶段的映射、vindex 回执。
- 带即时回滚的自动化 LLM CI/CD 流水线 — 运营层、自动化光谱、自动回滚回执。
- 如何用 7 步诊断定制 LLM QA 失败 — 诊断决策树;大约每七次告警中,只有一次真正的答案是“模型“。
- 2026 年定制 LLM 的自动化回归测试 — 切片感知的 Spearman 门禁、校准过的评审器、闭环的生产追踪回放。
- **本篇。**让上述一切在每个 PR 上都可行的 CI 基础设施。
各部分相互组合:清单是审计原语,门禁是安全层,诊断树是恢复回路,vindex 回执是外部锚点,而分层蛋糕让整套东西在每次提交时都负担得起。如果你的定制 LLM 发布流程没有把这五者凑齐,这八篇文章一直在讲的,就是其中的缺口。
FAQ
我能在每次提交时跑的最便宜的测试是什么?
一个 prompt 模板渲染检查。它以毫秒计运行,无需评审器,能抓到出乎意料一部分的破损,且永远不会花掉一分可测量的钱。如果你还没在跑它,这是我们所知 CI 中 ROI 最高的单项。
我应该预期定制 LLM CI 流水线花多少钱?
典型 PR 每个几分钱,候选发布 PR 每个低位个位数美元。比例取决于评审器价格,以及你的 PR 中有多大比例触及 prompt 或模型配置。上文 4% 的候选发布占比是典型值;对于 prompt 迭代频繁的产品,该占比会上升,均值也随之走高。
我应该在每次提交时都跑完整套件吗?
不应该。用路径过滤把它限定到触及 prompt、模型配置、检索或评估代码的 PR。对于其他变更,契约 + 冒烟就够用;而在 README 拼写修复上等 12 分钟,会让你在一个 sprint 之内失去团队的信任。完整套件很珍贵;把它花在变更确实可能挪动某个质量维度的地方。
我如何引入一个新门禁而不破坏所有人?
两周影子窗口,非拦截。根据影子期间观察到的假阳性率调阈值。只有当持续假阳性率低于你的容忍度(我们用 5%)时才翻为拦截。其他做法都是教大家学会忽视一个门禁。
如果只追踪一个数字,我应该追踪哪个?
在生产前被抓住的已确认回归占比。本文的直方图把这个数字定在成熟 Divinci 部署中的 ~97%。漏出的 3% 就是即时回滚存在的原因。那 97% 才是套件存在的意义。
参考文献
- DORA / Google Cloud. "Accelerate State of DevOps — CI velocity, change-failure-rate and time-to-restore-service." 跨行业基线,使"每 PR 12 分钟太慢"成为一个可辩护的主张,而不是一种意见。
- Zheng et al. "Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena." arXiv:2306.05685. 经验证据表明,在冒烟与完整层所用的批量大小下,批处理的 LLM-as-judge 调用可保持校准——这是本文成本数字得以成立的原因。
- Pope et al. "Efficiently Scaling Transformer Inference." arXiv:2211.05102. CI 车队规模章节引用的 KV 缓存复用与前缀共享技术。
- Pan, Tianpan. "The Semver Lie: how a minor LLM update broke production." 2026 年 4 月 29 日。2026 年仅依赖聚合的回归套件具名失败模式;CI 分层蛋糕从头到尾都坚持切片感知的原因。
- GitHub. "GitHub Actions — chaining jobs with `needs:` and conditional execution." 本文 .yaml 所组合的基础原语。
- Zhang et al. "BERTScore: Evaluating Text Generation with BERT." arXiv:1904.09675. 作为更便宜层的 LLM-as-judge 替代项,引用的启发式语义相似度度量;并非我们在门禁时所用,但在契约层用于大规模禁用短语检测时很有用。
Ready to Build Your Custom AI Solution?
Discover how Divinci AI can help you implement RAG systems, automate quality assurance, and streamline your AI development process.
Get Started Today