检索增强生成(Retrieval-Augmented Generation, RAG)是当前企业知识库问答的主流架构。这篇教程从预训练模型的结构性痛点出发,讲清 RAG 的离线索引与在线检索全链路、Chunk 切分与评估方法,并给出它与搜索、微调、长上下文的选型边界。

它解决什么问题

预训练大语言模型(LLM)有三个结构性痛点:

  1. 知识截止:模型参数中的知识冻结在训练数据截止的那一刻,之后的新政策、新产品文档、新事件它天然不知道。
  2. 私有数据不可见:企业内部的制度、工单、代码库从未进入训练语料,直接提问只能得到泛泛而谈或凭空编造的答案。
  3. 幻觉(Hallucination):模型对不知道的内容也会流畅地编造,且语气自信,人工甄别成本高。

RAG 的定位是:不改模型参数,在回答前先从外部知识库检索相关片段,把证据连同问题一起交给模型,让答案落在可核对、可引用的材料上。它解决的是「模型不知道」的问题,而不是「模型不会按你的方式表达」的问题——后者属于微调(Fine-tuning)的领域,这个区分是后续一切选型的前提。

核心原理

RAG 的工程链路分两段:离线索引(Indexing)与在线检索生成(Retrieval & Generation)。

离线索引

  1. 摄入(Ingestion):接入 PDF、Word、网页、数据库记录等原始文档。
  2. 清洗:去除 HTML 标签、页眉页脚、乱码等噪声,保留正文结构。
  3. 元数据补充:打上时间戳、部门、文档类型、权限标签,供检索阶段过滤。
  4. 切分(Chunking):把长文档切成语义相对完整的片段。
  5. 向量化(Embedding):用嵌入模型把每个片段映射为高维稠密向量。
  6. 入库:向量、原文、元数据一起写入向量存储(如 Milvus、pgvector、Elasticsearch 向量字段等)。

在线链路

  1. Query 改写:把口语化提问改写或扩写成更适合检索的形式,多轮对话中还要消解指代。
  2. 向量检索:Query 经同一个 Embedding 模型向量化后,在向量库中做近似最近邻(ANN)搜索,捞出 top-k 片段。
  3. 上下文组装:把检索片段、系统指令、引用要求和原始问题拼装成 Prompt。
  4. 生成 + 引用:LLM 基于证据生成答案,并标注引用来源,便于用户溯源核对。

Embedding 的几何直觉

嵌入模型把文本映射到高维空间,训练目标是让语义相近的文本在该空间中距离更近。于是「找相关资料」这个模糊问题被转化为一个几何问题:在向量空间里找离 Query 最近的邻居。相似度最常用余弦相似度(Cosine Similarity)度量——它只关注向量方向是否一致,对向量长度不敏感,恰好契合「语义方向接近」的检索目标。一个容易踩的坑是:查询与文档必须使用同一个 Embedding 模型,否则两者不在同一坐标系,算出来的距离没有意义。

深入机制

Chunk 切分策略

切分粒度直接决定召回质量:

  • 固定长度切分:按字符或 token 数切,带一定重叠(overlap)防止句子被拦腰截断。实现最简单,但容易切断语义单元。
  • 语义切分:按段落、标题层级或语义突变点切,保持单元完整,代价是实现复杂、块大小不均。
  • 父子块(Parent-Child):小块用于检索(匹配精度高),命中后把所属父块喂给模型(上下文更完整),兼顾精度与语境。

块太大会引入噪声、稀释关键信息;块太小则丢失上下文、答案碎片化。合理尺寸没有通用值,必须在业务评测集上调出来。

top-k 与相似度阈值

top-k 决定给模型看多少证据:太小可能漏掉关键片段导致召回不足;太大则塞入噪声,推高 token 成本,还会加剧「Lost in the Middle」式的注意力稀释——关键信息埋在长上下文中间时更容易被模型忽略。相似度阈值用于拒答:当最高分仍低于阈值时,说明库里大概率没有答案,应直接告知用户而非硬答。工程上常见的组合是先放宽 top-k 保证召回,再用重排(Rerank)模型精排,最后按阈值截断。

检索增强的 Prompt 组装

组装时通常包含:系统指令(要求仅依据证据回答、证据不足时明确说明)、带编号和来源的检索片段、用户问题。一段极简示例:

