反例优于正例
LLM 怎么从禁止中学习
一、原理:为什么"不要做 X"比"应该做 Y"更有效
写 prompt 的人大多有这种本能——告诉 LLM "应该做什么"。
应该用简洁的语气。 应该按步骤分析问题。 应该用 markdown 格式输出。
这种"正面指令"看起来合理——你告诉 LLM 目标,它朝目标走。但实际效果常常令人失望。LLM 收到"应该简洁"的指令,依然写出 2000 字的回答;收到"应该用 markdown"的指令,依然忘记加代码块。
为什么?因为正面指令的边界是模糊的。
"应该简洁"是多简洁?2 行算简洁、20 行也算简洁。LLM 自己定义"简洁"会按它训练时见过的"理想长度"——大概率比你想要的长。
"应该按步骤"是哪种步骤?数字编号步骤、bullet 步骤、连贯叙述里的"first/then/finally"也算步骤。LLM 自己选哪种是它的训练偏好。
"应该用 markdown"是什么级别的 markdown?只用 header 还是全套?什么时候加 bold 什么时候不加?LLM 自己判断也按训练偏好——可能跟你预期不一致。
正面指令的本质是给出目标,让 LLM 自己规划路径。这种规划利用了 LLM 的"训练时偏好"——而这个偏好不一定跟你的产品需求一致。
反面指令(禁令)反过来——给出明确的边界,不告诉 LLM 该往哪走,告诉它"哪里不能去"。
不要说 "Got it"。 不要用 em dash。 不要在每个回答前加铺垫。
这些禁令的特征是具体、可识别、无解释空间。LLM 看到 "Got it" 这三个字符就识别得出来,不会"我说的是 'Got that' 不算 'Got it'"——没有这种空间。
禁令的另一个特征是可累加。你不能写无穷个正面指令——它们会互相冲突("应该简洁"和"应该详细"打架)。但你可以写无穷个禁令——它们不会冲突("不要说 X"和"不要说 Y"并存毫无问题)。
第三个特征是剩余空间被 LLM 自然填补。禁令禁掉了某些路径,剩下的路径里 LLM 选哪条?它会选最符合训练时"理想行为"的——这通常恰好是你想要的(你的 prompt 是在矫正坏行为,不是从零塑造行为)。
把这三个特征合起来,禁令的效果常常远好于正面指令。这不是"应该简洁"和"不要啰嗦"哪个对——它们指的是同一件事。它们的区别在表达方式。
但这只是一般规律。实际中要让禁令真正有效,写法有讲究——具体性、密度、位置、措辞,每一项都影响效果。这一章我们看 opencode 怎么用反例。
二、案例:task.txt 的 6 条反例 + 跨工具的"何时不用我"模式
我们已经在 1.5 章看过 gpt.txt 的禁令清单。这里看一个更深入的案例——task.txt 的"反例至上"设计。
packages/opencode/src/tool/task.txt 是 Task 工具的描述。Task 工具用来让父 agent 派生 subagent 处理复杂任务——是最贵的工具之一(每次 task 调用都启动新 LLM session)。
task.txt 的"Usage notes"段落里有 6 条反例——明确告诉 LLM 什么时候不要用 task 工具:
When NOT to use the Task tool:
- If you want to read a specific file path, use the Read or Glob tool instead of the Task tool, to find the match more quickly.
- If you are searching for a specific class definition like "class Foo", use the Grep tool instead, to find the match more quickly.
- If you are searching for code within a specific file or set of 2-3 files, use the Read tool instead of the Task tool, to find the match more quickly.
- If no agent description matches the task at hand, use the other tools directly to make progress.
- If the task can be completed with a single tool call from one of the tools listed above, use that tool directly instead.
- If a previous task call returned a task_id, you can resume that task by passing the same task_id again.
注意几个细节。
细节 1:每条反例都给出替代方案。"don't do X, do Y instead"。不是单纯禁止,是引导到更便宜的替代路径。这让 LLM 不仅知道"不该用 task",还知道"该用 Read"或"该用 Grep"。
细节 2:反例都对应具体场景。"读特定文件"、"找特定类定义"、"搜 2-3 个文件"——每条都是 LLM 在实际工作中会遇到的具体情形。这种具体性让 LLM 看到当下场景就能识别"这是不该用 task 的场景"。
细节 3:反例数量超过正例。task.txt 关于"什么时候用 task"的正面指令很短——大概一句话。"什么时候不用 task"的反例有 6 条,长度是正面指令的 5-6 倍。反例占主导。
细节 4:放在 Usage notes 开头。"When NOT to use" 是 Usage notes 第一段。LLM 看 task 工具描述时,最先吸收的是"不要用"——这种位置安排让"不要用"成为默认认知,"该用"成为例外。
这种反例设计的效果是什么?是让 LLM 在每个工具调用决策时,先排除 task。"我要读文件——task 不适用(反例 1),用 Read"。"我要找类——task 不适用(反例 2),用 Grep"。task 工具变成"剩下的最后选择"——只有当其他工具都不合适时才考虑。
这种"先排除"的认知模式在 prompt engineering 里特别强力——它把 LLM 的默认选择从"用最高级的工具"(task 听起来很厉害)改成"用最小够用的工具"(节省成本)。
task.txt 不是 opencode 唯一用这种模式的工具。我们看几个相关例子:
glob.txt 里:
When you are doing an open-ended search that may require multiple rounds of globbing and grepping, use the Task tool instead.
glob 告诉 LLM "复杂搜索不要用我,用 Task"。
grep.txt 里:
If you need to identify/count the number of matches within files, use the Bash tool with rg (ripgrep) directly. Do NOT use grep.
grep 告诉 LLM "数匹配不要用我,用 Bash rg"。
webfetch.txt 里:
IMPORTANT: if another tool is present that offers better web fetching capabilities, is more targeted to the task, or has fewer restrictions, prefer using that tool instead.
webfetch 告诉 LLM "有别的工具更适合就用别的"。
这些"工具的自我反例"加起来构成一张工具间的决策网——每个工具都告诉 LLM "我不擅长 X,X 用别的"。LLM 在决策时不需要自己穷举对比,每个工具描述都已经隐含了"何时不该选我"。
这种设计的产品价值是成本控制。如果 LLM 总是选最高级工具(task、webfetch),每次对话都会很贵。让每个工具自己"推让"轻量场景给更小的工具,整体成本能压下来。
这是 prompt engineering 在产品工程层面的实际收益——不是炫技,是省钱。
三、设计启示:反例的实用框架
这一章的核心论点:反例不是补充正例的,反例是 prompt engineering 的主战场。
如果你做 AI 产品想用好反例,下面几条原则有用。
1. 反例要具体到可识别的字符串或模式。
- 弱反例:"Don't be verbose"(什么算 verbose?)
- 强反例:"Don't say 'Got it' or 'Let me think' at the start"(具体词,可识别)
2. 反例最好附带替代方案。
- 弱反例:"Don't use the Task tool for simple file reads"
- 强反例:"Don't use the Task tool for simple file reads — use the Read tool instead"
替代方案让 LLM 知道"那我该用什么"——没有替代方案的反例会让 LLM 不知所措,有时候反而做出更糟的选择。
3. 反例的位置在 prompt 前 1/3。
LLM 对开头的 attention 强。重要的反例不能放在 prompt 末尾——会被"中间遗忘"问题影响(lost in the middle)。task.txt 把"When NOT to use"放 Usage notes 第一段就是这个原因。
4. 反例数量可以多,但要"独立可触发"。
每条反例应该对应一个具体场景。一条反例覆盖一个场景。不要写"组合反例"——比如"don't use Task for file reads, class searches, or small file sets"——这种合并反例的识别率低于三条独立反例。
5. 反例和正例的比例:场景越复杂,反例越多。
简单工具(write)的描述可以是 70% 正例 + 30% 反例。复杂工具(task)的描述可以是 30% 正例 + 70% 反例。复杂场景里 LLM 决策容易跑偏,需要更多反例划定边界。
6. 反例不要给理由。
弱反例:"Don't say 'Got it' because it's too informal for technical contexts" 强反例:"Don't say 'Got it'"
理由会稀释禁令——LLM 会评估"我这个场景算不算 informal context",给自己留例外。无理由的禁令是绝对的,更容易遵守。
7. 用 NEVER / MUST NOT 强化最重要的反例。
弱反例:"Don't make tool calls when max steps reached" 强反例:"Do NOT make any tool calls when max steps reached"
大写的 NEVER / MUST NOT / DO NOT 在 LLM 的注意力上有显著加权——它学过这种格式表示"特别重要"。
8. 监控反例效果,迭代加密度。
如果一条反例的实际遵守率不够(你观察 LLM 还是经常违反),不要"加更多解释"——加更多反例。一条不够就写两条不同表述的同一反例。三条不够就写四条。"反例密度"是可以累加的,到一定密度 LLM 一定会服从。
最后一个观察。反例为什么被低估?因为正例符合人类的"积极指引"直觉——告诉别人"该做什么"看起来更友善、更专业。告诉别人"不该做什么"看起来负面、苛刻。但LLM 不是人,它不在乎你的语气是积极还是消极。它对负向指令的识别率往往比对正向指令更高——可能是 SFT 数据里"违反 instruction 的回答被拒绝"的样本比"模糊遵守 instruction 的回答被接受"的样本更多。
放下"prompt 应该积极正向"的偏见,用反例去解决真问题。这是这一篇——也是整本书第一篇——的核心启示。
第 1 篇小结
第 1 篇我们看了 10 章——从 system prompt 的本质到具体的 prompt engineering 技巧。核心论点贯穿全篇:
Prompt engineering 不是文案优化,是模型诊断学。每个 LLM 有自己的"病症"——Claude 谄媚、GPT 啰嗦、Gemini 粗心。好的 prompt 是针对这些病症的"处方"——anthropic.txt 治谄媚、gpt.txt 治啰嗦、gemini.txt 用示例治粗心、beast.txt 治早停、default.txt 是兜底。
围绕这个核心论点,5 个原则浮现:
- 风险标记驱动文本密度——高风险场景需要更密集的禁令清单(1.5 章)
- 示例胜过规则(对某些模型)(1.6 章)
- 少即是多(在兜底场景)(1.7 章)
- 隐性管理 + Self-persuasion(max-steps 那种)(1.9 章)
- 反例优于正例(1.10 章)
这 5 个原则不是"全套秘籍"——是从 opencode 5 张脸里提炼出来的真实做法。你做自己的 AI 产品时可能有别的原则,但这 5 个是经过 opencode 这种规模产品验证过的。
接下来第 2 篇我们离开 prompt 层,进入 AI Agent 的另一个核心机制——主循环。AI Agent 不是一次对话,是一个能跑很多轮的循环。怎么管理这个循环、怎么处理上下文增长、怎么决定终止——是另一类完全不同的工程问题。