同一件事——让模型在故障诊断里给出一个能用的结论——一位算法研究者和我团队写出了两份气质完全不同的 Skill。把它们的差异摊开看,会发现分歧不在严谨程度,而在「验证」这个词各自指什么。
本文基于两份诊断 Skill 的规格文档、规则集、门禁脚本与一次沙盒执行记录做对比。已隐去参与者、项目名与业务标识,保留可复核的方法与数字;涉及准确率的数字均为原实验口径,未做二次加工。
两种验证,两条不同的路
一份 483 行、两万六千字,把「什么结论可以从证据中合法推出」写成了一套类型系统;另一份 130 行,解决的是「什么东西可以交付、并且能持续运行」。这两份东西放在一起看,第一反应往往是「严谨程度差太多」,全篇九十几条禁令对二十几条,几乎是两个物种。
但继续往下读,会发现问题不是严谨不严谨。两边都把验证当成瓶颈,只是验证的对象不同:一边验证的是推理的合法性,一边验证的是系统的可用性。前者关心「这个结论有没有资格被说出来」,后者关心「这条链路明天早上还能不能跑起来」。
这个分野一旦确立,后面所有具体选择都变得可以预测。删功能用什么标准、要不要给「不知道」开一个正式出口、预算拿来干什么、降级算不算作弊——两个阵营给出的答案截然不同,而且各自在自己的目标函数下都是对的。
要点:差的不是严谨程度,是那套推理本身的类型系统。补严谨可以靠加规则,补类型系统只能靠换视角。
用可证伪性删功能,而不是用「没人用」删功能
工程里删代码的依据通常是「没有调用方」,或者「线上没人报错」。算法里删规则的依据是「它在拟合噪声」。这两句话听起来相似,实际指向完全不同的动作:前者只需要一次调用关系扫描,后者需要先设计一个能让规则自己认输的实验。
那次实验的规模小到不像实验:30 个人工金标样本,两类容易混淆的故障(打滑与过流),要求判断每一条属于哪一类。三种做法各跑一遍。
同一批 30 条样本上的三种做法

