RAG 工程化:从文档到可评估的知识增强系统
1. 这一篇解决什么问题
RAG 不是把文档切块塞进向量库这么简单。工程化 RAG 要解决文档解析、清洗、切块、Embedding、索引、检索、重排、引用、评测、反馈和迭代。
Demo 版本的 RAG 通常只需要几步:上传文档、切分文本、写入向量库、检索 TopK、拼接上下文、调用模型。但真实项目中,每一步都会影响最终质量。
如果 RAG 回答不好,问题不一定在模型,也可能在文档解析失败、Chunk 粒度不合适、Embedding 版本混乱、过滤条件过严、Rerank 缺失、上下文构建错误或引用信息丢失。
2. 学习目标
- 理解 RAG 的完整工程链路。
- 掌握文档解析、清洗和 Chunk 策略。
- 理解 Embedding 版本管理的重要性。
- 掌握向量检索、关键词检索、Hybrid Search 和 Rerank 的适用场景。
- 理解引用溯源、评测指标和失败样本库。
- 能把 RAG 能力迁移到真实 Agent 项目中。
3. RAG 基础链路
文档上传 → 文档解析 → 文本清洗 → Chunk 切分 → Embedding → 入库 → Query 改写 → 检索 → Rerank → 上下文构建 → 生成答案 → 引用溯源 → 质量评估文档上传
文档上传是知识进入系统的入口。上传阶段要记录文档来源、上传用户、文件类型、大小、hash 和权限信息。
如果不记录来源和权限,后续检索时就无法做引用溯源和访问控制。
文档解析
文档解析把 PDF、Word、Markdown、HTML、表格等格式转换成可处理文本。解析阶段要尽量保留标题、页码、段落和表格结构。
解析失败不能静默忽略。失败原因应该记录到数据库,方便重试和人工排查。
文本清洗
文本清洗用于去除页眉页脚、导航栏、广告、重复空白和无意义字符。清洗的目标不是“越干净越好”,而是保留对问答有用的信息。
过度清洗可能删除标题、编号或表格含义,反而降低检索质量。
Chunk 切分
Chunk 切分决定了知识被检索的最小单元。Chunk 太大会引入噪声,Chunk 太小会丢失上下文。
切分时要记录 document_id、chunk_id、章节、页码、顺序号和 token 数,方便引用和排查。
Embedding
Embedding 把 Chunk 文本转换成向量表示。Embedding 模型的语言能力、领域适配和向量维度都会影响召回质量和成本。
工程上必须记录 embedding_model、embedding_version 和 chunk_hash,否则后续很难重建和对比。
入库
入库包括写数据库和写向量库。数据库保存文档、Chunk、版本和元数据,向量库保存向量和检索所需 metadata。
入库要支持幂等。重复上传同一文档时,应能通过 content_hash 判断是否需要重新处理。
Query 改写
用户问题可能过短、口语化或缺少上下文。Query 改写用于补全语义、统一术语或生成多个检索表达。
改写不是必须每次都做。对于明确查询,过度改写可能改变用户意图。
检索
检索负责从知识库中找候选 Chunk。常见方式包括向量检索、关键词检索和 Hybrid Search。
生产环境检索通常还要结合 metadata filter,例如租户、权限、文档类型和时间范围。
Rerank
初始检索结果不一定排序准确。Rerank 使用更精细的模型或规则对候选结果重新排序。
常见策略是先召回较多候选,例如 Top20,再通过 Rerank 选出 Top5 进入上下文。
上下文构建
上下文构建把候选 Chunk 组织成模型输入。需要控制总长度、去重、保留来源标识,并避免把互相矛盾或权限不一致的内容混在一起。
上下文构建质量会直接影响答案质量。检索到了正确内容,但上下文组织混乱,模型仍可能答错。
生成答案
生成阶段要求模型基于上下文回答。如果上下文不足,应该允许模型说明信息不足,而不是强行编造。
生成结果应和引用信息一起返回,便于用户验证。
引用溯源
引用溯源把答案中的依据连接回 document_id、chunk_id、source_uri 和页码。
引用不是装饰,而是可信度证据。没有引用的答案很难评测和排错。
质量评估
质量评估用于判断 RAG 版本是否变好。评测要覆盖检索质量、上下文质量、答案相关性、事实一致性和引用准确性。
没有评测的 RAG 系统,效果好坏只能依赖人工感觉,无法稳定迭代。
4. 文档解析
不同文档来源的处理重点不同。
PDF
PDF 常见问题包括多栏排版、页眉页脚、表格、扫描件和分页断句。解析时要尽量保留页码和段落位置。
如果是扫描件,需要 OCR。OCR 结果可能有错别字和断行问题,应记录 OCR 置信度或解析质量。
Word
Word 文档通常有标题层级、列表、表格和嵌入对象。解析时要保留标题层级和段落结构。
如果文档中包含图片或复杂表格,可以先保留占位信息,再通过专门流程处理。
Markdown
Markdown 的结构相对清晰,标题、列表、代码块和表格都比较容易识别。切分时可以优先按标题层级处理。
代码块要保持完整,不要把一个代码块切成多个不连续片段。
HTML
HTML 解析要去除导航、广告、脚注和推荐内容,只保留正文。还要注意标题、链接和表格。
对于网页文档,source_uri 很重要,后续引用需要能回到原始页面。
表格
表格不能简单按行拼接。表头、单位和列含义对理解非常关键。
一种常见做法是把表格转换成结构化文本,例如“列名: 值”的形式,或者同时保存原始表格结构和文本摘要。
图片 OCR
图片 OCR 可以简单接入,但不建议在初版中过度展开。工程上至少要记录 OCR 来源、识别结果和失败原因。
5. Chunk 策略
| 策略 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 固定长度切分 | 实现简单,Chunk 大小稳定 | 容易切断语义 | 文本结构弱、快速原型 |
| 按标题切分 | 保留文档结构,语义完整 | Chunk 大小可能不均匀 | Markdown、技术文档、手册 |
| 按语义切分 | 更符合自然语义边界 | 成本较高,实现复杂 | 高质量知识库、长文档问答 |
| 滑动窗口 | 保留相邻上下文 | 增加重复内容和存储成本 | 需要上下文连续性的文档 |
| Parent-Child Chunk | 兼顾小粒度召回和大上下文生成 | 数据结构更复杂 | 长文档、章节型知识库 |
| Chunk overlap | 降低边界信息丢失 | overlap 过大导致重复召回 | 段落边界不稳定的文本 |
Chunk overlap 要适度。重叠太小会丢失边界信息,重叠太大会导致检索结果重复,影响上下文多样性。
Parent-Child Chunk 的思路是用小 Chunk 做召回,用父级段落或章节做上下文补充。这样可以提高召回精度,同时保留回答所需背景。
6. Embedding 版本管理
工程化 RAG 必须记录以下信息:
embedding_modelembedding_versionchunk_hash- 向量维度
- 生成时间
模型变更后需要重建向量。不同 Embedding 模型的向量空间通常不兼容,不能简单混用。
不同版本混用会影响召回稳定性。一次查询可能在旧模型向量和新模型向量之间比较,分数没有可比性,召回结果会变得不可解释。
chunk_hash 用于判断 Chunk 内容是否变化。如果文档重新上传但 Chunk 文本不变,可以避免重复生成 Embedding。
7. 检索策略
| 策略 | 说明 | 适用场景 |
|---|---|---|
| 向量检索 | 根据语义相似度召回 Chunk | 用户问题和文档表达不同但语义相近 |
| 关键词检索 | 根据关键词、BM25 等方式召回 | 专有名词、编号、错误码、精确短语 |
| Hybrid Search | 融合向量检索和关键词检索 | 企业知识库、技术文档、工单系统 |
| Metadata Filter | 按租户、权限、类型、时间过滤 | 多用户、多知识库、权限敏感场景 |
| Query Rewrite | 改写用户问题以提升召回 | 问题口语化、缺少上下文、术语不统一 |
| Multi-query | 生成多个查询表达再融合结果 | 问题复杂或表达可能有多种角度 |
| Rerank | 对候选结果重新排序 | 初始召回有噪声但候选中包含正确答案 |
向量检索适合语义匹配,但对编号、型号、错误码不一定稳定。关键词检索适合精确匹配,但对同义表达不够灵活。
Hybrid Search 在 RAG 工程中很常见,因为真实业务问题往往既有语义表达,也包含专有名词和结构化条件。
8. 引用溯源
答案必须能返回 document_id、chunk_id、source_uri 和 page。如果是网页或系统内文档,也可以返回章节、标题和段落位置。
引用不是装饰,而是可信度证据。用户可以根据引用判断答案是否来自正确资料,评测系统也可以根据引用判断检索是否命中目标文档。
没有引用的答案很难评测和排错。即使答案看起来正确,也无法判断它是基于知识库生成,还是模型根据自身知识猜出来的。
工程上建议把引用作为响应模型的一部分,而不是让模型在自然语言中自由生成引用。
9. RAG 评测指标
| 指标 | 关注点 |
|---|---|
| Recall@K | 正确文档或 Chunk 是否出现在前 K 个检索结果中 |
| MRR | 第一个正确结果排在多靠前 |
| Context Precision | 进入上下文的内容中有多少是相关的 |
| Context Recall | 答案所需信息是否被上下文覆盖 |
| Faithfulness | 答案是否忠实于给定上下文 |
| Answer Relevance | 答案是否真正回答了用户问题 |
| Citation Accuracy | 引用是否指向支持答案的正确来源 |
| Human Review | 人工从业务正确性和可用性角度复核 |
Recall@K 适合评估召回阶段。如果正确文档没有进入候选集,后面的 Rerank 和生成再强也很难补救。
Faithfulness 关注答案是否基于上下文。它可以帮助发现模型在上下文不足时编造内容的问题。
Citation Accuracy 对企业 RAG 很关键。答案正确但引用错误,会降低可信度,也会影响问题排查。
10. 失败样本库
失败样本库应该记录以下类型:
- 检索不到。
- 检索到了但答案没用。
- 答案有幻觉。
- 引用错误。
- 文档解析失败。
- 用户反馈差。
失败样本不是简单收集错误答案,而是要记录问题、期望答案、实际答案、检索结果、引用、模型版本、检索参数和用户反馈。
失败样本可以用于迭代:
- 调整 Chunk。
- 调整检索参数。
- 加 Rerank。
- 改 Query Rewrite。
- 增加评测集。
例如,如果失败样本显示正确文档从未被召回,优先检查解析、Chunk、Embedding 和检索策略;如果正确文档被召回但答案错误,优先检查上下文构建和生成提示。
11. 最小实现伪代码
下面是一个示例设计:
def rag_query(question: str):
rewritten_query = rewrite_query(question)
candidates = hybrid_search(rewritten_query, top_k=20)
reranked = rerank(question, candidates)
context = build_context(reranked[:5])
answer = generate_answer(question, context)
citations = collect_citations(reranked[:5])
return {
"answer": answer,
"citations": citations,
}这个伪代码表达的是生产 RAG 的基本结构:先扩大候选召回,再精排,再构建上下文,最后生成答案和引用。
真实系统中还需要补充权限过滤、Trace 记录、错误处理、缓存、评测记录和用户反馈。
12. 工程化设计思路
RAG 工程化要把链路拆开看。解析、切块、向量化、检索、重排、生成和评测都应该有独立日志和可观测指标。
每次查询应该生成 run_id。在这个 run_id 下记录 Query Rewrite、候选 Chunk、Rerank 分数、最终上下文、答案、引用和用户反馈。
文档入库和查询链路要分离。文档入库通常是异步任务,查询链路要求低延迟,两者的性能目标不同。
13. 生产环境注意点
- 文档增量更新。
- 重复文档去重。
- 长文档解析失败处理。
- 向量库重建策略。
- Embedding 成本控制。
- 检索延迟优化。
- 敏感文档权限过滤。
- 用户反馈闭环。
文档增量更新要基于 content_hash 和 chunk_hash。只要文档内容没有变化,就不应该重复解析和重复生成 Embedding。
向量库重建要有计划。Embedding 模型升级、Chunk 策略变化或 metadata 结构变化,都可能需要批量重建向量。
敏感文档权限过滤必须在检索阶段生效。不能先检索出无权限内容,再指望生成阶段不使用。
14. 常见误区
误区一:只调向量库,不做评测
向量库只能完成相似度检索,不能证明系统效果好。没有评测指标,就无法判断改动是否有效。
误区二:Chunk 越大越好
Chunk 太大会带来噪声,降低上下文有效信息密度,也增加模型输入成本。
误区三:top_k 越大越好
TopK 太大可能引入无关内容,增加 Rerank 和模型上下文成本。TopK 要结合评测调参。
误区四:不保存引用
没有引用就无法验证答案来源,也很难判断检索是否命中正确文档。
误区五:不记录 embedding 版本
Embedding 版本混乱会让召回结果不可解释,模型升级后也无法稳定回滚或对比。
误区六:不做权限过滤
RAG 不是普通搜索。多用户和多租户场景中,检索阶段必须过滤无权限文档。
误区七:把 RAG 效果只归因于 Prompt
Prompt 会影响生成,但 RAG 质量还取决于解析、Chunk、Embedding、检索、Rerank、上下文构建和评测。
15. 和 AI Agent / RAG 项目的关系
在 RAG 工单系统中,RAG 链路负责从历史工单、产品文档和知识库中找到相关依据,再生成回答或工单建议。
Agent 系统可以把 RAG 作为一个工具调用。Agent 在执行任务时先检索知识,再决定是否调用其他工具或生成结果。
无论是直接 RAG 问答,还是 Agent 内部使用 RAG,都需要引用、权限过滤、Trace 和评测,否则系统不可解释也不可持续优化。
16. 面试表达
我会把 RAG 拆成解析、切块、向量化、检索、重排、生成、引用、评测几个环节。每个环节都可能影响最终效果,所以我不会只把问题归因于模型或 Prompt。
如果 RAG 效果不好,我会先判断是解析问题、召回问题、排序问题、上下文构建问题还是生成问题。比如正确文档没有被召回,就检查 Chunk、Embedding 和检索策略;正确文档被召回但答案错误,就检查上下文构建和生成约束。
生产级 RAG 需要评测集和失败样本库,而不是只靠人工感觉。每次调整 Chunk、Embedding、Rerank 或 Prompt,都应该通过指标和样本对比验证效果。
17. 后续学习 TODO
- 补充 RAG 评测集构建示例。
- 补充 Hybrid Search 的融合排序示例。
- 补充 Parent-Child Chunk 的数据结构示例。
- 补充用户反馈如何进入失败样本库。