Skip to content

RAG 工程化:从文档到可评估答案 ​

这篇文章解决什么问题 ​

很多人学习 RAG 时只关注"怎么接向量库"——上传文档、切分文本、写入向量库、检索 TopK、拼接上下文、调用模型。这个流程能跑通 Demo,但上线后会发现效果不稳定、答案不可信、问题无法定位。

真正做项目时,RAG 的效果取决于完整链路:

text
文档解析 → 文本清洗 → Chunk 切分 → Embedding → 向量入库 → Query 改写 → 检索 → Rerank → 上下文构建 → 答案生成 → 引用溯源 → 质量评测 → 失败样本迭代

任何一环出问题,最终答案质量都会受影响。文档解析失败会导致知识缺失,Chunk 粒度不合适会导致召回不准,Embedding 版本混乱会导致结果不可复现,没有引用溯源就无法判断答案依据,没有评测集就只能凭感觉判断效果。

核心观点:RAG 的难点不是把文档塞进向量库,而是建立一条可评估、可追踪、可迭代的知识增强链路。

RAG 为什么不能只理解为"向量库 + Prompt" ​

向量库只解决相似内容召回,不保证答案正确。用户问"订单 12345 的状态",向量检索可能召回一堆关于"订单状态"的文档片段,但不一定包含 12345 这个具体订单的信息。

Prompt 只能组织上下文,不解决文档解析和召回质量。如果检索结果本身就不对,Prompt 再精巧也救不回来。

没有引用溯源,就很难判断答案依据。答案看起来正确,但你不知道它是基于知识库生成的,还是模型根据自身知识猜出来的。如果是后者,知识库更新后答案可能悄悄变错。

没有评测集,就只能凭感觉判断效果。"感觉还行"不是工程标准。改了 Chunk 策略、换了 Embedding 模型、调了 top_k,如果没有指标对比,就不知道改动是变好了还是变差了。

没有失败样本库,就无法持续迭代。每次上线后发现的坏案例应该被收集、分析、归类,变成下一轮优化的输入。不收集失败样本,优化就是盲目的。

RAG 工程链路总览 ​

阶段作用常见问题
文档解析把 PDF、Word、HTML、表格等格式转成可处理文本解析失败静默丢弃、表格结构丢失、扫描件 OCR 质量差
文本清洗去除页眉页脚、导航栏、广告、重复空白过度清洗删除标题和编号、清洗规则不统一
Chunk 切分把文档切成可检索的最小单元Chunk 太大引入噪声、太小丢上下文、切断语义
Embedding把 Chunk 文本转成向量表示模型版本不记录、不同版本混用、不支持增量更新
向量入库写入向量库和元数据库不支持幂等、metadata 缺失、权限信息没写入
Query Rewrite改写用户问题以提升召回过度改写改变原意、不改写导致口语化问题召回差
Hybrid Search结合向量检索和关键词检索只用向量检索导致编号/术语召回差、融合权重不合理
Rerank对候选结果重新排序不做 Rerank 导致噪声进上下文、Rerank 模型选择不当
Context Build把候选 Chunk 组织成模型输入总长度超限、来源标识丢失、矛盾内容混入
Answer Generation基于上下文生成答案上下文不足时强行编造、不引用来源、格式不一致
Citation把答案依据连接回原始文档引用缺失、引用指向错误 Chunk、引用格式不统一
Evaluation系统化评估 RAG 效果没有评测集、只靠人工感觉、不版本化评测结果
Feedback Loop收集失败样本驱动迭代失败样本不收集、不分析、不回到测试集

文档解析:RAG 质量的第一道关 ​

文档解析是知识进入系统的第一个环节。如果解析阶段就丢失了信息,后面的 Chunk、Embedding、检索再怎么优化也补不回来。