def answer(question: str, index, embed_model, llm, top_k: int = 5, threshold: float = 0.5):
q_vec = embed_model.embed(question)
hits = index.search(q_vec, top_k=top_k) # 近似最近邻检索
hits = [h for h in hits if h.score >= threshold]
if not hits: # 低分拒答
return "知识库中未找到相关资料。", []
context = "\n\n".join(f"[{i+1}] {h.text}" for i, h in enumerate(hits))
prompt = (
"请仅依据以下资料回答问题,资料不足时明确说明。\n\n"
f"{context}\n\n问题:{question}"
)
return llm.generate(prompt), [h.source for h in hits]

工程实践

  • 文档解析质量是第一道坎。PDF 的表格、双栏排版、扫描件 OCR 错误都会污染切片;在解析环节的投入,收益往往大于在检索算法上的折腾。
  • 增量更新:知识库持续变化,索引应支持按文档 ID 粒度增删改,避免每次全量重建;同时维护向量与源文档版本的对应关系,防止引用指向过期内容。
  • 权限过滤(ACL):按用户身份在检索侧过滤无权访问的片段。过滤必须发生在内容进入模型上下文之前,不能只靠在 Prompt 里叮嘱模型保密。
  • 评估指标:检索侧看召回率(Recall@k)与排序质量;生成侧看答案忠实度(Faithfulness,答案是否被证据支撑)和相关性。没有标注好的评测集,任何「优化」都无法验证。
  • 延迟与成本:改写、向量化、检索、重排、生成每一步都叠加耗时;喂给模型的片段越多,输入 token 越贵。生产系统通常需要缓存、流式输出和片段数量预算。

对比与权衡

维度 RAG 传统搜索引擎 微调 长上下文直塞
知识时效 更新索引即可生效 更新索引即可生效 需重新准备数据并训练,周期长 每次请求自带材料
私有数据 留在知识库,按需检索 留在索引中 模式会固化进参数,难撤回 每次全量传入,难控权限
成本结构 检索 + 输入 token + 向量库运维 低,链路短 训练算力 + 数据标注 + 版本管理 输入 token 随材料线性增长
幻觉控制 可引用溯源,但有残余幻觉 不生成内容,无幻觉但也无答案 仍会编造,且引用不天然可见 材料内可靠,材料外照旧
适用场景 知识密集问答、跨文档总结 找原文、找模板、精确查找 风格、格式、任务行为定制 单份长文档的深度分析

选型建议:用户想「找到材料」用搜索,想「读完材料给结论」用 RAG;知识频繁变动、需要引用来源,优先 RAG;输出风格和格式不稳定,考虑微调;材料少而深,直接长上下文。实践中常见组合是搜索与 RAG 双入口并存,或微调定风格、RAG 供知识。

面试视角

RAG 和微调怎么选? 看问题性质:模型「不知道」(缺知识、知识过期、私有数据)用 RAG;模型「不会做」(风格、格式、术语习惯不符)用微调。知识更新成本上,RAG 改索引即可,微调要重新训练;两者也常组合,微调对齐领域表达,RAG 提供实时知识。

如何评估一个 RAG 系统? 拆成检索和生成两层:检索层用 Recall@k 等指标衡量证据是否捞全、排对;生成层用忠实度和答案相关性衡量是否忠于证据。前提是有一个人工标注的「问题—证据—答案」评测集,并持续回归。

幻觉从哪些环节产生,怎么治理? 三个来源:检索错(召回无关片段)、组装错(噪声淹没关键证据)、生成错(模型不遵循证据或过度推断)。治理手段分别对应混合检索与重排、控制上下文长度并去噪、低分拒答加强制引用,必要时对答案做引用校验。

Embedding 模型怎么选? 看语种与领域匹配度、向量维度带来的存储与计算开销、部署形态(API 还是私有化)。公开榜单(如 MTEB)只作初筛,最终必须在自有业务语料上实测召回效果;查询与文档必须共用同一模型。

有了长上下文还需要 RAG 吗? 需要。长上下文解决「读一份长材料」,解决不了海量知识库的检索成本、权限隔离与引用溯源;上下文过长还会推高延迟并稀释注意力。

小结

  1. RAG 的定位是「回答前先检索证据」,缓解知识截止、私有数据、幻觉三个痛点,但不能消灭幻觉。
  2. 链路分离线索引与在线检索生成;Embedding 把语义匹配转化为向量空间的最近邻问题,查询与文档必须同模型。
  3. Chunk 切分、top-k、相似度阈值与重排共同决定召回质量,是调优的主战场。
  4. 生产落地的难点在解析质量、增量索引、ACL 权限与评测闭环,而非接入向量库本身。
  5. 选型看用户目标:找文档用搜索,读文档答问题用 RAG,定风格用微调,单份长材料用长上下文。