同一个错犯了五次:阈值与判据的量纲必须匹配
这个错我在同一个项目里犯了五次。每一次的形式都不一样,但根因是同一个:一个阈值或判据,本来是为「某个场景」推出来的,却被拿去判断了「另一个场景」。后果只有两种——该触发的永远不触发,或者不该触发的每次都触发。两种都不报错。
一、五次事故
| # | 错配 | 后果 |
|---|---|---|
| 1 | CONF_MIN_TOP_GAP 设为 0.15,而 RRF 分差上限只有 0.0161 |
条件恒不触发——阈值比这个量纲的最大可能值还大一个数量级 |
| 2 | BASE_TOKEN_THRESHOLD = 2000 是为「多轮累积」设计的,却用来判「单轮检索」 |
每轮都触发压缩,每轮白花 10.9 秒 |
| 3 | SQL join 时漏了 batch 字段 |
算出「73% 数据不自洽」的假结论 |
| 4 | 假设「最低分与最低位次必须同源」 | 差点把顶尖院校的百名位次误判成数据错误 |
| 5 | 把 admission_query(「话题属于录取」)当成「需要考生个人条件」 |
新用户第一句咨询就被要求先报分数 |
第 3 和第 4 条是同一天、在同一个 SQL 查询上连犯两次,而且两次都是「对照组推翻了结论」才发现的。
二、第 1 条:阈值比量纲上限还大
这是最典型的一种。我设了一个「置信度不足要触发澄清」的条件,要求两路检索的分数差小于 0.15。单测里手搓的 state 给了 0.01 的差值,所以测试通过。
但真实链路用的是 RRF 融合后的分数,而 RRF 分数的天然上限极小——1/(60+1) + 1/(60+1) ≈ 0.0328,实际分差上限只有 0.0161。拿 0.15 去比,等于「要求一个人的身高小于 0.15 米才排队」,永远不成立。
# RRF 分数天然量级
score = 1/(60 + rank_dense) + 1/(60 + rank_sparse)
# 最优情况(两路都排第 1)≈ 0.0328
# 实际分差上限 ≈ 0.0161
if top_gap < 0.15: # ← 永远为 False
request_clarification()
修复方式不是「把 0.15 改小」,而是先看这个分数实际分布在哪儿——我做了阈值网格校准,把不同阈值下的触发率和效果列成表,再选一个真正落在分布里的值。网格校准还顺带暴露了另外两个此前未被发现的静默失效。
三、第 2 条:用「多轮」的标准判「单轮」
更值得说的是这条,因为它的代价可以直接量化。
BASE_TOKEN_THRESHOLD = 2000 这个阈值的语义是「当累积上下文超过 2000 token 时,压缩历史」。它本来是为多轮对话累积设计的——聊了十几轮,上下文变长,该压缩了。
但在实际链路里,它被用来判断单轮检索的上下文。而一次检索召回 3~5 个父块,必然超过 2000 token。
结果:每一轮都触发压缩,压的还是这一轮正要用的材料。节点级剖析把这件事拍得清清楚楚:
orchestrator 0.74s ← 第 1 轮 LLM
tools 0.07s ← 只调了 1 次工具
compress_context 10.86s ← ★ 白花
orchestrator 6.41s ← 第 2 轮 LLM
一次 10.86 秒的压缩,占了大头,而且压的是马上要用的内容——纯粹的自伤。
把阈值改成 12000 后:首字延迟 22.3s → 7.0s(-68%),三题复测总时长 27.2s → 9.7s(-64%)。
注意这里的修复思路:不是「压缩太慢,优化压缩」,而是「这个阈值本来就不该在这条路径上触发」。搞清楚「这个参数是为谁设计的」,比优化它的执行效率重要得多。
四、第 5 条:判据改在代码里,不靠提示词
这条比较特别,值得单独说。
现象是:用户问「什么是平行志愿」这类方法论问题(问概念,不需要个人条件),系统却要求「请先告诉我你的分数」。这很冒犯——新用户的第一句话就被索要隐私信息。
第一轮修复加了一个 is_methodology_question() 判断,挡掉了 4 题。但还剩 3 题被拦住,而且三题措辞各不相同,搜遍代码也找不到对应模板——说明是模型各自现场生成的追问。
于是直接在 10-04 改了提示词,明确写「方法论问题不要索要分数」。改完再跑——这 3 题依然被索要。
结论很清楚:提示词是概率约束,不是规则保证。最终把判据挪到LLM 调用之前的代码里(is_closed_judgement_question),用确定性逻辑拦下。
修复后的实测:h029 12.4s / 422 字、h011 11.1s / 540 字、h007 12.5s / 462 字,均正常作答。同时「我要不要报兰大」仍然追问(4.3s)——个人决策必须要有个人条件,这道门没有因此放得过宽。
五、动手前问自己一句话
这个阈值 / 判据,是为「什么场景」推出来的?现在我用它判的是哪个场景?
这句话能挡掉上面全部五条中的四条。配套还有两条:
- 看到数字先看分布。阈值、分差、token 数——先知道它实际落在什么范围,再谈设多少。
- 「说了但没做」比报错更危险。条件恒不触发、分支永远不走、日志打了但逻辑没改,这些都不报错。