芥末
发布于 2026-05-08 / 0 阅读
0
0

RAG 生产落地的三大难点:文档解析、检索调优与效果评估

RAG(Retrieval-Augmented Generation,检索增强生成)常被用来解决大语言模型知识不足、知识过期、无法访问私有数据的问题。它的基本思路很直接:用户提问后,系统先从知识库里检索相关资料,再把这些资料连同问题一起交给 LLM(Large Language Model,大语言模型)生成答案。

一个最小可运行的 RAG Demo 并不复杂:解析文档、切成 chunk、计算 Embedding、写入向量库、检索、拼 Prompt、调用模型。真正困难的是生产落地,因为每一步都会把误差传给后面的环节。

flowchart LR
    A[原始文档] --> B[文档解析与清洗]
    B --> C[Chunking 切分]
    C --> D[Embedding 向量化]
    D --> E[(向量库 / 索引)]

    U[用户问题] --> Q[Query 改写]
    Q --> R[召回检索]
    E --> R
    R --> K[Rerank 重排]
    K --> P[拼接上下文]
    P --> L[LLM 生成答案]
    L --> O[返回结果]

RAG 落地最难的地方通常不是某个单点技术,而是整条链路的质量控制。文档解析不干净,检索就会拿到错误材料;检索召回不到关键内容,模型只能凭空猜;没有评估体系,优化就只能靠人工试几条问题。

可以把生产级 RAG 的难点拆成三层:

层级主要问题典型后果
文档预处理PDF 乱序、表格丢结构、扫描件无法提取、图片信息缺失知识库内容本身就是错的
检索质量调优chunk 不合理、Embedding 不匹配、Query 与文档表达不一致、精确词召回差找不到真正相关的知识
效果评估缺少标准问题集、指标只看最终回答、无法定位问题环节不知道优化是否有效,也不知道该改哪里

文档预处理:知识库质量从源头决定

很多 RAG 系统效果差,并不是模型能力不够,而是知识库里的内容已经被解析坏了。原始文档进入系统前,需要经历格式识别、正文抽取、结构还原、清洗、元数据补充等步骤。任何一步处理粗糙,都会影响后续检索。

常见的文档类型和处理难点如下:

文档类型难点处理建议
普通 PDF段落顺序可能错乱,页眉页脚混入正文使用版面感知能力更强的解析工具
表格型 PDF行列关系容易丢失pdfplumbercamelot 等表格抽取工具
扫描件没有可直接抽取的文本层先做 OCR(Optical Character Recognition,光学字符识别)
图片型文档关键信息在截图、流程图、票据中结合 OCR 或多模态模型解析
代码文档代码块被切断后语义失真按函数、类、标题层级切分
合同 / 财报 / 专利版式复杂,信息密度高高价值文档可使用多模态模型辅助解析

PDF 是最容易低估的坑。很多通用 PDF 工具更擅长抽取文本流,但复杂排版并不只是“把文字读出来”这么简单。双栏论文、带合并单元格的报表、嵌套表格、页脚注释,都需要还原阅读顺序和结构关系。

比如一个产品规格表原本长这样:

型号内存价格
A1008GB3999
A20016GB4999

如果解析后变成:

型号 内存 价格 A100 A200 8GB 16GB 3999 4999

向量化模型并不知道哪个价格对应哪个型号。后续即使召回了这个 chunk,LLM 也可能根据错误上下文生成错误答案。

更可靠的文档预处理流程一般长这样:

flowchart TD
    A[上传文档] --> B{识别文档类型}
    B -->|文本 PDF / Word / HTML| C[结构化正文抽取]
    B -->|扫描件| D[OCR 识别]
    B -->|表格密集| E[表格抽取]
    B -->|图片信息重要| F[多模态解析]

    C --> G[清洗噪声]
    D --> G
    E --> G
    F --> G

    G --> H[保留标题 / 表格 / 页码 / 来源等元数据]
    H --> I[进入 Chunking]

预处理阶段不只要抽文本,还要尽量保留结构信息。标题层级、表格字段、页码、章节路径、文档来源、更新时间,这些元数据后面都能用于过滤、重排和答案溯源。

一个 chunk 可以设计成这样的结构:

{
  "id": "doc-001-page-12-chunk-03",
  "content": "退款申请需要在订单完成后的 7 天内提交,超过期限需人工审核。",
  "metadata": {
    "doc_name": "售后服务手册",
    "section": "退款规则 > 申请时限",
    "page": 12,
    "updated_at": "2026-04-01"
  }
}

这样做的好处是,检索时不只依赖正文相似度,还可以按文档类型、业务线、时间范围做过滤;生成答案时也能给出来源,方便用户核对。

检索质量调优:召回不到,模型再强也没用

RAG 的生成质量有一个硬前提:相关材料必须被检索出来。如果检索阶段没有召回关键文档,LLM 只能基于不完整上下文回答,轻则答非所问,重则产生幻觉。

检索质量差通常来自四类问题。

