为什么你的RAG效果总差强人意?
文档切片不科学导致信息丢失?向量模型选型不当影响召回精度?大模型调参混乱难以稳定输出?
本文基于百家企业落地验证,‌系统性拆解RAG五大核心阶段(文档准备→解析切片→向量存储→检索召回→生成优化)‌,直击开发瓶颈,提供‌可复用的框架级解决方案‌!

📌 你将获得
✅ 从Token切片到语义切片的‌文档解析最佳实践
✅ Embedding选型与向量数据库的‌性能-成本平衡策略
✅ 检索前问题改写&标签增强、检索后Rerank的‌双重精度提升方案
✅ 大模型Prompt工程与参数调优的‌工业级适配技巧

适合人群‌:
🔹 企业技术负责人:构建高可用RAG架构,降低试错成本
🔹 开发者:快速定位问题环节,掌握全链路调优方法论
🔹 技术团队:复制标准化开发范式,加速项目交付

前面已经对RAG的基础原理做了介绍,不清楚的可以先看RAG的基础原理-CSDN博客

这里我们还是先回顾 RAG 的工作流程

RAG(Retrieval Augmented Generation,检索增强生成)是一种结合了信息检索和生成式模型的技术,能够在生成答案时利用外部知识库中的相关信息。它的工作流程可以分为几个关键步骤:解析与切片、向量存储、检索召回、生成答案等。

基于上面的工作流程,我们将从 RAG 中的每一个环节入手,尝试优化 RAG 的效果。

1 文档准备阶段

首先,你需要确保文档包含员工需要的全部信息,提前做好准备:

  • 包含相关知识:你需要验证文档中是否包含了用户可能提出的问题。 例如,公司新增了一个"数据分析部",但知识库中没有相关文档,导致无法回答"数据分析部的职责是什么?"的问题。解决方案是补充该部门的职责文档。

  • 表达要直接和清晰:你需要改进文档的表达方式,确保信息能被快速检索和准确理解,从而更好匹配用户的提问需求,避免隐藏关键信息。 例如,员工提问"张伟的工作职责是什么?“,但文档中只提到"张伟负责IT支持”,没有明确职责描述。可以在文档中补充更详细的职责描述。

这两项准备工作是保证 RAG 应用优化效果的前提,如果你已经做好准备,就可以开始深入了解和优化 RAG 应用的各个环节了。

2 文档解析与切片阶段

首先,RAG 应用会解析你的文档内容,然后对文档内容进行切片。

大模型在回答问题时拿到的文档切片如果缺少关键信息,会回答不准确;如果拿到的文档切片非关联信息过多(噪声),也会影响回答质量。即过少或过多的信息,都会影响模型的回答效果。

因此,在对文档进行解析与切片时,需要确保最终的切片信息完整,但不要包含太多干扰信息。

在这个阶段你可能会遇到以下问题:

类别 细分类型 改进策略 场景化示例
文档解析 文档类型不统一,部分格式的文档不支持解析 比如前面用到的 SimpleDirectoryLoader 并不支持 Keynote 格式的文件 开发对应格式的解析器,或转换文档格式 例如,某公司使用了大量的 Keynote 文件存储员工信息,但现有的解析器不支持 Keynote 格式。可以开发 Keynote 解析器或将文件转换为支持的格式(如 PDF)。
已支持解析的文档格式里,存在一些特殊内容 比如文档里嵌入了表格、图片、视频等 改进文档解析器 例如,某文档中包含了大量的表格和图片,现有解析器无法正确提取表格中的信息。可以改进解析器,使其能够处理表格和图片。
... ... ...
文档切片 文档中有很多主题接近的内容 比如工作手册文档中,需求分析、开发、发布等每个阶段都有注意事项、操作指导 扩写文档标题及子标题 「注意事项」=>「需求分析>注意事项」 建立文档元数据(打标) 例如,某文档中包含多个阶段的注意事项,用户提问“需求分析的注意事项是什么?”时,系统返回了所有阶段的注意事项。可以通过扩展标题和打标来区分不同阶段的内容。
文档切片长度过大,引入过多干扰项 减少切片长度,或结合具体业务开发为更合适的切片策略 例如,某文档的切片长度过大,包含了多个不相关的主题,导致检索时返回了无关信息。可以减少切片长度,确保每个切片只包含一个主题。
文档切片长度过短,有效信息被截断 扩大切片长度,或结合具体业务开发为更合适的切片策略 例如,某文档中每个切片只有一句话,导致检索时无法获取完整的上下文信息。可以增加切片长度,确保每个切片包含完整的上下文。
... ... ...

