让模型拦截 Git 推送,误判如何评估
Sphinx 在 Git 推送前读取代码差异,通过提问检验开发者是否理解 AI 生成的改动,并提供记录跳过的通道。文章将重点放在其评测目录与尚未解决的漏判问题,审视模型评分作为强制门禁的可靠性。
AI 深度解读
AI 写完代码,推送前先回答一道理解题,答对才放行。Sphinx 把这个想法做成了 Git hook,但原文最值得读的部分,是它公开的评测记录:560 项测试全部通过,语义评测却仍有未解决的误判。
这篇解读关注的不是程序员该不该被考,而是一个更实际的问题:当模型有权阻断你的工作,怎么证明它判得合理?Sphinx 留下的错误样本、复跑约束和失败分类,给采用 AI 审查的团队提供了一份检查依据。
推送前考一道题,真正被考的还有评分模型
Sphinx 的使用路径很直接:执行 npm install -g sphinx-understand,然后正常 git push。在受保护的分支上,Git 会暂停。一个没看过聊天会话的模型读取 diff,针对改动实际产生的行为提问。
回答错误,系统先解释,再围绕同一概念换一个场景追问;回答正确,推送继续。用户也可以执行 sphinx yolo 放行,工具会记录这次跳过。
原文记录时,这个 MIT 许可的项目有 117 stars,Show HN 讨论获得 242 points、85 条评论。有人质疑适用对象,也有人做过类似工具,讨论中还有一个在 PR 层面实现同类检查的项目。这些数字只能说明关注度,不能证明评分可靠。
理解检查一旦接上推送流程,就多了一层风险:模型不仅要读懂代码,还要判断人用自由文本写出的解释是否充分。这是典型的 LLM-as-judge,也就是让大模型充当裁判。
管道能跑通,不等于裁判判得对。
Sphinx 的 eval/RESULTS.md 明确记录:即使 560 项带防护的单元与集成测试全部通过,实时语义评测也没有全绿。前一类测试验证程序流程,后一类才涉及模型是否会接受合理答案。把两者合并成一个「测试通过率」,会掩盖最影响使用体验的问题。
两道答对却没过的题,暴露了什么问题
项目在 eval/fixtures/known-false-negatives.json 中保存了两个已知假阴性:答案已经足够正确,评分器却没有给过。文件明确限定,它们是合成题目和人工编写的答案,是未解决的回归问题,不是通过的模型断言。
第一道是 CSS。改动在基础 .button 规则之上,新增 .card .button,只设置 padding-block。问题要求分别预测卡片按钮和工具栏按钮在各轴上的变化,并解释原因。
保存的答案说明,工具栏按钮不受影响,因为新选择器要求存在 card 祖先;水平方向的 padding 仍由基础规则提供,因此不变。评分器却给出 ambiguous,理由是答案没有明确说出竖直方向发生变化。
按原文对该题的审计,答案已经交代卡片内的新 padding、未变化的水平 padding,以及工具栏不受影响的边界,评分条件却收缩成了对 vertical 这个词的检查。
后续用更强的模型复跑同一题、同一答案,1 次请求、4.6 秒就复现了误判。不过,比较按停止条件在这里结束,不能据此断言更强模型在整个测试矩阵上表现如何。
第二道是授权检查。补丁把文档不存在和跨租户访问合并到一个条件中,统一返回 { status: 404 }。人工答案说明,不同租户得到没有响应体的 404,与不存在的 id 一样。评分器仍要求明确的否定回答,以及「单一合并条件」这个实现机制。
更关键的是,经过源码审计的验收标准本身就写进了这个要求。源码审计保证了答案依据正确,却没有保证评分尺度合理。
答案与源码一致,不代表评分标准就公平。
这里检查的是外部行为是否泄露文档存在性,还是答题者是否复述了条件表达式?两者应先分清。否则,理解检查很容易变成对标准答案措辞的匹配。
评测要能复跑,还要分清究竟是谁错了
原文最推崇的是 Sphinx 的复跑协议。它要求从 CASES 选择对应的 source.caseId,覆盖保存的 source.definition,再调用 createFixture(fixture, config) 创建夹具。
随后通过 createReviewWorkspace(snapshot, config, { env }) 和 canonicalEvidence(snapshot, record.source.requiredPaths, workspace) 读取不可变证据,建立绑定新 snapshot.id 的审查,再调用 evaluator.gradeAnswer(...)。执行时沿用带计量的评测传输层,并明确设置实时调用预算。
这些步骤的目的,是固定输入,只改变待验证的评分环节。协议禁止复用旧临时快照 ID、重新审计成更容易的题目,也禁止替换已保存的人工答案。
如果改了评分器,却顺手改写答案直到通过,就无法证明原来的误判已经修复。
项目还让 npm test 和 npm run check 向 Node 及其子进程预加载网络防护,拒绝对外的 fetch、HTTP 和 socket 调用,同时允许 loopback。它防的是模型 mock 不完整,测试意外请求真实服务商,造成调用与费用失控。
另一个配置案例则提醒我们:红色结果也可能不属于评分器。题目写的是 BATCH_SIZE is set to 5,独立编写的探针适配器只识别 BATCH_SIZE=number,结果按不同数值回答了另一个场景。这个结果只能记为输入表述未覆盖,无法证明模型会怎样评价原题的充分答案。
同一轮运行还出现了响应格式错误:原始响应称预测为 ambiguous,承认没有实质误解,随后又输出 incorrect,并缺少要求的迁移验证内容。校验器拒绝了该响应。它属于输出结构问题,不能与语义误判混算。
此外,项目把评估策略升级到 contract version 3,避免旧契约下生成的问题和凭据被静默复用。规则变了,规则产生的授权凭据也必须跟着失效。
我的判断
我赞同原文把重点放在 eval/。对于准备接入阻断流程的 AI 工具,公开尚未解决的错误,通常比展示一串成功案例更有判断价值。它至少让使用者知道,系统在哪些地方仍不可靠。
但这些材料也有边界。两个假阴性来自合成题目和人工答案,不能据此估算真实团队的误拒率。更强模型的一次复现,也不足以完成模型选型。原文没有给出与普通代码审查、非阻断提示或人工抽查的对照数据,无法证明推送答题能提高理解程度,或降低缺陷数量。
有疑问时拒绝放行,只能证明系统保守,不能证明评分正确。
原文称之为「安全地未完成」:ambiguous 会让检查点继续等待,不授予批准。但合理答案如果经常过不了,团队承担的就是额外中断。阻止了多少次推送,不该单独成为成功指标;充分答案有多少被接受,才是评价评分质量的关键。
对一般团队,我更倾向于先用非阻断方式观察误判,再决定是否启用硬门禁。这是基于中断成本的取舍,不是原文已经验证的最佳方案。选 Kodus 或其他 AI reviewer 时,也可以追问同一个问题:有没有保存仍会判错的固定输入,修复后能不能原样复跑?
能拿来做什么
- 如果准备在 AI 辅助开发中加入推送答题,先保存一批能代表日常改动的题目和人工答案,特别覆盖 CSS 作用范围、授权边界、配置优先级这些容易遗漏条件的场景,再观察充分答案能否稳定通过。
- 如果正在维护 Node 评测脚本,让测试进程及其子进程默认禁止访问外部模型服务,把实时评测放到有明确预算的独立路径,减少 mock 遗漏造成的意外调用。
- 如果已有 AI 审查报表,分别统计答案误判、输入未覆盖和响应格式错误,并单独观察充分答案的接受情况。不要把所有红色结果都记到模型理解能力上。
- 如果会修改提示词或评估策略,给题目、评分标准和批准凭据一起记录版本,避免旧规则生成的凭据继续授权新规则下的推送。