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 设计要点:

  1. 格式绝对一致(结构上的对称让模型更容易模仿)

  2. 覆盖边界情况(不要只放典型案例)

  3. 最后一个例子影响最大(Recency Bias)

  4. 质量 > 数量(3 个精准 > 10 个模糊)

下一章 → Chain-of-Thought:让模型「先思考再回答」,大幅提升推理能力