返回 AIPA 笔记
AIPA Day 76

每日 evals 防退化固化 — daily runner、退化告警与 CI gate

每日 evals 防退化固化 — daily runner、退化告警与 CI gate

2026-08-29
daily-evalsregression-gatecidrift-alert

日期: 2026-08-29 阶段: Phase 3 - AML 调查 Copilot 标签: #daily-evals #regression-gate #ci #drift-alert

核心问题

W11 到这里,证据/类型学/SAR 三组 evaluator 都已经写好(P1 沉淀的 evalChecks 代码型检查 + P3 的 judge),judge 也用 Day 17 的 Cohen's κ 校准过。但还剩一个最容易被忽略、却最致命的缺口:这些 eval 只在我手动跑 npx vitest 时才跑。

这意味着退化在两种时机会悄无声息地溜进来:

  1. 代码变更:改了 sarDraft.ts 的段落拼装、动了 typology.ts 的阈值——本地忘跑全套 eval 就 merge。
  2. 模型漂移:供应商静默换了权重(Day 17 SOTA 检查里已警示的「κ 漂移」同源威胁),我这边一行代码没动,SAR 质量却跌了——没有定时跑,根本看不见

今天回答三个问题:(A) 三组 evals 怎么固化成每日自动跑的 daily runner,PR 跑什么、夜间跑什么、为什么分层;(B) 退化怎么从「人眼看趋势」变成可阻断 merge 的硬告警——给出退化判定的统计方法与阈值表;(C) 这套 daily runner 怎么和 P1 已有的 evalChecks.ts + CI 衔接,不另造一套。

贯穿全篇一个判断:eval 不是「写出来」就完事,是要「定时自动跑 + 退化即阻断」才算落地。 一个躺在仓库里、只在我记得时才跑的 eval suite,对抗模型漂移的能力等于零。

关键内容

A. 三层分跑:PR 快检 / 夜间全扫 / canary——为什么不能只跑一层

2026 的工程共识(FutureAGI CI/CD eval 工作流,2026-03-31 发布 / 2026-05-20 更新)把 eval 按成本-频率-覆盖切成三层,绝不在每个 PR 上跑全量 judge:

┌─ 每个 PR(path-scoped 快检) ──────────────────┐
│  · 确定性代码检查 + 分类器级联(无 LLM 或廉价 LLM)│
│  · 仅跑受改动影响的路由:100-200 例              │
│  · 硬约束:<5 分钟、~$0.20/PR                    │
│  · 失败即 hard-fail PR(exit 2)                 │
└────────────────────────────────────────────────┘
                  │ merge 后
                  ▼
┌─ 夜间(scheduled cron,全量 judge 扫) ─────────┐
│  · 完整语料 LLM-judge sweep:500-2000 例/路由    │
│  · 产出滚动 7 天基线 JSON                        │
│  · 失败 → Slack webhook 告警(最低限度)         │
└────────────────────────────────────────────────┘
                  │ 部署后
                  ▼
┌─ canary(1-5% 生产流量) ───────────────────────┐
│  · 同一 rubric 库给 candidate 与 baseline 双打分 │
│  · 持续漂移 >2-3 个百分点 / 15-60 分钟 → 自动回滚 │
└────────────────────────────────────────────────┘

为什么必须分层,FutureAGI 给了一句话定律:

反直觉洞察①(CI eval 三角不可能,硬选俩):「CI eval on every PR has to be cheap, fast, and statistically significant. Pick any two and the gate is theater.」(FutureAGI, 2026-03)——廉价 + 快 + 统计显著,三者只能取其二。想在每个 PR 上跑全量 judge 求统计显著,就既不廉价也不快,开发者会嫌慢而绕过 gate,gate 沦为摆设(theater)。正解不是「三个都要」,而是把它们拆到不同层:PR 层要快和廉价(确定性检查,放弃单 PR 的统计显著性),夜间层要统计显著(全量、慢、贵都能接受,因为不阻塞人)。把这个三角硬塞进一层,是 eval 落地最常见的死法。

