AI 笔记 · 2026 年 11 月 11 日

微调还是 RAG?一张决策表讲清楚

「模型不懂我们的业务」这句话,八成该用 RAG 解,不是微调。把知识、行为、格式和成本四件事分开看,用一张表决定该改索引还是改权重,以及先跑哪个基线。

「模型不懂我们的业务」是今年最常听到的立项理由,紧接着的一句通常是「所以我们准备微调」。

这两个判断之间其实缺了一步:模型不懂的到底是知识,还是行为。知识是「退款几天到账」,行为是「回答时先给结论再给依据、金额必须带币种、不确定就拒答」。前者靠检索喂进去,后者靠微调或 prompt 固化。搞反了,钱和时间都白花。

微调改的是行为,不是事实#

一个被反复验证的结论:微调能让模型学会「怎么说」,很难让它可靠地记住「是什么」。

原因是训练目标。微调优化的是在样本分布上生成下一个 token 的似然,模型学到的是句式、语气、格式和判断倾向。你往训练集里塞两百条产品条款,它可能记住一部分,但没有任何机制保证查询时能准确复述,更不会在条款更新后自动同步。

而 RAG 把事实放在上下文里,改一条文档就改一次行为,不需要重训。代价是每次请求都要带上下文,成本和延迟都更高。

所以判断的第一问不是「数据够不够」,而是「我要改变的是知识还是行为」。答案若是行为,才继续往下走。

决策表#

判断维度 倾向 RAG 倾向微调
需求本质 补充事实、条款、时效信息 固定格式、语气、判断标准
更新频率 内容每周都在变 规则半年不变
可解释性 必须给出处、能追溯 出处不重要,结果对就行
数据量 有文档,缺标注 有几百到几千条高质量输入输出
迭代速度 当天改当天生效 能接受重新训练与回归
输出稳定性 格式靠 prompt 约束 prompt 怎么调都漂移
成本敏感度 能接受长上下文 想压短 prompt、降延迟

多数真实需求落在中间:知识要 RAG,格式要微调,两件事同时存在。这时候先做 RAG,把知识供给做对,再看剩下多少错误是格式问题。

三个常见误判#

把「答得不好」归因于模型能力。检索没召回、切片切碎、上下文塞太满,都会表现为「模型不懂」。先修检索,往往比微调便宜一个数量级,而且当天见效。

用微调补知识缺口。训练集里写「支持三十天退货」,线上政策改成七天,模型不会知道。它会把旧答案说得更自信,因为微调同时提升了「遵循训练分布」的倾向。

忽略灾难性遗忘。几百条垂直样本训下去,通用能力会掉,尤其是长上下文和指令遵循。上线前必须用一份通用评测集做前后对比,不能只看垂类任务涨了。

注意

微调数据里的隐私和合规问题比想象中严重:客服对话里常有手机号、地址、订单号,直接进训练集就是把它们写进权重。清洗要在构建数据集那一步做,不要指望事后补救。

先跑 prompt 基线,再决定要不要训#

在动手准备数据之前,先花半天做三件事:

  1. 用 few-shot 加结构化输出,把格式要求写死;
  2. 用检索把事实补上;
  3. 在同一批样本上评估。

如果这套组合的可用率已经到八成,剩下的两成大概率不值得一个训练流水线。如果格式仍然漂移、拒答行为不稳定,微调才有明确的目标。

微调的最小可行路径是「小数据 + 严格验收」:两百到五百条高质量样本,LoRA 这类参数高效方法,留出同分布的验证集和一份通用能力评测集,训完两边都跑。

至于数据准备,麻烦的从来不是训练,而是清洗和去重。下面这段代码把 QA 对整理成对话格式,同时做空值、超长和重复检查。

python
"""build_sft.py —— 把 QA 对整理成对话格式训练集,并做训练前体检(纯标准库)"""
import json
from collections import Counter
RAW = [
    {"q": "退款多久到账", "a": "原路退回,通常 1-3 个工作日。", "src": "faq-12"},
    {"q": "退款多久到账", "a": "原路退回,通常 1-3 个工作日。", "src": "faq-12"},  # 完全重复
    {"q": "退款几天能到账?", "a": "原路退回,通常 1-3 个工作日。", "src": "faq-12"},
    {"q": "", "a": "无效样本", "src": "faq-13"},                                  # 空问题
]
SYSTEM = "你是客服助手,只根据提供的知识回答;没有依据时明确说不知道。"

def norm(s):
    return "".join(ch for ch in s.lower() if ch.isalnum())

def to_record(item):
    return {"messages": [{"role": "system", "content": SYSTEM},
                         {"role": "user", "content": item["q"].strip()},
                         {"role": "assistant", "content": item["a"].strip()}]}

def build(raw, max_chars=1200):
    seen, kept, dropped = set(), [], Counter()
    for item in raw:
        q, a = item.get("q", "").strip(), item.get("a", "").strip()
        if not (q and a) or len(q) + len(a) > max_chars:  # 空字段与超长一并丢弃
            dropped["空或超长"] += 1
            continue
        key = norm(q + a)          # 问题与答案同时重复才算重复
        if key in seen:
            dropped["重复"] += 1
            continue
        seen.add(key)
        kept.append(to_record(item))
    return kept, dropped
if __name__ == "__main__":
    records, dropped = build(RAW)
    with open("train.jsonl", "w", encoding="utf-8") as f:
        for r in records:
            f.write(json.dumps(r, ensure_ascii=False) + "\n")
    print(f"保留 {len(records)} 条,丢弃 {dict(dropped)}")

注意 key 用的是问题加答案:同一个答案配不同的问法要保留,这是让模型学会识别同义提问的关键;只有问答都重复才丢掉。

混合方案通常才是答案#

实际项目里最常见的落地形态是混合:检索负责事实,模型或轻量分类器负责路由与表达。

举个例子。客服助手先用小模型判断问题类型(退货、发票、物流),再按类型选择不同的检索集合和回答模板,最后由大模型基于检索结果生成。这里的「判断类型」适合微调,它是一个样本充足、边界固定的分类任务;「产品条款」适合检索,因为它每周都在变。

拆开之后还有一个实际好处:两部分的回归可以分开做。检索错了看召回指标,分类错了看混淆矩阵,不用对着一个整体准确率猜是哪里退步了。

结论#

  • 先分清要补的是知识还是行为,知识走 RAG,行为才考虑微调。
  • 内容频繁变化、必须给出处的场景,微调不是合适工具。
  • 微调前先跑 prompt 加检索的基线,八成可用率就意味着不必上训练。
  • 训练集的质量取决于清洗,去重、脱敏、超长过滤要在构建阶段完成。
  • 训完必须同时跑垂类评测和通用能力评测,只看前者会漏掉遗忘。

决策顺序比工具重要:先问改变什么,再问数据够不够,最后才是选方法。

完

#微调#RAG#选型#LLM
回到顶部 ↑