逐日AI
第 1 周 · D6约 4 小时

回归门禁:让退化在合并之前就被拦住

评估只有进了流水线才会真正拦住事故。这一天把基线固化成快照,在一个每次跑分都会抖动的系统上定出既不漏报也不天天误报的阈值,再解决流水线里最现实的两个问题:跑一次要多少钱,以及红了之后谁来判断真假。

今日目标 0/3

登录后可以勾选并保存进度。

今日目标

  1. 能把一次评估结果固化成基线快照,并说出快照里除了分数还必须记什么才能复现
  2. 能在有噪声的通过率上定出退化判定规则,并解释为什么单次跑分下降不足以判定退化
  3. 能设计一条控制成本的流水线策略,说明哪些任务每次都跑、哪些只在发版前跑

小白版讲解

门禁刷卡:不合格就是进不去

写字楼的门禁是一台很笨的机器。它不懂你是谁、今天心情如何、这次加班多重要,它只做一件事:卡不对,闸机不开。

这台机器的价值恰恰在于它笨。如果它会「看情况通融一下」,那它就不再是门禁,而是一个建议。

前五天我们做的全部是建议。评分器、基准集、裁判、轨迹评分、成本面板,它们产出的是一份报告,而报告是要人去读的。人会忙、会忘、会在发版前一小时告诉自己「这次应该没问题」。

今天要做的事只有一件:把那份报告接成闸机。 它不需要聪明,它只需要在退化发生时把闸门关上,并且关得足够硬——硬到没人能在不留痕迹的情况下绕过去。

对应到工程上,这个「硬」只有一个技术含义:进程以非零退出码结束。 流水线只认这一件事。你的评估脚本打印多少红色的字都没用,只要它 exit 0,合并就会发生。

快照里只记分数,等于什么都没记

先做第一件事:把一次评估结果固化下来,作为以后比较的基准。这份东西叫基线快照

新手的第一版快照通常长这样:

TextText
baseline: 0.925

下周它掉到了 0.865。现在请回答:出什么事了?

你答不上来。因为这两周之间可能发生了四件事,而它们里只有一件是真的退化:

  1. 有人换了模型;
  2. 有人改了系统提示词里的一句话;
  3. 有人往基准集里加了两条更难的任务;
  4. 什么都没改,这次只是运气差。

四选一,而快照里的那个数字对此一言不发。更糟的是,你会下意识地假定是第四种,重跑一次,如果分数回去了就当无事发生——这正是门禁机制最常见的死法

所以快照必须分成两半:

部分内容作用
环境指纹模型、提示词版本、任务集版本、评估框架版本、试次数、档位判断两次结果能不能比
分数总体通过率、逐任务通过率判断比出来是多少

外加一个单独的字段:随机种子

种子的用途是复现——门禁红了,你要能一字不差地重跑出那一次去排查。但它刻意不进指纹。理由值得想一分钟:换一个种子跑出来的分数,恰恰是我们用来估计噪声的样本。如果种子进了指纹,那么每次换种子都会被判成「不可比」,门禁就退化成「只能在同一个种子上比」——那等于把非确定性从评估里删掉了,而非确定性正是这门课存在的全部理由。

baseline.js
// 环境指纹:这几项里任何一项变了,两次分数就不能直接相减
const fingerprint = {
  model: process.env.LLM_MODEL ?? 'offline',
  promptVersion: 'refund-agent@1.3.0',
  taskSetVersion: '2026.09.14-1',
  evalkitVersion: 'evalkit@0.6.0',
  k: 5,
  tier: 'full',
}
 
// 返回不一致的字段名;空数组表示可比
export function compareFingerprint(a, b) {
  const keys = ['model', 'promptVersion', 'taskSetVersion', 'evalkitVersion', 'k', 'tier']
  return keys.filter((key) => a[key] !== b[key])
}

提示词版本这一项最容易被忽略,也最容易出事。提示词是一个字符串,改它不需要发起代码评审的仪式感,很多团队甚至把它放在配置后台里随时可改。结果就是:分数变了,没有任何一次提交能解释它。给提示词编个版本号,改一个字就加一个版本,这件事的成本几乎为零,收益是把一整类查不出原因的分数波动变成可查的。

