大模型知道很多事,但不知道你公司昨天刚发的制度、你自己写的项目文档,也不知道最新一期的行业报告。微调可以把知识“灌”进模型,但成本高、更新慢,还容易遗忘。RAG(Retrieval-Augmented Generation,检索增强生成)换了一个思路:不改模型,而是在回答问题前,先把相关资料找出来,连同问题一起交给模型。

打个比方,闭卷考试考的是模型记住了多少;RAG 是开卷考试,考的是它能不能在资料里找对地方、读懂内容、组织答案。

1. 一张图看懂 RAG

RAG 的流程可以分成两条链路:离线索引和在线问答。看图比看文字快:

图 1 · RAG 的两条链路与三个阶段
图 1 · RAG 的两条链路与三个阶段

上半部分是离线索引,把文档变成可检索的向量;下半部分是在线问答,从一句提问走回一份带引用的答案。两条链路共用同一个 Embedding 模型和向量维度,这是整张图里唯一一处“必须对齐”的约束。

对应到经典的三阶段划分:

阶段 输入 输出 核心目标
索引 Indexing 原始文档 可检索的向量与元数据 把知识整理成“找得到”的形态
检索 Retrieval 用户问题 若干相关文本块 在有限时间内找全、找准
生成 Generation 问题 + 上下文 答案与引用 基于证据回答,而不是自由发挥

离线链路通常只在文档变更时运行,在线链路则要在几百毫秒到几秒内完成。两条链路使用同一个 Embedding 模型,否则向量空间对不上,检索结果会非常离谱。

2. 索引流程

索引阶段的目标只有一个:让知识变得“找得到”。下面五步,每一小步的粗糙程度都会在后文被放大。

图 2 · 索引流程的五个步骤
图 2 · 索引流程的五个步骤

2.1 文档解析

第一步是把各种格式的文档变成纯文本,同时尽量保留结构信息。

  • PDF 是最麻烦的:双栏排版、页眉页脚、表格、扫描件都会干扰抽取。文本型 PDF 可以用 pypdf、pdfplumber,复杂版式建议用 unstructured、docling 这类版面感知工具;扫描件需要先走 OCR。
  • Word、Markdown、HTML 相对简单,但要保留标题层级、列表、代码块和表格。
  • 表格最好转成 Markdown 或“字段: 值”的描述,直接拍平成一行文本会丢失列与列的对应关系。
  • 图片、流程图可以考虑多模态模型生成描述,再作为文本入库。

解析质量决定了整个系统的上限。垃圾进,垃圾出,后面再好的模型也救不回来。

2.2 清洗与元数据

解析出来的文本通常带有大量噪音:导航栏、版权声明、重复页眉、断行、乱码。需要做去重、去噪、段落合并和编码修正。

比清洗更容易被忽略的是元数据。每个文本块至少应该带上:

  • 来源文件、页码、章节标题
  • 文档版本、更新时间
  • 租户、部门、密级等权限字段
  • 文档类型(制度、手册、FAQ、代码文档)

元数据决定了检索时能不能做过滤,也决定了答案能不能给出可追溯的引用。没有元数据的 RAG,回答错了很难定位原因。

2.3 分块(Chunking)

文本太长不能整篇塞进模型,需要切成小块。分块是 RAG 中最值得反复调优的环节之一。

策略 做法 优点 适用场景
固定长度 按字符或 token 切,块间重叠 简单、块大小均匀 结构混乱的纯文本
递归切分 按段落、句子、标点逐级切 尽量保留自然边界 通用默认方案
结构感知 按标题、章节、表格切 语义完整、便于引用 Markdown、HTML、规范文档
语义分块 相邻句子向量相似度骤降时断开 主题更聚焦 长篇文章、会议记录
父子分块 索引小块,返回其所属大块 检索准、上下文全 问答精度要求高

几个实践经验:

  • 块太小,语义不完整,模型看不懂;块太大,噪音多,向量被稀释。中文场景可以从 300~600 字、重叠 10%~20% 开始试。
  • 不要把一个标题和它的正文切散;标题往往是最好的语义信号,可以在每个块前面拼上章节路径。
  • 表格、代码、操作步骤尽量保持完整,不要从中间截断。
  • 分块参数没有标准答案,最终要靠评估集来定。

2.4 向量化(Embedding)

Embedding 模型把文本映射成高维向量,语义相近的文本在向量空间里距离更近。

常见选择:

  • 中文/多语言:bge-m3、bge-large-zh-v1.5、text-embedding-v3、jina-embeddings-v3
  • 英文为主:text-embedding-3-small/large、e5 系列
  • 本地部署:bge 系列配合 sentence-transformers,成本可控,数据不出内网

