课程0基础Agent开发课 / Prompt工程 / Few-shot与CoT思维链
— 11 min read

Few-shot与CoT思维链

> **本文适合谁**

Few-shot 与 CoT 思维链:让模型推理更准确

本文适合谁

已经会写基础 Prompt、但在分类/推理类任务上效果不稳定的开发者。本文告诉你为什么给几个例子能显著提升效果(Few-shot),以及为什么让模型"先想再答"能减少错误(CoT)——两种技术的原理和使用场景都有详细说明。


在需求分类这类任务中,直接提问的效果往往不稳定。以 Jira 需求自动打标签为例,当 Prompt 中没有任何示例时,模型对边界情况容易判断错误——"重构登录模块以支持 OAuth2",到底是技术债还是功能需求?模型在没有判断标准的情况下,只能依赖自身的通用理解,导致准确率偏低。

加入几个精心挑选的示例后,准确率会显著提升。例子的作用不是让模型记住更多知识,而是把特定的判断逻辑直接传递给它。

1.1 从 Zero-shot 到 Few-shot

这三个概念的区别在于提供给模型的参考数量。

Zero-shot(零样本提示,即不给任何例子直接提问):直接提问,不给任何例子。适合简单任务,比如翻译、摘要。但对于有特定判断标准、输出格式要求严格的任务,Zero-shot 很不稳定。

One-shot:给一个例子。比 Zero-shot 好,但一个例子覆盖不了太多情况。

Few-shot:给 3 到 5 个例子。这是最常用的,也是效果提升最明显的。

1.1.1 Few-shot 为什么有效:In-Context Learning 的机制

要理解 Few-shot 为什么有效,需要理解一个概念:In-Context Learning(上下文学习)

大语言模型在训练完成后,参数就固定了——你不是在"重新训练"它。但当你在 Prompt 里提供几个例子时,模型会在推理阶段临时调整自己的行为。这个过程不会修改任何模型参数,只靠 Prompt 本身就完成了"学习"。

这是怎么做到的?训练 LLM 时,模型见过海量的文本,其中包含大量"模式 + 续写"的结构:新闻文章后面有类似的新闻、代码注释后面有对应的实现、例题解析后面有类似题目的解法。模型学会了"看到几个例子之后,按照相同的模式继续"。这个能力在规模足够大的模型里自然涌现,是大模型的核心特性之一。

所以 Few-shot 的本质是:你提供的例子激活了模型内部与该任务相关的知识分布,让模型在当前对话中暂时"切换"到这个任务的判断框架。例子不是在教模型新知识,而是在告诉模型"用你已有知识中的哪个部分来回答"。

这个理解有一个重要推论:Few-shot 对模型"已经懂"的知识有效,对模型从未见过的全新领域效果有限。例子无法让模型凭空学会它训练数据里完全没有的东西。

以需求分类为例,加入 Few-shot 后的 Prompt 结构如下:

python
system_prompt = """你是一名项目管理助手,负责对研发需求进行分类。

分类规则:
- 功能需求:新增用户可见的功能
- 性能优化:提升系统速度、吞吐量、降低延迟
- Bug 修复:修复已知的错误行为
- 技术债:重构、升级依赖、改善代码结构,不改变外部行为

示例:

需求:用户可以通过手机号注册账号
分类:功能需求

需求:登录接口 P99 延迟超过 2 秒,需要优化
分类:性能优化

需求:修复用户头像上传失败的问题
分类:Bug 修复

需求:将 Spring Boot 从 2.x 升级到 3.x
分类:技术债

只输出分类结果,不要解释。"""

例子的选取至关重要。不要选简单的典型案例,要选边界情况容易混淆的情况。"重构登录模块以支持 OAuth2"这种,既有重构(技术债)又有新功能(OAuth2),需要在例子里明确体现判断标准。

例子的质量远比数量重要。5 个精挑细选的例子,比 20 个随便堆的例子效果好得多。

CoT 思维链

任务指令

示例:问题 → 推理步骤 → 答案