对本项目(静态站、无生产流量)的裁剪:canary 层 P3 无法落(没有 1-5% 真实流量可分流),但 PR 快检 + 夜间全扫这两层完全可落——PR 层就是 P1 已有的 codeCheckPassRate(纯确定性、毫秒级、零 LLM 成本),夜间层是新加的 daily runner,把三组 evaluator 在全量金标(66 案 + P3 扩集)上跑一遍并落基线。

三组 evaluator 与三层的映射:

evaluator 组判定方式PR 快检夜间全扫退化关注的核心指标
证据组确定性(cited_tx_exist / cited_tx_nonempty引用幻觉率、证据非空率
类型学组确定性(top_typology_consistent + 金标 recall/FPR)per-typology recall、normal FPR
SAR 组LLM-judge(语义:faithfulness/coverage)❌ 太贵judge 通过率、judge×人工 κ

证据组与类型学组是确定性的,两层都跑;SAR 组是 LLM-judge,只在夜间全扫跑——这正是 A 节三角定律的直接应用:judge 既不廉价也不快,放进 PR 层会让每次 commit 都慢且烧 token,所以下放到夜间。

B. 退化判定:从「看趋势」到「可阻断 merge 的硬告警」

退化告警最业余的做法是「画个折线图,人眼看着跌了就警觉」。问题是 eval 分数本身有噪声——judge 重跑、采样抖动都会让分数上下飘几个点,把噪声当退化会让 gate 频繁误报、最终被无视。必须用统计方法把「真退化」和「噪声抖动」分开。

FutureAGI 的退化判定逻辑(2026-03)是一个双条件合取,缺一不可:

function isRegression(candidate, baseline, rubric):
    delta = baseline.score - candidate.score        # 跌了多少
    # 条件①:统计显著(连续指标用 Welch's t-test,二元用 z-test)
    p = welch_t_test(candidate.samples, baseline.samples)
    # 条件②:效应量超过该 rubric 的噪声地板(~0.03 on 0-1 scale)
    significant = (p < 0.05) AND (delta > rubric.noise_floor)   # ≈ 0.03
    return significant

两个条件都要满足才算退化p < 0.05(不是随机抖动) delta > 0.03(跌幅超过该 rubric 的噪声地板)。只满足 p 值不够——大样本下哪怕 0.005 的微跌也能统计显著,但那在实践上无意义;只满足跌幅不够——一次抖动也可能跌 0.04 但不显著。合取才能既不漏真退化、又不被噪声刷屏。

配套两个进阶技巧(FutureAGI):

  • 配对自助法(bootstrap CI on paired deltas):candidate 和 baseline 跑同一批样本,对配对差值做自助置信区间——比独立双样本检验方差更紧,更早抓到真退化。本项目金标固定 66 案,天然适合配对。
  • 尾部用 p95 而非均值:对「长尾失败」型 rubric(少数案件烂得离谱、均值被多数好案件撑住),gate 用 p95_score 而非 mean——均值会掩盖尾部塌陷。AML 的幻觉就是这种尾部分布:99% 的 SAR 不幻觉、1% 凭空捏造交易,均值看不出,p95 一抓一个准

退化告警阈值表(本项目设计口径,绝对地板对齐 P1 的 CI 门槛,相对退化对齐 FutureAGI):

指标绝对地板(跌破即 hard-fail)相对退化(vs 7 日基线)噪声地板触发动作
structuring recall≥ 0.85(P1 既定)Δ < −0.03 且 p<0.05~0.03阻断 merge(exit 2)
layering / mule recall≥ 0.80Δ < −0.03 且 p<0.05~0.03阻断 merge
normal FPR≤ 0.15Δ > +0.05 且 p<0.05~0.03阻断 merge
证据引用幻觉率(p95)= 0(任一幻觉即红)任一新增即报0hard-fail(critical)
SAR judge 通过率≥ 基线Δ < −0.03 且 p<0.05~0.03Slack 告警(夜间)
judge×人工 κ≥ 0.6(Day 17)跌破 0.6judge 降级回人工

退化的退出码契约(直接复用 FutureAGI 的 exit code 设计,落进 CI):

exit code含义CI 策略
0全过merge 放行
2断言失败(跌破绝对地板 / 出现幻觉)hard-fail PR
3警告(--strict 下的相对退化)Slack 通知、不阻断
6judge API 错误重试,持续则报错
7超时提高 timeout 或分片

关键设计:幻觉是 exit 2(hard-fail),SAR judge 退化是 exit 3(告警不阻断)。理由——幻觉是 critical(凭空捏造交易,AML 绝对红线,见 failureTaxonomyhallucination severity=critical),任何新增即阻断;而 judge 语义评分有主观性、可能是 judge 自身漂移而非产物退化,先告警人工看一眼,不直接卡死 merge。

反直觉洞察②(aggregate 涨了,仍可能必须阻断 merge):最隐蔽的退化是总分涨、关键 cohort 跌。FutureAGI 给的真实例子:「aggregate pass rate improves from 0.91 to 0.93, but the refund cohort drops from 0.94 to 0.83 on ToolSelectionAccuracy」——总通过率从 0.91 涨到 0.93,看着是改进,但退款 cohort 在工具选择上从 0.94 崩到 0.83。映射到 AML:整体 SAR 通过率涨了,但 structuring 这一类的 recall 从 1.0 跌到 0.88——总分被 layering/mule/normal 的好表现撑住,structuring 的塌陷被平均掉了。所以 gate 绝不能只看 aggregate,必须先暴露 per-evaluator / per-cohort delta,再做聚合 pass/fail 决策(FutureAGI:「expose per-evaluator deltas before any aggregate pass/fail decision」「keep the baseline run immutable」)。一个能改进聚合却让某个 release-critical cohort 退化的 candidate,必须被拦下——哪怕总分更好看。

C. 与 P1 evalChecks + CI 的衔接:不另造一套

daily runner 不是新发明一套 eval,而是把 P1 已沉淀的资产调度起来 + 加退化对比。三处衔接:

衔接①——复用 codeCheckPassRate 作 PR 快检层evalChecks.tscodeCheckPassRate(dataset, assess, draft) 已经在全 66 案上跑五类确定性检查并按 checkId/failuresByClass 聚合——这就是 A 节的 PR 快检层,毫秒级、零 LLM 成本、已进测试。daily runner 的证据组与类型学组直接调它,不重写。

衔接②——evalBaseline 产出即「不可变基线」evalRuleBaseline(ds) 已产出 per-typology recall + normalFPR + confusion——这正是 B 节退化对比要的「上一次通过的基线」。daily runner 把每晚的 BaselineEval 落盘成带日期的 JSON(evalBaseline-YYYY-MM-DD.json),形成 FutureAGI 说的「rolling 7-day baseline」;对比时基线行只增不改(immutable baseline——呼应 Day 75 审计 trail 的 append-only 同源思想:基线被悄悄改写 = 退化检测失效)。

衔接③——退化报告挂回 failureTaxonomy 归桶。退化不只报「分数跌了」,还要报「跌在哪类失败」——daily runner 把跌破的检查按 relatedFailure 字段(evalChecks 里每条 CheckResult 已带)归到 6 类 taxonomy,输出「本次退化新增 N 例 hallucination / M 例 typology_misjudge」,直接对接 Day 17 的分歧驱动迭代闭环(知道跌在哪类,才知道改 rubric 的哪一段)。

daily runner 的执行步骤(伪代码,全确定性可单测,judge 段在无 key 时诚实降级):

function runDailyEvals(dataset, prevBaseline):
    # 1) 确定性层(证据 + 类型学)——复用 P1
    code = codeCheckPassRate(dataset, assessCase, draftSar)
    base = evalRuleBaseline(dataset)
    # 2) judge 层(SAR 语义)——无 key 时跳过并标记 skipped,不伪造分数
    sar  = hasJudgeKey() ? runSarJudge(dataset) : { skipped: true }
    # 3) 退化对比(B 节双条件)
    regs = []
    for metric in [recall_struct, recall_layer, recall_mule, fpr_normal, halluc_p95]:
        if isRegression(code/base[metric], prevBaseline[metric], NOISE_FLOOR):
            regs.push({ metric, delta, severity, relatedFailure })
    # 4) 落基线 + 定退出码
    writeBaseline(`evalBaseline-${today}.json`, { code, base, sar })
    return exitCode(regs)   # 有 critical → 2;仅 warning → 3;全过 → 0

设计要点/决策表

要点决策理由
分层PR 快检(确定性)/ 夜间全扫(judge)/ canary(P3 无流量,不落)三角不可能,硬选俩(洞察①)
PR 层跑什么codeCheckPassRate(毫秒级、零 LLM)快+廉价,放弃单 PR 统计显著性
夜间层跑什么全量金标 + SAR judge + 落 7 日基线慢/贵可接受,换统计显著
退化判定p<0.05 Δ>噪声地板(~0.03) 双条件合取缺一则误报或漏报
配对检验bootstrap CI on paired deltas(同 66 案)方差更紧,更早抓真退化
尾部指标幻觉率用 p95 非 mean均值掩盖尾部塌陷
聚合纪律先暴露 per-cohort delta,再聚合判定aggregate 涨可掩盖 cohort 跌(洞察②)
退出码幻觉=exit2 阻断;judge 退化=exit3 告警幻觉 critical 必拦,judge 主观先告警
基线append-only、带日期落盘、只增不改基线被改 = 退化检测失效(呼应 Day 75)
不另造复用 codeCheckPassRate/evalBaseline/failureTaxonomydaily runner 是调度器不是新 eval

对本项目的落地

  • 新建 src/aml/dailyEvalRunner.ts:导出 runDailyEvals(dataset, prevBaseline) → { code, base, sar, regressions, exitCode }(实现 C 节伪代码)。证据组/类型学组直接调 codeCheckPassRateevalChecks.ts)与 evalRuleBaselineevalBaseline.ts),不重写检查逻辑;SAR judge 段在无 API key 时返回 { skipped: true }不伪造分数(沿用 evalChecks.ts 头注的「无 key 诚实降级」纪律)。
  • 退化判定 detectRegression(candidate, baseline, noiseFloor=0.03):实现 B 节双条件合取。统计检验 v1 用保守占位——固定 66 案样本量小,先用「Δ > 噪声地板 + 方向判定」做确定性判定,Welch's t-test / bootstrap CI 标注为「P3 扩集(≥100 案)后接入」,不谎称已做统计显著性检验(小样本下 t-test 本就不可靠,诚实标注比假装严谨更重要)。
  • 基线落盘writeBaseline() 把每次 BaselineEval + 代码检查汇总 + judge 结果序列化为 evalBaseline-YYYY-MM-DD.json(结构对齐 evalBaseline.tsBaselineEval),形成滚动 7 日窗口;基线文件只增不改,与 Day 75 审计 trail 的 append-only 同源——基线可被悄改,退化检测就失去意义。
  • 退化报告归桶:退化项按 evalChecks 已带的 relatedFailure 字段归到 failureTaxonomy.ts 的 6 类,输出「本次新增 N 例 hallucination / M 例 typology_misjudge」,喂给 Day 17 分歧驱动闭环(定位改 rubric 哪一段)。
  • CI 接线:在测试里断言 runDailyEvals(getGoldenDataset(), lastBaseline).exitCode !== 2(无 critical 退化)才允许 merge;退出码契约对齐 B 节表(幻觉=2 阻断、judge 退化=3 告警)。诚实标注:cron 夜间调度 + Slack webhook 为 P3 上线后的运营动作(当前静态站无后端调度),W11 仅落「可被 CI/手动触发的 runner 函数 + 退化对比 + 基线落盘」纯 TS 实现,不谎称已有夜间定时任务在跑