注意几点:

  1. 文档和问题必须用同一个模型,维度也要一致。
  2. 相似度通常用余弦相似度;向量归一化后,内积等价于余弦相似度。
  3. 有些模型区分“文档侧”和“查询侧”输入,比如 BGE 中文模型建议给查询加指令前缀,效果会更好。
  4. 换模型意味着全量重建索引,选型时要考虑长期维护成本。

2.5 建索引与存储

向量写入向量数据库,常见选项有 Milvus、Qdrant、Weaviate、pgvector、FAISS、Elasticsearch/OpenSearch 的向量能力。

索引结构上:

  • Flat:暴力精确检索,适合几万条以内的小数据量。
  • HNSW:图索引,召回高、延迟低,内存占用较大,是大多数场景的默认选择。
  • IVF / PQ:通过聚类和量化压缩内存,适合亿级数据,但会损失一些精度。

工程上还要考虑:增量更新、删除与版本管理、多租户隔离、快照备份,以及混合索引——同时保存稠密向量和 BM25 倒排,后面检索时可以两路召回。

3. 检索流程

检索的难点在于:用户问得很随意,而文档写得很正式。中间要做的事,就是把两者对齐。

图 3 · 检索流程:改写、双路召回、重排、组装
图 3 · 检索流程:改写、双路召回、重排、组装

3.1 查询改写

用户的原始问题往往不适合直接检索:有指代(“它怎么配置”)、有口语化表达、有多层意图,或者依赖上一轮对话。

常见的改写手段:

  • 对话补全:把“它的默认端口是多少”改写成“Redis 的默认端口是多少”。
  • 多查询扩展:让模型生成 3~5 个同义或不同角度的查询,分别检索后合并结果。
  • 查询分解:把“A 和 B 有什么区别”拆成两个子问题,分别检索。
  • HyDE:先让模型生成一段假设性答案,再用这段答案去检索。答案和文档的表述风格更接近,有时能提升召回。
  • 意图与过滤条件抽取:识别时间、部门、文档类型等条件,转成元数据过滤。

改写不是越多越好。每多一步就多一次模型调用和延迟,简单问题直接检索反而更稳。

3.2 召回

召回的目标是“宁可多拿一些,别漏掉”。常见做法:

  • 稠密检索:问题向量与块向量做近似最近邻搜索,取 Top-K(常见 20~100)。
  • 稀疏检索:BM25 对专有名词、错误码、型号、数字特别有效,正好弥补向量检索的短板。
  • 混合检索:两路结果用 RRF(倒数排名融合)或加权分数合并,通常比单路更稳。
  • 元数据过滤:先按租户、时间、文档类型过滤,再在子集内做向量搜索,既提精度又避免越权。
  • 多索引路由:不同知识域建不同集合,先判断问题属于哪个域,再去对应集合检索。

3.3 重排(Rerank)

向量召回追求的是速度和召回率,排序未必准。重排模型(Cross-Encoder)把“问题 + 文档块”一起输入,直接打分,精度明显更高,但计算量大,所以只对少量候选做。

典型链路是:召回 50 条 → 重排取前 5~10 条。常用模型有 bge-reranker 系列、Cohere Rerank 等。

除了相关性,还可以考虑:

  • MMR(最大边际相关性):在相关和多样之间取平衡,避免返回一堆内容重复的块。
  • 分数阈值:低于阈值的结果直接丢弃,宁可不答,也不要拿不相关内容硬答。
  • 时间衰减:制度、价格、版本类文档,旧版本应该降权。

3.4 上下文组装

拿到候选块后,不能简单拼接了事。需要做去重、按来源合并、控制 token 预算,并给每个块加上编号或来源标记,方便模型引用。

还有一个容易被忽略的现象:模型对上下文开头和结尾的内容更敏感,中间部分容易被忽略(Lost in the Middle)。因此:

  • 最相关的块放在前面;
  • 同一文档的块尽量按原文顺序排列,避免逻辑断裂;
  • 总长度控制在模型窗口的合理比例内,别把 10 个块硬塞进去。

4. 生成流程

生成阶段的 Prompt 通常包含四部分:角色与任务、参考资料、回答约束、用户问题。

你是一个严谨的知识库助手。请仅根据下面的参考资料回答问题。

要求:
1. 如果资料不足以回答,明确说“根据现有资料无法确定”,不要编造。
2. 回答中的关键结论用 [1]、[2] 标注来源。
3. 如果多个资料冲突,指出冲突并说明各自的出处。
4. 使用简洁的中文,必要时分点说明。

参考资料:
[1] {chunk_1}
[2] {chunk_2}
...

用户问题:{question}

几个关键约束:

  • 只依据上下文回答,这是抑制幻觉的第一道防线。
  • 要求引用,既方便用户核对,也方便排查是检索错了还是生成错了。
  • 允许拒答,比一本正经地胡说八道更有价值。
  • 结构化输出:需要接入下游系统时,用 JSON Schema 或函数调用约束格式。