新问题 → 模型先推理再作答

Few-shot

任务指令

示例1 → 答案1

示例2 → 答案2

新问题 → 模型仿照输出

Zero-shot

任务指令
直接提问

无示例

模型直接输出答案

Zero-shot、Few-shot 与 CoT 三种方式的输入结构对比

1.2 CoT:让模型先想再答

Few-shot 解决了"告诉模型应该输出什么"的问题,但对于需要推理的任务,光靠例子不够,还需要让模型把推理过程显式地写出来。

这就是 Chain of Thought(思维链,缩写 CoT,让模型把推理过程像解题步骤一样逐步写出来)。

原理很简单:让模型在给出答案之前,先把思考步骤一步一步写出来。

1.2.1 CoT 为什么有效:自回归生成的数学基础

要真正理解 CoT 为什么有效,需要了解 LLM 的生成机制。

大语言模型是**自回归(Autoregressive)**的:它每次只生成一个 Token,下一个 Token 的概率分布由前面所有 Token 共同决定。从数学上看,生成序列 $t_1, t_2, ..., t_n$ 的联合概率是:

$$P(t_1, t_2, ..., t_n) = P(t_1) \cdot P(t_2|t_1) \cdot P(t_3|t_1,t_2) \cdot ... \cdot P(t_n|t_1,...,t_{n-1})$$

这意味着:已经生成的每一个 Token,都会影响后续 Token 的概率分布

当你让模型直接回答"这道题答案是多少"时,模型在一次前向传播中就必须把所有推理压缩进去。但当你让模型先写推理步骤时,中间步骤的 Token(比如"首先计算 5-2=3")会影响后续 Token 的生成方向,相当于给模型提供了"草稿纸"——每一步推理结果都成为下一步推理的条件概率输入。

第二个原因是错误可见性:写出推理步骤后,如果某个中间步骤出错,模型在生成后续 Token 时有机会感知到矛盾并自我修正;而直接给答案时,这种纠错机会就完全没有了。

这两个机制共同解释了为什么 CoT 在复杂推理任务上效果显著,而在简单事实问答上效果不明显——简单问题本来就不需要多步推理,强制加推理步骤反而可能引入噪音。

1.2.2 "Let's think step by step" 为什么有效

2022 年谷歌的一篇论文(Large Language Models are Zero-Shot Reasoners)发现,在 Prompt 末尾加上 "Let's think step by step",在数学推理任务上准确率能提升几十个百分点。中文版是"让我们一步一步思考"。

这句话触发的机制和上面说的一样:强迫模型不要直接跳到答案,而是先把推理链条写出来。

Zero-shot CoT:不给例子,只加"一步一步思考"。对于模型本身有能力推理但容易偷懒的任务很有效。

Few-shot CoT:给的例子里包含推理过程,不只是输入输出对,而是"输入 → 推理步骤 → 输出"。这是效果最好的组合,但写起来也最费劲。

1.3 一个实际例子:代码 Bug 分析

以下对比展示了 CoT 的效果差异。假设需要分析一段代码为什么会抛出 ConcurrentModificationException

不用 CoT 的 Prompt(验证目的:观察没有推理步骤时模型给出的答案质量):

python
response = client.chat.completions.create(
    model="deepseek-chat",
    messages=[
        {
            "role": "user",
            "content": """以下 Java 代码为什么会抛出 ConcurrentModificationException?

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c", "d"));
for (String item : list) {
    if (item.equals("b")) {
        list.remove(item);
    }
}

直接给出原因。"""
        }
    ]
)

这个 Prompt 大概率会得到一个正确但笼统的答案:"因为在遍历 ArrayList 的同时修改了它"。

用 Few-shot CoT 的 Prompt(验证目的:观察加入推理步骤模板后,模型是否能给出完整的因果链分析):