不同格式的处理重点不同:

  • PDF:多栏排版、页眉页脚、表格、扫描件、分页断句。解析时要保留页码和段落位置。扫描件需要 OCR,OCR 结果可能有错别字,应记录置信度。
  • Word:标题层级、列表、表格、嵌入对象。解析时保留标题层级和段落结构。
  • Markdown:结构相对清晰,标题、列表、代码块、表格容易识别。代码块要保持完整,不要切成多个片段。
  • HTML:去除导航、广告、脚注,只保留正文。保留 source_uri 用于引用溯源。
  • 表格:不能简单按行拼接。表头、单位、列含义对理解关键。建议转成结构化文本或同时保存原始结构和文本摘要。

错误示例:直接用 split("\n\n") 切分 PDF 文本,不保留页码、标题和表格结构。结果是 Chunk 没有来源信息,引用溯源无法工作,表格数据被拆散成无意义的片段。

更合理的做法:使用结构化解析工具提取 PDF 的标题、段落、表格和页码。解析结果携带 document_id、page、section 等 metadata。解析失败的文档记录错误原因,进入重试队列,而不是静默丢弃。

Chunk 策略:不是越大越好,也不是越小越好 ​

Chunk 质量直接影响召回质量。Chunk 太大容易引入噪声,模型在长文本中找不到关键信息;Chunk 太小容易丢上下文,模型看到片段但不知道前后文在说什么。

Chunk 策略优点风险适用场景
固定长度切分实现简单,Chunk 大小稳定容易切断语义文本结构弱、快速原型
按标题切分保留文档结构,语义完整Chunk 大小可能不均匀Markdown、技术文档、手册
按语义切分更符合自然语义边界成本较高,实现复杂高质量知识库、长文档问答
滑动窗口保留相邻上下文增加重复内容和存储成本需要上下文连续性的文档
Parent-Child Chunk兼顾小粒度召回和大上下文生成数据结构更复杂长文档、章节型知识库
Chunk Overlap降低边界信息丢失Overlap 过大导致重复召回段落边界不稳定的文本

Parent-Child Chunk 的思路值得单独说明:用小 Chunk 做召回(提高精度),用父级段落或章节做上下文补充(保留背景)。检索时命中小 Chunk,但送入模型的是包含该 Chunk 的更大段落。

Chunk 切分时必须记录 document_id、chunk_id、章节、页码、顺序号和 token 数。这些信息是引用溯源和问题排查的基础。

Embedding 版本管理 ​

RAG 项目必须记录以下信息:

字段作用
embedding_model记录使用的 Embedding 模型名称
embedding_version记录模型版本,支持升级后重建
chunk_hash判断 Chunk 内容是否变化,支持去重和增量更新
document_id关联原始文档
chunk_id唯一标识 Chunk
vector_id向量库中的存储标识
created_at记录生成时间,支持过期清理

为什么需要这些信息:

  • 模型升级后需要重建向量。不同 Embedding 模型的向量空间不兼容,不能简单混用。
  • 不同版本混用会影响召回稳定性。一次查询可能在旧模型向量和新模型向量之间比较,分数没有可比性,召回结果变得不可解释。
  • chunk_hash 支持去重和增量更新。文档重新上传但 Chunk 文本不变时,可以避免重复生成 Embedding。
  • version 信息有利于评测对比和问题排查。对比不同版本的召回结果,定位问题是模型退化还是数据变化。
策略说明适用场景
向量检索根据语义相似度召回 Chunk用户问题和文档表达不同但语义相近
关键词检索根据 BM25 等方式召回编号、术语、错误码、精确短语
Hybrid Search融合向量检索和关键词检索企业知识库、技术文档、工单系统
Metadata Filter按租户、权限、类型、时间过滤多用户、多知识库、权限敏感场景
Query Rewrite改写用户问题以提升召回问题口语化、缺少上下文、术语不统一
Multi-query生成多个查询表达再融合结果问题复杂或表达可能有多种角度
Rerank对候选结果重新排序初始召回有噪声但候选中包含正确答案

向量检索适合语义匹配,但对编号、型号、错误码不一定稳定。用户问"ERR-4012 怎么解决",向量检索可能召回一堆"错误码"相关的内容,但不包含 ERR-4012 的具体信息。

关键词检索适合精确匹配,但对同义表达不够灵活。用户问"怎么退款",关键词检索可能找不到标题为"退货流程"的文档。

