Workbench + 评估框架:Prompt Generator / Datadog 监控 / 系统性 Eval 方法论
第七十三章:Few-Shot 与 Chain-of-Thought 的科学:从原理到工程实践
73.1 为什么需要示例与推理链
大语言模型的能力边界由两个因素共同决定:模型本身的参数量与训练数据,以及推理时的上下文输入质量。前者是固定的,后者是工程师可以主动干预的变量。Few-Shot Prompting 和 Chain-of-Thought(CoT)正是两类最经过实证验证的上下文干预手段。
Few-Shot 的核心假设是:模型在见到任务的输入-输出示例之后,能够推断出任务的隐式规则,并将其应用到新样本上。 这与人类学习类比推理的机制高度相似。
Chain-of-Thought 的核心假设是:将推理步骤显式化,能够帮助模型在生成最终答案之前,通过中间步骤"想清楚"问题。 这本质上是将模型的计算资源从"直接输出"重新分配到"逐步推导"。
这两者并非互斥,实践中最强的 Prompt 往往是两者的结合:Few-Shot CoT,即提供带有推理过程的示例。
73.2 Few-Shot 的科学基础
73.2.1 In-Context Learning 的机制
研究者对 In-Context Learning(ICL)的工作原理至今仍有争论,但有几点已形成共识:
-
格式学习(Format Learning)优先于内容学习(Content Learning):模型从示例中学到的最重要的信息是"任务的输入-输出格式长什么样",而非"具体哪个输入映射到哪个输出"。Brown et al.(2020) 的实验表明,即使使用随机错误的标签,Few-Shot 仍然比 Zero-Shot 表现更好,这支持了格式学习的假说。
-
示例激活相关表示(Activation of Relevant Representations):示例的存在会激活模型参数中与当前任务相关的知识子集,引导模型进入正确的"任务模式"。
-
分布对齐(Distribution Alignment):示例帮助模型理解当前任务的输出分布特征,例如"答案应该是一个三位数的整数"或"回复应该使用正式商务语体"。
73.2.2 示例数量的权衡
| 示例数量 | 适用场景 | 风险 |
|---|---|---|
| 0-shot | 任务描述足够清晰,或模型对该任务有强大的先验 | 格式不稳定,输出多样 |
| 1-shot | Token 预算有限,任务相对标准 | 单一示例可能引入偏见 |
| 3-5 shot | 大多数结构化任务的黄金区间 | 消耗更多上下文 |
| 10+ shot | 高度复杂或罕见的任务类型 | 收益递减,可能超出上下文窗口 |
研究表明,对于大多数任务,3-5 个高质量示例的效果与 20 个低质量示例相当甚至更好。示例质量远比数量重要。
73.2.3 示例选择策略
示例选择是 Few-Shot 工程中最被低估的环节。以下是四种主要策略:
策略一:随机采样(Random Sampling)
从示例池中随机选取,实现简单,但质量不稳定。适合作为基线。
策略二:相似度检索(Similarity-Based Retrieval)
将测试样本嵌入为向量,从示例池中检索最相似的 K 个示例。这是目前生产环境中最常用的策略。
from anthropic import Anthropic
import numpy as np
client = Anthropic()
def cosine_similarity(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
def retrieve_similar_examples(query_embedding, example_pool, k=5):
"""
从示例池中检索最相似的 K 个示例
example_pool: [{"input": ..., "output": ..., "embedding": [...]}]
"""
similarities = [
(cosine_similarity(query_embedding, ex["embedding"]), ex)
for ex in example_pool
]
similarities.sort(key=lambda x: x[0], reverse=True)
return [ex for _, ex in similarities[:k]]
策略三:多样性采样(Diversity-Based Sampling)
确保示例覆盖任务的不同子类型,防止示例集合过于同质化。可以使用 K-means 聚类后从每个类中采样一个代表性示例。
策略四:难度梯度(Difficulty Gradient)
从简单到复杂排列示例,让模型先看到容易的样本建立基础理解,再看到复杂样本。
73.2.4 示例格式一致性
格式一致性是 Few-Shot 成功的关键约束,且这一点经常被忽视。
不一致的格式示例(错误示范):
示例1:
输入: 苹果
输出: 水果
示例2:
Input: 香蕉
Output: Fruit
示例3:
Q: 西瓜属于什么类别?
A: 这是一种水果。
一致的格式示例(正确示范):
示例1:
输入: 苹果
类别: 水果
示例2:
输入: 香蕉
类别: 水果
示例3:
输入: 西瓜
类别: 水果
格式一致性检查清单:
- 分隔符(冒号、换行、XML 标签)保持统一
- 字段名称大小写保持统一
- 输出长度风格保持统一(简短 vs 完整句子)
- 语言保持统一(中文/英文/混合)
73.3 Chain-of-Thought 的科学基础
73.3.1 为什么推理链有效
Wei et al.(2022) 在论文《Chain-of-Thought Prompting Elicits Reasoning in Large Language Models》中首次系统性证明了 CoT 的有效性。核心发现包括:
-
涌现行为:CoT 的效果在模型参数量超过约 100B 时才显著涌现。对于较小的模型,CoT 甚至可能有负面效果。对于 Claude 这样的大型模型,CoT 几乎总是有益的。
-
任务依赖性:CoT 对需要多步推理的任务(数学、逻辑推断、代码调试)效果显著;对单步任务(情感分类、命名实体识别)效果有限,甚至增加了不必要的复杂度。
-
计算分配:CoT 将 Transformer 的 KV-cache 注意力机制引导到中间推理步骤上,相当于给模型提供了"草稿纸"。
73.3.2 CoT 的主要变体
Zero-Shot CoT
在 Prompt 末尾加入"让我们一步一步地思考"(Let's think step by step),这是成本最低的 CoT 实现方式。
def zero_shot_cot_prompt(question: str) -> str:
return f"""{question}
请一步一步地分析这个问题,然后给出最终答案。"""
Few-Shot CoT
提供带有推理过程的示例,这是效果最强的变体。
示例:
问题:一个工厂有 240 名工人,其中 1/3 是女性。在女性工人中,有 40% 持有大学学位。
持有大学学位的女性工人有多少?
推理过程:
第一步:计算女性工人数量
女性工人 = 240 × (1/3) = 80 人
第二步:计算持有大学学位的女性工人数量
有学位的女性 = 80 × 40% = 32 人
最终答案:32 人
自洽性 CoT(Self-Consistency CoT)
对同一问题生成多条推理链(使用较高温度),然后通过投票选择最常见的答案。这显著提升了数学和逻辑推理的准确率,代价是 API 调用成本倍增。
from collections import Counter
def self_consistency_cot(question: str, n_samples: int = 5) -> str:
"""
生成多条推理链,通过多数投票得出最终答案
"""
answers = []
for _ in range(n_samples):
response = client.messages.create(
model="claude-opus-4-5",
max_tokens=1024,
temperature=0.7, # 较高温度增加多样性
messages=[{
"role": "user",
"content": f"{question}\n\n请一步一步思考,最后在 <answer> 标签中给出答案。"
}]
)
# 提取答案
text = response.content[0].text
if "<answer>" in text:
answer = text.split("<answer>")[1].split("</answer>")[0].strip()
answers.append(answer)
# 多数投票
if answers:
counter = Counter(answers)
return counter.most_common(1)[0][0]
return "无法确定"
树状思维(Tree of Thoughts, ToT)
探索多条并行推理路径,在每个步骤评估哪条路径更有前途,类似树搜索。适合需要回溯的复杂规划任务。
73.3.3 CoT 的工程实现要点
在 Claude 的生产部署中,以下几点至关重要:
1. 推理链放在 Assistant 预填充中
# 通过预填充强制 Claude 先进行推理
response = client.messages.create(
model="claude-opus-4-5",
max_tokens=2048,
messages=[
{"role": "user", "content": "分析以下合同条款是否存在法律风险:..."},
{"role": "assistant", "content": "让我系统地分析这份合同的各个风险维度:\n\n**第一步:识别关键条款**\n"}
]
)
2. 结构化推理与答案分离
def structured_cot_prompt(task: str) -> str:
return f"""任务:{task}
请按以下格式回答:
<thinking>
在这里进行详细的逐步分析。检查所有相关因素,考虑不同的可能性。
</thinking>
<answer>
在这里给出简洁的最终答案。
</answer>"""
3. 推理链的验证
推理链本身可能包含错误。在高风险场景(财务、医疗、法律)中,应当将推理链作为额外的可审计信息,而不是盲目信任其正确性。
73.4 Few-Shot CoT 的组合实践
73.4.1 构建高质量示例库
# 示例库的数据结构设计
example_schema = {
"id": "ex_001",
"task_type": "financial_analysis",
"difficulty": "medium",
"input": "...",
"reasoning": "第一步:...\n第二步:...",
"output": "...",
"embedding": [...], # 用于相似度检索
"quality_score": 0.95, # 人工标注的质量分
"created_at": "2025-01-15",
"tags": ["revenue", "yoy_growth"]
}
73.4.2 动态示例组装
def build_few_shot_cot_prompt(
user_query: str,
task_instruction: str,
example_pool: list,
query_embedding: list,
n_examples: int = 3
) -> list:
"""
动态构建 Few-Shot CoT Prompt
"""
# 检索最相关的示例
similar_examples = retrieve_similar_examples(
query_embedding, example_pool, k=n_examples
)
# 按难度从低到高排序
similar_examples.sort(key=lambda x: x.get("difficulty_score", 0.5))
# 构建示例文本
examples_text = ""
for i, ex in enumerate(similar_examples, 1):
examples_text += f"""
示例 {i}:
输入:{ex['input']}
分析过程:
{ex['reasoning']}
输出:{ex['output']}
---"""
system_prompt = f"""{task_instruction}
以下是一些参考示例,展示了正确的分析思路:
{examples_text}
请按照相同的分析框架处理用户的请求。"""
return [
{"role": "user", "content": system_prompt + f"\n\n现在请处理:\n{user_query}"}
]
73.4.3 Few-Shot vs Fine-tuning 的决策树
任务类型?
├── 格式化/结构化输出 → Few-Shot 通常足够
├── 需要领域专业知识 → 考虑 RAG + Few-Shot
├── 需要特定风格/语气 → Fine-tuning 更合适
└── 需要高精度推理 → Few-Shot CoT 或 Fine-tuning + CoT
数据量?
├── < 100 个高质量样本 → Few-Shot
├── 100-1000 个样本 → 评估 Fine-tuning 成本收益
└── > 1000 个样本 → Fine-tuning 可能显著提升效果
延迟要求?
├── < 1 秒 → Zero-Shot 或 1-Shot(最短上下文)
├── 1-5 秒 → 3-5 Shot CoT
└── > 5 秒可接受 → Self-Consistency CoT
73.5 常见陷阱与调试技巧
73.5.1 示例偏见(Example Bias)
当所有示例都属于同一类别时,模型会产生明显的分类偏见。解决方案:确保示例集合在标签分布上与真实测试集相近,或明确在指令中说明"注意保持输出平衡"。
73.5.2 示例顺序效应(Recency Bias)
模型对序列末尾的示例赋予更高权重(Recency Bias)。在分类任务中,这会导致模型倾向于预测最后一个示例的标签。
缓解策略:
- 对示例顺序进行随机化或多次评估后取平均
- 将困难或边界案例放在末尾,让模型在处理测试样本前刚刚见到挑战性案例
73.5.3 推理链的幻觉(Hallucinated Reasoning)
模型可能生成听起来合理但实际错误的推理链。这在以下情况下尤为危险:推理链本身没有被人工审阅,而是直接驱动下游动作。
防御措施:
- 要求模型在推理链中引用来源或说明推理依据
- 对关键中间步骤设置验证节点(独立调用 Claude 验证)
- 将高置信度阈值作为触发自动执行的条件
73.5.4 上下文窗口管理
大量示例会占用宝贵的上下文窗口。在 Claude 的 200K Token 窗口中,这通常不是瓶颈,但在需要同时处理长文档的场景中需要谨慎规划 Token 预算。
def estimate_token_budget(
system_prompt: str,
examples: list,
user_query: str,
target_output_length: int = 500
) -> dict:
"""简单的 Token 预算估算"""
# 粗略估算:英文约 4 字符/token,中文约 1.5-2 字符/token
system_tokens = len(system_prompt) // 3
example_tokens = sum(
len(ex.get("input", "") + ex.get("output", "") + ex.get("reasoning", "")) // 3
for ex in examples
)
query_tokens = len(user_query) // 3
total_input = system_tokens + example_tokens + query_tokens
return {
"system_tokens": system_tokens,
"example_tokens": example_tokens,
"query_tokens": query_tokens,
"total_input": total_input,
"remaining_for_output": 200000 - total_input,
"is_feasible": total_input + target_output_length < 200000
}
73.6 生产部署的工程考量
73.6.1 示例缓存策略
频繁检索相似示例会带来嵌入计算开销。在生产环境中:
import hashlib
from functools import lru_cache
@lru_cache(maxsize=1000)
def get_query_embedding_cached(query: str) -> tuple:
"""带缓存的嵌入计算(将 list 转为 tuple 以支持 LRU 缓存)"""
# 调用嵌入模型 API
embedding = compute_embedding(query)
return tuple(embedding)
73.6.2 A/B 测试框架
不同的示例选择策略效果差异显著,需要通过 A/B 测试来量化。
import random
class PromptABTester:
def __init__(self, strategies: dict):
"""
strategies: {"strategy_name": strategy_function}
"""
self.strategies = strategies
self.results = {name: [] for name in strategies}
def run_experiment(self, query: str, ground_truth: str):
strategy_name = random.choice(list(self.strategies.keys()))
prompt = self.strategies[strategy_name](query)
response = client.messages.create(
model="claude-opus-4-5",
max_tokens=512,
messages=[{"role": "user", "content": prompt}]
)
output = response.content[0].text
score = evaluate_output(output, ground_truth)
self.results[strategy_name].append(score)
return {"strategy": strategy_name, "output": output, "score": score}
def get_summary(self) -> dict:
return {
name: {
"mean_score": sum(scores) / len(scores) if scores else 0,
"n_samples": len(scores)
}
for name, scores in self.results.items()
}
73.6.3 示例库的维护
生产环境中的示例库需要持续维护:
- 质量衰减监控:定期评估现有示例的效果,淘汰效果下降的示例
- 分布漂移检测:监控真实用户查询与示例库的分布差距
- 版本管理:对示例库进行版本控制,支持回滚
- 人工审核流水线:新增示例需经过人工质量审核再进入生产库
小结
Few-Shot 和 Chain-of-Thought 是 Prompt 工程中经过最多实证研究支撑的两类技术。Few-Shot 的核心在于高质量示例的选择与格式一致性;CoT 的核心在于将推理过程显式化,为模型提供"草稿纸"。两者结合形成的 Few-Shot CoT 是复杂推理任务的黄金标准。
从工程角度看,示例库的构建、动态检索、格式规范化、以及持续的 A/B 测试构成了一套完整的 Few-Shot 工程实践体系。理解这些技术的工作原理不仅帮助工程师做出更好的设计决策,也能在出现问题时快速定位根因。