什么是 Chain-of-Thought
2022 年 Google Brain 发表的论文《Chain-of-Thought Prompting Elicits Reasoning in Large Language Models》证明:让模型在给出答案前先写出推理步骤,准确率会显著提高。这个看似简单的发现,催生了整个 CoT 提示工程体系,也是推理模型(o1、R1)背后的核心洞见。
Zero-shot CoT:最简单的触发词
2022 年 Kojima 等人发现:在问题末尾添加 "Let's think step by step" 就能显著提升推理准确率,无需任何示例。这个发现极为重要——它说明大型 LLM 已经具备了推理能力,只是默认不会"展示"出来。
import anthropic
client = anthropic.Anthropic()
def zero_shot_cot(problem: str) -> str:
"""Zero-shot CoT:在问题末尾添加触发词,无需示例"""
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{
"role": "user",
"content": f"{problem}\n\n请一步步思考,然后给出最终答案。"
}]
)
return response.content[0].text
# 有效的 Zero-shot CoT 触发词(按效果强弱排序)
triggers = [
"Let's think step by step.", # Google Brain 原版,英文通用
"请一步步分析这个问题。", # 中文通用
"Think carefully before answering.", # 更谨慎的版本
"Show your reasoning process.", # 要求显示过程
"Work through this step by step.", # 强调步骤性
"Let me verify my answer step by step.", # 强调验证
]
# 双步骤 CoT:先推理,再提取答案(降低格式混乱)
def two_step_cot(problem: str) -> tuple[str, str]:
"""Step 1: 自由推理; Step 2: 提取最终答案"""
# Step 1: 获取推理过程
reasoning = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user",
"content": f"{problem}\n\n请一步步分析。"}]
).content[0].text
# Step 2: 基于推理提取结构化答案
answer = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=128,
messages=[
{"role": "user", "content": f"{problem}\n\n请一步步分析。"},
{"role": "assistant", "content": reasoning},
{"role": "user", "content": "综合以上分析,最终答案是(只输出答案,不要解释):"}
]
).content[0].text
return reasoning, answer
Few-shot CoT:用示例教会推理风格
当 Zero-shot CoT 不够精准时,提供 3-8 个带完整推理过程的示例,引导模型模仿特定的推理风格。Few-shot CoT 的关键不是"示例数量多",而是"示例质量高且覆盖不同推理子类型"。
FEW_SHOT_COT_PROMPT = """解题示例:
Q: 一个水箱可以储存 500 升水,现在已装了 60%,再装 50 升后是多少升?
A: 分析:
当前水量 = 500 × 60% = 300 升
加水后 = 300 + 50 = 350 升
验证:350 ≤ 500(未超容量)✓
最终答案:350 升
Q: 一个工人每天工作 8 小时,月薪 6400 元,时薪是多少?
A: 分析:
每月工作天数 = 22 天(国内标准工作制)
每月总工时 = 22 × 8 = 176 小时
时薪 = 6400 ÷ 176 ≈ 36.36 元
验证:合理范围(行业最低工资约 20 元/h)✓
最终答案:约 36.4 元/小时
Q: 一家商店原价 200 元的商品打八折后再优惠 20 元,最终价格是多少?
A: 分析:
八折后价格 = 200 × 0.8 = 160 元
再减 20 元 = 160 - 20 = 140 元
最终答案:140 元
现在请解答:
Q: {question}
A: 分析:"""
# Few-shot 示例设计原则(重要):
# 1. 推理风格要与期望输出格式完全一致
# 2. 示例要覆盖不同子类型(加法、百分比、多步骤)
# 3. 示例中要包含验证步骤,引导模型自我检查
# 4. 示例数量 3-5 个效果最好,太多占用上下文
# 5. 确保示例本身没有错误(错误示例会被模型学习)
Few-shot 示例的推理路径会强烈影响模型的推理方式。如果示例使用了特定方法(如代入法),模型可能强行用同样方法解所有题,即使其他方法更简单。示例设计要多样化,或者明确说明"请选择最适合的方法"。另外,示例中的任何错误都会被模型学习——错误的示例比没有示例更糟糕。
Self-Consistency:多路径投票提升可靠性
Single-path CoT 存在随机性——不同的采样可能得到不同答案。Self-Consistency(Wang et al. 2023)通过多次采样取多数票来提高可靠性,本质上是将 CoT 与投票机制结合。
from collections import Counter
import re
def extract_final_answer(text: str) -> str:
"""提取最后一行或"答案是X"格式的答案"""
# 尝试匹配 "答案是X" 或 "最终答案:X" 格式
patterns = [
r'最终答案[::]\s*(.+?)[\s。]?$',
r'答案是[::]\s*(.+?)[\s。]?$',
r'= (.+?)$',
]
for pattern in patterns:
match = re.search(pattern, text, re.MULTILINE)
if match:
return match.group(1).strip()
# 回退:取最后一行非空内容
lines = [l.strip() for l in text.split('\n') if l.strip()]
return lines[-1] if lines else ""
def self_consistency(problem: str, n_samples: int = 5) -> dict:
"""
Self-Consistency:采样 n 次,取多数票答案
n_samples: 采样次数,推荐 5-20(越多越准确但成本越高)
"""
answers = []
reasoning_paths = []
for i in range(n_samples):
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=512,
temperature=0.7, # 适当随机性产生多样的推理路径
messages=[{
"role": "user",
"content": f"{problem}\n\n请一步步思考。最后一行只写最终数字答案,格式:答案:[数字]"
}]
)
text = response.content[0].text
answer = extract_final_answer(text)
answers.append(answer)
reasoning_paths.append(text)
# 多数投票
counter = Counter(answers)
majority_answer, majority_count = counter.most_common(1)[0]
confidence = majority_count / n_samples
# 找到与多数答案对应的最佳推理路径(用于解释)
best_reasoning = reasoning_paths[answers.index(majority_answer)]
return {
"answer": majority_answer,
"confidence": confidence,
"vote_distribution": dict(counter),
"best_reasoning": best_reasoning
}
# 示例输出:
# {"answer": "140", "confidence": 0.8, "vote_distribution": {"140": 4, "160": 1}}
# confidence=0.8 表示 5次中有4次得到相同答案,可信度较高
在 GSM8K 数学基准上,Self-Consistency (n=40) 比单次 CoT 提升约 10-20 个百分点。对于高风险场景(医疗计算、金融决策),值得投入这额外的成本。实践建议:先用 n=3 快速验证,若置信度低(<67%)再增加到 n=10。置信度高(>80%)可以信任多数结果。
Tree-of-Thought:树状搜索解决复杂问题
Self-Consistency 是"并行的多路径"——所有路径相互独立。Tree-of-Thought(Yao et al. 2023)是主动探索+评估+剪枝的树状搜索,更像人类解决难题时的行为:探索一条路,发现不行就回头换路。
def generate_thoughts(problem: str, context: str = "", n: int = 3) -> list[str]:
"""生成 n 个独立的推理思路"""
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1000,
messages=[{"role": "user", "content": f"""
问题:{problem}
{f"已有推理:{context}" if context else ""}
请提供 {n} 个不同的推理思路(每个思路用 === 分隔,每个思路 2-3 句话):"""}]
)
return response.content[0].text.split("===")
def evaluate_thought(problem: str, thought: str) -> float:
"""让 LLM 评估某个思路的价值(0-10分)"""
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=100,
messages=[{"role": "user", "content": f"""
问题:{problem}
推理思路:{thought}
评估这个思路解决问题的可行性(0-10分)。只输出一个整数。"""}]
)
try:
return float(response.content[0].text.strip())
except:
return 5.0 # 无法解析时给中等分
def tree_of_thought(problem: str, breadth: int = 3, depth: int = 3) -> str:
"""
Tree-of-Thought BFS 搜索
breadth: 每一层保留的最优思路数(剪枝宽度)
depth: 最大搜索深度
"""
thoughts = [""] # 初始思路为空
for step in range(depth):
# 1. 展开:每个现有思路生成新的子思路
new_thoughts = []
for thought in thoughts:
next_thoughts = generate_thoughts(problem, thought, n=2)
new_thoughts.extend(next_thoughts)
# 2. 评估:为每个思路打分
scored = [(t, evaluate_thought(problem, t)) for t in new_thoughts]
# 3. 剪枝:只保留 top-k 思路
thoughts = [t for t, s in sorted(scored, key=lambda x: -x[1])[:breadth]]
# 4. 基于最佳推理路径生成最终答案
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=500,
messages=[{"role": "user", "content": f"""
问题:{problem}
最优推理路径:{thoughts[0]}
基于以上推理,给出最终答案:"""}]
)
return response.content[0].text
Tree-of-Thought 需要 breadth × depth 次 LLM 评估调用(生成调用另计),总调用次数约为普通 CoT 的 10-30 倍,成本较高。仅在以下场景使用:(1) 问题有多条可行路径需要探索,(2) 错误成本高(复杂代码设计、数学证明),(3) 明确需要最优解而非可行解。对于简单多步计算,直接用 Self-Consistency 即可。
Least-to-Most:从简到难的问题分解
对于"大问题套小问题"的复杂场景,Least-to-Most Prompting 先识别并解决所有必要的子问题,再综合成最终答案。这是一种"归约"策略:把当前无法直接解决的问题,归约为可以解决的子问题。
LEAST_TO_MOST_DECOMPOSE = """要解决以下问题,需要先解决哪些更简单的子问题?
按难度从低到高列出所有必要的子问题(每行一个):
问题:{problem}
子问题(从最简单开始):"""
LEAST_TO_MOST_SOLVE = """问题:{problem}
已解决的子问题及答案:
{solved_so_far}
现在解决子问题:{current_sub}
答案:"""
def least_to_most(complex_problem: str) -> str:
"""Least-to-Most:先分解子问题,再逐步解决,最后综合"""
# Step 1: 识别所有子问题
decompose_resp = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=500,
messages=[{"role": "user",
"content": LEAST_TO_MOST_DECOMPOSE.format(problem=complex_problem)}]
)
sub_problems = [
line.strip().lstrip("0123456789.-) ")
for line in decompose_resp.content[0].text.split("\n")
if line.strip()
]
# Step 2: 逐步解决每个子问题(每步都用前面的结果)
solved = []
for sub in sub_problems:
solved_text = "\n".join([f"- {q}: {a}" for q, a in solved])
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=256,
messages=[{"role": "user", "content": LEAST_TO_MOST_SOLVE.format(
problem=complex_problem,
solved_so_far=solved_text or "(暂无)",
current_sub=sub
)}]
)
answer = response.content[0].text.strip()
solved.append((sub, answer))
# Step 3: 综合所有子答案给出最终结论
all_solutions = "\n".join([f"- {q}: {a}" for q, a in solved])
final = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=500,
messages=[{"role": "user", "content": f"""
原始问题:{complex_problem}
已解决的所有子问题:
{all_solutions}
请综合以上结果,给出最终完整答案:"""}]
)
return final.content[0].text
CoT 方法对比与选择指南
| 方法 | 适用场景 | 成本(相对单次) | 实现难度 | 准确率提升 |
|---|---|---|---|---|
| Zero-shot CoT | 通用推理,快速原型 | 1x | 极低(一行代码) | +10~20% |
| Few-shot CoT | 特定格式、领域推理 | 1x(+示例 token) | 低(设计示例) | +15~30% |
| Self-Consistency | 数学、有确定答案的推理 | 5x~20x | 低(循环采样) | +20~40% |
| Tree-of-Thought | 需要探索多路径的复杂问题 | 10x~50x | 中(实现搜索树) | +30~50% |
| Least-to-Most | 有明确层级结构的复合问题 | 3x~8x | 低(分解+组合) | +25~45% |
Chain-of-Thought 是推理模型背后的核心思想在提示工程层面的体现。Zero-shot CoT 简单有效(加一句触发词),Few-shot CoT 教会推理风格(设计高质量示例),Self-Consistency 用多路径投票提高可靠性,ToT 用树搜索解决需要探索的复杂问题,Least-to-Most 用分解策略处理复合问题。实际选择时以"最简单能解决问题的方法"为原则。下一章解析 DeepSeek-R1 如何用强化学习训练推理能力——将 CoT 内化到模型权重本身。