Hybrid Search 结合两者,在 RAG 工程中很常见。真实业务问题往往既有语义表达,也包含专有名词和结构化条件。

Query Rewrite 解决用户问题表达不完整的问题。"订单出问题了"太模糊,改写后可以变成"订单状态查询"或"订单异常处理"。

Rerank 是检索的第二道关。先用宽松策略召回较多候选(如 Top20),再用更精细的模型或规则选出 Top5 进入上下文。

引用溯源:答案可信度的证据 ​

答案应尽量返回以下信息:

字段作用
document_id标识来源文档
chunk_id标识具体 Chunk
source_uri指向原始文档位置
page页码,方便定位
section章节标题,方便理解上下文
score检索分数,辅助判断相关性

引用不是装饰,而是答案可信度的证据。用户可以根据引用判断答案是否来自正确资料,评测系统可以根据引用判断检索是否命中目标文档。

没有引用的答案很难评测和排错。即使答案看起来正确,也无法判断它是基于知识库生成,还是模型根据自身知识猜出来的。如果是后者,知识库更新后答案可能悄悄变错。

工程上建议把引用作为响应结构的一部分,而不是让模型在自然语言中自由生成引用。结构化引用可以被程序验证,自由生成的引用可能指向不存在的来源。

RAG 评测指标 ​

指标关注点
Recall@K正确文档或 Chunk 是否出现在前 K 个检索结果中
MRR第一个正确结果排在多靠前
Context Precision进入上下文的内容中有多少是相关的
Context Recall答案所需信息是否被上下文覆盖
Faithfulness答案是否忠实于给定上下文
Answer Relevance答案是否真正回答了用户问题
Citation Accuracy引用是否指向支持答案的正确来源
Human Review人工从业务正确性和可用性角度复核

Recall@K 适合评估召回阶段。如果正确文档没有进入候选集,后面的 Rerank 和生成再强也很难补救。

Faithfulness 关注答案是否基于上下文。它可以帮助发现模型在上下文不足时编造内容的问题——上下文里没有的信息,答案不应该包含。

Citation Accuracy 对企业 RAG 很关键。答案正确但引用错误,会降低可信度,也会影响问题排查。

Human Review 是最后的兜底。自动评测能覆盖大部分场景,但边界案例、业务正确性、用户体验还需要人工判断。

失败样本库:RAG 迭代的核心资产 ​

失败样本库应该记录以下类型:

失败类型典型表现迭代方向
检索不到正确文档不在候选集中检查 Chunk、Embedding、检索策略
检索到了但排序靠后正确文档在 Top20 但不在 Top5增加 Rerank、调整融合权重
检索结果无关召回的 Chunk 和问题不相关检查 Query Rewrite、Metadata Filter
答案幻觉答案包含上下文没有的信息调整 Prompt、检查 Faithfulness
引用错误引用指向不相关的 Chunk检查引用映射逻辑
文档解析失败文档没有进入知识库检查解析器、记录错误原因
权限过滤错误用户看到无权限的内容检查 Metadata Filter
用户反馈差用户标记答案无用综合分析,进入人工审查

失败样本不是简单收集错误答案,而是要记录:问题、期望答案、实际答案、检索结果、引用、模型版本、检索参数、用户反馈。

失败样本驱动迭代的典型路径:

  • 正确文档从未被召回 → 检查解析、Chunk、Embedding、检索策略
  • 正确文档被召回但答案错误 → 检查上下文构建、生成 Prompt
  • 答案正确但引用错误 → 检查引用映射逻辑
  • 大量用户反馈差 → 综合分析,可能需要调整整体架构

一个最小 RAG 工程伪代码 ​

python
def rag_query(question: str, user_context: dict):
    # 1. Query 改写
    rewritten_query = rewrite_query(question)

    # 2. Hybrid Search + 权限过滤
    candidates = hybrid_search(
        query=rewritten_query,
        filters={
            "tenant_id": user_context["tenant_id"],
            "permission_level": user_context["permission_level"],
        },
        top_k=20,
    )

    # 3. Rerank
    reranked = rerank(question, candidates)

    # 4. 上下文构建
    context = build_context(reranked[:5])

    # 5. 答案生成
    answer = generate_answer(question, context)

    # 6. 引用收集
    citations = collect_citations(reranked[:5])

    # 7. Trace 记录
    record_trace(question, candidates, reranked, answer, citations)

    return {
        "answer": answer,
        "citations": citations,
    }