2.1文档解析

LLM大模型交互由于是文字交互,为了尽可能保证格式,所以采用markdown的格式。

对于一些纯文本文件相对简单,但是在实际工作中,编写代码将 PDF 妥善地转为 Markdown 并非易事。

由于pdf/docx等多种文件格式来源的多样性,文件解析到markdown过程中可能存在一些格式上的小问题,比如 PDF 里的跨页表格行可能被解析为多行、表格中的信息解析、图片内容解析等。

不具备较强编程能力的团队建议优先借助第三方的解析平台,它们可以使用大模型对生成的markdown文本进行润色,修正目录层级、缺失信息等。

例如:阿里百炼提供的 DashScopeParse 来完成 PDF、Word 等格式的文件解析。或者是腾讯的相关服务。他们都有不错的效果,当然你也可以使用开源方案来进行文件解析,多费点时间,也能取得不错的效果。

2.2 文档切片

在文档切片的过程中,切片方式会影响检索召回的效果。让我们通过具体例子来了解不同切片方法的特点。

首先创建一个通用的示例文本: “LlamaIndex是一个强大的RAG框架。它提供了多种文档处理方式。用可以根据需选择合适的方法。”

接下来,让我们看看各种切片方法的特点和示例:

2.2.1 Token 切片

适合对 Token 数量有严格要求的场景,比如使用上下文长度较小的模型时。

使用Token切片(chunk_size=10)后可能的结果:

  • 切片1: “LlamaIndex是一个强大的RAG”

  • 切片2: “框架。它提供了多种文”

  • 切片3: “档处理方式。用户可以”

2.2.2 句子切片

这是默认的切片策略,会保持句子的完整性。

同样的文本使用句子切片后:

  • 切片1: “LlamaIndex是一个强大的RAG框架。”

  • 切片2: “它提供了多种文档处理方式。”

  • 切片3: “用户可以根据需求选择合适的方法。”

2.2.3 句子窗口切片

每个切片都包含周围的句子作为上下文窗口。

示例文本使用句子窗口切片(window_size=1)后:

  • 切片1: “LlamaIndex是一个强大的RAG框架。” 上下文: “它提供了多种文档处理方式。”

  • 切片2: “它提供了多种文档处理方式。” 上下文: “LlamaIndex是一个强大的RAG框架。用户可以根据需求选择合适的方法。”

  • 切片3: “用户可以根据需求选择合适的方法。” 上下文: “它提供了多种文档处理方式。”

2.2.4 语义切片

根据语义相关性自适应地选择切片点。

示例文本: “LlamaIndex是一个强大的RAG框架。它提供了多种文档处理方式。用户可以根据需求选择合适的方法。此外,它还支持向量检索。这种检索方式非常高效。”

语义切片可能的结果:

  • 切片1: “LlamaIndex是一个强大的RAG框架。它提供了多种文档处理方式。用户可以根据需求选择合适的方法。”

  • 切片2: “此外,它还支持向量检索。这种检索方式非常高效。” (注意这里是按语义相关性分组的)

2.2.5 Markdown 切片

专门针对 Markdown 文档优化的切片方法。

示例 Markdown 文本:

# RAG框架
LlamaIndex是一个强大的RAG框架。

## 特点
- 提供多种文档处理方式
- 支持向量检索
- 使用简单方便

### 详细说明
用户可以根据需求选择合适的方法。

Markdown切片会根据标题层级进行智能分割:

  • 切片1: “# RAG框架\nLlamaIndex是一个强大的RAG框架。”

  • 切片2: “## 特点\n- 提供多种文档处理方式\n- 支持向量检索\n- 使用简单方便”

  • 切片3: “### 详细说明\n用户可以根据需求选择合适的方法。”

