> 指令写进 prompt 不等于它生效了——加一道"引文必须在原文里"的机械核验

Research Tools Intermediate 2026-10-12 06:18 2026-10-12
#llm #verification #hallucination #prompts #grounding #tooling

一个工具派三个 LLM "冷读者"去读论文,每份提示里都写着 "回答时请引用原文"。这句话写了很久,而没有任何东西检查过它有没有生效。

// TABLE_OF_CONTENTS

结论先行

一个工具派三个 LLM "冷读者"去读论文,每份提示里都写着 "回答时请引用原文"。这句话写了很久,而没有任何东西检查过它有没有生效。

生效与否不是小事:读者的输入一薄(文件被截断、某节抽取失败、宏没展开), 它就会补,而补出来的东西很流畅,看起来和真发现一模一样。 实际发生过的最严重一次:一个读者报告"每个模型的值从 -0.115 到 0.076, 然而全部为负",作为致命的内部矛盾。两个数字在论文里都不存在。

修法不是把指令写得更重,是加一个几十行的子命令:

nonexpert_review.py quotes <正文文件> <读者的回答>

空白与弯引号归一化之后做子串匹配,不做任何语义判断。 虚构的数字、虚构的句子、把抽取器的损坏当成论文内容描述 —— 三者以同一种方式失败:那句引文不在文件里。 一条引文核不上,这条发现就不可采纳,不管几个读者同时命中。

数字单独报、而且更弱,因为读者本来就该算一些数: 有个读者独立把 4.52 → 0.29 推成 93.6%,并和论文自己写的区间核对过。

为什么

这个项目在同一份文档里记了三次同形状的失败,而这是第四次: 一条登记了的子原因背后的代码已经不存在; 一条 caption 检查坐在 return 之后永远走不到; 一个 --subject 守卫读者口头同意了然后照旧无视。 都是"声明了"与"生效了"之间的缝。

LLM 这一侧更要紧,因为它的"遵守"是概率性的,而不遵守时的产物 在格式上完全合规。你能看到的只有一份结构正确的回答。 所以这里唯一可靠的手段不是更强的措辞,而是在输出侧做一道不需要智能的核验。 子串匹配听着原始,恰恰因为原始才可信 —— 它不会和被核验的对象共享失效模式。 同一条规矩在别处也成立:见 只活在 prompt 里的规则,会在模型状态不好的那一周失效——而那正是它最该生效的一周(代码里强制执行配额, 而不是只在提示里要求),以及 为保住行号而做的填充,让一个宏的值能不能到达下游取决于宏名有多长(拒绝把有洞的文件交给模型)。

分两级报(引文 = 硬门槛,数字 = 软提示)这个设计也值得记。 一刀切会把读者的正当推算也判死,而正当推算是这个工具想要的东西之一。 核验的严格程度要按"这一类输出本来允许多少自由"来定,不是按统一标准。

还有一层:这道核验保护的是排序。这个工具的排序信号是 "几个读者同时命中",而虚构的发现往往落在某个读者的职责范围正中间, 于是排序规则会主动把它顶上去。一条不可采纳的发现如果还参与计数, 它造成的损害比单独出现时更大。

怎么用

让每个"请你 X"的指令配一条"X 到底有没有发生"的检查。 这条检查不需要和指令同样聪明,常常是一行字符串匹配。 写不出检查的指令,要重新问它是不是真的在约束什么。

核验用一个和被核验对象无关的机制。 用另一个 LLM 去判断引文是否忠实, 是让两个相关的仪器互相背书。子串匹配、集合运算、行数比对这类 "笨"方法的价值就在于它们的失效模式和模型无关。

核不上就不可采纳,而且要先于排序。 顺序很重要: 先过核验,再做计数和排序。反过来会让最可疑的发现获得最高的权重。

按输出类型分级。 必须逐字落地的(引文)用硬门槛; 允许派生的(计算结果、换算)单独报、降权。一刀切会在两个方向上都犯错。

出处

~/Tools/nonexpert-review:CALIBRATION.md 的 「消费侧的地板,以及为什么生产侧那道不够」一节 (含 quotes 的归一化口径、"一条指令存在不等于一条指令被核验过"、 4.52 → 0.29 → 93.6% 那个正当推算的例子)、 与「虚构的那一条」一节(-0.115 / 0.076 的来历); fixtures/extract_all.tex 是生产侧的地板。