Prompt Engineering 进阶:Chain-of-Thought、Few-shot、ReAct 模式,如何用 Prompt 替代微调
问题
面试中常见这样的问题:
"你如何设计 Prompt 来让模型完成复杂推理任务?Chain-of-Thought 和 Few-shot 有什么区别?什么时候应该用 ReAct 而不是普通 Prompt?如果老板说『别微调了,搞个 Prompt 就上线』,你会怎么回答?"
这些问题的背后,本质上是考察对 Prompt Engineering 从"写清楚提示词"到"系统化引导模型行为"的认知升级。
三大核心模式
1. Chain-of-Thought(CoT)—— 让模型学会"先想再说"
核心思想:在 Prompt 中加入"Let's think step by step",引导模型输出中间推理步骤,再给出最终答案。
Why it works:LLM 本质上是一个"下一个 token 预测器",直接让它输出答案,它会跳过推理过程,导致逻辑跳跃。CoT 相当于给模型一个"思考的脚手架"——让概率分布在推理路径上逐步收敛,而不是一步跳到最终答案。
# Zero-shot CoT 示例
prompt = """
Q: 小明有 12 个苹果,给了小红 3 个,又从超市买了 5 个,现在比原来多几个?
A: Let's think step by step.
"""
# 模型输出:
# 1. 小明原来有 12 个苹果
# 2. 给了小红 3 个,剩下 12 - 3 = 9 个
# 3. 又买了 5 个,变成 9 + 5 = 14 个
# 4. 原来 12 个,现在 14 个,多了 14 - 12 = 2 个
# 答案:2底层原理:CoT 有效不是因为模型真的"学会了推理",而是因为中间步骤把推理路径拆成了多个小步骤,每个步骤的 token 预测难度大幅降低。Wei et al. (2022) 在 GSM8K 上验证:Zero-shot 准确率 10.4% → CoT 58.4%,提升 5 倍。核心原因是分步降低了每一步的条件熵。
进阶变体:
- Self-Consistency:对同一个问题多次采样 CoT 推理,取多数答案。数学推理任务上可以再提 5-10 个点。原理:单一 CoT 路径可能落入局部最优,多次采样 + 投票能平滑掉随机波动。
- CoT-SC(Self-Consistency with CoT):采样 → 多数投票 → 输出。OpenAI 的 Let's Verify Step by Step 论文证明了这一点。在 GSMA8K 上,CoT-SC 把准确率从 58% 推到 72%。
# Self-Consistency 伪代码
def solve_with_self_consistency(prompt, n=5, temperature=0.7):
responses = [llm.generate(
prompt + " Let's think step by step.",
temperature=temperature
) for _ in range(n)]
answers = [extract_answer(r) for r in responses]
vote = Counter(answers)
# 调试用:打印投票分布
print(f"Vote distribution: {vote.most_common()}")
return vote.most_common(1)[0][0]踩坑经验:temperature 设太高(>0.7)会导致 CoT 路径发散,设太低(<0.1)又失去多样性。实操中推荐 temperature=0.3~0.5,采样 5 次,取多数。另外,数字推理场景中 LLM 的"四则运算"经常出错,建议加 Format 约束让模型输出中间算式,由后端解析器做实际计算——这叫"CoT + 计算委托"。
2. Few-shot —— 用示例教会模型"上下文学习"
核心思想:在 Prompt 中给出 2-5 个问答示例,让模型在当前对话上下文中学会模式。
适用场景:分类、格式转换、命名实体识别等结构化输出任务。
底层原理(In-Context Learning):Few-shot 不是"训练"了模型,而是激活了预训练阶段见过的模式。每个示例相当于在 attention 计算中为特定输出格式提供了"锚点"。示例越多,attention 越收敛到目标分布。但示例数量超过某个阈值(通常 5-10 个)后收益递减,因为上下文窗口有限,注意力被稀释。
# Few-shot 示例 —— 情感分类
prompt = """
文本:这部电影太棒了,看得我热泪盈眶。
情感:正面
文本:剧情拖沓,演员演技尴尬。
情感:负面
文本:服务员态度很差,但菜的味道还不错。
情感:混合
文本:{{user_input}}
情感:
"""关键参数:
- 示例数量(k):k=2 通常就够了,太多会浪费 token。实测:k=1 准确率 78%,k=3 准确率 92%,k=7 准确率 93%,边际收益骤降。
- 示例顺序:越靠近 query 的示例影响越大。因为 attention 对近端 token 的权重更高。实战:把最难分的示例放在最后。
- 示例多样性:覆盖不同类别,避免分布偏差。例如情感分类,2 正 2 负 1 混合,不要 4 正 1 负。
踩坑经验:Few-shot 中如果示例本身的标签有噪声(比如一条负面情感被标成了正面),模型会学到这个错误模式。线上场景中,务必将 few-shot 示例纳入 CI 校验——每次模型更新都跑一遍示例集,确保输出格式不变。我见过一个事故:GPT-4 升级后,Few-shot 情感分类的格式从"正面/负面/混合"变成了"Positive/Negative/Mixed",导致下游 NER 解析全部报错,回滚花了 2 小时。
3. ReAct —— 推理 + 行动循环
核心思想:让模型交替输出"推理"和"行动",行动调用外部工具,观察结果后再推理,形成闭环。
这是 Agent 系统的核心框架。ReAct 论文(Yao et al., 2023)在 HotPotQA 和 ALFWorld 上验证了:ReAct 比 CoT(仅推理,无行动)和 Act-only(仅行动,无推理)分别高出 22% 和 34%。原因是推理修正行动,行动反馈推理,形成正反馈循环。
时序流程:
┌─────────────────────────────────┐
│ 用户输入 Query │
└────────────┬────────────────────┘
▼
┌─────────────────────────┐
│ Thought: 分析当前状态 │
│ "需要先查天气数据" │
└────────────┬─────────────┘
▼
┌─────────────────────────┐
│ Action: 调用工具 │
│ get_weather(city, date) │
└────────────┬─────────────┘
▼
┌─────────────────────────┐
│ Observation: 工具返回 │
│ {"temp": 28, ...} │
└────────────┬─────────────┘
▼
┌─────────────────────────┐
│ Thought: 分析结果 │
│ "28°C 多云,适合跑步" │
└────────────┬─────────────┘
▼
┌─────────────────────────┐
│ Action: 再查辅助信息 │
│ check_running_advice() │
└────────────┬─────────────┘
(循环直到条件满足)
▼
┌─────────────────────────┐
│ Final Answer: 综合输出 │
└─────────────────────────┘生产环境 ReAct Agent 实现(带错误处理):
import json
from typing import Dict, List, Optional
class ReActAgent:
def __init__(self, llm, tools: Dict, max_steps: int = 5):
self.llm = llm
self.tools = tools # {"get_weather": func, "search": func, ...}
self.max_steps = max_steps
self.history: List[Dict] = []
def run(self, query: str) -> str:
prompt = self._build_prompt(query)
step = 0
while step < self.max_steps:
response = self.llm.generate(prompt)
# 解析输出
if "Final Answer:" in response:
return response.split("Final Answer:")[-1].strip()
action = self._parse_action(response)
if not action:
# 模型没输出 Action,强制回退
prompt += "\nObservation: 你没有输出 Action,请重新思考并输出 Action\n"
step += 1
continue
try:
tool_func = self.tools.get(action["name"])
if not tool_func:
observation = f"Error: 工具 {action['name']} 不存在,可用工具: {list(self.tools.keys())}"
else:
observation = tool_func(**action["input"])
except Exception as e:
observation = f"Error: 工具调用失败: {str(e)}"
self.history.append({
"step": step,
"thought": self._extract_thought(response),
"action": action,
"observation": observation
})
prompt += f"\nObservation: {observation}\n"
step += 1
# 超过最大步数,强制返回
return f"Agent 达到最大步数 {self.max_steps},未完成完整推理"
def _parse_action(self, response: str) -> Optional[Dict]:
"""解析 Action 块,支持 JSON 和文本两种格式"""
if "Action:" not in response:
return None
try:
lines = response.split("\n")
name = None
input_str = None
for line in lines:
if line.startswith("Action:"):
name = line.split("Action:")[-1].strip()
elif line.startswith("Action Input:"):
input_str = line.split("Action Input:")[-1].strip()
if name and input_str:
return {"name": name, "input": json.loads(input_str)}
except:
pass
return None生产环境中的三大坑:
- 死循环陷阱:ReAct Agent 经常在"我回答不了 → 我查一下 → 查不到 → 再查"之间循环。必须设置 max_steps 和重复检测(连续 3 步查同一个工具 + 同一个参数就打断)。
- 工具幻觉:模型会编造不存在的工具名。比如虚构一个
get_weather_by_city而不调用真实的get_weather。固定解法:在 system prompt 中列出精确的工具名,并在解析层做白名单校验,命中白名单外的工具抛异常并重试。 - Observation 太长截断:工具返回的 JSON 太大会溢出上下文窗口。实战中遇到 ES 返回 200 条结果,Observation 占了 15K token,下一步的 Thought 直接被截没了。解法:对 Observation 做长度截断 + 摘要(
str(obs)[:2000] + "...")。
核心问题:什么时候用 Prompt 替代微调?
现实场景中,很多团队面临一个选择:是花几周微调一个模型,还是花几天设计一套 Prompt + RAG 系统?
80% 场景不需要微调
在 GPT-4 / Claude 3.5 级别的大模型上,80% 的垂直场景可以通过精心设计的 Prompt + RAG 解决:
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 客服问答 | Prompt + RAG | 知识更新快,无需重训模型 |
| 文本分类 | Few-shot Prompt | 示例足够,模型本身理解能力强 |
| 代码生成 | CoT + 约束 Prompt | 标准任务,模型已预训练大量代码 |
| 数据提取 | 结构化 Prompt + JSON Schema | 比微调更容易迭代 |
| 搜索意图识别 | Few-shot + CoT 组合 | 先用 CoT 推理意图,再用 Few-shot 输出格式 |
| 工具调用 | ReAct + 工具白名单 | 工具可动态增删,无需重新训练 |
什么时候才需要微调?
- 模型需要学会特定行为模式(如严格遵守某种格式、拒绝回答某些问题)
- 需要降低推理成本(用更小的模型替代大模型,微调后达到同等效果)
- 领域知识非常特殊且静态(如法律条文理解、医疗诊断辅助)
真实案例:搜索系统
某电商团队的搜索意图识别模块,一开始用 GPT-4 + Few-shot Prompt 上线,准确率 94%。之后用 GPT-4 的蒸馏数据微调一个 7B 模型,准确率 95.5%,推理成本降低 80%。
LTV 曲线:0-2 周用 Prompt 快速验证 → 2-4 周收集真实数据 → 4-8 周微调小模型上线 → 8 周后删掉大模型 Prompt 调用。这是被验证的最佳实践路径。
三种模式对比表
| 维度 | CoT | Few-shot | ReAct |
|---|---|---|---|
| 核心能力 | 推理能力 | 格式学习 | 工具调用 + 推理闭环 |
| 适用场景 | 数学推理、逻辑分析 | 分类、结构化输出 | Agent、多步骤任务 |
| 需要外部工具 | 否 | 否 | 是 |
| Token 消耗 | 中(需输出推理步骤) | 中(需提供示例) | 高(多轮循环) |
| 延迟 | 低-中 | 低 | 高(取决于步骤数) |
| 稳定性 | 中(依赖模型推理能力) | 高(示例固定) | 中-低(容易跑偏) |
| 面试常见追问 | Self-Consistency 如何选温度 | 示例顺序为什么重要 | 死循环怎么处理 |
| 可以和哪个组合 | +Few-shot、+Self-Consistency | +CoT(CoT-Few-shot) | +CoT(内部推理步骤) |
Prompt 工程化的三大局限
面试官不会只问好处,还会问局限。
- Token 成本:长 Prompt 每次推理都消耗大量 token。如果一个 Prompt 包含 5000 token,每秒 10 次请求,一天就是 432 万 token。成本不可忽视。拿 GPT-4 算:432 万 token × $0.03/1K = $129.6/天,一个月 $3888。如果用微调后的 7B 模型,推理成本降到 $0.5/天,一个月 $15。
- 上下文窗口限制:Few-shot 示例越多,占用的窗口越大。如果示例太多,可能挤占输入内容的空间。长上下文窗口(如 128K、200K)也带来了延迟问题——attention 计算是 O(n²),10K→100K token,延迟从 0.5s 涨到 8s。
- 一致性问题:同样的 Prompt 在不同模型版本上的行为可能不同。OpenAI 的 GPT-4-0613 到 GPT-4-1106 就有明显的输出风格变化。我遇到过:GPT-4-0613 上 Few-shot 输出 JSON 格式完美,升级到 GPT-4-1106 后开始输出 Markdown 格式的 JSON,导致下游解析全挂。解决方案:在 CI 中跑 Prompt 回归测试,每次模型版本更新都跑一遍,而不是等线上报警。
实战组合策略
真实面试中,祥哥你可能会被问到:"一个复杂的客服 Agent,你会怎么设计它的 Prompt?"
我的回答框架:
系统 Prompt:
角色定义(你是客服 Agent,用友好语气)
工具列表(严格白名单,每个工具名精确)
行为约束(不要编造信息,不要猜测用户意图)
输出格式(JSON,带 confidence 字段)
限制条件(max_steps=5,异常时返回 fallback 话术)
用户 Query:
[用户输入]
Few-shot 示例(2-3 个):
示例 1: 简单查询 → 直接调用工具
示例 2: 复杂查询 → CoT 推理 → 调多个工具 → 聚合答案
示例 3: 异常情况(工具返回空)→ 回退 + 转人工
CoT 引导:
Let's work through this step by step.
ReAct 循环:
Thought → Action → Observation → ...这样同时用了 CoT(推理)、Few-shot(格式和模式)、ReAct(工具调用),三者不是互斥的,而是可以组合的。
进阶方向
如果面试官继续追问,展示你了解这些:
- Meta-Prompting:让 LLM 自己优化 Prompt。DSPy 框架(Khattab et al., 2024)实现了这个思路,通过声明式编程自动搜索最优 Prompt。实战:DSPy 可以自动选择合适的 few-shot 示例数量和顺序,将分类任务准确率从 92% 提升到 95%。
- Prompt Compression(LLMLingua):用更小的模型将长 Prompt 压缩到 20-30%,在效果几乎不变的情况下节省 token。实测:5K token 的 Prompt 压缩到 1.2K,准确率只降 0.3%,每百万 token 从 $15 降到 $3.6。
- Prompt 版本管理:Prompt 应该像代码一样纳入 CI/CD 流程。非侵入式检查(如输出格式校验、正则匹配)和 A/B 测试是工程落地的关键。推荐:用 SQLite 存每条 Prompt 的版本号 + 输出样本 + 人工评分,做效果追踪。
总结
- CoT 解决"模型不会推理"的问题,适合复杂推理,原理是分步降低 token 预测难度
- Few-shot 解决"模型不知道我要什么格式"的问题,适合结构化输出,靠 attention 锚点激活预训练模式
- ReAct 解决"模型不会调用外部工具"的问题,适合 Agent 系统,核心是推理闭环 + 错误处理
- 三种模式可以组合(CoT-Few-shot-ReAct),不是互斥的
- Case by case:先 Prompt 上线,用数据驱动决定是否微调
- Prompt 工程化要考虑成本、窗口限制和一致性,并引入版本管理和元优化
- 生产环境要关注死循环、工具幻觉、Observation 截断三大坑
参考:Chain-of-Thought (Wei et al., 2022)、ReAct (Yao et al., 2023)、DSPy、OpenAI Prompt Engineering Guide、LLMLingua、LLMLingua2
下一篇预告:Function Calling / Tool Use —— 大模型如何调用外部 API?