大模型的知识来自训练数据,训练截止之后发生的事它不知道;企业内部的文档、代码规范、产品手册,它更不可能见过。更麻烦的是,当模型对某个问题”没有把握”时,它并不会停下来,而是顺着语言习惯把答案编下去——这就是幻觉(hallucination)。
RAG(Retrieval-Augmented Generation,检索增强生成)的基本思路非常朴素:回答问题之前,先给模型发资料。就像开卷考试,考生不需要背下整本书,但需要会查目录、会翻到正确的那一页。模型负责理解和组织语言,检索系统负责提供事实依据。
一、为什么不是”全都塞进上下文”
有人会问:上下文窗口都几十万 Token 了,把整个知识库塞进去不就行了?现实里通常不行,原因有三:
- 成本与延迟。每次调用都带全量文档,Token 消耗和响应时间都会失控,长上下文的推理开销也更高。
- 信噪比。把十万字的资料塞进去,模型并不会自动找到那句关键的话。无关内容越多,答案被稀释和带偏的概率越大。
- 数据形态。私有数据往往散落在各种系统里,以数据库行、PDF、网页、工单的形式存在,不适合”全部塞进 prompt”这种粗暴做法。
RAG 用”检索”换”全量”:只取和问题最相关的几段材料,拼进上下文。这就是它长期作为企业知识问答主流方案的核心原因。
二、整条链路:从原始文档到最终答案
一个典型的 RAG 系统分为离线(索引构建)和在线(查询应答)两大部分:
离线:文档 → 切块(chunk) → 向量化(embedding) → 写入向量库
在线:问题 → 向量化 → 相似检索 → (可选 rerank) → 拼接上下文 → LLM 生成
1. 切块(chunking)
文档不能整篇做向量,需要切成若干”块”。块是检索单元,也是最终拼进上下文的单元。常见策略:
- 固定长度切分:按 Token 数或字符数切,比如每 500 Token 一块,带 50 Token 的重叠。实现简单,但会把一个完整的段落或表格拦腰切断。
- 结构化切分:按标题、段落、列表、代码块等语义边界切。效果更自然,但对解析器有要求。
- 父子块(small-to-big):用小块做检索匹配,命中后返回包含它的大块给模型。兼顾检索精度和上下文完整性。
重叠(overlap)是为了避免语义在边界处丢失:相邻块有少量重复内容,跨边界的句子也能被召回。
2. 向量化(embedding)
块文本经过 embedding 模型变成高维向量(常见维度在几百到几千),语义相近的文本在向量空间里距离更近。检索”登录失败怎么办”时,即使文档里写的是”认证错误处理”,向量相似度也能把它捞出来——这是关键词检索做不到的。
3. 向量库
向量库负责存储和近似最近邻(ANN)检索。选型时看几个维度:是否支持元数据过滤(比如只搜某个产品线的文档)、是否支持混合检索(向量 + 关键词)、能否横向扩展、运维成本如何。数据量不大的场景,很多轻量方案甚至直接用内存索引就够了。
4. 检索与重排序
向量召回通常是”粗排”:快,但排序未必精确。重排序(rerank) 是在召回之后加一道精排:用专门的 rerank 模型对”问题-文档对”打相关性分数,把最相关的几条排到最前面。rerank 的位置在检索之后、拼接上下文之前,它是提升答案质量性价比很高的一环,代价是多一次模型推理、增加一些延迟。
5. 拼接与生成
把排好序的块按固定格式拼进 prompt,通常会带上来源标识,方便模型引用和事后溯源。这里有两个工程细节:
- 控制总 Token 量,给问题和答案留出空间。
- 对模型明确指令:”只依据提供的材料回答,材料里没有的信息就说不知道”。这条指令不能百分之百杜绝幻觉,但能明显降低编造概率。
三、chunk 大小怎么权衡
chunk 大小是 RAG 里最值得调的超参数,没有之一:
| chunk 偏小 | chunk 偏大 |
|---|---|
| 检索更精准,命中粒度细 | 上下文更完整,语义连贯 |
| 单块信息量少,可能答不全 | 噪声多,容易稀释重点 |
| 向量数量多,索引更大 | 边界模糊,语义被平均化 |
实践上可以按文档类型分别配置:FAQ 类小块,技术手册按小节切,法律合同按条款切。统一用一个固定大小去切所有文档,往往是最差的选择。
四、评估:没有评估的 RAG 是在盲调
RAG 的输出由”检索质量”和”生成质量”共同决定,出问题要能定位是哪一环。常见指标分两层:
检索层(离线可算)
- 召回率:正确答案所在的块,被检索回来的比例。召回率低,后面生成再强也救不回来。
- 排序质量:正确块是否排在前面(比如 MRR、NDCG 一类指标)。排太后,截断拼接时就轮不到它。
生成层
- 忠实度(faithfulness):答案是否严格基于检索到的材料,有没有加入材料之外的”私货”。
- 答案相关性:是否正面回答了用户的问题,而不是答非所问。
- 引用正确性:如果要求模型标注来源,引用是否真实对应答案内容。
一套实用的评估流程是:准备几十条”问题 + 期望答案要点”的测试集,每次调整 chunk 策略或检索参数后跑一遍,看召回率和忠实度的变化。没有测试集的 RAG 调优,基本是在凭感觉换参数。
五、RAG 还是微调
两者解决的问题不同,不是替代关系:
| 维度 | RAG | 微调 |
|---|---|---|
| 知识更新 | 改文档即可,即时生效 | 需要重新训练,周期长 |
| 私有数据 | 天然适配,知识不进模型权重 | 知识进权重,更新和回滚成本高 |
| 可溯源 | 答案可指向具体来源 | 难以说明某个说法出自哪里 |
| 风格/任务适配 | 靠提示词,能力有限 | 擅长调整语气、格式、领域习惯 |
| 依赖条件 | 需要向量库与检索链路 | 需要训练数据与算力 |
选择的判断题其实很简单:你要解决的是”知识”问题还是”行为”问题?
- 让模型知道某份内部文档的内容、需要引用来源、知识频繁更新 → RAG。
- 让模型的输出风格稳定在某种格式、适应某个垂直领域的表达习惯、学会某种固定的任务套路 → 微调更合适。
两者也可以叠加:先微调出符合领域习惯的基础模型,再用 RAG 提供最新事实。工程上从 RAG 起步通常更稳,因为它的效果可以逐环节调试,而微调的效果混合在权重里,出了问题更难归因。
六、几个容易踩的坑
- 文档解析质量差。PDF 的表格被抽成乱码、图片里的文字丢失,检索做得再好也是在垃圾里找垃圾。索引构建前先检查解析结果。
- 元数据缺失。没有来源、时间、产品线等元数据,就无法做时效性过滤和权限过滤。面向企业内部的问答,权限控制是硬需求,不能靠模型”自觉”。
- 忽略在线延迟。检索、rerank、生成三段串行,很容易把首字延迟推高。该缓存的检索结果要缓存,该并行的步骤要并行。
- 测试集太乐观。只用写测试的人自己想的问题,真实用户的提问方式往往完全不同。测试问题最好来自真实日志或用户反馈。
RAG 没有银弹,它的本质是把”模型不知道”的问题,转化成”检索准不准、拼得好不好、生成守不守规矩”三个可度量、可迭代的工程问题。这恰恰是它最大的价值所在。
评论(2)
我用的是另一个版本,行为略有不同,但原理一致。
第三节讲的 merge 和 rebase 区别,正好解了我上周的困惑。
请 登录 后参与评论。