生成之后还可以做后处理:检查引用编号是否存在、答案是否包含上下文中没有的实体、敏感信息是否需要脱敏。对可靠性要求高的场景,可以让另一个模型做一次“忠实性校验”。

5. 进阶形态

基础 RAG 是“一次检索、一次生成”。遇到复杂问题,可以在此基础上扩展:

  • Agentic RAG:让模型自己决定要不要检索、检索什么、是否继续追问,支持多轮工具调用。
  • Self-RAG / CRAG:模型对检索结果做相关性判断,不够就改写查询重试,或者退回网络搜索。
  • GraphRAG:从文档中抽取实体和关系构建知识图谱,适合“全局总结”“跨文档关联”这类向量检索不擅长的问题。
  • 多模态 RAG:文本、图片、表格统一向量化,回答时可以引用图表。
  • 记忆机制:把历史对话和用户偏好沉淀下来,避免每轮都从零检索。

这些方案不是越复杂越好。先用基础 RAG 跑通评估,再针对具体失败案例加组件,通常比一上来就搭 Agent 更有效。

6. 如何评估

RAG 的评估要拆开看,否则出了问题不知道该修检索还是修生成。下图把“指标—归因—回归”串成了一个闭环:

图 4 · 评估分层与坏例归因的闭环
图 4 · 评估分层与坏例归因的闭环
环节 指标 说明
检索 Recall@K 正确答案所在的块有没有被召回
检索 MRR / nDCG 正确结果排得够不够靠前
生成 Faithfulness 答案是否忠实于上下文
生成 Answer Relevancy 答案是否切题
生成 Context Precision / Recall 上下文是否精准且覆盖充分
端到端 人工通过率、用户采纳率 最终业务效果

实践建议:

  1. 从真实业务里整理 50~200 条问题,标注标准答案和对应文档,作为回归集。
  2. 每次调整分块、模型、Top-K、Prompt,都跑一遍同一套评估,避免“感觉变好了”。
  3. 建立坏例分析习惯:是没召回、召回但不相关、还是召回了但模型没用上?三类问题的解法完全不同。
  4. 线上记录问题、召回结果、最终答案和用户反馈,持续补充评估集。

7. 常见坑

  • 只调模型,不调数据:多数 RAG 效果差,根源在解析和分块,而不是模型不够强。
  • 块切得太碎:检索命中率高,但模型拿到的上下文残缺,答案似是而非。
  • 只用向量检索:遇到型号、错误码、人名等精确匹配时召回很差,应该加 BM25 做混合检索。
  • Top-K 越大越好:无关内容会稀释注意力,增加延迟和成本,重排后保留 5~10 条通常足够。
  • 没有权限过滤:检索阶段就把无权访问的文档过滤掉,不要等生成后再脱敏。
  • 索引不更新:文档变了索引没变,答案就会“过期”。需要建立增量同步和版本机制。
  • 没有评估集:每次改完靠感觉判断,问题和优化都无法复现。
  • 忽视 Prompt 注入:文档里可能藏有恶意指令,需要在 Prompt 中明确“资料只是资料”,并对输出做检查。

8. 一个最小可用的流程

把上面的步骤压缩成伪代码,大概是这样:

# ---------- 离线索引 ----------
docs = load_and_parse("docs/") # 解析 PDF / Word / Markdown
chunks = split(docs, size=500, overlap=80) # 按结构或递归分块
chunks = attach_metadata(chunks) # 来源、页码、章节、权限
vectors = embed_documents(chunks) # 与查询侧使用同一模型
vector_store.upsert(chunks, vectors)

# ---------- 在线问答 ----------
def answer(question, history, user):
query = rewrite_query(question, history) # 补全指代、扩展查询
query_vector = embed_query(query)
candidates = vector_store.search(
query_vector,
top_k=50,
filter={"tenant": user.tenant}, # 权限与范围过滤
)
candidates = rerank(query, candidates, top_k=8) # 精排
context = build_context(candidates, max_tokens=3000, with_citation=True)
return llm.generate(prompt=QA_PROMPT, question=question, context=context)

更完整的可运行示例(LangChain + FAISS + 通义千问),可以看之前的 RAG 练习 一文。

9. 总结

RAG 的流程并不复杂:把文档整理好、切好、向量化存起来;用户提问时找回来、排好序、拼进 Prompt,再让模型基于证据回答。

真正拉开差距的是细节:解析是否干净、分块是否合理、检索是否混合、重排是否到位、Prompt 是否克制,以及有没有一套能持续回归的评估集。模型只是流程中的一环,RAG 的效果上限,很大程度上由检索质量决定。

落地时不妨按这个顺序推进:先跑通最小链路,再建评估集,然后针对坏例逐个优化。每加一个组件,都要能回答一个问题:它解决了哪类失败案例,代价是多少延迟和成本。


本站由 卡卡龙 使用 Stellar 1.33.1主题创建

本站访问量 次. 本文阅读量 次.