**摘要:**本文围绕 RAG 四大核心步骤展开,提供可落地的实战技巧与最佳实践。


下面我们还是从实现 RAG 的 4 大核心步骤,来实战 RAG 开发的最佳实践和优化技巧。
文档收集和切割
文档的质量决定了 AI 回答能力的上限,其他优化策略只是让 AI 回答能力不断接近上限。
因此,文档处理是 RAG 系统中最基础也最重要的环节。
1 优化原始文档
知识完备性是文档质量的首要条件。如果知识库缺失相关内容,大模型将无法准确回答对应问题。我们需要通过收集用户反馈或统计知识库检索命中率,不断完善和优化知识库内容。
在知识完整的前提下,我们要注意 3 个方面:
1)内容结构化:
-
原始文档需排版清晰、结构规整,明确标注案例编号、项目概述、设计要点等核心模块
-
各级标题层级分明,同一标题下内容表述简洁完整、逻辑清晰
-
列表内容尽量扁平化,避免在单条列表项下继续多级嵌套,减少层级复杂度
2)内容规范化:
-
语言统一:文档语言需与用户提示语保持一致,专业术语可同步标注多语言对照说明
-
表述统一:同一概念、同一业务名词需采用固定表述方式,长文档可分段处理以保证一致性
-
减少噪音:尽量去除水印、冗余表格、非必要图片等可能影响内容解析的干扰元素
3)格式标准化:
-
优先使用 Markdown、DOC/DOCX 等文本格式;PDF 文件可通过百炼 DashScopeParse 工具转换为 Markdown 后,再使用大模型优化整理
-
文档中如需包含图片,需替换为可公网访问的 URL 链接,确保图片可正常加载展示
这里提出了 "AI 原生文档" 的概念,也就是专门为 AI 知识库创作的文档。我们可以将上述规则输入给 AI 大模型,让它对已有文档进行优化。
2 文档切片
合适的文档切片大小和方式对检索效果至关重要,因此文档切片尺寸需要根据具体情况灵活调整,避免两个极端:切片过短导致语义缺失,切片过长引入无关信息。
具体需结合以下因素:
-
文档类型:对于专业类文献,增加长度通常有助于保留更多上下文信息;而对于社交类帖子,缩短长度则能更准确地捕捉语义
-
提示词复杂度:如果用户的提示词较复杂且具体,则可能需要增加切片长度;反之,缩短长度会更为合适
不当的切片方式可能导致以下问题:
1)文本切片过短:出现语义缺失,导致检索时无法匹配
2)文本切片过长:包含不相关主题,导致召回时返回无关信息
3)明显的语义截断:文本切片出现了强制性的语义截断,导致召回时缺失内容
最佳文档切片策略是结合智能分块算法和人工二次校验。智能分块算法基于分句标识符先划分为段落,再根据语义相关性动态选择切片点,避免固定长度切分导致的语义断裂。在实际应用中,应尽量让文本切片包含完整信息,同时避免包含过多干扰信息。
在编程实现上,可以通过 Spring AI 的 ETL Pipeline 提供的 DocumentTransformer 来调整切分规则,代码如下:
java
@Component
class MyTokenTextSplitter {
public List<Document> splitDocuments(List<Document> documents) {
TokenTextSplitter splitter = new TokenTextSplitter();
return splitter.apply(documents);
}
public List<Document> splitCustomized(List<Document> documents) {
TokenTextSplitter splitter = new TokenTextSplitter(200, 100, 10, 5000, true);
return splitter.apply(documents);
}
}
使用切分器:
java
@Resource
private MyTokenTextSplitter myTokenTextSplitter;
@Bean
VectorStore loveAppVectorStore(EmbeddingModel dashscopeEmbeddingModel) {
SimpleVectorStore simpleVectorStore = SimpleVectorStore.builder(dashscopeEmbeddingModel)
.build();
// 加载文档
List<Document> documents = loveAppDocumentLoader.loadMarkdowns();
// 自主切分
List<Document> splitDocuments = myTokenTextSplitter.splitCustomized(documents);
simpleVectorStore.add(splitDocuments);
return simpleVectorStore;
}
然而,手动调整切分参数很难把握合适值,容易破坏语义完整性。
如果使用云服务,如阿里云百炼,推荐在创建知识库时选择 智能切分,这是百炼经过大量评估后总结出的推荐策略:

采用智能切分策略时,知识库会:
-
首先利用系统内置的分句标识符将文档划分为若干段落
-
基于划分的段落,根据语义相关性自适应地选择切片点进行切分,而非根据固定长度切分
这种方法能更好地保障文档语义完整性,避免不必要的断裂。这一策略将应用于知识库中的所有文档(包括后续导入的文档)。
此外,建议在文档导入知识库后进行一次人工检查,确认文本切片内容的语义完整性和正确性。如果发现切分不当或解析错误,可以直接编辑文本切片进行修正:

需要注意,这里修改的只是知识库中的文本切片,而非原始文档。因此,后续再次导入知识库时,仍需进行人工检查和修正。
3 元数据标注
可为文档添加丰富的结构化信息,俗称元信息,形成多维索引,便于后续向量化处理和精准检索。
在编程实现中,可以通过多种方式为文档添加元数据:
1)手动添加元信息(单个文档):
java
documents.add(new Document(
"案例编号:LR-2023-001\n" +
"项目概述:180平米大平层现代简约风格客厅改造\n" +
"设计要点:\n" +
"1. 采用5.2米挑高的落地窗,最大化自然采光\n" +
"2. 主色调:云雾白(哑光,NCS S0500-N)配合莫兰迪灰\n" +
"3. 家具选择:意大利B&B品牌真皮沙发,北欧白橡木茶几\n" +
"空间效果:通透大气,适合商务接待和家庭日常起居",
Map.of(
"type", "interior", // 文档类型
"year", "2025", // 年份
"month", "05", // 月份
"style", "modern", // 装修风格
)));
2)利用 DocumentReader 批量添加元信息
比如我们可以在 loadMarkdown 时为每篇文章添加特定标签,例如"恋爱状态":
java
// 提取文档倒数第 3 和第 2 个字作为标签
String status = fileName.substring(fileName.length() - 6, fileName.length() - 4);
MarkdownDocumentReaderConfig config = MarkdownDocumentReaderConfig.builder()
.withHorizontalRuleCreateDocument(true)
.withIncludeCodeBlock(false)
.withIncludeBlockquote(false)
.withAdditionalMetadata("filename", fileName)
.withAdditionalMetadata("status", status)
.build();

3)自动添加元信息:
在阿里云百炼等云服务平台中,均支持元数据与标签功能。可通过平台 API 或界面进行标签设置,并基于标签实现内容快速筛选与检索。

a.为某个文档设置标签:

b.在创建知识库并导入数据时,可以配置自动 metadata 抽取:
创建后将无法再配置抽取规则或更新已有元信息


向量转换和存储
向量转换和存储是 RAG 系统的核心环节,直接影响检索的效率和准确性。
向量存储配置
向量存储方案需综合成本、数据规模、性能要求、开发成本进行选型,常用方案包括:
- 内存存储(轻量测试)
- Redis(高性能缓存场景)
- MongoDB(文档 + 向量混合存储)
- 专业向量数据库(Elasticsearch、Pinecone、Milvus、阿里云向量数据库)
在编程实现中,可以通过以下方式配置向量存储:
java
SimpleVectorStore vectorStore = SimpleVectorStore.builder(embeddingModel)
.build();
在云平台中,通常提供多种存储选项,比如内置的向量存储或者云数据库:

选择合适的嵌入模型
嵌入模型负责将文本转换为向量,模型质量直接决定检索准确率,是 RAG 系统关键的组件之一。
1)按业务语言:
优先选用与业务语言一致的嵌入模型,避免使用英文模型处理中文数据,否则检索效果显著下降
2)按业务场景:
- 通用场景(如文档、客服、知识库)可直接使用通用嵌入模型
- 专业场景(如医疗、法律、金融、代码)需使用领域微调嵌入模型,检索效果提升明显
- 高精度要求场景(如法律合同、专利、标准文件),应选用重量级、高维度模型
3)按向量维度:
- 向量维度越高,语义表达能力越强、检索越准确,但存储占用与检索耗时会相应增加
- 追求检索精度可选用 1024 维,追求检索速度可选用 512 维,低成本场景可选用 256 维
4)按性能与成本:
- 本地部署优先选用轻量模型
- 使用云服务时直接选用平台托管模型,性价比更高
- 高并发场景需选择低延迟、高吞吐量的嵌入模型
只需替换 embeddingModel 实例,即可更换模型:
java
SimpleVectorStore vectorStore = SimpleVectorStore.builder(embeddingModel)
.build();
云平台通常提供多种嵌入模型选项:

文档过滤和检索
这个环节是我们开发者最能大显身手的地方,在技术已经确定的情况下,优化这个环节可以显著提升系统整体效果。
多查询扩展
在多轮会话场景中,用户输入的提示词有时可能不够完整,或者存在歧义。多查询扩展技术可以扩大检索范围,提高相关文档的召回率。
使用多查询扩展时,要注意:
-
设置合适的查询数量(建议 3 - 5 个),过多会影响性能、增大成本
-
保留原始查询的核心语义
在编程实现中,可以通过以下代码实现多查询扩展:
java
MultiQueryExpander queryExpander = MultiQueryExpander.builder()
.chatClientBuilder(chatClientBuilder)
.numberOfQueries(3)
.build();
List<Query> queries = queryExpander.expand(new Query("算法是啥?"));
获得扩展查询后,可以直接用于检索文档、或者提取查询文本来改写提示词:
java
DocumentRetriever retriever = VectorStoreDocumentRetriever.builder()
.vectorStore(vectorStore)
.similarityThreshold(0.73)
.topK(5)
.filterExpression(new FilterExpressionBuilder()
.eq("genre", "fairytale")
.build())
.build();
// 直接用扩展后的查询来获取文档
List<Document> retrievedDocuments = documentRetriever.retrieve(query);
// 输出扩展后的查询文本
System.out.println(query.text());
多查询扩展的完整使用流程可以包括三个步骤:
-
使用扩展后的查询召回文档:遍历扩展后的查询列表,对每个查询使用
DocumentRetriever来召回相关文档。 -
整合召回的文档:将每个查询召回的文档进行整合,形成包含所有相关信息的文档集合。
-
使用召回的文档改写 Prompt:将整合后的文档内容添加到原始 Prompt 中,为大语言模型提供更丰富的上下文信息。
注意:多查询扩展会增加查询次数和计算成本,效果也不易量化评估,所以建议慎用此优化方式。
查询重写和翻译
查询重写和翻译可以使查询更加精确和专业,但是要注意保持查询的语义完整性。
主要应用包括:
-
使用
RewriteQueryTransformer优化查询结构 -
配置
TranslationQueryTransformer支持多语言
参考 官方文档 实现查询重写:
java
@Component
public class QueryRewriter {
private final QueryTransformer queryTransformer;
public QueryRewriter(ChatModel dashscopeChatModel) {
ChatClient.Builder builder = ChatClient.builder(dashscopeChatModel);
// 创建查询重写转换器
queryTransformer = RewriteQueryTransformer.builder()
.chatClientBuilder(builder)
.build();
}
public String doQueryRewrite(String prompt) {
Query query = new Query(prompt);
// 执行查询重写
Query transformedQuery = queryTransformer.transform(query);
// 输出重写后的查询
return transformedQuery.text();
}
}
应用查询重写器:
java
@Resource
private QueryRewriter queryRewriter;
public String doChatWithRag(String message, String chatId) {
// 查询重写
String rewrittenMessage = queryRewriter.doQueryRewrite(message);
ChatResponse chatResponse = chatClient
.prompt()
.user(rewrittenMessage)
.call()
.chatResponse();
String content = chatResponse.getResult().getOutput().getText();
return content;
}
在云服务中,可以开启 多轮会话改写 功能,自动将用户的提示词转换为更完整的形式:

检索器配置
检索器配置是影响检索质量的关键因素,包括三个方面:相似度阈值、返回文档数量和过滤规则。
1)设置合理的相似度阈值
相似度阈值控制文档被召回的标准,需根据具体问题调整:
| 问题 | 解决方案 |
|---|---|
| 知识库的召回结果不完整,没有包含全部相关的文本切片 | 建议降低 相似度阈值,提高 召回片段数,以召回一些原本应被检索到的信息 |
| 知识库的召回结果中包含大量无关的文本切片 | 建议提高相似度阈值,以排除与用户提示词相似度低的信息 |
在编程实现中,可以通过文档检索器配置:
java
DocumentRetriever documentRetriever = VectorStoreDocumentRetriever.builder()
.vectorStore(loveAppVectorStore)
.similarityThreshold(0.5) // 相似度阈值
.build();
云平台提供了便捷的配置界面,参考文档:

2)控制返回文档数量(召回片段数)
控制返回给模型的文档数量,平衡信息完整性和噪音水平。在编程,可通过文档检索器配置:
java
DocumentRetriever documentRetriever = VectorStoreDocumentRetriever.builder()
.vectorStore(loveAppVectorStore)
.similarityThreshold(0.5) // 相似度阈值
.topK(3) // 返回文档数量
.build();
使用云平台,可以在编辑百炼应用时调整召回片段数,参考文档的 提高召回片段数 部分:

召回片段数即多路召回策略中的 K 值。系统最终会选取相似度分数最高的 K 个文本切片。不合适的 K 值可能导致 RAG 漏掉正确的文本切片,影响回答质量。
在多路召回场景下,如果应用关联了多个知识库,系统会从这些库中检索相关文本切片,然后通过重排序,选出最相关的前 K 条提供给大模型参考。
查询增强和关联
经过前面的文档检索,系统已经获取了与用户查询相关的文档。此时,大模型需要根据用户提示词和检索内容生成最终回答。然而,返回结果可能仍未达到预期效果,需要进一步优化。
错误处理机制
在实际应用中,可能出现多种异常情况,如找不到相关文档、相似度过低、查询超时等。良好的错误处理机制可以提升用户体验。
异常处理主要包括:
-
允许空上下文查询(即处理边界情况)
-
提供友好的错误提示
-
引导用户提供必要信息
边界情况处理可以使用 Spring AI 的 ContextualQueryAugmenter 上下文查询增强器:
java
RetrievalAugmentationAdvisor.builder()
.queryAugmenter(
ContextualQueryAugmenter.builder()
.allowEmptyContext(false)
.build()
)
如果不使用自定义处理器,或者未启用 "允许空上下文" 选项,系统在找不到相关文档时会默认改写用户查询 userText:
java
The user query is outside your knowledge base.
Politely inform the user that you can't answer it.
如果启用 "允许空上下文",系统会自动处理空 Prompt,不会改写用户输入,而是使用原本查询。
我们也可自定义错误处理逻辑,来运用工厂模式创建一个自定义的 ContextualQueryAugmenter:
java
public class CsdnAppContextualQueryAugmenterFactory {
public static ContextualQueryAugmenter createInstance() {
PromptTemplate emptyContextPromptTemplate = new PromptTemplate("""
抱歉,我仅能回答苍穹外卖项目相关的问题。
非相关领域的问题,我无法为您解答,请您提出对应范畴的问题。
若问题仍无法解决,您可访问作者博客:https://guochang.blog.csdn.net/?type=blog 进行提问咨询。
""");
return ContextualQueryAugmenter.builder()
.allowEmptyContext(false)
.emptyContextPromptTemplate(emptyContextPromptTemplate)
.build();
}
}
给检索增强生成 Advisor 应用自定义的 ContextualQueryAugmenter:
java
RetrievalAugmentationAdvisor.builder()
.documentRetriever(documentRetriever)
.queryAugmenter(LoveAppContextualQueryAugmenterFactory.createInstance())
.build();
当系统无法找到相关文档时,会返回我们自定义的友好提示。
抱歉,我仅能回答苍穹外卖项目相关的问题。 非相关领域的问题,我无法为您解答,请您提出对应范畴的问题。 若问题仍无法解决,您可访问作者博客:https://guochang.blog.csdn.net/?type=blog 进行提问咨询。
其他建议
除了上述优化策略外,还可以考虑以下方面的改进:
| 问题类型 | 改进策略 |
|---|---|
| 大模型并未理解知识和用户提示词之间的关系,答案生硬拼凑 | 建议选择合适的大模型,提升语义理解能力 |
| 返回的结果没有按照要求,或者不够全面 | 建议优化提示词模板,引导模型生成更符合要求的回答 |
| 返回结果不够准确,混入了模型自身的通用知识 | 建议开启拒识功能,限制模型只基于知识库回答 |
| 相似提示词,希望控制回答的一致性或多样性 | 建议调整大模型参数,如温度值等 |
恭喜你学习完成!❀