在实际应用中,选择切片方法时不必过于纠结,你可以这样思考:

  • 如果你刚开始接触 RAG,建议先使用默认的句子切片方法,它在大多数场景下都能提供不错的效果

  • 当你发现检索结果不够理想时,可以尝试:

    • 处理长文档且需要保持上下文?试试句子窗口切片

    • 文档逻辑性强、内容专业?语义切片可能会有帮助

    • 模型总是报 Token 超限?Token 切片可以帮你精确控制

    • 处理 Markdown 文档?别忘了有专门的 Markdown 切片

没有最好的切片方法,只有最适合你场景的方法。你可以尝试不同的切片方法,观察 Ragas (下一篇文章会分享)评估结果,找到最适合你需求的方案。学习的过程就是不断尝试和调整的过程!

3 切片向量化与存储阶段

文档切片后,你还需要对其建立索引,以便后续检索。一个常见的方案是使用嵌入(Embedding)模型将切片向量化,并存储到向量数据库中。

在这一阶段,你需要选择合适的 Embedding 模型以及向量数据库,这对于提升检索效果至关重要。

3.1 了解 Embedding 与向量化

Embedding 模型可以将文本转换为高维向量,用于表示文本语义,相似的文本会映射到相近的向量上,检索时可以根据问题的向量找到相似度高的文档切片。

平面坐标系中的有向线段是 2 维向量。例如,从原点 (0, 0) 到 A (xa, ya) 的有向线段可以称为向量 A。向量 A 与向量 B 之间的夹角越小,也就意味着其相似度越高。

3.2 选择合适的 Embedding 模型

不同的 Embedding 模型对相同的几段文字进行计算时,得到的向量可能会完全不同。通常越新的 Embedding 模型,其表现越好。例如开始使用的是 text-embedding-v2。如果换成更新的版本 text-embedding-v3 你会发现即使不去做前面的优化,检索效果也会有一定的提升。

3.3 选择合适的向量数据库

在构建 RAG 应用时,你有多种向量存储方案可以选择,从简单到复杂依次是:

3.3.1 内存向量存储

最简单的方式是使用 LlamaIndex 内置的内存向量存储。只需安装 llama-index 包,无需额外配置,就能快速开发和测试 RAG 应用。

优点是快速上手,适合开发测试;缺点是数据无法持久化,且受限于内存大小。

3.3.2 本地向量数据库

当数据量增大时,可以使用开源的向量数据库,如 Milvus、Qdrant 等。这些数据库提供了数据持久化和高效检索能力。

优点是功能完整、可控性强;缺点是需要自行部署维护。

3.3.3 云服务向量存储

对于生产环境,推荐使用云服务提供的向量存储能力。阿里云提供了多种选择:

  • 向量检索服务(DashVector):按量付费、自动扩容,适合快速启动项目。

  • 向量检索服务 Milvus 版:兼容开源 Milvus,便于迁移已有应用。

  • 已有数据库的向量能力:如果已使用阿里云数据库(RDS、PolarDB等),可直接使用其向量功能

云服务的优势在于:

  • 无需关注运维,自动扩容

  • 提供完善的监控和管理工具

  • 按量付费,成本可控

  • 支持向量 + 标量的混合检索,提升检索准确性

选择建议:

  1. 开发测试时使用内存向量存储

  2. 小规模应用可以使用本地向量数据库

  3. 生产环境如果运维团队经验不足,推荐使用云服务,可根据具体需求选择合适的服务类型

4 检索召回阶段

检索阶段会遇到的主要问题就是,很难从众多文档切片中,找出和用户问题最相关、且包含正确答案信息的片段。

从切入时机来看,可以将解法分为两大类:

  1. 在执行检索前,很多用户问题描述是不完整、甚至有歧义的,你需要想办法还原用户真实意图,以便提升检索效果。

  2. 在执行检索后,你可能会发现存在一些无关的信息,需要想办法减少无关信息,避免干扰下一步的答案生成。