抖动不是退化

这是今天最核心的一条,也是整套机制成立与否的分水岭。

先看一组真实数据。这是本课的靶子 Agent,任务集、评分器、模型、提示词全部不动,只换随机种子,连跑三十轮,记录总体通过率:

TextText
噪声实测:full 档 8 条任务,k=5,30 个种子
  区间 0.8500 – 0.9750
  均值 0.9167 最大单轮偏离均值 0.0667

什么都没改,通过率能差 12.5 个百分点。

现在回到那个最自然的门禁写法:「比基线低就拦」。把它放到这组数据上,结果是:它每周要误报好几次。而误报的代价不是麻烦,是这条门禁会死掉。流程大致是这样的:

第一次红,大家认真查,查了两小时,发现重跑一下就绿了。第二次红,有人说「上次也是这样」,重跑,绿了。第三次红,已经没人查了。第四次,有人在仓库里加了一行 continue-on-error: true

到这一步,门禁还在流水线里,还在跑,还是绿的,但它的实际拦截能力是零。这比没有门禁更糟——没有门禁的时候,大家至少知道自己没有防护。

定阈值:先量噪声,再定位置

阈值不是拍脑袋定的,也不能抄别人的。它是你这套任务集、这个试次数、这个模型的一个属性。定它只需要三步:

  1. 固定一切,只换种子。 任务集、k、模型、提示词全部不动,连跑二三十轮。
  2. 看波动区间。 记下最高值、最低值、均值。最坏情况下能凭运气掉多少,就是区间的宽度。
  3. 把阈值放到区间之外。 警告线压在区间宽度附近,拦截线明显更高。

按上面那组实测数据(区间宽度 0.125),本课定的是:

阈值依据
警告线下降 12 个百分点实测区间宽度是 12.5 个百分点,也就是「最幸运的一轮当基线、最倒霉的一轮来对比」时运气能掉的最大值。阈值压在它略内侧
拦截线下降 20 个百分点区间宽度的 1.6 倍,运气凑不出来

这里有个容易被跳过的细节:试次数直接决定噪声宽度。 同一个 Agent,把八条任务的全量档换成三条任务的冒烟档(k 不变,总试次数从 40 掉到 15),噪声区间从 12.5 个百分点涨到了 20 个百分点。

所以冒烟档的阈值必须单独测、而且宽得多。这条推论很不舒服但必须说出来:冒烟档只能抓住二十个百分点以上的事故,小幅退化它根本看不见。 把冒烟档的绿色当成「没有退化」,是这套机制里最常见的自我安慰。

三态门禁与灰区复跑

有了噪声区间,判定就不该是两态了。两态门禁在灰区里必须二选一,而灰区的定义就是「样本不足以下结论」——强行下结论只会错。

所以做三态:

状态含义退出码
通过下降在噪声范围内0
警告下降落在灰区,复跑之后收敛了0,但要在报告里留痕
拦截下降超过噪声,或出现了不可能是抖动的信号非零

灰区的处理办法是加大样本再判一次:只对落在灰区的这一次运行,把 k 从 5 提到 15 再跑一遍。样本多了,噪声自然收窄;如果差距收敛回噪声内,判警告放行;如果仍然在灰区之外,那就不是运气,拦。

除了总分,还有一类信号不能用噪声解释,必须单独拦:某条任务从「每次都过」变成了「每次都挂」。

这一条之所以要单独写,是因为总分会骗人。八条任务里一条塌了、另一条恰好变好,总分可能纹丝不动,而你丢掉的是一个具体功能。逐任务比对能抓到它,总分不能。

