零样本、少样本与微调:Agent 能力获取的三条路径
零样本、少样本与微调:Agent 能力获取的三条路径
一、一个典型的选型困惑
团队要把 Agent 的意图识别准确率从 78% 提到 95%。方案会上出现了三种意见:
- “再优化一下提示词,把意图定义写清楚,加几个例子。”
- “把示例从 3 个加到 20 个,做个动态示例选择。”
- “收集几百条标注数据,微调一个小模型专门做分类。”
三种意见对应三种能力获取路径:零样本(zero-shot)、少样本(few-shot)、微调(fine-tuning)。它们不是"哪个更先进"的关系,而是成本、效果、维护难度各不相同的三种工程选择。
本文讨论:三条路径的机制差异;各自的成本结构;以及如何在具体场景下做选择。
二、三条路径的机制
2.1 零样本:只给指令
把任务描述写进提示,不提供任何示例。
请判断用户消息属于以下哪一类意图:
- 退款:用户要求退款、退货、取消订单
- 咨询:用户询问产品信息、使用方法
- 投诉:用户表达不满、要求处理问题
用户消息:{message}
输出:只输出类别名称。
机制:完全依赖模型预训练时获得的能力。模型通过指令理解任务,通过概念定义(“退款:用户要求退款……”)来对齐分类标准。
特点:零标注成本、迭代最快;但效果受模型能力与任务复杂度限制,且对边界案例的处理不稳定(比如"我要退掉这个但我想换个新的",算退款还是咨询?)。
2.2 少样本:给几个例子
在提示中加入若干"输入 → 输出"示例。
用户消息:我要退货 → 退款
用户消息:这个怎么用 → 咨询
用户消息:你们太差了 → 投诉
用户消息:{message} → ?
机制:模型从示例中归纳模式(in-context learning)。示例不仅说明"要做什么",还隐式定义了"边界怎么划"。
特点:标注成本低(几个到几十个例子)、效果通常明显优于零样本;但消耗上下文预算,且效果对示例选择很敏感。
2.3 微调:改参数
用标注数据继续训练模型,让模式固化到参数里。
机制:通过梯度下降调整模型权重,把任务模式从"上下文中的临时提示"变成"参数中的固有能力"。
特点:效果上限最高(尤其对特定领域、特定格式)、推理时不需要示例(省上下文、降延迟);但需要标注数据、需要训练流程、迭代周期长,且模型更新后需要重新适配。
2.4 机制对比
| 维度 | 零样本 | 少样本 | 微调 |
|---|---|---|---|
| 改变对象 | 提示 | 提示(含示例) | 模型参数 |
| 数据需求 | 无 | 3–50 条示例 | 数百到数万条标注 |
| 上下文占用 | 低 | 中高 | 无(或极低) |
| 效果上限 | 中 | 中高 | 高 |
| 迭代速度 | 分钟级 | 分钟级 | 天级 |
| 推理成本 | 低 | 较高(示例占 token) | 低 |
| 维护成本 | 低 | 中 | 高(需重训、需版本管理) |
三、少样本的关键工程问题
少样本看起来简单(“加几个例子”),但效果差异极大,原因在于几个工程细节。
3.1 示例数量
示例不是越多越好。常见观察:
- 3–5 个示例通常能带来最大的边际收益;
- 继续增加到 10–20 个,收益递减;
- 示例过多会占用大量上下文,且可能引入噪声(示例之间的风格不一致反而干扰模型)。
建议:从 3 个开始,逐步增加到收益不再明显为止。
3.2 示例选择
示例的质量比数量重要。好的示例集应满足:
- 覆盖各类别:每个类别至少一个示例,避免类别不平衡;
- 包含边界案例:把最容易混淆的情况作为示例(比如"我要换货"该归哪类);
- 格式一致:所有示例的输入输出格式必须完全统一,否则模型会学到不一致的模式;
- 典型性:示例应代表该类别最常见的情况,而不是极端特例。
3.3 动态示例选择
当示例库较大(几十到几百条)时,可以根据当前输入动态挑选最相关的几个示例。
实现方式:把示例库向量化,对当前输入做相似度检索,取 top-k 作为示例。这本质上是把 RAG(检索增强生成)的思路用在示例选择上。
收益:在示例库大、类别细分的任务上效果明显。
代价:增加一次检索的开销,且检索质量影响示例质量。
3.4 示例顺序
示例的排列顺序会影响结果,尤其当示例中存在与当前输入相似的样本时,模型可能受最近的示例影响更大。
实践建议:把最有代表性的示例放在最后(靠近实际输入),或通过实验确定顺序。
四、微调的适用判断
微调的成本很高,不应该作为第一选择。以下是明确适合微调的信号。
信号一:任务格式高度固定,且重复调用量大。
如果每天要调用百万次,示例占用的 token 成本会累积成显著开支,此时微调能省下这部分。
信号二:领域术语与常识差异大。
比如医疗、法律、特定行业术语。模型对这些领域的理解可能不足,微调能显著改善。
信号三:输出格式要求严格且复杂。
比如必须输出特定结构的 JSON、必须遵循特定的分类体系。微调能让格式遵循更稳定。
信号四:需要更小的模型来降本。
用小模型 + 微调,可能达到大模型 + 提示的效果,同时显著降低推理成本与延迟。
信号五:少样本已经达到瓶颈。
如果加示例的收益已经停滞,且仍有明显错误,说明需要参数级的适配。
不适合微调的信号:
- 任务本身还在频繁变化(今天这么分类,明天改标准)——微调跟不上变化;
- 数据量不足以支撑(几十条数据微调容易过拟合);
- 零样本/少样本已经满足需求——没必要增加复杂度;
- 团队没有模型训练与部署的能力。
五、成本结构的完整对比
选型时最容易犯的错误是只看效果,不看全生命周期成本。
5.1 一次性成本
| 路径 | 一次性成本 |
|---|---|
| 零样本 | 写提示的时间(小时级) |
| 少样本 | 写提示 + 挑选示例(天级) |
| 微调 | 数据标注 + 训练 + 评估 + 部署(周级到月级) |
5.2 持续成本
| 路径 | 持续成本 |
|---|---|
| 零样本 | 每次调用都带完整指令 |
| 少样本 | 每次调用都带示例(token 更多) |
| 微调 | 推理便宜,但模型更新需重新适配 |
5.3 变更成本
这一项常被忽略。当任务需求变化时:
- 零样本:改提示即可,分钟级;
- 少样本:改示例即可,分钟级;
- 微调:需要重新标注、重新训练、重新评估、重新部署——周期以周计。
这意味着:如果任务需求不稳定,微调会成为负担。微调适合"任务定义已经稳定、且调用量大"的场景。
5.4 一个粗略的决策框架
任务需求是否稳定?
├─ 否 → 零样本 / 少样本(便于快速迭代)
└─ 是 → 效果是否已满足?
├─ 是 → 零样本 / 少样本(够用就别加复杂度)
└─ 否 → 调用量是否大?
├─ 否 → 少样本 + 动态示例
└─ 是 → 考虑微调(或小模型 + 微调)
六、三段式混合:把三条路径组合起来
实践中,三条路径常常组合使用,而不是二选一。
一个典型的混合结构:
第一层:小模型 + 微调 → 处理高频、简单的意图分类(快、便宜)
第二层:大模型 + 少样本 → 处理第一层不确定的样本
第三层:人工 → 处理前两层都不确定的样本
这种"级联"结构的优势是成本与效果的平衡:大部分请求由便宜的路径处理,只有少数复杂请求走昂贵路径。
类似地,也可以:
- 用零样本做快速原型验证,验证可行后再补示例;
- 用少样本收集数据(把线上请求与模型输出作为候选标注),积累到一定量后再考虑微调;
- 微调模型负责格式约束,大模型负责语义理解。
关键洞察:三条路径不是互斥选项,而是可以按请求特征分流的处理层。
七、一段示例选择的实现(示意代码)
下面实现一个"动态示例选择"的最小版本,演示如何根据当前输入挑选最相关的示例。这是示意代码,用简单的词重叠度代替向量检索,以便离线运行。
环境与依赖:Python 3.9+,仅用标准库。保存为 example_select.py,执行 python example_select.py。
"""少样本动态示例选择的示意实现(离线可运行)。
说明:
- 用"字符 bigram 重叠度"近似语义相似度,避免引入向量库依赖;
- 真实系统应使用嵌入模型做语义检索;
- 这是示意代码,用于说明动态示例选择的结构。
"""
import re
from dataclasses import dataclass
# ---------- 示例库(每类至少一条,含边界案例) ----------
EXAMPLES = [
{"text": "我要退货,这个东西不合适", "label": "退款"},
{"text": "订单下错了,帮我取消", "label": "退款"},
{"text": "这个功能怎么用啊", "label": "咨询"},
{"text": "支持哪些支付方式", "label": "咨询"},
{"text": "等了三天还没发货,太差了", "label": "投诉"},
{"text": "你们的服务态度让人失望", "label": "投诉"},
# 边界案例:既有退款意图又像咨询
{"text": "我想换成另一个颜色,能退吗", "label": "退款"},
]
LABELS = ["退款", "咨询", "投诉"]
def bigrams(s: str) -> set:
s = re.sub(r"\s+", "", s)
return {s[i:i + 2] for i in range(len(s) - 1)} or {s}
def similarity(a: str, b: str) -> float:
"""字符 bigram 的 Jaccard 相似度(近似语义相似度)。"""
ba, bb = bigrams(a), bigrams(b)
if not ba or not bb:
return 0.0
return len(ba & bb) / len(ba | bb)
def select_examples(query: str, k: int = 4, ensure_per_label: bool = True) -> list:
"""动态挑选 k 个最相关示例;保证每个类别至少出现一次。"""
ranked = sorted(EXAMPLES, key=lambda e: similarity(query, e["text"]), reverse=True)
picked, seen = [], set()
if ensure_per_label:
for label in LABELS: # 先保底每类一个
for e in ranked:
if e["label"] == label and e["text"] not in seen:
picked.append(e); seen.add(e["text"]); break
for e in ranked: # 再按相似度补齐
if len(picked) >= k:
break
if e["text"] not in seen:
picked.append(e); seen.add(e["text"])
return picked[:k]
def build_prompt(query: str, examples: list) -> str:
lines = ["请判断用户消息的意图类别,只输出类别名称。", ""]
for e in examples:
lines.append(f"用户消息:{e['text']} → {e['label']}")
lines.append("")
lines.append(f"用户消息:{query} → ?")
return "\n".join(lines)
if __name__ == "__main__":
for q in ["买错了想退款", "怎么修改密码"]:
exs = select_examples(q, k=4)
print(f"=== 查询:{q} ===")
print(build_prompt(q, exs))
print()
输出(示意):
=== 查询:买错了想退款 ===
请判断用户消息的意图类别,只输出类别名称。
用户消息:我要退货,这个东西不合适 → 退款
用户消息:这个功能怎么用啊 → 咨询
用户消息:等了三天还没发货,太差了 → 投诉
用户消息:买错了想退款 → ?
...
注意 select_examples 里的两个设计:
- 保底每类一个(
ensure_per_label):避免动态选择时某个类别完全缺席,导致模型对该类无参照; - 按相似度补齐:在保证类别覆盖的前提下,优先选与当前输入最相似的示例。
这正是动态示例选择的实际做法——类别覆盖 + 相似度优先,而不是单纯的 top-k 相似。
八、常见错误
错误一:默认"微调效果最好",直接上微调。
微调的一次性成本与变更成本都很高。多数场景先用少样本就能满足需求。
错误二:示例只堆数量,不管覆盖。
10 个示例全是"退款",模型对"投诉"的判断就缺乏参照。
错误三:示例格式不统一。
有的示例输出是"退款",有的是"退款类",有的是"REFUND"。模型会学到混乱的模式。
错误四:忽略上下文成本。
示例占用的 token 是每次调用都要付的。在调用量大的场景,这部分成本可能超过微调的一次性投入。
错误五:任务变化后忘记更新示例/模型。
分类标准变了,但示例还是旧的,或者微调模型还是按旧标准训练的。
错误六:不评估就直接替换。
从零样本换到少样本、或从少样本换到微调,都应该在固定测试集上做对比,而不是凭感觉。
九、局限
- 本文的成本量级描述(“小时级/天级/周级”)是粗略的示意,实际取决于团队能力与任务复杂度。
- 微调的具体效果高度依赖数据质量与训练方法,本文未展开训练细节,具体数值不应外推。
- 动态示例选择的相似度实现(本文用 bigram)只是近似,真实系统应使用嵌入模型;近似方法的检索质量会明显低于语义检索。
- 三条路径的效果对比必须基于你自己的任务实测,公开研究中的结论不必然适用于你的场景。
十、小结
- 三条路径分别是:零样本(只给指令)、少样本(给示例)、微调(改参数),改变的对象依次是提示、提示、模型。
- 少样本的关键不在数量,而在示例的覆盖、边界案例与格式一致性;示例库大时应做动态选择。
- 微调适合任务稳定、调用量大、领域特殊、需要降本的场景;需求频繁变化的场景不适合。
- 选型必须看全生命周期成本:一次性成本、持续成本、以及最易被忽略的变更成本。
- 三条路径可以级联组合:小模型微调处理高频简单请求,大模型少样本处理复杂请求,人工兜底。
- 原则:从最简单的路径开始,只在遇到明确瓶颈时升级。
动手练习:从你 Agent 的线上日志里随机抽 50 条请求,用零样本跑一遍,统计准确率;再从错误案例中挑出 5 条作为示例,用少样本跑一遍,对比准确率变化。如果少样本带来的提升很明显,说明示例质量是关键变量,此时应该继续优化示例集(补边界案例、统一格式);如果提升不明显,说明问题可能在任务定义本身,而不是示例不足——此时加更多示例或微调都不会有本质改善。
- 点赞
- 收藏
- 关注作者
评论(0)