三条柱子并不在同一场比赛里:交叉验证的那条低于基线,说明单特征在拟合噪声;训练集内的 80% 没有留出样本,不能与另外两条并列比较。
关键的一步在后面:最优单特征的留一交叉验证只有 43%,低于「全判同一类」的 60% 基线。于是整条规则被删掉,脚本改成「只呈现测量,不下机理标签」。
工程师的本能是「再加个特征试试」。43% 不理想,那就找第二个特征,80% 看着不错。但 80% 是训练集内的数字,样本 30 条、特征 2 个,这个数的方差比它本身还大;把它当成绩写进规格,等于给自己埋了一个永远不会被触发的失败。
更值得学的是他往下追问的那一层:标注里带着日志之外的现场信息——人当时在现场看见了什么。这意味着任何只看日志的模型都有一个天花板,而且这个天花板不是模型能力决定的,是数据里根本没有那个信息。想清楚这点之后,问题被改派给采集侧:先让日志能采到区分两类故障所需的量,再谈模型。
先算天花板,再决定要不要做。这一条在工程文档里几乎不会出现,因为工程文档默认「做不出来是排期问题」。但天花板一旦算出来,最省力的方案其实是承认它,并且把缺口派给唯一能补的地方。
要点:一个规则的删除依据,如果是「交叉验证不如多数类基线」,那么这条规则从一开始就不该被写进生产路径。它带来的不是准确率,是准确率的幻觉。
三种边、三个「不知道」、两轮盲判
因果、时序、响应是三种边,不能混成一个箭头
在一张因果图里,causes 和 enables 都要求源证据与目标落在同一个 attempt 内,并且源证据在时间上严格早于目标;protective_response 是被触发的响应,不能当作 trigger 使用;reports 这条边是反向存储的——终局结果指向被它报告的那个响应。
工程日志只记录「A 之后 B」。这套类型系统问的是另一个问题:「B 有没有可能只是 A 的报告动作,而不是 A 的后果?」这个问题一旦问出来,很多看起来确定的因果链会当场松动。
同一条日志,两种读法
| 边的类型 | 语义要求 | 日志不做区分时会被读成 |
|---|---|---|
| causes | 同一 attempt 内,源证据严格早于目标 | A 之后 B,所以 A 导致 B |
| enables | 同一 attempt 内,提供的是条件而非原因 | 直接并进上一条因果 |
| protective_response | 被触发的响应,不能当 trigger | 被当成故障原因 |
| reports | 反向存储:终局结果指向被它报告的响应 | 被当成事件发生的顺序 |
左边是类型系统要求的语义,右边是工程日志不做区分时的默认读法。差别不在数据,在是否允许自己把顺序当因果。
把「不知道」做成一等产品状态
责任强度只有三档:single、one_of、unknown。机理强度也只有三档:confirmed、partial、unknown。置信度拆成五个维度分别打分,并且有一条硬约束——校准没开就禁止写成百分比,只能写「未校准因果支持分 N/100」。
配置文件里老老实实写着 n=1、enabled=false。评测说明里写的是:这套东西只能称「内部合同回放」,不能称盲测准确率。也就是说,作者宁可对外承认自己没测过,也不肯把一个漂亮的数字放到会被人引用的地方。
工程交付很少这么做。遇到说不清的样本,通常的处置是兜底:用最可能的那一类填上,让下游拿到一个确定值。填完之后流程通了,错误也被抹平了——没有人知道这一单其实没有结论。
针对裁判的认知偏差设计协议
裁决分两轮。第一轮要求 Agent 盲写假设,规则候选封存在压缩包里禁止读取;第二轮才披露,而且披露时明确写着「规则候选只表示必须比较,不拥有选因优先级」。
这是在防锚定效应。工程把 LLM 当一个有接口的组件,给输入、取输出;他把 LLM 当一个会被诱导的陪审员,所以连「什么时候给它看什么」都要设计。门禁脚本里还有一条扣分项叫「疑似照抄规则原文」——这条规则不是技术约束,是人性洞察。
预算是停机条件,不只是成本控制
两条规则:连续两次无信息增益就熔断;同一错误指纹累计不超过 6 次。
工程里的超时和重试次数只看资源——花了多少时间、烧了多少 token、够不够本。这两条规则看的是另一件事:这一轮还值不值得继续。把检索当成采样过程看,信息增益为零就意味着后面每一轮都在重复同一个分布,继续跑只是把钱换成更长的日志。
这个视角切换很便宜,但很少有人做。原因是它需要一个前提:你得知道「这一轮有没有带来新信息」,而这件事工程上几乎没有度量。多数重试循环连「上一次和这一次的输入有没有差别」都不记录。
注意:重试次数是成本参数,熔断条件是信息参数。两者混用的时候,系统会在最没有价值的地方花最多的钱。
算法思维的四个盲区,正好是工程的强项
把 prompt 当合同写,忘了读者注意力有限
原版规格两万六千字,近百条禁令。每一条禁令背后大概都是一次被模型坑过的经历,这和我文档里的「铁律」本质相同,都是疤。区别在于承载方式:我把约束放进 hook 和验证脚本,文档里只留一句带根因的提醒;他在门禁代码里已经强制了同样的东西,却又在文档里重复了一遍。
同一套约束,两种写法
| 情态动词 | 原版(483 行) | 瘦身后(130 行) |
|---|---|---|
| 禁止 | 18 | 5 |
| 不得 | 16 | 5 |
| 必须 | 42 | 7 |
| 不能 | 21 | 4 |
行数从 483 压到 130,情态动词的密度下降得更快。约束并没有变少,只是从文档搬进了脚本。
重复的代价是可测的:沙盒实测里,一个小尺寸模型连续吃了 16 次 invalid_final_diagnosis,把轮次直接耗尽。规范写得越全,单次调用的有效注意力越少,模型越容易在格式上摔跤——而格式恰恰是最该由代码保证、最不该由文档保证的部分。
为理想环境设计,没有降级路径
有个输入补充环节硬性要求下载工具可用,而执行环境永远不出网。两个都正确的前提凑在一起,结果是整单死锁——不是报错,是卡住,而且看不出卡在哪。
工程侧的修法是加一层输入降级。但平台侧后来追加的要求才是真正的补刀:降级可以,但必须计数——跳过了哪些数据包、兜底了多少行,都要上报。因为静默降级会改变错误分布,让下游的指标看起来一切正常,而实际上样本已经悄悄换了一批。
这条追加要求暴露了一个更普遍的问题:把人工兜底伪装成系统能力。兜底跑通了,成功率好看,但那份成功率里混着手工填的值。不计数,就永远分不清哪些是系统算出来的,哪些是人补上去的。
正确性复杂度失控
每遇到一个线上卡点就加一条规则,这就是「堆条件」的完整生命周期。单个条件的理由都很充分,几百个条件叠在一起,就没人能说清系统整体在保证什么了。
一个 soundness 很高、但没有跑完过的系统
| 指标 | 数值 | 说明 |
|---|---|---|
| 代码规模 | 6.7 万行 | 单文件最大 2,596 行,次大 2,197 行 |
| JSON schema | 28 个 | 两代规则引擎同时存活,靠 shim 兼容 |
| 专家评测集 | 21 条 | 全部指向包外路径,不在交付物里 |
| 校准样本 | n=1 | confidence_calibration 里 enabled=false |
最后一行是最诚实的:验证闭环没有跟着交付物一起发货。输出目录里唯一的执行历史是两次诊断、两次全 error。这套系统的内部一致性极高,但它在真实环境里一次都没有走完。
注意:通过率如果不是门禁设计出来的,就会变成门禁要防的东西。一个 8/8 的全绿结果,在一套没有校准、没有留出样本的门禁里,说明的是门禁太松,不是系统太好。
两种人格,以及各自该拿走什么
一边是完美主义的独立研究者:单人署名,宁可不交付也不交付可疑结论。失败模式是「在脑子里能证明正确的事」压过了「周五之前要有一条可以看的结论」。
一边是交付导向的编排者:先跑通再收紧,每条铁律后面跟着一次事故。失败模式是指标被当成目标本身——通过率、成功率、覆盖率,这些数字一旦变成考核项,就会以最快速度失去信息量。
这两种人格谁也说服不了谁,因为它们优化的不是同一个东西。但可以互相拿走一部分,而且不需要改任何代码。
四个不需要改代码的习惯
一、每个兜底配一个计数器和一个基线问题。这个 fallback 吃了多少行?不加它的多数类基线是多少?两个数放在一起,兜底的价值立刻从「流程通了」变成「比瞎猜好多少」。
二、新规则先分类再落地。它是推理不变量(时序先于因果),还是事故记忆(某个排序在某个版本上害过一次)?前者进代码的类型系统,后者进一行带根因的铁律。分类错了,两种都会腐化:不变量写成铁律会被绕过,事故记忆写进类型系统会把架构焊死在一个补丁上。
三、让 unknown 成为合法交付。现有的 source: heuristic 已经是这个方向,再往前一步是把「证据不足」从「降一点 confidence」升级成独立的 verdict。降 confidence 的下游会照常消费,独立 verdict 的下游必须显式处理——前者是委婉,后者才是约束。
四、验证全绿之后多问一句:它测不到什么?确定性断言测的是契约,不是泛化。全绿说明交付物没有撒谎,不说明它有用。
反过来的账也一样清楚。算法那边能从工程这边拿走的,是给有限注意力的读者写接口、给真实环境写降级、给文件设长度上限、先发货再校准。这四件事没有一件需要牺牲严谨性,它们只影响严谨性被不被看见。
两个方向最后会撞在同一句话上:验证是瓶颈。一个在证明结论配不配被说出来,一个在证明系统能不能活到明天。前者决定天花板,后者决定地板。缺了任何一边,另一边都会显得很有道理但没人用。
参考与延伸
下面几份材料支撑了文中的几个外围判断:校准为什么必须显式声明、指标为什么会自我腐化、锚定效应为什么需要在协议层防、以及信息增益为什么可以当停机条件。
引用与来源
- On Calibration of Modern Neural Networks — 置信度与准确率脱钩的经典证据,核对于 2026-11
- Goodhart’s law — 指标成为目标即失效,核对于 2026-11
- Judgment under Uncertainty: Heuristics and Biases — 锚定效应的原始论文,核对于 2026-11
- Value of information — 把「继续采样值不值」形式化的基础概念,核对于 2026-11