gate.js
export function judge(baseline, current, config) {
  // 第一道:指纹对不上,拒绝比较
  const mismatched = compareFingerprint(baseline.fingerprint, current.fingerprint)
  if (mismatched.length > 0) {
    return { verdict: 'block', reason: `环境指纹不一致:${mismatched.join('、')},两次结果不可比` }
  }
 
  // 第二道:逐任务的「全绿变全红」,噪声解释不了
  const now = new Map(current.scores.tasks.map((t) => [t.taskId, t]))
  const collapsed = baseline.scores.tasks
    .filter((b) => b.passRate >= 1 && (now.get(b.taskId)?.passRate ?? 0) <= 0)
    .map((b) => b.taskId)
  if (collapsed.length > 0) {
    return { verdict: 'block', reason: `任务从每次都过变成每次都挂:${collapsed.join('、')}` }
  }
 
  // 第三道:总体通过率按区间比,落在中间的交给复跑
  const drop = baseline.scores.passRate - current.scores.passRate
  if (drop >= config.blockDrop) return { verdict: 'block', reason: '下降远离噪声区间' }
  if (drop > config.warnDrop) return { verdict: 'gray', reason: '落在灰区,样本不足' }
  return { verdict: 'pass', reason: '变化在噪声范围内' }
}

红了之后:怎么不让团队养成忽视的习惯

门禁红了,接下来发生什么,比门禁本身更决定这套机制的寿命。

有三条设计原则,它们都指向同一件事:让「跳过」比「修」更麻烦,而不是相反。

第一,红的时候必须给出可执行的下一步,而不是一个数字。 一条只说「通过率 57.5%,低于基线 92.5%」的报错,会把排查成本整个推给读到它的人。它应该直接给出:用哪个种子能复现那一次、哪几条任务掉了、以及先读哪几条失败试次的轨迹。这也是前五天的工作在今天收口的地方——你有轨迹,就把它用在这里。

第二,放宽标准必须留下痕迹。 「重新记录基线」这个动作等价于「我承认现在这个分数是新的正常水平」,它应该是一次带审阅的提交,而不是某个人在自己机器上跑一下命令。做法很简单:把基线文件放进版本库。这样每次放宽都会出现在 diff 里,有人能看见。

第三,绝不提供「一键跳过」。 需要跳过的场景确实存在(比如有意的行为变更),但它的正确表达是「更新基线并说明原因」,不是「本次忽略」。一个存在的跳过按钮,最终一定会变成默认路径。

成本分层:哪些每次跑,哪些发版跑

评估要花钱,这是 D1 就说过的第三个难点。今天它变成一个具体的取舍:不可能每次提交都跑全量评估。

分档的依据不是「重要程度」,而是覆盖面除以成本。冒烟档要用最少的任务盖住最多的故障类别,全量档负责长尾。

本课两档的实测账单:

TextText
冒烟档  3 条任务 × 5 次 = 15 次试次,36 次工具调用,成本 3.77 分
全量档  8 条任务 × 5 次 = 40 次试次,105 次工具调用,成本 10.42 分

离线跑感觉不到差别,换成真实网关就是 2.8 倍的账单和 2.8 倍的等待——而真实项目里这个倍数通常是二三十倍,因为全量集有几百条任务。

一条可用的分层:

什么时候跑目标
冒烟档每次提交几分钟内拦住大事故
全量档打 tag、发版前、每晚定时抓小幅退化与长尾
人工评审按季度,抽样校准自动评分器本身

接进流水线只有一个硬要求:拦截时退出码非零。

YAMLYAML
- run: pnpm start -- --tier smoke
- run: pnpm start -- --tier full
  if: startsWith(github.ref, 'refs/tags/')

线上抽样:离线集看不见的那一半

最后补一块。离线基准集只覆盖你想得到的任务,而线上每天都在产生你想不到的。

做法是按比例抽样线上流量,跑同一套评分器。这件事今天能做,是因为 D5 已经把每次运行的轨迹记下来了:抽样评估不需要重新跑一遍 Agent,只需要把已有的轨迹喂给评分器。

三条注意:

  1. 抽样要分层,不能纯随机。失败与异常样本必须全量保留,成功样本才抽比例——不然最有信息量的那部分正好被抽没了。
  2. 线上分数和离线分数不能混在一个数字里报。两者的任务分布完全不同,加权平均出来的数字既不代表线上也不代表离线。
  3. 线上评估的结论要回流成任务。抽样发现的新故障形态,应该变成基准集里的新任务——这正好接回 D2 的「从线上翻的车里攒基准集」,闭环在这里合上。

源码导读