这个伪代码表达的是生产 RAG 的基本结构:先扩大候选召回,再精排,再构建上下文,最后生成答案和引用。每一步都应该有独立的日志和错误处理。

真实系统还需要补充:Trace 记录(用于问题排查)、失败样本收集(用于迭代)、评测指标计算(用于版本对比)、缓存(用于性能优化)。

对个人项目的启发 ​

RAG 工程化的能力可以迁移到多个方向的项目中。

通用迁移思路:不管做什么类型的项目,只要涉及"让模型基于外部知识回答问题",就需要考虑文档解析、Chunk 策略、Embedding 版本管理、引用溯源和评测闭环。这些能力不绑定具体业务,是 RAG 工程的通用骨架。

项目 A RAG 工单系统可以迁移:

  • 文档解析:工单系统的产品文档、历史工单、知识库都有自己的格式,解析时要保留标题、页码、表格结构,失败不能静默丢弃。
  • Chunk 策略:工单文档的粒度和通用文档不同,需要根据工单场景调优 Chunk 大小和切分方式。
  • Embedding 版本:记录每次入库使用的模型和版本,模型升级后能重建向量,也能对比不同版本的召回效果。
  • 引用溯源:答案必须返回来源工单 ID、文档 ID、页码,让用户能验证答案依据。
  • 用户反馈:用户标记"答案无用"的案例自动进入失败样本库。
  • 失败样本库:收集检索不到、答案幻觉、引用错误等失败类型,定期分析后更新测试集和优化策略。
  • RAG 评测:构建覆盖常见场景和边界情况的评测集,每次调整 Chunk、Embedding 或 Rerank 后运行回归测试。

项目 B 多 Agent Copilot 可以迁移:

  • Agent 检索外部知识时记录引用:多 Agent 系统中,每个 Agent 调用 RAG 工具时,返回结果必须携带引用信息,方便追踪知识来源。
  • Agent 使用知识库时记录 run_id:每次 RAG 查询关联到 Agent 的 run_id,出了问题可以通过 run_id 追溯完整的执行链路。
  • 多 Agent 输出结果要能追踪依据:最终聚合结果需要能还原到每个 Agent 的检索来源和引用,不能只看最终答案。

面试表达 ​

我不会把 RAG 简化成"向量库 + Prompt"。向量库只解决相似内容召回,不保证答案正确;Prompt 只能组织上下文,不解决文档解析和召回质量。真正做项目时,RAG 的效果取决于完整链路——从文档解析、Chunk 切分、Embedding、检索、Rerank、上下文构建、答案生成到引用溯源和质量评测,每一环都可能影响最终效果。

我会把 RAG 拆成解析、切块、向量化、检索、重排、上下文构建、生成、引用和评测几个环节。如果 RAG 效果不好,我会先定位是解析问题、召回问题、排序问题、上下文构建问题还是生成问题。比如正确文档没有被召回,就检查 Chunk、Embedding 和检索策略;正确文档被召回但答案错误,就检查上下文构建和生成约束。

生产级 RAG 必须有评测集和失败样本库。评测集覆盖常见场景和边界情况,每次修改后运行回归测试验证没有退化。失败样本库收集线上发现的坏案例,分析失败模式后驱动迭代——调 Chunk、调检索参数、加 Rerank、改 Query Rewrite。引用溯源是答案可信度的基础,没有引用的答案无法评测也无法排错。

后续 TODO ​

  • 补充 RAG 评测样例,包括测试用例的标注方法和评测脚本。
  • 补充 Chunk 策略对比实验,验证不同切分方式对召回质量的影响。
  • 补充 Hybrid Search 实现示例,包括 RRF 融合排序的代码。
  • 补充项目 A 的 RAG 链路图,展示从文档入库到查询回答的完整流程。

相关链接 ​