参考资料

  1. FutureAGI — CI/CD LLM Eval with GitHub Actions: 2026 Workflow(PR 快检 100-200 例 <5min ~$0.20、夜间全扫 500-2000 例/路由出 7 日基线、canary 1-5% 流量漂移 >2-3pp/15-60min 自动回滚;退化判定 p<0.05 且效应量 > 噪声地板 ~0.03;Welch's t-test 连续/z-test 二元;p95 尾部指标;退出码契约 0/2/3/6/7;「cheap, fast, statistically significant — pick any two and the gate is theater」)(2026-03-31 发布 / 2026-05-20 更新)
  2. FutureAGI — LLM Regression Testing Guide(回归=受控对比「same rows, same evaluators, same metric thresholds, new candidate」;immutable baseline;「expose per-evaluator deltas before any aggregate pass/fail decision」;真实反例「aggregate 0.91→0.93 但 refund cohort 0.94→0.83 on ToolSelectionAccuracy」;控制重跑 ±0.03 噪声;供应商静默更新 2-4pp 漂移;versioned dataset 行只增不改)(2026)
  3. testquality.com — LLM Regression Testing Pipeline for QA Engineers: RAG Triad & Gold Sets in 2026(每 PR 跑 Gold Set + LLM-judge、对比 main 分支出回归报告)(2026)
  4. EY — Rethinking transaction monitoring for AML(告警量激增、95%+ FP、投资者复核能力受限的结构性压力;持续控制监控含场景自动化测试 + 漂移检测 + controls-health 看板)(2026)
  5. 本仓库 src/aml/evalChecks.tscodeCheckPassRate / relatedFailure 归桶)、src/aml/evalBaseline.tsevalRuleBaseline 产 per-typology recall/FPR/confusion)、src/aml/failureTaxonomy.ts(6 类 + severity)、docs/aipa/day17-judge-calibration.md(κ 漂移监控)、docs/aipa/day75-immutable-trail.md(append-only 基线同源)(2026-06~08)