今天的两份材料一份是方法,一份是参照实现。

Anthropic 工程博客的评估方法文章里,今天用到的是它对回归评估的定义:回归评估应该长期贴近满分,掉下来就是出事了。这句话是整条门禁的判据来源——正因为回归评估的期望值是「不掉」,所以「掉了多少算掉」才成为一个必须回答的问题。文章里另一条与今天直接相关的结论是「评估任务本身可能是坏的」,那意味着门禁红了之后的排查顺序里,判分缺陷要排在模型退化前面。这一点 D7 会完整展开。

promptfoo 是一个配置驱动的评估与对照工具(实测许可为 MIT,仓库活跃)。今天值得读的是它怎么把评估表达成一个可以进版本库的配置文件:测试用例、断言、对照的多个提示词版本全部写在一份声明式配置里,命令行跑完给出退出码。这套形态和我们今天手写的门禁是同一个思路,区别在于它把「任务集」和「阈值」做成了配置项而不是代码。

读它的时候带着一个具体问题:它的阈值是怎么表达的? 你会发现绝大多数现成工具提供的都是「分数低于 X 就失败」这种单点判据。这不是它们做得不好,而是噪声区间没法由工具替你决定——它是你那套任务集的属性,工具不知道你跑了几次、抖得多厉害。所以无论用哪个框架,今天这一步「先量噪声再定阈值」都得你自己做。

动手实验

🧪 D6:回归门禁

代码位置:labs/agent-evals-7days/day-06-ci-gate

今天给 evalkit 加两个模块:基线快照与三态门禁。练习有四处,但真正的验收在手动清单里——它要你亲手让门禁红一次

理由在 lab 的 README 里写了一遍,这里再说一次:一条从来没红过的门禁,和一条永远放行的门禁,在流水线上长得完全一样,都是绿的。你必须制造一次真实的退化,看着它被拦下,才知道自己装的不是一个摆设。

制造退化的办法是给靶子包一层削弱装饰器(不改靶子本身,那是冻结文件),让它有一定概率多退一笔款。选「多退一笔」这种缺陷形态有讲究:它对正向任务和反向任务有害。如果选一个只伤害一半任务的退化,总分会被另一半抹平,你以为在验证门禁,其实在验证一个碰巧不掉分的改动。

跑完之后你会看到这样一条拦截:

TextText
门禁:拦截
  基线通过率 92.5% 本次 57.5%
  · 总体通过率下降 35.0 个百分点,超过拦截线 20.0 个百分点,已远离噪声区间。
 
退出码非零,合并被拦住。排查从这里开始:
  1. 用基线里的种子 20260914 复现那一次运行
  2. 读几条失败试次的轨迹,先判断是 Agent 退化还是判分写错了
  3. 确认是有意的行为变更之后,才重新记录一份基线

面试题

今天的四道题围绕噪声下的判定、快照的完整性、成本分层,以及最后一道最难的:门禁红了但团队开始跳过它,你怎么改这条流程。

最后那道题几乎不考技术,考的是你有没有在真实团队里维护过这类闸门。答「加强宣导」「要求必须查清楚」拿不到分——正确的方向是降低修的成本、提高跳过的成本,并且承认误报率过高本身就是一个需要修的技术问题。

检查清单与明日预告

今天结束时,你应该能做到:

  • 说出基线快照里除分数之外必须记的四样东西,以及少记任何一样会出什么问题
  • 解释随机种子为什么要记录但不参与可比性判断
  • 用一组真实的噪声数据说明「单次下降五个点不能判定退化」
  • 说出三态门禁各自的判据,以及为什么只对灰区复跑
  • MOCK=1 pnpm selftest 十一项全绿
  • 亲手制造一次退化,看着门禁以非零退出码把它拦下

明天是 D7《体检套件本身:饱和、坏任务与判分缺陷》。今天我们默认了一件事:基线是可信的、判据是对的、分数掉了就是 Agent 的问题。明天把矛头转回评估系统自己——一条判分写错的任务能让同一个模型在同一套件上差出五十多个百分点,而一个逼近满分的套件已经不再提供任何改进信号。最后一天要回答的问题是:凭什么相信你的评估。

