上一篇我们把提示词拆成任务、上下文、约束和输出格式。面对格式固定、规则较多的任务,仅靠文字描述仍可能出现边界理解不一致的问题。本篇继续沿着这条路线,只讲一个核心知识点:使用 Few-shot 示例和模板,把“应该怎样处理”展示给模型,并让同一套任务说明能够安全地复用。

Few-shot 是什么

Few-shot 的意思是:在真正的用户输入之前,先给模型一小组“输入—输出”示例,让它从示例中理解分类边界、表达方式或字段格式。它不是训练模型,也不会永久改变模型参数;示例只在当前请求的上下文中生效。

例如,要把反馈分成 bugfeature_requestquestion,单独写规则可能仍有歧义。加入两个覆盖典型情况的示例,模型就能看到“什么样的句子对应什么标签”。示例的价值不在数量多,而在于代表性强:应该优先覆盖容易混淆的类别、缺少信息的情况和输出边界。

示例通常包含三部分:示例输入、示例输出,以及待处理的新输入。示例输出必须符合最终要求;如果示例中混入解释,而最终又要求只返回标签,模型可能会照着示例输出额外内容。

示例如何设计

写 Few-shot 示例前,先明确每个类别的判定依据,不要只给标签而不给可观察的线索。好的示例具有以下特点:

  • 短而完整:只保留区分结果所需的信息,避免无关背景抢占上下文。
  • 覆盖边界:为相近类别各准备至少一个容易混淆的例子。
  • 格式一致:所有示例的键名、标点和字段类型与目标输出一致。
  • 没有冲突:示例不能违反正文规则;发现冲突时,应先修改示例。
  • 不含秘密:示例中的邮箱、订单号等数据应使用虚构值或脱敏值。

示例顺序也值得注意。可以把最接近当前任务的例子放在后面,或者按照“规则简单到边界复杂”的顺序排列;但不要把提示词当成神奇的固定配方,最终仍要用真实样本测试,而不是凭少量成功案例下结论。

模板解决什么问题

模板是把固定指令和每次变化的数据分开的文本结构。例如任务规则、类别定义和输出格式通常不会变,待分类的反馈每次都会变。如果把它们直接拼接在多处代码里,改规则时容易漏改,也难以比较不同版本。

一个可维护的模板至少应区分:

  1. 固定部分:任务、规则、约束、输出格式和示例。
  2. 变量部分:本次请求的输入,例如用户反馈或待摘要文本。
  3. 边界部分:明确标记变量的开始和结束,减少输入内容被误当成指令的机会。

Python 的 str.format() 能完成简单模板替换。变量内容来自用户时,要注意大括号等特殊字符会影响格式化;更复杂的模板可以使用专门的模板库,但本篇先保持最小依赖。模板不是安全隔离机制,仍需在应用层限制长度、过滤敏感信息并审查输入。

最小可运行示例:分类用户反馈

下面使用 OpenAI Python SDK 的 Responses API。模型名和密钥都从环境变量读取;运行前请按照当前服务商官方文档安装 SDK,并配置 OPENAI_API_KEYAI_MODELOPENAI_BASE_URL 是可选项,用于兼容服务的自定义地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import os

from openai import OpenAI

client = OpenAI(
api_key=os.environ["OPENAI_API_KEY"],
base_url=os.environ.get("OPENAI_BASE_URL"),
)

PROMPT_TEMPLATE = """
任务:将用户反馈归为一个类别。

可选类别:bug、feature_request、question。
规则:
- bug:现有功能不能正常工作。
- feature_request:建议新增或改进功能。
- question:询问如何使用现有功能。

参考示例:
输入:登录后页面一直显示空白。
输出:bug

输入:希望增加按日期导出订单的功能。
输出:feature_request

约束:只能根据反馈内容选择一个类别,不要解释,不要输出 Markdown。
待处理反馈:
---
{feedback}
---
只输出类别名称。
"""


def classify(feedback: str) -> str:
prompt = PROMPT_TEMPLATE.format(feedback=feedback)
response = client.responses.create(
model=os.environ["AI_MODEL"],
input=prompt,
)
return response.output_text.strip()


if __name__ == "__main__":
print(classify("导出的报表中没有包含本月的数据。"))

PROMPT_TEMPLATE 把稳定规则和变量 feedback 分开;两个示例分别展示了 bugfeature_request,没有覆盖的 question 仍由文字规则定义。调用函数只负责填充模板、发送请求和取出文本,因此后续替换模型或增加日志时,影响范围比较小。

示例输出不应该被直接当成程序可接受的事实。即使提示词要求只能输出三个值,模型仍可能返回空字符串、大小写变化或附加说明。实际项目应在返回后做白名单校验:不在集合中的值进入错误处理流程,而不是静默写入数据库。结构更复杂时,可以结合结构化输出和数据模型校验;Few-shot 负责展示意图,程序校验负责守住边界。

常见问题

示例越多越好吗?

不是。示例会增加输入 token,也会占用上下文窗口;过多的相似例子可能带来重复和冲突。先选择少量高区分度样本,再根据失败案例增补。每次增补最好只解决一个已观察到的问题,并记录前后效果。

示例和规则冲突怎么办?

这是提示词设计错误,不应期待模型稳定地猜出优先级。检查类别定义、示例输入和示例输出,统一术语和边界。可以在模板中明确“示例必须遵守上述规则”,但这不能替代消除矛盾。

用户输入会不会覆盖模板指令?

用户输入可能包含“忽略前面要求”之类的文字。分隔符、明确的“输入仅作为待处理数据”声明有帮助,但不是绝对防护。对高风险场景,还要限制可执行动作、隔离工具权限,并在代码中验证模型输出。

什么时候不适合使用 Few-shot?

如果任务非常简单、规则已经明确,零样本提示词可能更省 token;如果示例需要频繁更新,应该把它们作为可版本管理的数据,而不是散落在代码中。对于事实知识缺失的问题,增加示例也不能替代可靠的上下文来源。

小结

Few-shot 示例把抽象规则变成可观察的输入输出,提示词模板则把固定说明与动态数据分离。两者结合时,应优先选择少量、无冲突、覆盖边界的示例,保持示例格式与最终格式一致,并对模型结果做白名单或结构校验。下一步面对长文本时,仍然要先定义任务和验收标准,再决定如何组织上下文;模板只是工程工具,不是质量保证的替代品。