时机 改进策略 示例
检索前 问题改写 「附近有好吃的餐厅吗?」=> 「请推荐我附近的几家评价较高的餐厅」
问题扩写 通过增加更多信息,让检索结果更全面 「张伟是哪个部门的?」=> 「张伟是哪个部门的?他的联系方式、职责范围、工作目标是什么?」
基于用户画像扩展上下文 结合用户信息、行为等数据扩写问题 内容工程师提问「工作注意事项」=> 「内容工程师有哪些工作注意事项」 项目经理提问「工作注意事项」=> 「项目经理有哪些工作注意事项」
提取标签 提取标签,用于后续标签过滤+向量相似度检索 「内容工程师有哪些工作注意事项」=> 标签过滤:{"岗位": "内容工程师"}向量检索:「内容工程师有哪些工作注意事项」
反问用户 「工作职责是什么」=> 大模型反问:「请问你想了解哪个岗位的工作职责」 实现反问的提示词
思考并规划多次检索 「张伟不在,可以找谁」 => 大模型思考规划: => task_1:张伟的职责是什么, task_2:${task_1_result}职责的人有谁 => 按顺序执行多次检索
... ...
检索后 重排序 ReRank + 过滤 多数向量数据库会考虑效率,牺牲一定精确度,召回的切片中可能有一些实际相关性不够高 chunk1、chunk2...、chunk10 => chunk 2、chunk4、chunk5
滑动窗口检索 在检索到一个切片后,补充前后相邻的若干个切片。这样做的原因是:相邻切片之间往往存在语义联系,仅看单个切片可能会丢失重要信息。 滑动窗口检索确保了不会因为过度切分而丢失文本间的语义连接。 常见的实现是句子滑动窗口,你可以用下方的简化形式来理解: 假设原始文本为:ABCDEFG(每个字母代表一个句子) 当检索到切片:D 补充相邻切片后:BCDEF(前后各取2个切片) 这里的BC和EF是D的上下文。比如:BC可能包含解释D的背景信息EF可能包含D的后续发展或结果这些上下文信息能帮助你更准确地理解D的完整含义通过召回这些相关的上下文切片,你可以提高检索结果的准确性和完整性。
... ...

4.1 执行检索前的优化方向

4.1.1 问题改写

🤔 为什么需要问题改写?

想象一下你在搜索"找张伟"或者"张伟 部门"这样的关键词。看似简单,但对于RAG系统来说,这样零散的搜索词可能不太好回答。因为在真实场景中,可能存在多个叫张伟的同事,而且用户输入的关键词往往过于简单,缺少必要的上下文信息。

问题改写能带来什么?

问题改写就像是帮助系统更好地理解用户意图。比如当你问"找张伟"时,系统可以把问题改写为更完整的形式,比如"请告诉我公司中所有叫张伟的员工及其所在部门"。这样的改写不仅能提高检索的准确性,还能让回答更加全面。

接下来,你可以通过实际案例来体验不同的问题改写策略。在这个案例中,你将使用以下配置:

  • 文档:Markdown格式

  • 切片:默认句子切片策略

  • 模型:text-embedding-v3

  • 存储:默认向量存储

【方法一:使用大模型扩充用户问题】

你可以让大模型充当一个问题改写助手。它会帮你把简单的问题改写得更加完整和清晰。比如,它不仅会考虑到可能存在多个张伟的情况,还会把相关的上下文信息都补充进去。看看具体怎么做:

例如:

系统角色设定:
你是一个专业的问题改写助手。你的任务是将用户的原始问题扩充为一个更完整、更全面的问题。
规则:
1. 将可能的歧义、相关概念和上下文信息整合到一个完整的问题中
2. 使用括号对歧义概念进行补充说明
3. 添加关键的限定词和修饰语
4. 确保改写后的问题清晰且语义完整
5. 对于模糊概念,在括号中列举主要可能性
原始问题:
{query}
请生成一个综合的改写问题,确保:
- 包含原始问题的核心意图
- 涵盖可能的歧义解释
- 使用清晰的逻辑关系词连接不同方面
- 必要时使用括号补充说明
输出格式:
[综合改写] - 改写后的问题

【方法二:将单一查询改写为多步骤查询】

