SpringAI:RAG 最佳实践与调优

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

下面我们还؜؜؜؜؜؜؜是从实现 RAG⁠⁠⁠⁠⁠⁠ ⁠的 4 大核心‏‏‏‏‏‏步骤‏,来实战 R‌‌‌‌‌‌AG ‌开发的最佳‏‏‏‏‏‏实践和‏优化技巧。

文档收集和切割

文档的质量؜؜؜؜؜؜؜决定了 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;
}

然而,手动调整؜؜؜؜切؜؜؜分参数很难把握合适值,容⁠⁠⁠⁠易破坏语义完整⁠⁠⁠性。

如果使用云服务,如阿里云百炼,推荐在创建知识库时选择 智能切分,这是百炼经过大量评估后总结出的推荐策略:

采用智能切分策略时,知识库会:

  1. 首先利用系统内置的分句标识符将文档划分为若干段落

  2. 基于划分的段落,根据语义相关性自适应地选择切片点进行切分,而非根据固定长度切分

这种方法能؜؜؜؜؜؜؜更好地保障文档语义完⁠⁠⁠⁠⁠⁠⁠整性,避免不必要的‏‏‏‏‏‏‏断裂。这一策略将应用于‌‌‌‌‌‌‌知识库中的所有文档(‏‏‏‏‏‏‏包括后续导入的文档)。

此外,建议在文؜؜؜؜؜؜؜档导入知识库后进行一次人工⁠⁠⁠⁠⁠⁠⁠检查,确认文本切片内容的语‏‏‏‏‏‏‏义完整性和正确性。如果发现‌‌‌‌‌‌‌切分不当或解析错误,可以直‏‏‏‏‏‏‏接编辑文本切片进行修正:

需要注意,؜؜؜这里؜؜؜؜修改的只是知识库⁠⁠⁠中的文本切片,而⁠⁠⁠⁠非原始‏‏‏文档。因此,后续再‌‌‌次‏‏‏‏导入知识库时,仍需‏‏‏进行人工检查和‌‌‌‌修正。

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 批量添加元信息

比如我们可؜؜؜؜؜؜؜以在 load⁠⁠⁠⁠⁠M⁠a⁠rkdow‏‏‏‏‏n ‏时为‏每篇文‌‌‌‌‌章添加‌特定标‌签‏‏‏‏‏,例如"‏恋爱状态‏":

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.在创建知؜؜؜؜؜؜؜识库并导入数据时,可以⁠⁠⁠⁠⁠⁠⁠配置自动 metada‏‏‏‏‏‏‏ta 抽取:

创‌‌‌‌‌‌‌建后将无法再配置抽取规‏‏‏‏‏‏‏则或更新已有元信息

向量转换和存储

向量转换和؜؜؜؜؜؜存储是 R⁠⁠A⁠G⁠ ⁠系⁠统‏‏的核‏心环‏节,‏‌‌直接‏‌影响检‌索‏‏的效‌率和‌‏准确‏性。


向量存储配置

向量存储方案需综合成本、数据规模、性能要求、开发成本进行选型,常用方案包括:

  • 内存存储(轻量测试)
  • 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());

多查询扩展的完整使用流程可以包括三个步骤:

  1. 使用扩展后的查询召回文档:遍历扩展后的查询列表,对每个查询使用 DocumentRetriever 来召回相关文档。

  2. 整合召回的文档:将每个查询召回的文档进行整合,形成包含所有相关信息的文档集合。

  3. 使用召回的文档改写 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 条提供给大模型参考。

查询增强和关联

经过前面的文档检؜؜؜؜؜؜索,系统已经获取了与用户查询相⁠⁠⁠⁠⁠⁠关的文档。此时,大模型需要根据‏‏‏‏‏‏用户提示词和检索内容生成最‌‌‌‌‌‌终回答。然而,返回结果可能仍未达到‏‏‏‏‏‏预期效果,需要进一步优化。

错误处理机制