Chunking 切分不合理

Chunking 是把长文档切成较小文本块的过程。切得太大,chunk 内部混入大量无关内容,向量表示会变“平均”,检索时相关度下降;切得太小,上下文不完整,单个 chunk 无法回答问题。

切分方式优点风险适合场景
固定长度切分实现简单,速度快容易切断语义普通说明文档、低复杂度文本
按标题层级切分保留章节结构依赖文档结构质量手册、知识库、制度文档
语义切分更接近自然语义边界实现成本更高问答知识库、长文档
代码结构切分保留函数、类、模块边界需要语言解析器API 文档、代码仓库

一个比较稳妥的策略是:优先按结构切分,再用长度约束兜底,同时保留适当 overlap(重叠文本)。例如:

chunk_config = {
    "split_by": ["heading", "paragraph"],
    "max_tokens": 500,
    "overlap_tokens": 80,
    "keep_metadata": ["doc_name", "section", "page"]
}

overlap 的作用是避免关键信息刚好落在两个 chunk 边界上。例如“退款条件”在上一段,“申请入口”在下一段,如果完全硬切,两个信息点可能被拆散。适当重叠能提高召回完整性,但 overlap 太大会增加存储量和重复召回。

Query 和文档表达不一致

用户提问往往很口语化,文档写法通常更正式。用户可能问:

为什么这个功能打不开?

知识库里对应的标题却是:

系统异常排查指南:功能入口不可用的处理流程

两者语义相关,但字面表达差异很大。单纯依赖向量相似度时,召回结果可能不稳定。

常见解决方式有两种:

  1. Query 改写:把用户问题改写成更适合检索的形式。
  2. 假设性问题增强:为每个 chunk 生成可能被用户问到的问题,并一起写入索引。

Query 改写示例:

用户问题:
这个功能怎么用不了?

改写后:
功能入口不可用的原因是什么?如何排查系统功能无法使用的问题?

假设性问题增强可以把一个知识块扩展成多个检索入口:

{
  "content": "当功能入口不可用时,需检查用户权限、系统版本、灰度开关和服务状态。",
  "generated_questions": [
    "为什么功能入口打不开?",
    "功能无法使用应该怎么排查?",
    "用户看不到某个功能是什么原因?"
  ]
}

这样做会增加索引规模,但能缓解用户表达和文档表达之间的差距。

纯向量检索不擅长精确词

Embedding 擅长语义相似,但不一定擅长精确匹配。产品型号、订单号、错误码、接口名、缩写、专有名词,往往更适合关键词检索。

例如用户搜索:

ERR_AUTH_401 怎么处理?

如果知识库里有一段明确包含 ERR_AUTH_401,BM25 这类关键词检索通常比纯向量检索更可靠。BM25 是一种经典文本相关性算法,会根据关键词出现频率、文档长度和词项稀有度计算相关度。

生产系统更常见的做法是混合检索:

flowchart LR
    A[用户 Query] --> B[Query 改写]
    B --> C[向量检索]
    B --> D[BM25 关键词检索]
    C --> E[候选结果合并]
    D --> E
    E --> F[去重]
    F --> G[Rerank 重排]
    G --> H[Top-K 上下文]

向量检索负责找语义相关内容,BM25 负责抓精确词和专有名词,Rerank 负责在候选结果中重新排序。Rerank 模型通常会同时读取 Query 和候选 chunk,判断二者是否真的相关,精度比单纯向量相似度更高,但延迟和成本也更高。

Embedding 模型不匹配业务语料

Embedding 模型不是越大越一定合适。不同模型在中文、英文、代码、金融、医疗、法律等领域的表现差异明显。选型时至少要关注这些点:

维度需要确认的问题
语言中文、英文、多语言是否都稳定
领域是否理解业务术语、缩写、行业表达
上下文长度单个 chunk 的长度是否超过模型支持范围
向量维度存储成本和检索性能是否可接受
延迟批量入库和在线检索是否满足要求
可迁移性更换模型后是否需要全量重建索引

Embedding 一旦更换,历史向量通常需要重新计算并重建索引,所以不要只凭几条样例做决定。更稳的方式是准备一批真实问题和标准相关文档,在同一套评估集上比较不同模型的 Hit@K、召回耗时和成本。

效果评估:没有指标就无法稳定优化

RAG 系统最容易陷入的状态是:改了 chunk 大小,感觉好了一点;换了 Embedding,又感觉有些问题变差了;加了 Rerank,单条样例看起来更准,但整体效果不清楚。

解决办法是把评估拆开,不要只看最终回答。RAG 至少要评估两层:检索层和生成层。

flowchart TD
    A[评估问题集] --> B[检索层评估]
    A --> C[端到端生成评估]

    B --> B1[Hit@K]
    B --> B2[MRR]
    B --> B3[nDCG]

    C --> C1[Faithfulness 忠实度]
    C --> C2[Answer Relevancy 答案相关性]
    C --> C3[Context Recall 上下文召回率]

    B1 --> D[定位召回问题]
    C1 --> E[定位幻觉问题]
    C2 --> F[定位答非所问]
    C3 --> G[定位上下文缺失]