除了改写问题,你还可以尝试另一种思路:把复杂的问题拆解成简单的步骤。LlamaIndex 提供了两个强大的工具来实现这个功能:

  • StepDecomposeQueryTransform: 这个工具可以帮你把一个复杂问题分解成多个子问题。比如对于"张伟是哪个部门的?",它会先分解为:

    • “公司里有几个叫张伟的员工?”

    • “这些张伟分别在哪些部门?”

这样可以更全面地获取所有张伟的信息。

  • MultiStepQueryEngine: 这个查询引擎会按顺序处理这些子问题。它会先获取公司所有张伟的信息,然后再查询每个张伟的部门信息,最终将答案整合成一个完整的回应,告诉用户"公司有三名张伟,分别在教研部、课程开发部和IT部"。

这种方法就像解决数学题一样 - 把大问题分解成小问题往往更容易得到准确的答案。不过要注意,这种方法会多次调用大模型,所以会消耗更多的token。

方法三:用假设文档来增强检索(HyDE)

前面的方法都是在调整问题本身,现在让我们换个思路:如果我们先假设一个可能的答案会怎样?这就是HyDE(Hypothetical Document Embeddings)方法的独特之处。

它的工作方式很有趣:

  1. 先让大模型基于问题编一个"假想的答案文档"

  2. 用这个假想文档来检索真实文档

  3. 最后用检索到的真实文档来生成实际答案

这就像你在找一本书时,心里已经有了一个大致的内容轮廓,然后用这个轮廓去图书馆匹配相似的书籍。让我们看看具体怎么实现:

有趣的是,虽然这个"假想文档"完全是AI编造的,但它的结构和风格与真实的公司员工信息非常相似。LlamaIndex提供了灵活的控制机制来优化这个过程:

HyDEQueryTransform类允许我们通过以下方式精确控制假想文档的生成:

  • 自定义LLM:通过llm参数传入不同的大模型配置,可以选择更适合的语言模型来生成假想文档

  • 提示词模板:通过hyde_prompt参数自定义提示词模板,精确控制输出的格式和内容

  • 查询策略:使用include_original参数决定是否将原始查询与假想文档结合使用

TransformQueryEngine则作为查询引擎的包装器,它会:

  1. 先调用HyDEQueryTransform生成假想文档

  2. 使用假想文档进行向量检索

  3. 最后返回查询结果

这种架构让我们能在不修改底层查询引擎的情况下,通过调整HyDEQueryTransform的参数来优化检索效果。即使假想文档的具体内容可能不够准确,但通过精心设计的配置,它可以帮助系统更准确地检索相关信息。

4.4.2 提取标签增强检索

在向量检索的基础上,我们还可以添加标签过滤来提升检索精度。这种方式类似于图书馆既有书名检索,又有分类编号系统,能让检索更精准。

标签提取有两个关键场景:

  1. 建立索引时,从文档切片中提取结构化标签

  2. 检索时,从用户问题中提取对应的标签进行过滤