SOTA 检查 (2026-08-29 / 复核基线 2026-06-11)

  • 三层分跑(PR 快检 / 夜间全扫 / canary)是 2026 现行主流:FutureAGI(2026-03/05)、testquality(2026)、Adaline 完整指南口径一致——「确定性检查上 PR、全量 judge 上夜间、统计门控 + 自动回滚上 canary」已是 table-stakes,未见替代架构。本项目无生产流量,canary 层不落,PR + 夜间两层可落。
  • 退化判定「p<0.05 且 Δ>噪声地板」双条件为现行做法:单看 p 值(大样本微跌也显著)或单看跌幅(一次抖动也可能超阈)都会误判,合取是共识。本项目诚实标注:固定 66 案样本量小,v1 用「Δ>噪声地板 + 方向」确定性判定,t-test/bootstrap 留待 P3 扩集(≥100 案)接入——小样本硬套 t-test 是伪严谨。
  • 「先 per-cohort delta 再聚合」是本篇最易被忽略的活踩坑点:FutureAGI 的 0.91→0.93 / cohort 0.94→0.83 反例(洞察②)说明只看 aggregate 的 gate 会放过 release-critical cohort 退化;映射 AML 即「总通过率涨、structuring recall 跌」。任何只断言总分的 CI gate 都不合格。
  • 过时认知警示:①「eval 写出来就算落地」过时——不定时自动跑就抓不住模型漂移;②「每个 PR 跑全量 judge」过时——三角不可能,judge 下放夜间;③「看折线图判退化」过时——噪声会刷屏,须统计门控;④「基线可滚动覆盖」过时——基线须 append-only,被改即检测失效。
  • 待跟踪:P3 扩集到 ≥100 案后,评估 Welch's t-test / 配对 bootstrap CI 是否接入(样本量是否足够支撑统计显著性判定);canary 层在「若 P3 上线后有真实流量」时是否补;judge 漂移(Day 17 κ 重算节奏)与本日 daily runner 的调度合并为同一夜间任务。