上一章的结论是:把隐含决定写成显式契约。这一章处理它留下的那个缺口——有些边界,无论怎么写都写不全。
这时候需要换一种沟通方式:不再向模型描述规则,而是把规则演给它看。
一、加了例子,反而更差了
先看一个反复出现的失败形态。下面用上一章那个退货分类任务来演示,场景是构造的,但形态本身在生产中很常见。
按上一章的方法,你已经把分类契约写得相当扎实:类别有定义,冲突有裁决规则,还给了“无法判断”的出口。但线上跑下来,“描述不符”和“质量问题”这两类还是经常混。
于是你做了一件很自然的事:加例子。挑了十来条真实对话,标好答案,放进提示词里。
结果是:整体准确率没什么变化,而且原本判得挺准的几个类别,反而开始出错了。
这个现象很容易被解释成“示例数量不够”或者“模型能力不行”,然后继续加更多例子——那通常会让情况更糟。真正的原因往往在别处:
你的标签体系本身就是重叠的。
“收到的杯子有个缺口”——算质量问题,还是算描述不符(页面上的杯子是完好的)?如果这个问题在你自己的标注规则里就没有答案,那么你挑出来的那十几条示例里,多半就有几条的标注是互相矛盾的:同类情况,这条标了 A,那条标了 B。
而模型会忠实地学习你演示的模式——包括你演示的矛盾。
生产观察 上面的场景是为说明而构造的,但这一失败形态本身来自实践:当加了示例反而变差时,先怀疑标注口径,不要先怀疑模型。这往往不是模型学不会,而是分类本身没有分开。
这也说明了示例的本质。它不是“给模型的额外提示”,而是规则的另一种表达形式——用样本来表达那些用语言说不清楚的边界。既然是规则的表达,它就受到和规则一样的约束:可以不完整,但不能自相矛盾。
记住这句话,本章后面还会用到:示例也是规则,因此也会形成技术债。
二、示例在替你说什么
对写提示词的人来说,有三件事值得知道。
第一,示例不是微调。 它不会在这次调用里改变模型的参数,改变的是模型在这一次调用中的行为:告诉它标签空间有哪些取值、输出应该长什么样、某些边界情况该怎么处理。这更像一次运行时的适配,而不是把新知识永久写进模型。
因此在本书讨论的这类生产任务里,示例最常见的价值是对齐口径,而不是替代训练,也不是补充模型缺少的知识——后者是检索该做的事,属于第 7 章的范围。
第二,模型会一并学习你没打算演示的东西。 示例的语气、长度、标点、句式,甚至你选样本时的偏好,都可能被一起模仿。一组示例如果全是短句,输出也常常偏短;如果示例里的输入都特别工整,遇到真实用户那种残缺表述时就容易失手。
第三,示例的成本是上下文。 每条示例都占位置、占预算,也占模型的注意力。所以问题不是“要不要多给”,而是“给哪些、给多少才划算”——这需要用评测来回答,而不是靠一个记住的数字。
三、四个变量
一组示例的效果,主要由四件事决定。
表 4-1 示例设计的四个变量
| 变量 | 要点 |
|---|---|
| 数量 | 从少量开始,而不是预设一个最佳数量。 2–5 条是低成本的起点;是否继续增加应由评测决定。不同任务和模型的收益曲线差别很大——在长上下文模型上,继续加到几十甚至更多示例,某些任务仍会继续受益。 |
| 顺序 | 顺序确实会影响结果,但不存在跨任务、跨模型稳定成立的最佳顺序。与其记住某个排列技巧,不如把示例顺序纳入回归测试:如果换个顺序结果就大幅变化,这本身就是稳定性风险的信号。 |
| 多样性 | 只给典型正例,很难显式告诉模型边界落在哪里。一组好的示例应包含:典型正例、真实场景中的常见变体、边界题,以及反例(明确演示“这个不算”)。 |
| 格式一致性 | 输出示例的结构应尽量一致,字段名、枚举值、层级这类契约项尤其不要无意变化。核心原则是不要无意中给模型多个互相冲突的模式;如果某种差异本来就是允许的,应当明确说明,而不是让模型自己猜。 |
研究支持 示例的数量、顺序与选择会影响结果,这一点在多项研究中被反复观察到:既有工作发现少量示例即可显著改变模型行为,也有工作发现在长上下文条件下继续增加示例仍能带来收益;示例顺序的敏感性同样被观察到,但“好的顺序”往往难以迁移到另一个模型。这些结论的方向与幅度高度依赖任务与模型,不应作为通则套用。 具体文献与实验条件见附录 E。
四、怎么挑,以及怎么挑坏
六条实用原则:
- 示例要贴近真实输入的分布——优先从经过脱敏、授权和清洗的真实数据中选取。真实用户的表述往往不完整、口语化、碎片化,还常常省略上下文,而自己编的样本容易过于工整;
- 必要时可以用合成样本补足——罕见边界、极端情况、攻击样本往往没有足够的真实数据。合成不是禁忌,但必须拿真实分布验证它是否具有代表性;
- 格式高度统一——同一个字段名、同一种标点、同一个结构;
- 优先覆盖边界题——如果一组示例只能演示典型情况,它带来的信息增量往往很有限;
- 覆盖优于数量——两条典型正例+两条高频变体+两条边界题+一条脏样本,通常胜过二十条同质样本;
- 先让标签体系自洽——这是第一节的教训,也是所有工作的前提。
六种常见挑坏的方式:
表 4-2 示例选择的常见错误
| 挑坏的方式 | 后果 |
|---|---|
| 全是“好学生”样本 | 遇到真实的残缺表述就失手 |
| 全是顺风题 | 边界完全没有被划出来 |
| 标签体系有重叠 | 演示了矛盾,越加越差 |
| 格式不统一 | 模型认为多种格式都可接受 |
| 与真实业务脱节 | 在演示里表现很好,上线就崩 |
| 未经脱敏直接取用日志 | 个人信息与客户信息进入提示词,成为合规风险 |
五、纠偏示例:先诊断,再决定要不要加
示例最有价值的一种用法,是覆盖已经发生过的边界错误。
但这里有个陷阱,而且它和第 1 章的故事是同一个陷阱。
第 1 章讲的是:每次线上出问题就在提示词里加一条规则,几个月之后两千两百行。如果把这个习惯换成“每次误判就加一条示例”,那只是换了一种形态的补丁循环——示例同样占上下文、同样会互相干扰、同样会积累成没人敢删的技术债。
所以正确的顺序是:先诊断,再决定要不要加示例。
表 4-3 纠偏示例的诊断动作
| 失败原因 | 正确的动作 |
|---|---|
| 标签定义本身冲突 | 修标签体系,不要加示例(第一节的情况) |
| 判断标准缺失或模糊 | 补规则或裁决条件(第 3 章的方法) |
| 规则清楚,但语言变体没覆盖到 | 这时才考虑补充或替换示例 |
| 跨轮状态、流程阶段出错 | 示例解决不了,那是第三篇的问题 |
| 偶发的模型方差 | 先进回归集观察,不要急着改提示词 |
只有第三种情况,增加示例才直接针对问题。
一个容易被忽略的测试问题
把线上事故加进示例,还有一个更隐蔽的风险:它会污染你的测试证据。
如果一条输入已经被逐字放进提示词,你再拿同一条输入去测,模型答对了——这说明不了什么。答案已经写在题面上了。
生产观察 同一个线上事故可以同时成为“示例的来源”和“回归用例的来源”,但不要把已经逐字进入提示词的案例,当成证明泛化能力的独立证据。
可行的做法是把一次事故拆成三份用途:
线上事故
├── 诊断原因(决定该改规则、改示例,还是改别的)
├── 若确属示例问题 → 提炼出一条纠偏示例,进提示词
└── 回归集里保留两类用例:
· 原始事故本身 —— 确认"这个故障不再复发"
· 若干语义相近但表述不同的留出用例 —— 确认"边界真的学会了"
后一类才是泛化证据。第 19 章会讲怎么把这些用例组织成一张可反复运行的回归表。
最后一条纪律:纠偏示例要纳入数量预算,并定期清退。 积累一段时间后,你的提示词里可能已经塞着许多为历史问题准备的示例,其中一部分对应的问题早已不再出现。什么时候一条规则或一条示例应该退役,是第 22 章的主题。
六、当示例也不够:给模型更多推理计算
有些任务的困难不在于“不知道你要什么”,而在于这件事本身需要几步才能想清楚:归因分析、多条件判断、含计算的推理、需要权衡的决策。
这类任务往往需要更多的推理计算。但这里要区分两件经常被混为一谈的事:
任务可能需要更多推理计算,不等于“要求模型把推理过程写出来”就会推理得更好。
这个区分在今天尤其重要,因为不同模型和平台提供的手段并不相同。
如果模型或平台没有独立的推理预算机制,可以尝试这些做法:
表 4-4 推理方法的名称与本质
| 常见名称 | 本质 |
|---|---|
| 逐步推理(Chain-of-Thought) | 允许模型产出中间步骤,而不是一步到位 |
| 回退一步(Step-Back) | 先抽象出原则或标准,再处理具体问题 |
| 多路径探索(Tree-of-Thought) | 同时展开多个候选路径再择优 |
| 多次采样取一致(Self-Consistency) | 采样多条路径后取一致结果 |
| 任务分解 | 拆成若干次调用,每次都有明确产物 |
这不是几套必须掌握的咒语,而是增加推理计算的不同方式。 它们的收益高度依赖任务与模型,是否值得用,应当由评测决定。
如果模型或平台支持原生推理、推理强度或推理预算一类的机制,情况就不同了。这时机械地重复“请一步一步思考”往往收益很小而成本照增。更合理的做法是:把任务、目标和约束说清楚,在平台支持的范围内调节推理预算,再通过评测判断这份额外计算值不值。
适用边界 展开推理通常会增加计算量、成本和延迟;如果还要求模型把中间步骤显式写出来,可见输出也会明显变长。多次采样的成本则成倍增加。而且并非所有任务都从中受益——格式转换、信息抽取、简单分类这类任务,展开推理往往只是让输出变长;在某些归纳与模式识别类任务上,强行要求显式推理甚至可能损害表现。
一个实用的启发式判断是:如果这件事交给人做,人通常会不会先打草稿、列步骤或拆问题? 会,就值得试着给模型这个空间;不会,就先别给。但最终仍应由评测决定这份推理成本值不值。
七、静默:不要把内心独白当成系统状态
上面这些做法在生产系统里有一条硬性纪律:模型的原始推理轨迹,不要直接当成给用户的答案。
这条纪律需要先划清一个界限。可以给用户看的是:经过整理的解释、可核验的计算步骤、决策理由和引用依据——“因为 A 满足这项要求而 B 不满足,所以推荐 A”,这种话当然应该说,很多场景下还必须说。不该直接暴露的,是模型自由生成的原始思考过程:草稿、候选路径、自我说明,以及夹杂在其中的内部规则和对用户的判断。
后者一旦原样送到用户面前,代价不只是不好看:
- 泄露内部判断。 “从用户表述看,他可能只是随口问问,暂时不用继续收集联系信息”——这句话一旦出现在回复里,用户看到的是一个正在评估他的系统;
- 暴露规则。 展开的推理常常复述提示词里的规则,等于把系统的判断逻辑直接交给对方;
- 表达失控。 分析和结论混在一段文字里,你无法单独控制最终说出去的那句话。
于是问题变成:这些推理内容,哪些该丢掉,哪些该留下?
需要把“推理”分成两类,它们的处置完全不同。
第一类是模型自由生成的分析过程——草稿、候选路径、自我说明。这类内容不给用户看,也不建议当作系统状态保存下来。 有两个原因:模型写出来的说明未必对应它实际的计算过程,可能只是一段事后解释;而且即使平台能够提供更完整的推理轨迹,也不应该把一段自由文本直接当成权威的系统状态。把“内心独白”存进系统,你得到的是一份看起来很详细、却无法验证的东西。
第二类是可验证的中间产物——如果系统需要保留决策依据,应当优先保留这一类结构化产物。比如:
{
"evidence_ids": ["E3", "E7"],
"matched_rule": "R12",
"decision": "ASK_CONTACT"
}
这份输出可以被逐项核对:E3 和 E7 是不是真的出现在输入里?R12 是不是真的适用?决策是不是符合规则?而一段“我认为用户可能想……”没法核对。
至于这些中间产物哪些需要长期保存、保存多久、谁可以访问,属于隐私与日志治理的范畴——第 23 章会讨论其中的取舍。
不要把自由文本的推理过程当成系统状态。需要保留的,是可验证的决策依据,而不是模型的内心独白。
这条判断在本书后面会反复出现,而且越来越重要。第 11 章讲证据、第 13 章讲判定依据、第 20 章讲执行追踪,本质上都是同一件事:把“为什么这么做”变成可以核对的结构,而不是一段读起来很有道理的话。
至于“先算、再说”这个动作本身——负责判断的那一步和负责表达的那一步分开——第 17 章会把它扩展成完整的三段管线:抽取、决策、渲染各司其职,渲染层只能读取已经批准的决定,不能自己重算。而这一切的起点,就是这里这条静默纪律。
八、第一篇的边界
第一篇需要的基础工具已经齐了:
- 契约——把能说清楚的决定说清楚;
- 示例——处理说不清楚的边界;
- 推理——处理需要展开才能想明白的任务。
它们能覆盖的范围比很多人预期的要大。相当多的生产任务,做扎实这三样就已经够了:意图分类、信息抽取、内容改写、格式转换、单轮问答——不需要状态,不需要闸门,更不需要状态机。
这三件工具还有一个共同的性质。契约主要约束单次调用;示例塑造的是当前这次调用里的判断方式;推理与任务分解甚至可以扩展到若干中间步骤、若干次调用。所以它们的共同点并不是“只发生在一次调用里”,而是:
它们都在为当前任务提供临时的运行时信息,并没有建立一份跨轮持续存在的权威业务状态。
契约给的是规则,示例给的是边界,推理给的是空间——都是围绕“这一次要办的事”做的信息供给。
它们也不是“只能看见这一轮”:系统提示词每轮都在生效,对话历史可以塞进上下文,规则里甚至能描述跨轮行为。真正的问题出现在更后面——
当业务的正确性开始依赖“前几轮的哪些事实现在仍然有效”“用户是不是已经拒绝过”“流程现在走到了哪一步”时,仅靠把历史重新塞回上下文,就越来越难保证稳定。
因为你需要的不再是“这一轮的信息供给”,而是一份权威的、持久的、可以被合并和查询的状态。而这,恰恰是仅靠契约、示例和推理给不了的东西。
换句话说:
当运行时信息需要跨轮持续存在时,它就不应该继续只存在于提示词里。
那么,怎么判断自己是不是已经走到了这条边界?以及更重要的——怎么确认自己还没到,从而避免过度设计?