跳到主要内容

验证器陷阱:事后防御如何从内部腐蚀你的提示词

· 阅读需 10 分钟
Tian Pan
Software Engineer

第一次验证器捕捉到糟糕的 LLM 输出时,感觉像是一场胜利。第二次,你会调整提示词以降低失败的可能性。到第二十次时,团队中没人能解释为什么提示词中存在那三个段落 —— 它们是早已被遗忘的事故留下的瘢痕组织,而模型在阅读警告上花费的 Token 比推理实际任务还要多。

这就是验证器陷阱。你添加的每一个事后防护(post-hoc guard)—— JSON 模式检查、正则表达式、内容分类器、第二个作为裁判的 LLM —— 都会对上游提示词施加反馈压力。提示词会增加防御性指令来安抚验证器,验证器反过来又会捕捉到一类新的失败,接着你又会添加更多指令。每一次迭代在局部看来都是合理且明智的。但总体而言,系统变得越来越慢、越来越贵,而且在原本设计的任务上的表现也明显变差了。

陷阱是如何触发的

验证器很少作为设计的一部分出现。它们通常是在事故发生后才出现的。一个空字段破坏了下游仪表盘。一个未转义的引号污染了 SQL 查询。本应是三个枚举值之一的字段却出现了一长串解释。于是有人添加了模式检查或重试循环,眼前的问题消失了。

接下来发生的事情是悄无声息的。为了在不消耗五次调用的情况下让重试成功,工程师会在提示词中添加引导:“仅返回有效的 JSON。不要包含 Markdown 代码块。每个字段都是必填的。不要解释你的答案。status 字段必须是 pendingactiveclosed 中的一个。”每一行看起来都无伤大雅。每一行都是为了防止已经发生过一次的失败再次发生。

下一次事故暴露了一个语义错误 —— 模型尽职地生成了有效的 JSON,但在一段乱码输入上,confidence 字段却是 0.99。显而易见的解决办法是增加另一条指令:“仅在证据确凿时才返回高置信度分数。”提示词的表面积变大了,需要解析的指令增多了,模型在开始处理实际任务之前必须权衡的竞争目标也更多了。

一年之后,提示词看起来不再像是一份规范说明,而更像是一份和解协议。它用了 60 行来描述模型“不准做”什么,而描述模型“应该做”什么却只用了 3 行。逐月测量提示词长度的团队会发现它增长了 5% 到 10%。这个增长率就是确凿的证据 —— 熵正在累积,却没有人为此预留预算。

为什么指令会剥夺推理能力

臃肿的提示词会导致输出质量下降,这并非玄学,而是有原因的。Transformer 会关注上下文窗口中的每一个 Token,模型的有效推理能力取决于有多少注意力可以分配给问题,而不是分配给指令。当提示词长度翻倍时,大部分新增的 Token 并不是新信息 —— 它们是冗余的约束,是不同工程师在不同事故中用略有不同的措辞重复陈述的要求。

模型会阅读所有内容。在较长的上下文中,研究人员反复证明,对于埋在提示词中间的任务,准确率会下降;而且随着指令被额外文本稀释,经过安全调优的模型变得更容易受到越狱攻击。一个最初简洁的任务描述提示词变成了一个草堆,实际的任务成了众多警告中的一根针。

一个有用的启发式方法:计算提示词要求模型同时执行多少个不同的认知任务。格式监管、内容过滤、人设维持、思维链塑形、工具选择以及实际任务。如果答案大于一,那么偏移是必然的,这种偏移会表现为看似随机的性能退化,因为没有哪个单一的提交(commit)导致了这一切。

验证器-提示词耦合审计

摆脱陷阱的第一步是让这种耦合变得可见。挑选出流水线中的每一个验证器和防护措施 —— 模式检查、正则过滤器、LLM 裁判、输出解析器、修复循环 —— 对于每一个,找出是因为它才存在的提示词指令。大多数团队会发现他们做不到。

这本身就是一个发现。当验证器捕获到一个失败时,随后的提示词修改很少会被标记上验证器的名字。六个月后,这条指令读起来就像是一直存在的一样。要显式地对这种耦合进行编码:通过注释、提示词片段注册表或 YAML 文件,列出每个防御性条款以及催生它的事故或验证器。例如:# 2025-11 添加:在长输出中 retry-loop-v2 持续产生 Markdown 代码块

加载中…
References:Let's stay in touch and Follow me for more thoughts and updates