import os
from openai import OpenAI
client = OpenAI(api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://dashscope.aliyuncs.com/compatible-mode/v1")
system_message = """你是一个标签提取专家。请从文本中提取结构化信息,并按要求输出标签。
---
【支持的标签类型】
- 人名
- 部门名称
- 职位名称
- 技术领域
- 产品名称
---
【输出要求】
1. 请用 JSON 格式输出,如:[{"key": "部门名称", "value": "教研部"}]
2. 如果某类标签未识别到,则不输出该类
---
待分析文本如下:
"""
def extract_tags(text):
    completion = client.chat.completions.create(
        model="qwen-turbo",
        messages=[
            {'role': 'system', 'content': system_message},
            {'role': 'user', 'content': text}
        ],
        response_format={"type": "json_object"}
    )
    return completion.choices[0].message.content

当我们建立索引时,可以将这些标签与文档切片一起存储。这样在检索时,比如用户问"张伟是哪个部门的",我们可以:

  1. 从问题中提取人名标签 {"key": "人名", "value": "张伟"}

  2. 先用标签过滤出所有包含"张伟"的文档切片

  3. 再用向量相似度检索找出最相关的内容

这种"标签过滤+向量检索"的组合方式,能大幅提升检索的准确性。特别是在处理结构化程度较高的企业文档时,这个方法效果更好。

4.2 在执行检索后

4.2.1 Rerank(重排序)

Rerank模型的工作原理

Rerank模型的核心任务是在召回阶段的结果基础上,进一步计算查询(Query)与每个候选之间的细粒度相关性。其工作原理通常基于交叉编码器(Cross-Encoder),直接建模Query和候选文本的交互,从而计算出相关性分数并进行重新排序。与双编码器(Bi-Encoder)相比,Rerank模型的计算量更大但精度更高,适合小规模候选集。

Rerank模型在RAG中的作用

  1. 重新排序和筛选:Rerank模型能够对检索到的文档进行再次排序和筛选,优先展示与问题更相关的文档。这样,当LLM开始生成回答时,它会优先考虑这些排名靠前的、更加相关的文档,从而提高生成回答的准确性和质量。

  2. 提高精度:Rerank模型通过更复杂的模型对候选结果重新打分和排序,消除召回阶段的噪声,提升结果精度。它结合上下文信息和用户意图,提供更相关的结果。

例如先从向量数据库中检索召回 20 条文档切片,再借助rerank模型进行重排序,并且筛选出其中最相关的 5 条参考信息。运行代码后你可以看到,同样是 5 条参考信息,这次大模型能够准确回答问题了。

现在,大模型会根据你的问题和检索召回的内容,生成最终的答案。

5 生成答案阶段

做完以上的优化动作,最终这个答案可能还是不及你的预期。你可能会遇到的问题有:

  1. 没有检索到相关信息,大模型捏造答案。

  2. 检索到了相关信息,但是大模型没有按照要求生成答案。

  3. 检索到了相关信息,大模型也给出了答案,但是你希望 AI 给出更全面的答案。

为了解决这些问题,你可以从以下角度着手分析与解决:

5.1 选择合适的大模型

  • 如果只是简单的信息查询总结,小参数量的模型足以满足需求。

  • 如果你希望答疑机器人能完成较为复杂的逻辑推理,建议选择参数量更大、推理能力更强的大模型。

  • 如果你的问题需要查阅大量的文档片段,建议选择上下文长度更大的模型。

  • 如果你构建的 RAG 应用面向一些非通用领域,如法律领域,建议使用面向特定领域训练的模型。

5.2 充分优化提示词模板

  • 明确要求不编造答案:大模型可能会产生一些不真实的内容,通常称为幻觉。你可以通过提示词要求大模型:「如果所提供的信息不足以回答问题,请明确告知"根据现有信息,我无法回答这个问题。"切勿编造答案。」,来减少大模型产生幻觉的几率。

  • 添加内容分隔标记:检索召回的文档切片如果随意混杂在提示词里,人也很难看清整个提示词的结构,大模型也会受到干扰。建议将提示词和检索切片明确地分开,以便大模型能够正确地理解你的意图。

  • 根据问题类型调整模板:不同问题的回答范式可能是不同的,你可以借助大模型识别问题类型,然后映射使用不同的提示词模板。比如有些问题,你希望大模型先输出整体框架,然后再输出细节;有些问题你可能希望大模型言简意赅的给出结论。

5.3 调整大模型的参数

  • 如果你希望大模型输出在相同的问题下,输出的内容尽可能相同,你可以在每次模型调用时传入相同的seed值。

  • 如果你希望大模型在回答用户问题时不要总是用重复的句子,你可以适当调高 presence_penalty 值。

  • 如果你希望查询事实性的内容,可以适当降低 temperature 或 top_p 值;反之,查询创造性的内容时,可以适当增加它们的值。

  • 如果你需要限制字数(如生成摘要、关键词)、控制成本或减少响应时间的场景,可以适当降低max_tokens的值,但是若max_tokens过小,可能会导致输出截断,反之,需要生成大段文本时,可以提高它的值。

5.4 调优大模型

如果上述方法都做了充分的尝试,仍然不及预期,或者希望有更进一步的效果提升,你也可以尝试面向你的场景调优一个模型。在后续的章节中,你将学习和实践这一点。

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