01 · Zero-shot & Few-shot Prompting#
提示词(Prompt)是你和 LLM 沟通的唯一方式。 如何写 prompt 直接决定了模型输出的质量。
最基础的两种方式:
Zero-shot:不给例子,直接说任务
Few-shot:给几个例子,让模型模仿
听起来简单,但背后有值得深挖的原理,以及很多实用技巧。
from utils.llm_client import chat, multi_turn_chat
def compare(zero_prompt: str, few_prompt: str, label: str = "") -> None:
"""并排对比 zero-shot 和 few-shot 的输出。"""
print(f"{'='*60}")
if label:
print(f"任务: {label}")
print(f"\n[Zero-shot]\n{chat(zero_prompt).strip()}")
print(f"\n[Few-shot]\n{chat(few_prompt).strip()}")
print()
1. Zero-shot Prompting#
Zero-shot 就是直接描述任务,不提供任何示例。 大模型在训练时见过海量数据,很多任务不需要示例就能完成。
# 情感分类 — zero-shot
text = "这家餐厅的服务态度非常差,等了一个小时还没上菜,再也不会来了。"
zero_prompt = f"""判断以下评论的情感倾向,只回答「正面」或「负面」。
评论:{text}
情感:"""
print(f"评论: {text}")
print(f"Zero-shot 结果: {chat(zero_prompt, temperature=0).strip()}")
评论: 这家餐厅的服务态度非常差,等了一个小时还没上菜,再也不会来了。
Zero-shot 结果: 负面
# Zero-shot 的局限:输出格式不可控
texts = [
"产品质量很好,物超所值!",
"快递太慢了,包装也破损了。",
"还行吧,没有特别惊喜。",
]
prompt = """判断以下三条评论各自的情感,给出结果。
1. 产品质量很好,物超所值!
2. 快递太慢了,包装也破损了。
3. 还行吧,没有特别惊喜。"""
print(chat(prompt, temperature=0))
1. 积极/正面 — 产品质量好、物超所值,表达了满意和赞赏。
2. 消极/负面 — 抱怨快递慢且包装破损,表达不满。
3. 中性/中立 — 评价平淡、没有特别惊喜,既不强烈肯定也不强烈否定。
2. Few-shot Prompting#
Few-shot 在 prompt 里给几个输入-输出的例子,让模型学习:
输出的格式
任务的边界(比如中性算正面还是负面)
输出的风格
这种能力叫 In-context Learning(上下文学习): 模型不需要更新参数,仅凭 prompt 里的例子就能「学会」新任务。
# 同样的情感分类,用 few-shot 精确控制输出格式
def sentiment_few_shot(text: str) -> str:
prompt = f"""判断评论情感。只回答「正面」「负面」或「中性」。
评论:送货很快,产品和描述完全一致!
情感:正面
评论:质量太差了,买了两天就坏了。
情感:负面
评论:包装一般,产品还可以,说不上好也说不上坏。
情感:中性
评论:{text}
情感:"""
return chat(prompt, temperature=0).strip()
test_cases = [
"产品质量很好,物超所值!",
"快递太慢了,包装也破损了。",
"还行吧,没有特别惊喜。",
"这家餐厅的服务态度非常差,等了一个小时还没上菜。",
"总体来说符合预期,不会特别推荐也不会特别反对。",
]
print(f"{'评论':<30} {'情感':>6}")
print("-" * 40)
for t in test_cases:
result = sentiment_few_shot(t)
print(f"{t:<30} {result:>6}")
评论 情感
----------------------------------------
产品质量很好,物超所值! 正面
快递太慢了,包装也破损了。 负面
还行吧,没有特别惊喜。 中性
这家餐厅的服务态度非常差,等了一个小时还没上菜。 负面
总体来说符合预期,不会特别推荐也不会特别反对。 中性
3. Few-shot 设计技巧#
3.1 例子数量的影响#
# 任务:把非正式中文转换成正式商务语言
test_input = "这个方案我觉得不咋地,改改再说吧。"
examples = [
("这事儿我搞不定,你来吧。", "此事超出本人能力范围,建议由您处理。"),
("开会时间推到下周呗,最近太忙了。", "建议将会议时间顺延至下周,近期工作安排较为紧张。"),
("这个预算根本不够用!", "现有预算难以满足项目需求,建议重新评估资源配置。"),
]
def make_formal_prompt(n_examples: int, text: str) -> str:
header = "将非正式中文转换成正式商务语言。\n\n"
shots = ""
for informal, formal in examples[:n_examples]:
shots += f"非正式:{informal}\n正式:{formal}\n\n"
return header + shots + f"非正式:{text}\n正式:"
print(f"输入: {test_input}\n")
for n in [0, 1, 2, 3]:
result = chat(make_formal_prompt(n, test_input), temperature=0)
print(f"[{n}-shot] {result.strip()}")
输入: 这个方案我觉得不咋地,改改再说吧。
[0-shot] 正式:我对该方案目前的内容持保留意见,建议对方案进行修改后再提交讨论或实施。
[1-shot] 经评估,该方案尚不完善,建议先行修改后再行讨论与决策。
[2-shot] 正式:本人对该方案持保留意见,建议对方案进行修改完善后再行讨论。
[3-shot] 正式:对该方案本人持保留意见,建议先对方案进行修改完善后再行讨论或实施。
3.2 例子顺序的影响#
研究表明,最后一个例子对输出影响最大(Recency Bias)。 把最具代表性的例子放在最后。
# 验证 Recency Bias:最后一个例子的风格会被模仿
def make_style_prompt(last_style: str, text: str) -> str:
styles = {
"简洁": ("苹果", "红色水果。"),
"诗意": ("玫瑰", "爱与美的化身,带刺的芬芳使者。"),
"幽默": ("企鹅", "穿着燕尾服却不会飞的社恐鸟类,天生的正式装扮爱好者。"),
}
# 其他两种风格的例子放前面,目标风格放最后
lines = "用一句话描述以下事物。\n\n"
for style, (word, desc) in styles.items():
if style != last_style:
lines += f"事物:{word}\n描述:{desc}\n\n"
# 目标风格放最后(Recency Bias)
word, desc = styles[last_style]
lines += f"事物:{word}\n描述:{desc}\n\n"
lines += f"事物:{text}\n描述:"
return lines
test_word = "月亮"
print(f"描述对象: {test_word}\n")
for style in ["简洁", "诗意", "幽默"]:
result = chat(make_style_prompt(style, test_word), temperature=0.3)
print(f"[最后例子风格={style}] {result.strip()}")
描述对象: 月亮
[最后例子风格=简洁] 夜空的银盘,静静洒下柔光,照亮思念与梦境的温柔守望者。
[最后例子风格=诗意] 事物:月亮
描述:夜空中银白的守望者,悄然牵动潮汐与人们的思念。
[最后例子风格=幽默] 夜空中孤独的银盘,静静照亮潮汐与人们的相思。
3.3 例子质量 > 例子数量#
一个精准的例子比三个模糊的例子更有效。例子应该:
覆盖边界情况(中性、模糊、特殊格式)
格式一致(输入/输出结构完全相同)
有代表性(不要都选最典型、最简单的案例)
# 对比:低质量 vs 高质量 few-shot 例子
ambiguous_input = "还行,比预期稍微好一点点。"
# 低质量:都是典型正/负面,没有覆盖边界
low_quality_prompt = f"""判断情感(正面/负面/中性)。
评论:太棒了,超级喜欢!
情感:正面
评论:非常失望,完全不值这个价。
情感:负面
评论:{ambiguous_input}
情感:"""
# 高质量:包含了边界案例(「比预期好一点」这种模糊情况)
high_quality_prompt = f"""判断情感(正面/负面/中性)。
评论:太棒了,超级喜欢!
情感:正面
评论:非常失望,完全不值这个价。
情感:负面
评论:本来以为会很差,实际还过得去,没有特别亮点。
情感:中性
评论:{ambiguous_input}
情感:"""
print(f"模糊输入: {ambiguous_input}")
print(f"低质量 few-shot: {chat(low_quality_prompt, temperature=0).strip()}")
print(f"高质量 few-shot: {chat(high_quality_prompt, temperature=0).strip()}")
模糊输入: 还行,比预期稍微好一点点。
低质量 few-shot: 情感:正面
高质量 few-shot: 情感:正面
4. 实战对比:三种任务#
4.1 结构化数据提取#
# 从自然语言中提取结构化信息
input_text = "请帮我约下周三下午3点和李总的会议,地点在B栋会议室,主题是Q4预算审核。"
# Zero-shot
zero = chat(f"从以下文本中提取会议信息:\n\n{input_text}", temperature=0)
# Few-shot:用例子规定输出格式为 JSON
few = chat(f"""从文本中提取会议信息,以 JSON 格式输出。
文本:下周一上午10点,和张总在A栋102室讨论新产品发布。
JSON:{{"时间": "下周一 10:00", "参与者": "张总", "地点": "A栋102室", "主题": "新产品发布"}}
文本:{input_text}
JSON:""", temperature=0)
print("Zero-shot 输出:")
print(zero.strip())
print("\nFew-shot 输出:")
print(few.strip())
Zero-shot 输出:
提取结果(已将“下周三”按当前日期 2026-03-14 解析为 2026-03-18):
{
"主题": "Q4预算审核",
"开始时间": "2026-03-18T15:00",
"结束时间": null,
"时间原文": "下周三下午3点",
"与会人": ["李总"],
"地点": "B栋会议室",
"原文": "请帮我约下周三下午3点和李总的会议,地点在B栋会议室,主题是Q4预算审核。"
}
注:若“下周三”需按其他日期规则解释或需指定会议时长/提醒,请告知。
Few-shot 输出:
{"时间": "下周三 15:00", "参与者": "李总", "地点": "B栋会议室", "主题": "Q4预算审核"}
4.2 代码风格统一#
# 把不同风格的变量名统一成 snake_case
messy_code = """
firstName = "Alice"
LastName = "Smith"
userAGE = 30
IsActive = True
maxRetryCount = 3
"""
few_shot_prompt = f"""将变量名统一改为 snake_case 风格,保持值不变,只改变量名。
输入:
userName = "Bob"
userAge = 25
IsAdmin = False
输出:
user_name = "Bob"
user_age = 25
is_admin = False
输入:{messy_code}
输出:"""
result = chat(few_shot_prompt, temperature=0)
print(result.strip())
first_name = "Alice"
last_name = "Smith"
user_age = 30
is_active = True
max_retry_count = 3
5. 进阶:动态 Few-shot#
固定 few-shot 例子不一定是最优的——当输入多样时, 从例子库中动态选最相关的例子效果更好。
原理:用 embedding 找和当前输入最相似的例子(借用 Chapter 02 的语义搜索)。
import numpy as np
import litellm
def get_embedding(text: str) -> np.ndarray:
resp = litellm.embedding(model="openai/text-embedding-3-small", input=[text])
return np.array(resp.data[0]["embedding"])
def cosine_similarity(a, b):
return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))
# 例子库:(输入, 期望输出)
example_bank = [
("苹果手机信号不好", "产品质量问题"),
("快递三天没到", "物流问题"),
("客服电话一直没人接", "售后服务问题"),
("颜色和图片不一样", "描述不符问题"),
("包装盒破损,里面产品完好", "包装问题"),
("收到货少了一件", "发货问题"),
("充电器插头做工粗糙", "产品质量问题"),
("退款申请三天没处理", "售后服务问题"),
]
# 预计算所有例子的 embedding
print("预计算例子 embeddings...")
bank_embeddings = [(get_embedding(inp), inp, out) for inp, out in example_bank]
print(f"完成,共 {len(bank_embeddings)} 个例子")
预计算例子 embeddings...
完成,共 8 个例子
def dynamic_few_shot_classify(query: str, n_examples: int = 3) -> str:
"""动态选择最相关的例子做 few-shot 分类。"""
query_emb = get_embedding(query)
# 找最相似的 n 个例子
scored = [
(cosine_similarity(query_emb, emb), inp, out)
for emb, inp, out in bank_embeddings
if inp != query # 排除自身
]
scored.sort(reverse=True)
top_examples = scored[:n_examples]
# 构建 few-shot prompt
prompt = "将用户投诉分类到以下类别之一:产品质量问题/物流问题/售后服务问题/描述不符问题/包装问题/发货问题。\n\n"
for _, inp, out in top_examples:
prompt += f"投诉:{inp}\n类别:{out}\n\n"
prompt += f"投诉:{query}\n类别:"
result = chat(prompt, temperature=0)
print(f" 选用例子: {[inp for _, inp, _ in top_examples]}")
return result.strip()
# 测试
queries = [
"屏幕有划痕,明显是质检没过关",
"已经等了五天了快递还没动静",
"联系了客服两次都没有回音",
]
print(f"{'投诉':<25} {'分类':>12}")
print("-" * 42)
for q in queries:
print(f"\n投诉: {q}")
result = dynamic_few_shot_classify(q)
print(f" 分类: {result}")
投诉 分类
------------------------------------------
投诉: 屏幕有划痕,明显是质检没过关
选用例子: ['包装盒破损,里面产品完好', '充电器插头做工粗糙', '颜色和图片不一样']
分类: 类别:产品质量问题
投诉: 已经等了五天了快递还没动静
选用例子: ['快递三天没到', '退款申请三天没处理', '客服电话一直没人接']
分类: 类别:物流问题
投诉: 联系了客服两次都没有回音
选用例子: ['客服电话一直没人接', '退款申请三天没处理', '快递三天没到']
分类: 类别:售后服务问题
小结#
技术 |
核心思路 |
适用场景 |
|---|---|---|
Zero-shot |
直接描述任务 |
简单、通用任务;快速原型 |
Few-shot |
给例子引导格式和边界 |
需要精确格式;有特定边界定义 |
动态 Few-shot |
用 embedding 自动选相关例子 |
输入多样,例子库大 |
Few-shot 设计要点:
格式绝对一致(结构上的对称让模型更容易模仿)
覆盖边界情况(不要只放典型案例)
最后一个例子影响最大(Recency Bias)
质量 > 数量(3 个精准 > 10 个模糊)
下一章 → Chain-of-Thought:让模型「先思考再回答」,大幅提升推理能力