检索层评估:相关文档有没有被找回来

检索层不关心 LLM 最终怎么回答,只看相关文档是否出现在 Top-K 结果中。最常用的指标是 Hit@K。

Hit@K 的含义是:对于一批问题,正确文档出现在前 K 条检索结果中的比例。

Hit@K = 命中问题数 / 总问题数

例如有 100 个问题,其中 82 个问题的标准相关文档出现在 Top 5 检索结果里,那么:

Hit@5 = 82 / 100 = 0.82

一个简单的评估代码可以这样写:

def hit_at_k(retriever, eval_set, k=5):
    """
    eval_set:
    [
        {
            "question": "退款申请时限是多久?",
            "relevant_doc_ids": {"doc-001-page-12-chunk-03"}
        }
    ]
    """
    hit_count = 0

    for item in eval_set:
        results = retriever.search(item["question"], top_k=k)
        retrieved_ids = {doc.id for doc in results}

        if retrieved_ids & item["relevant_doc_ids"]:
            hit_count += 1

    return hit_count / len(eval_set)

Hit@K 适合快速判断检索链路有没有问题。如果 Hit@5 很低,就不要急着调 Prompt,因为模型根本没拿到正确材料。

除了 Hit@K,还可以看 MRR(Mean Reciprocal Rank,平均倒数排名)和 nDCG(Normalized Discounted Cumulative Gain,归一化折损累计增益)。Hit@K 只关心有没有命中,MRR 更关心第一个正确结果排在第几位,nDCG 则适合处理多个相关文档且相关度有强弱差异的场景。

生成层评估:答案是否基于上下文正确回答

检索命中并不代表最终答案一定正确。LLM 可能忽略上下文、过度发挥,或者回答了另一个问题。生成层评估需要关注三个核心维度:

指标评估内容低分时通常说明什么
Faithfulness(忠实度)答案是否严格依据检索上下文模型编造、过度推理
Answer Relevancy(答案相关性)答案是否正面回应用户问题Prompt 设计不好,或问题理解错误
Context Recall(上下文召回率)检索上下文是否覆盖回答所需信息检索遗漏关键材料

RAGAs 是常用的 RAG 自动评估框架,可以围绕这些指标做批量打分。它不能完全替代人工评审,但能帮助工程团队快速发现趋势:某次改动到底让忠实度提高了,还是让召回率下降了。

一个评估样本通常包含问题、检索上下文、生成答案和参考答案:

{
  "question": "退款申请需要在多久内提交?",
  "contexts": [
    "退款申请需要在订单完成后的 7 天内提交,超过期限需人工审核。"
  ],
  "answer": "退款申请需要在订单完成后的 7 天内提交。",
  "ground_truth": "订单完成后 7 天内可以提交退款申请。"
}

当评估结果异常时,可以按下面的方式定位:

现象更可能的问题环节排查方向
Hit@5 低检索层chunk、Embedding、Query 改写、混合检索
Hit@5 高,但 Faithfulness 低生成层Prompt 约束、引用格式、拒答策略
Hit@5 高,但 Answer Relevancy 低问题理解或 PromptQuery 改写、意图识别、系统提示词
Context Recall 低召回覆盖不足Top-K、Rerank、chunk 粒度、多路召回
答案引用来源错误元数据或拼接逻辑doc id、页码、section、去重逻辑

生产落地的调优顺序

RAG 调优不要一上来就换模型。更合理的顺序是从数据源头到生成末端逐层排查:

flowchart TD
    A[构造真实评估集] --> B[检查文档解析质量]
    B --> C[检查 Chunking 是否破坏语义]
    C --> D[评估 Embedding 与向量检索]
    D --> E[加入 BM25 混合检索]
    E --> F[加入 Rerank]
    F --> G[优化 Prompt 与引用约束]
    G --> H[端到端评估]
    H --> I{指标是否达标}
    I -->|否| B
    I -->|是| J[上线与持续监控]

一套可执行的检查清单:

检查项判断标准
文档解析随机抽样查看正文、表格、标题层级是否正确
chunk 粒度单个 chunk 是否能表达完整知识点
元数据是否包含来源、页码、章节、更新时间
检索召回Hit@K 是否达到业务要求
精确词查询错误码、型号、接口名是否能搜到
重排效果Rerank 后相关内容是否进入更靠前位置
生成约束答案是否只依据上下文,缺信息时是否拒答
可观测性是否记录 Query、召回内容、分数、最终答案和用户反馈

RAG 的工程难点可以归结为一句话:跑通链路靠组件拼装,生产可用靠质量闭环。文档预处理决定知识库的原料质量,检索调优决定模型能拿到什么材料,效果评估决定每次优化是否真的朝正确方向前进。只有这三层都建立起来,RAG 才能从 Demo 变成稳定可维护的知识系统。


评论