在实际应用؜؜؜؜؜؜中,可能出现多种异常⁠⁠⁠⁠⁠⁠情况,如找不到相关文‏‏‏‏‏‏档、相似度过低、查询‌‌‌‌‌‌超时等。良好的错误处‏‏‏‏‏‏理机制可以提升用户体验。

异常处理主要包括:

  • 允许空上下文查询(即处理边界情况)

  • 提供友好的错误提示

  • 引导用户提供必要信息

边界情况处؜؜؜؜؜؜理可以使用 Spri⁠⁠⁠⁠⁠⁠ng AI 的 Co‏‏‏‏‏‏ntextualQu‌‌‌‌‌‌eryAugment‏‏‏‏‏‏er 上下文查询增强器:

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,不会改写用户输入‏‏‏‏‏‏,而是使用原本查询。

我们也可؜؜؜؜؜自定义错误处理逻辑,⁠⁠⁠⁠⁠⁠来运用工厂模式创建一‏‏‏‏‏‏个自定义的 Cont‌‌‌‌‌‌extualQuer‏‏‏‏‏‏yAugmenter:

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();
    }
}

给检索增强؜؜؜生؜؜؜成 Advis⁠⁠o⁠r 应用自定义⁠⁠⁠‏‏的 ‏Contex‌‌tua‌l‏‏‏Quer‏‏yAug‏menter:‌‌‌

java 复制代码
RetrievalAugmentationAdvisor.builder()
              .documentRetriever(documentRetriever)
              .queryAugmenter(LoveAppContextualQueryAugmenterFactory.createInstance())
              .build();

当系统无法找到相关文档时,会返回我们自定义的友好提示。

复制代码
抱歉,我仅能回答苍穹外卖项目相关的问题。
非相关领域的问题,我无法为您解答,请您提出对应范畴的问题。
若问题仍无法解决,您可访问作者博客:https://guochang.blog.csdn.net/?type=blog 进行提问咨询。
其他建议

除了上述优化策略外,还可以考虑以下方面的改进:

问题类型 改进策略
大模型并未理解知识和用户提示词之间的关系,答案生硬拼凑 建议选择合适的大模型,提升语义理解能力
返回的结果没有按照要求,或؜؜؜؜؜؜者不够全面 建议优化提示词模板,引导模型生成更⁠⁠⁠⁠⁠⁠符合要求的回答
返回结果不够准确,混入了模型自身的通‏‏‏‏‏‏用知识 建议开启拒识功能,限制模型只基于知识‌‌‌‌‌‌库回答
相似提示词,希望控制回答的一致性或多样性 ‏‏‏‏‏‏ 建议调整大模型参数,如温度值等

恭喜你学习完成!❀

相关推荐
odng2 小时前
Windsurf / Codex 默认只显示 3 个最近任务,如何改成 100 个
java
m0_716765232 小时前
C++巩固案例--通讯录管理系统详解
java·开发语言·c++·经验分享·学习·青少年编程·visual studio
Predestination王瀞潞2 小时前
Java EE3-我独自整合(第三章:Spring DI 入门案例)
java·spring·java-ee
Ttang232 小时前
Java爬虫:Jsoup+OkHttp实战指南
java·爬虫·okhttp
李庆政3702 小时前
OkHttp的基本使用 实现GET/POST请求 authenticator自动认证 Cookie管理 请求头设置
java·网络协议·http·okhttp·ssl
Chan162 小时前
SpringAI:MCP 协议介绍与接入方法
java·人工智能·spring boot·spring·java-ee·intellij-idea·mcp
CoderJia程序员甲2 小时前
GitHub 热榜项目 - 日榜(2026-04-01)
人工智能·ai·大模型·github·ai教程
zfoo-framework2 小时前
[推荐]使用docker compose快速部署本地SpringBoot dev环境自测
spring boot·docker·容器
m0_747124532 小时前
LangChain 嵌入向量详解
python·ai·langchain