面试题库

  • 评估分数每次都在抖,你怎么定一个既不漏报也不天天误报的门禁阈值?Evaluation scores fluctuate on every run. How do you set a gate threshold that neither misses regressions nor fires false alarms every day?
    国内高频海外高频深入#ci#regression#thresholds

    分析过程 · 先想清楚再作答

    1. 这题考的是「知不知道阈值要量出来」。回答「下降超过 5 个百分点就拦」的,无论那个数字是多少都只能拿一半分——问题不在数值,在于它是拍脑袋来的。
    2. 第一步是**量噪声**:固定任务集、试次数、模型、提示词,只换随机种子,连跑二三十轮,记下通过率的最高值、最低值和均值。区间宽度就是「什么都不改、纯靠运气能掉多少」。
    3. 第二步才是定位置:警告线压在区间宽度附近,拦截线明显更高(比如区间宽度的一点五到两倍)。关键是要说出判据——阈值要在噪声之外,否则拦的是运气不是退化。
    4. 第三步要点出「阈值是局部属性」:它属于你这套任务集和这个试次数,换任务集、换 k、换模型都要重新量。**抄别人的阈值等于没定阈值。** 顺带能说出「试次数直接决定噪声宽度,任务少的冒烟档噪声更大、阈值必须更宽」,就是加分项。
    5. 第四步给出两态之外的做法:落在灰区的那一次不强行下结论,而是加大样本复跑一次再判,三态(通过/警告/拦截)比两态更贴合噪声的真实形态。
    6. 最后要说清楚误报的代价,这是这题真正的区分点:**太紧比太松更危险**。天天误报会把团队训练成无视门禁的人,最终结果和没有门禁一样,但过程中所有人都以为自己有防护。

    How to reason about it · think before answering

    1. This tests whether you know thresholds must be measured. Answering 'block if it drops more than five points' earns half credit regardless of the number, because the number was guessed.
    2. Step one is to measure the noise: hold the task set, trial count, model and prompt fixed, vary only the random seed, run twenty or thirty rounds, and record the maximum, minimum and mean pass rate. The width of that band is how much you can lose to luck alone.
    3. Step two places the thresholds: a warning line near the band width and a blocking line clearly above it, say one and a half to two times the width. The point is the justification - the threshold must sit outside the noise, or you are blocking bad luck rather than regressions.
    4. Step three notes that a threshold is a local property of this task set and this trial count. Change the tasks, the k, or the model and you must measure again. Copying somebody else's threshold is the same as having none. A bonus point: trial count drives noise width directly, so a small smoke tier is noisier and needs a wider threshold.
    5. Step four adds the third state: rather than forcing a verdict in the gray band, rerun that one run with more trials and decide afterwards. Pass, warn and block fit the shape of noisy data better than a binary gate.
    6. Close on the cost of false alarms, which is the real discriminator: too tight is more dangerous than too loose. Daily false alarms train the team to ignore the gate, and the end state is identical to having no gate while everyone believes they are protected.

    答题要点

    • 先量噪声:固定一切只换种子连跑二三十轮,得到通过率的波动区间。
    • 阈值放到区间之外:警告线贴近区间宽度,拦截线明显更高。
    • 阈值是这套任务集与这个试次数的属性,换任何一项都要重新量,不能照抄。
    • 做三态而不是两态,灰区靠加大样本复跑来判,而不是强行下结论。
    • 误报率过高会让团队学会跳过门禁,太紧比太松更危险。

    Key points

    • Measure noise first: vary only the seed across twenty or thirty runs to get the pass-rate band.
    • Put thresholds outside the band: warning near the band width, block clearly above it.
    • A threshold belongs to this task set and this trial count; re-measure after any change, never copy.
    • Use three states, resolving the gray band by rerunning with more trials instead of forcing a verdict.
    • A high false-alarm rate teaches the team to bypass the gate; too tight is worse than too loose.
  • 基线快照里除了通过率,你还会记录什么?少记了会出什么问题?Beyond the pass rate, what else goes into a baseline snapshot? What breaks if you leave something out?
    国内高频海外高频进阶#ci#baseline#reproducibility

    分析过程 · 先想清楚再作答

    1. 这题的答案不是罗列字段,而是「每一项各自排除了哪一种解释」。只背字段名的回答,在追问「那少记提示词版本会怎样」的时候会卡住。
    2. 先说骨架:快照分两半。一半是**环境指纹**,包括模型、提示词版本、任务集版本、评估框架版本、试次数与档位;另一半才是分数(总体通过率加逐任务通过率)。指纹决定两次结果**能不能比**,分数决定**比出来是多少**。
    3. 少记的代价可以逐项说:少了模型,换了模型的分数会被当成退化;少了提示词版本,改一句话导致的下降查不到任何对应的提交;少了任务集版本,加了两条难题会被当成 Agent 变差;少了试次数,k 从 5 改成 3 会让分数的噪声结构完全不同却看不出来。**四个问题的共同点是:分数变了,但没人能说出是谁动的。**
    4. 提示词版本要单独强调:它是一个字符串,改它没有代码评审的仪式感,很多团队还放在配置后台里随时可改。给它编个版本号成本几乎为零,收益是把一整类查不出原因的波动变成可查的。
    5. 随机种子是个有意思的例外:**要记,但不参与可比性判断**。它的用途是复现——门禁红了要能一字不差重跑那一次;但换种子跑出来的分数恰恰是估计噪声的样本,把它塞进指纹会让每次换种子都被判成不可比,等于把非确定性从评估里删掉了。
    6. 最后说指纹不一致时的正确行为:**拒绝比较,判拦截**,而不是「算个差值再标注一下模型变了」。后者会产出一个毫无意义却看起来很正常的结论,要求人明确表态重新记录基线,才是安全的。

    How to reason about it · think before answering

    1. The answer is not a list of fields but what each field rules out. Reciting names collapses under the follow-up 'what happens if the prompt version is missing'.
    2. Start with the shape: a snapshot has two halves. The environment fingerprint - model, prompt version, task-set version, eval framework version, trial count, tier - and the scores, overall plus per task. The fingerprint decides whether two results are comparable at all; the scores decide by how much they differ.
    3. Go through the costs one by one. No model field and a model swap reads as a regression. No prompt version and a one-sentence edit produces a drop with no commit to blame. No task-set version and two newly added hard tasks look like a worse agent. No trial count and changing k from five to three silently changes the noise structure. Every case has the same shape: the number moved and nobody can say who moved it.
    4. Call out the prompt version specifically. It is a string, editing it carries none of the ceremony of code review, and many teams keep it in a config console. Versioning it costs almost nothing and converts a whole class of unexplainable drift into something traceable.
    5. The random seed is the interesting exception: record it, but keep it out of the comparability check. Its purpose is reproduction - when the gate fires you must be able to replay that exact run - while scores from different seeds are precisely the samples you use to estimate noise. Putting the seed in the fingerprint makes every seed change incomparable, which deletes non-determinism from the evaluation.
    6. Finish with the correct behavior on a fingerprint mismatch: refuse to compare and block, rather than computing a delta and annotating that the model changed. The latter produces a meaningless result that looks entirely normal. Requiring a human to re-record the baseline is the safe path.

    答题要点

    • 快照分两半:环境指纹(模型、提示词版本、任务集版本、框架版本、试次数、档位)与分数。
    • 每一项各排除一种解释,少记任何一项都会变成「分数变了但说不清是谁动的」。
    • 提示词版本最容易漏也最容易出事,因为改它不需要代码评审。
    • 随机种子要记但不进指纹:它用于复现,进指纹会让换种子被判成不可比。
    • 指纹不一致时正确行为是拒绝比较并拦截,不是硬算一个差值。

    Key points

    • Two halves: environment fingerprint (model, prompt version, task-set version, framework version, trial count, tier) plus scores.
    • Each field rules out one explanation; omitting any leaves you with a moved number and no suspect.
    • Prompt version is the most commonly missed and the most damaging, because editing it bypasses code review.
    • Record the random seed but keep it out of the fingerprint: it exists for reproduction, and including it makes seed changes incomparable.
    • On a fingerprint mismatch, refuse to compare and block rather than computing a meaningless delta.
  • 每次提交都跑全量评估太贵,你会怎么分层?依据是什么?Running the full evaluation on every commit is too expensive. How would you tier it, and on what basis?
    国内高频海外高频进阶#ci#cost#strategy

    分析过程 · 先想清楚再作答

    1. 这题考成本意识与取舍表达。回答「随机抽一部分任务跑」的方向就偏了——随机抽样每次覆盖的故障类别都不一样,门禁会变得时灵时不灵。
    2. 分层的依据不是「任务重不重要」,而是**覆盖面除以成本**:冒烟档要用最少的任务盖住最多的故障类别,全量档负责长尾。所以冒烟档是挑出来的,不是抽出来的。
    3. 一条可用的分层是三档:冒烟档每次提交跑,几分钟内拦住大事故;全量档在打 tag、发版前或每晚定时跑,抓小幅退化与长尾;昂贵的人工评审按季度抽样跑,用来校准自动评分器本身。
    4. 必须主动说出分层的**代价**,这是这题的区分点:任务变少意味着试次变少,试次变少意味着噪声变宽。同一个 Agent,任务从八条减到三条,噪声区间可能从十二个百分点涨到二十个。**所以冒烟档只能抓大事故,小幅退化它根本看不见**,冒烟档绿了不等于没有退化。
    5. 由此还能推出一条实践:两档的阈值要分别测,不能共用一套。共用一套的后果要么是冒烟档天天误报,要么是全量档过于迟钝。
    6. 可预期的追问是「怎么决定哪些任务进冒烟档」。答案是按故障类别覆盖去选:每一类已知的故障至少留一条代表,加上最近一次线上事故对应的那条;并且这份名单要定期复核,因为故障类别会随产品变化。

    How to reason about it · think before answering

    1. This probes cost awareness and how you express trade-offs. Answering 'sample a random subset each time' misses: random subsets cover different failure classes each run, so the gate becomes intermittently blind.
    2. The basis for tiering is coverage divided by cost, not importance. The smoke tier should cover the most failure classes with the fewest tasks, and the full tier carries the long tail. So the smoke tier is curated, not sampled.
    3. A workable three-tier split: smoke on every commit to catch major breakage in minutes; full on tags, pre-release, or a nightly schedule to catch small regressions and the long tail; expensive human review quarterly on a sample, to calibrate the automated graders themselves.
    4. State the cost of tiering, which is the discriminator here: fewer tasks means fewer trials, and fewer trials means wider noise. For the same agent, going from eight tasks to three can widen the noise band from twelve points to twenty. The smoke tier can therefore only catch large failures; small regressions are invisible to it, and a green smoke run does not mean no regression.
    5. That implies a practice: measure thresholds separately per tier. Sharing one threshold either makes the smoke tier alarm constantly or makes the full tier far too insensitive.
    6. Expected follow-up: how do you pick the smoke tasks? By failure-class coverage - one representative per known class, plus the task corresponding to the most recent production incident - and revisit the list periodically, because failure classes shift as the product changes.

    答题要点

    • 分层依据是覆盖面除以成本,冒烟档要挑选而不是随机抽样。
    • 三档:冒烟档每次提交、全量档发版或每晚、人工评审按季度抽样。
    • 任务少则试次少、噪声更宽,冒烟档只能抓大事故,绿了不等于没退化。
    • 两档的阈值必须分别测量,共用一套要么天天误报要么过于迟钝。
    • 冒烟档按故障类别覆盖来选,并把最近一次线上事故对应的任务放进去。

    Key points

    • Tier by coverage divided by cost; the smoke tier is curated, never randomly sampled.
    • Three tiers: smoke per commit, full on release or nightly, human review quarterly on a sample.
    • Fewer tasks means fewer trials and wider noise, so a green smoke run does not prove there is no regression.
    • Measure thresholds per tier; a shared threshold either alarms constantly or goes blind.
    • Select smoke tasks for failure-class coverage and include the task from the latest production incident.
  • 评估门禁红了,团队却越来越习惯直接跳过。你会怎么改这条流程?Your evaluation gate keeps firing, and the team has learned to skip it. How would you change the process?
    国内高频海外高频深入#ci#process#culture

    分析过程 · 先想清楚再作答

    1. 这题几乎不考技术,考的是有没有真的维护过这类闸门。答「加强宣导」「规定必须查清楚才能合并」的拿不到分——那是在要求人对抗激励,而不是改激励。
    2. 第一步要承认一件事:**跳过是理性行为**。如果门禁十次红里有八次重跑就绿,那么跳过在概率上是对的。所以要先量一下误报率——它多半就是根因,而且它是一个技术问题,不是态度问题。
    3. 第二步是**降低修的成本**。门禁红的时候不能只丢一个数字,要直接给出:用哪个种子能复现、哪几条任务掉了、先读哪几条失败试次的轨迹。排查成本从半小时降到五分钟,跳过的诱因就少了一大半。
    4. 第三步是**提高跳过的成本,但保留合法出口**。有意的行为变更确实需要放宽标准,它的正确表达是「更新基线并说明原因」,而不是「本次忽略」。把基线文件放进版本库,放宽就会出现在 diff 里、需要有人评审。**绝不提供一键跳过按钮**——存在的跳过按钮最终一定会变成默认路径。
    5. 第四步是把判定本身修对:引入三态与灰区复跑,让真正模棱两可的那一次不再强行拦截。这直接砍掉大部分误报,而且不牺牲对真实退化的灵敏度。
    6. 最后要说一条反直觉的:如果误报确实压不下来,**宁可先把拦截线放宽成警告**,也不要留着一条大家都在绕过的拦截。一条被绕过的门禁提供的是虚假的安全感,比一条明确说「我只警告」的门禁更危险。

    How to reason about it · think before answering

    1. This is barely a technical question; it asks whether you have actually maintained such a gate. Answering 'communicate more' or 'mandate investigation before merge' earns nothing - that asks people to fight the incentives instead of changing them.
    2. First, admit that skipping is rational. If eight of ten red runs go green on a rerun, skipping is the probabilistically correct move. So measure the false-alarm rate; it is usually the root cause, and it is a technical problem, not an attitude problem.
    3. Second, lower the cost of fixing. A red gate must not emit only a number: give the seed that reproduces the run, which tasks dropped, and which failing transcripts to read first. Cutting investigation from thirty minutes to five removes most of the incentive to skip.
    4. Third, raise the cost of skipping while keeping a legitimate exit. Intentional behavior changes really do need the bar moved, and the correct expression is 'update the baseline and say why', not 'ignore this run'. Keep the baseline file in version control so every loosening shows up in a diff and gets reviewed. Never ship a one-click skip button - one that exists eventually becomes the default path.
    5. Fourth, fix the verdict logic itself: three states with a gray-band rerun stop the genuinely ambiguous run from being blocked outright. That removes most false alarms without giving up sensitivity to real regressions.
    6. Close with the counterintuitive one: if false alarms truly cannot be brought down, demote the block to a warning rather than keep a blocking gate everyone routes around. A bypassed gate supplies false confidence, which is worse than a gate that honestly says it only warns.

    答题要点

    • 先承认跳过是理性行为,去量误报率——它通常就是根因,且是技术问题。
    • 降低修的成本:红的时候给出复现种子、掉分的任务、该读哪几条失败轨迹。
    • 提高跳过的成本但保留合法出口:更新基线要进版本库、要被评审,绝不做一键跳过。
    • 引入三态与灰区复跑,砍掉模棱两可那部分误报而不牺牲灵敏度。
    • 压不下误报时宁可把拦截降级成警告,也不要留一条大家都在绕过的门禁。

    Key points

    • Accept that skipping is rational and measure the false-alarm rate; it is usually the root cause and it is technical.
    • Lower the cost of fixing: emit the reproducing seed, the tasks that dropped, and which failing transcripts to read.
    • Raise the cost of skipping but keep a legitimate exit: baseline updates go through version control and review, never a one-click skip.
    • Adopt three states with a gray-band rerun to remove ambiguous false alarms without losing sensitivity.
    • If false alarms persist, demote blocking to warning rather than keep a gate everyone bypasses.

评论