事情是这样的。
我在做 AI 观测台的时候想验证一个说法:"提示词里加一句明确约束,效果会好很多。"
这句话人人都说。但到底好多少?
我跑了个测试。两组提示词,同一道题,各跑两次:
组 A:
用一句话解释递归
组 B:
用一句话解释递归,必须包含「调用自身」这四个字
判分规则:输出里必须出现"调用自身"。
结果:
| 组 A | 组 B | |
|---|---|---|
| 成功率 | 0% | 100% |
| 平均耗时 | 857ms | 925.5ms |
| 一致性 | 0.174 | 0.453 |
组 A 两次输出是这样的:
递归就是:一个函数在定义中调用自身,就像两面镜子对照时产生的无限镜像…… 递归就是:为了理解递归,你必须先理解递归。
第一次对了(碰巧),第二次开始说俏皮话 —— 明明问的是同一件事。
组 B 两次输出几乎一样:
递归就是函数通过调用自身来把大问题拆成同样结构的小问题,直到遇到终止条件。 递归就是函数通过调用自身来把大问题拆成同类小问题、直到遇到终止条件再逐层返回的求解方法。
加一句话,成功率 0% → 100%;一致性翻 2.6 倍。代价只有 68 毫秒。
这不是玄学。这可测量。
所以我做了个东西。
在 AI 观测台里新增一个段落。填两组提示词,各跑 N 次,出数字。
success_rate 成功率 passed / scored
avg_ms 平均耗时 成功那几次的均值
avg_tokens 平均消耗 token 花了多少
consistency 一致性 组内输出两两相似度(0-1)
"一致性"是我最想聊的那个。 它衡量的是:同样的输入跑多次,输出会不会飘。
很多人调提示词只看"这次输出好不好"——但真正要命的是第 5 次、第 10 次还好不好。一个成功率 90% 但一致性 0.2 的提示词,意味着你的下游代码要对十种不同格式做兼容。
一致性算法:把每次输出切成字符二元组集合,两两算 Jaccard 相似度取均值。自己实现的,没引依赖:
function abJaccard(a, b) {
let inter = 0;
for (const x of a) if (b.has(x)) inter++;
return inter / (a.size + b.size - inter);
}
没有判分规则的成功率,就是没有意义的数字。 所以这一步必须交给用户:
| 模式 | 判定 |
|---|---|
none | 不判分,只并排看 |
contains | 必须包含全部关键词(逗号分隔) |
regex | 正则能匹配 |
json | 必须是合法 JSON(可选校验键名) |
length | 字数 ≥ N |
"成功"的定义应该由判分者给,不该由测试工具猜。
跑完直接告诉你哪组更好,以及为什么:
结论: 成功率 B 更高(100% vs 0%)—— 提示词约束真的起作用了。
判优是有优先级的:先比成功率,成功率持平比一致性,都持平再比耗时。这条链路本身就是"什么样的提示词算好"的一个答案。
一次 A/B 扣 runs × 2 次配额。 跑两组各 3 次 = 6 次真实调用,不能让人拿它当免费批量 API。界面上实时显示消耗:
共需 6 次调用额度(每组 3 次 × 2 组)。
串行执行,不并发。 并发轰上游是最容易被封的行为,也是拿别人资源不负责的做法。
硬上限:每组最多 5 次、单请求最多 10 次调用、单组提示词 2000 字。
截断会被显式提示:
⚠️ 有输出被 max_tokens 截断(finish_reason=length),真实成功率可能被低估。
因为被截断的输出会被判为"失败"——如果不提示,这个测试在撒谎。
最实用的一个循环:
这就是提示词工程的循环 —— 每次只改一个变量,用数字说话。
我甚至在页面上放了建议实验:把温度调到 0 跑一次、再调到 1.5 跑一次,看一致性怎么塌。
我之前做的 AI 实验室,是"打开看看"。观测台,是"调调参数试试"。
这个 A/B 台不一样 —— 它是会被人反复回来用的。 因为提示词是要一直改的。每次改完,你得知道这次改对了没有。
"改提示词靠感觉"这件事,本质上是因为没有测量工具。 有工具之后,你会发现大部分"感觉变好了"其实就是随机波动;而真正有效的改动,效果好得超出直觉。
顺带说,这也是我给自己做的 —— 观测台里那六个 AI 工具的提示词,现在可以用数字迭代了。
提示词 A/B 测试台 → https://mjdbjgs.com/ai-observatory.html#ab
curl -s -X POST https://mjdbjgs.com/yekui/api/ai/abtest \
-H 'Content-Type: application/json' \
-d '{"promptA":"用一句话解释递归","promptB":"用一句话解释递归,必须包含「调用自身」","runs":2,"max_tokens":200,"score_mode":"contains","score_value":"调用自身"}'
限速:每 IP 每小时 15 次,一次 A/B 按 runs × 2 计。