评测结果怎么用:从失败样本到提示词规则
先给一个看起来完美的闭环。
8 条测试,v1 达标 6 条(0.75)。改了一句 Prompt,v2 达标 8 条(1.0)。
进步 +0.25,问题全修好。
但这份漂亮的结果有个前提:根因恰好落在 Prompt 上。 如果落在别处,那一句 Prompt 就是白改。
这篇讲的是中间那一步------怎么判断该改哪里。这一步在代码里只有一行 filter,却决定了后面所有工作的有效性。
一、先看两条失败的差异
v1 挂的两条都是价格:
text
[1] 玻尿酸保湿霜多少钱 → 报 328(库内 268)
[8] 积雪草精华多少钱 → 报 358(库内 398)
注意方向:一个高了 60,一个低了 40。
这个细节很重要。
如果两个都报高,可以解释成"模型倾向于加价"------那是系统性偏差,针对性写一句"不要加价"可能有效。
一个高一个低,说明它没有倾向。它不是估错,是在猜。
修法因此完全不同:面对随机猜测,"不要加价"这种约束毫无作用,它下次可能又报低。
正确做法是禁止这个动作本身:价格必须查库,不许凭记忆。
二、按 case 修会修错
看到这两条,最自然的反应是:
加两条规则:玻尿酸是 268,积雪草是 398。
测试能过。但下次问"美白精华多少钱",它照样猜。
按 case 修的本质,是给错题背答案。 背得再多,换一道题还是不会。
正确做法是从两个 case 往上抽一层:
| 层级 | 结论 | 改法 |
|---|---|---|
| case 层 | 玻尿酸报错、积雪草报错 | 写死两个价格 |
| 根因层 | 问价格时没查库 | 加"必须查库" |
根因层的改动覆盖整类问题,case 层只覆盖这两条。
回流的单位是根因,不是 case。
三、根因可能在三层
不只是 Prompt。根因可能在三个地方:
| 层 | 典型根因 | 改法 | 失败特征 |
|---|---|---|---|
| Prompt | 指令没写、写得含糊 | 加约束 | 同一类全错 |
| 词表 | 关键词漏了说法 | 补词 | 同类里错一部分 |
| 代码 | 分支逻辑有 bug | 改逻辑 | 特定条件触发 |
改错层的代价:问题不解决,还可能引入新问题。
一个真实的反例
用户说"我的东西到哪了",客服识别不出来,当闲聊答了。
按 case 修,你会在 Prompt 里加"用户问物流要查订单"。
但根因在词表------售后词表里只有"订单""物流",没有"东西到哪"这种口语。这句话压根没被路由到售后,改 Prompt 等于对空气喊话。
这类错误有个共同特征:它没进到你改的那一层。
四、判别方法:全错还是部分错
上面那张表的"失败特征",可以直接变成代码。
python
from collections import Counter
def diagnose(failures, total_by_cat):
"""根据失败分布判断根因层级。
failures: [(cat, dim), ...]
total_by_cat: {cat: 该类别总数}
"""
fail_by_cat = Counter(c for c, _ in failures)
verdict = {}
for cat, total in total_by_cat.items():
bad = fail_by_cat.get(cat, 0)
if bad == 0:
continue
verdict[cat] = "prompt_or_code" if bad == total else "lexicon"
return verdict
跑一下这次的场景:
python
>>> diagnose([("价格","faithfulness")] * 2,
... {"价格": 2, "推荐": 3, "物流": 3})
{'价格': 'prompt_or_code'}
问价格的 2 条全错 → 判为上层。一次改对。
如果 5 条问价格的里错 2 条:
python
>>> diagnose([("价格","faithfulness")] * 2, {"价格": 5})
{'价格': 'lexicon'}
部分错 → 判为下层。
为什么这么判
词表是白名单机制:覆盖到的通过,没覆盖的漏。所以部分错。
Prompt 是全局指令:缺了就是整类失效。所以全错。
代码 bug 是条件触发:只在特定输入组合下出问题,错得规律但不能用全错/部分错概括。
这个判别不是百分百准,但能挡住最常见的错判------把"整类缺指令"当成"个别说法没覆盖",然后往词表里加一堆词,问题还在。
五、"改对"和"改准"是两件事
这是我想强调的第二个判断。
改对 :测试通过,v2 是 8/8。 改准:同一类问题在真实场景里也被覆盖了。
这两件事经常不一致,因为测试集有边界。
这次 v2 全过,说明"改对"了。但"改准"了没有?
8 条样本里只有 2 条涉及价格。如果真实场景里有"用户问价格但其实在比较、应该先引导需求"的情况,加"必须查库"可能让它变得生硬------这种情况这次测不出来。
所以更准确的说法是:v2 在这 8 条上修好了,在真实分布上可能引入了新问题。
六、回归的另一半
回归的价值有两半:
- 前一半:确认修好了
- 后一半:确认没修坏别处
大部分人只做前一半。
要做后一半,测试集里必须有"容易被这次改动影响"的 case。
改价格逻辑,就该有:
- 用户问价格但还在犹豫(看会不会被强制查库打断)
- 用户问"贵不贵"(这是评价不是询价)
- 用户问两个产品的差价
这三条都不在原来的 8 条里。所以这次回归没有验证"没修坏"。
还有个统计问题
8 条里对 6 条,达标率 0.75,置信区间大概 ±30 个百分点。
真实达标率可能是 45%,也可能是 100%。
这个数字不足以支撑"v2 比 v1 好"的结论。
要验证 10 个点的改进,需要 50 条;5 个点,需要 200 条。
所以这次闭环的准确描述是:在 8 条上修好了,整体变好多少没有答案。
七、Prompt 会越改越长,然后互相打架
每次回流加一句,加十次:
text
价格必须查商品库,禁止凭记忆报价。
不要推荐竞品,也不要贬低竞品。
涉及过敏必须转人工,不要自行判断。
不承诺赔偿金额,不说"全额退款"。
回答控制在 200 字以内。
不要用"亲"开头,但可以用"您"。
这里已经有冲突了:
- "不要推荐竞品" vs "客户问竞品时要如实对比"
- "200 字以内" vs "说清退货流程"
规则一多,模型会优先遵守最后出现的 和语气最强的,前面的被稀释。
我的做法是分三组:
| 组 | 内容 | 冲突时 |
|---|---|---|
| 硬红线 | 不编造、不承诺、涉法转人工 | 最高 |
| 行为约束 | 查库报价、控制长度 | 让步于红线 |
| 风格偏好 | 称呼、语气 | 可放弃 |
每次加规则前问一句:它属于哪组?如果和红线冲突,这条不该加。
Prompt 是有技术债的。 加的时候不加利息,还的时候一起还。
八、测试集:分两层
回流的规定动作是"新 bad case 加进测试集"。后果是越攒越厚。
8 条 → 300 条,跑一次十几分钟。每次改 Prompt 都跑全量不现实。
| 层 | 规模 | 频率 | 用途 |
|---|---|---|---|
| 主集 | 50 条 | 每次改动 | 不能退步的底线 |
| 扩展集 | 全部历史 bad case | 每周/按模块 | 防复发 |
扩展集分数不进主分,只做参考。
这样既保住"防复发",又不拖死日常流程。
九、结论
五句话:
- 回流的单位是根因,不是 case。 单条 case 只说"这里错了",改代码要改"为什么错"。
- 看失败分布判断层级。 全错是上层缺指令,部分错是下层漏覆盖,条件触发是代码 bug。
- "改对"和"改准"不同。 测试通过只证明前者。
- 回归的后一半(没修坏)比前一半更有价值,但需要测试集里有相关 case 才能测。
- Prompt 有技术债。 一直加会互相打架,要分组管理。
最后一句:判断该改哪里,比改本身难得多。
改一句话是一分钟的事。定位它该写在哪一层、覆盖多大范围,可能要半小时。而定位错了,改得越快错得越远。
这五条里,只有第二条能写成代码。其余四条靠经验和踩坑。
下一篇讲部署,把这套东西从脚本变成服务。技术难度低于这一篇------跑起来是工程问题,判断改哪里是经验问题。