python
system_prompt = """你是一名 Java 调试专家。分析代码问题时,请按以下步骤思考:
1. 确认异常类型和触发时机
2. 追踪相关操作的执行顺序
3. 找到根本原因
4. 给出修复方案

示例:

问题代码:
  Iterator<String> it = list.iterator();
  while (it.hasNext()) {
    String s = it.next();
    if (s.isEmpty()) list.remove(s);  // 直接用 list.remove
  }

分析过程:
  步骤1:NullPointerException?不是。ConcurrentModificationException?是的,在迭代器遍历过程中触发。
  步骤2:迭代器内部维护一个 modCount,记录列表被修改的次数。list.remove() 会让 modCount +1,但迭代器的 expectedModCount 没有更新。
  步骤3:下一次调用 it.next() 时,迭代器检测到 modCount != expectedModCount,抛出异常。根本原因:绕过迭代器直接修改了列表。
  步骤4:把 list.remove(s) 改成 it.remove(),通过迭代器自身的 remove 方法删除,会同步更新 expectedModCount。

现在分析以下问题:"""

user_prompt = """问题代码:
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c", "d"));
for (String item : list) {
    if (item.equals("b")) {
        list.remove(item);
    }
}"""

response = client.chat.completions.create(
    model="deepseek-chat",
    messages=[
        {"role": "system", "content": system_prompt},
        {"role": "user", "content": user_prompt}
    ],
    temperature=0.1
)

用了 Few-shot CoT 之后,模型会照着例子的推理步骤走,解释 for-each 循环背后其实也是迭代器,分析 modCount 的变化,最后给出用 Iterator.remove()removeIf() 修复的方案,而不是给一句"因为并发修改"了事。

1.4 什么时候用 Few-shot,什么时候用 CoT

理解了两种技术的底层机制,选择依据就变得清晰了。

Few-shot 解决的问题是:告诉模型用什么标准判断、输出什么格式。

Few-shot 适合的场景:

任务有固定的输出格式。比如信息提取,每次都要输出一个结构化的 JSON,给几个例子让模型照着格式走。

分类任务。判断标准不是通用的,而是业务特有的,需要用例子传递判断逻辑。

需要特定风格。比如让模型的回复风格和团队的 Wiki 保持一致,给几篇 Wiki 做示范。

CoT 解决的问题是:给模型足够的"计算空间"来完成多步推理。

CoT 适合的场景:

复杂推理。比如分析一个系统设计方案有哪些潜在风险,需要从多个维度思考。

数学计算或逻辑推导。直接给答案错误率很高,让它写推理过程能大幅降低出错率。

多步骤任务。任务本身就有先后依赖关系,比如先分析需求,再拆分子任务,再评估优先级。

判断标准的一个简洁版本:如果问题的核心难点是"模型不知道用什么规则",用 Few-shot;如果核心难点是"模型需要多步推导才能得出答案",用 CoT;如果两者都有,就组合使用。

1.5 Few-shot + CoT:最常用的组合

两个技术经常组合使用。给几个包含推理步骤的例子(Few-shot CoT),比单独用任何一个都强。

先试 Zero-shot,如果效果不稳定,加 Few-shot;如果涉及复杂推理,再加 CoT。不需要一上来就把所有技巧全堆上去,Prompt 越复杂维护成本越高。

下一章讲 ReAct,那是 Agent 能够调用工具、操作外部世界的基础模式。


1.6 Prompt 速查卡片

技巧 解决什么问题 典型触发条件 示例
Zero-shot 模型本身能力足够的简单任务 通用翻译、简单摘要 直接提问
Few-shot 需要特定判断标准或输出格式 业务分类、特定格式提取 提供 3-5 个示例
CoT 多步推理任务,直接回答容易出错 数学题、复杂逻辑分析 "让我们一步一步思考"
Few-shot CoT 需要特定标准 + 多步推理 复杂业务判断 + 需要解释 示例中包含推理步骤

好的 Few-shot vs 差的 Few-shot

差的做法:随机选几个典型例子作为示范。

好的做法:专门选边界情况和容易混淆的案例——这些才是模型最需要判断标准的地方。5 个精挑细选的例子,比 20 个随便堆的例子效果好得多。

本页目录