欢迎光临
我们一直在努力

一文吃透 Spring AI Alibaba RAG:三种架构模式 + 全流程优化详解

本文系统讲解 RAG(检索增强生成)的核心原理、三种架构模式、全流程优化策略,并基于 Spring AI Alibaba 给出完整代码实现。无论你是刚接触 RAG 的开发者,还是正在优化线上 RAG 系统的工程师,都能从中找到实用的参考。


一、什么是 RAG?

1.1 一句话定义

RAG(Retrieval-Augmented Generation,检索增强生成)是一种先查资料、再回答问题的技术范式——在 LLM 生成回答之前,先从外部知识库中检索出与问题相关的文档片段,将这些片段作为上下文注入 Prompt,让模型的回答有据可依。

用考试来类比:

  • 不用 RAG = 闭卷考试,凭记忆作答,可能答错或编造
  • 用 RAG = 开卷考试,先翻参考资料再作答,答案有据可查

1.2 RAG 的核心价值

RAG 解决了 LLM 的三大痛点:

痛点一:幻觉(Hallucination)

LLM 在不确定时会"自信地编造"答案。比如你问"公司差旅报销标准是多少",模型可能凭训练数据中的通用信息给出一个完全不靠谱的数字。RAG 通过注入真实文档内容,让模型基于事实回答,大幅降低幻觉率。

痛点二:知识时效性

模型的训练数据有截止日期,无法回答训练之后的新信息。比如"2026年最新的员工病假政策是什么",模型的知识库中根本没有。RAG 让模型可以实时查询最新文档,回答永远基于最新信息。

痛点三:领域专业性

通用模型对企业内部知识(规章制度、产品文档、技术规范)了解有限。RAG 允许接入私有知识库,让模型输出专家级的精准回答。

1.3 RAG 的四大核心步骤

一个完整的 RAG 流程分为离线和在线两个阶段:

离线阶段(文档摄取 / ETL):

原始文档 → 文档切割(Chunking)→ 向量编码(Embedding)→ 写入向量数据库

在线阶段(检索增强生成):

用户提问 → 向量检索相似文档 → 上下文注入 Prompt → LLM 生成回答

拆开来看,四大步骤分别是:

  • 文档切割:将海量文档转化为易检索的知识碎片。就像把厚重词典拆解成单词卡片,采用智能分块算法保持语义连贯性,给每个知识碎片打标签。优质的知识切割如同图书馆分类系统,决定了后续检索效率。

  • 向量编码:用 Embedding 模型将文字转化为高维数学向量,使语义相近的内容产生相似的数学特征。比如"续航时间"和"电池容量"会被编码为相似向量,存入专用的向量数据库并建立快速检索索引。

  • 相似检索:将用户问题同样转化为向量,在向量数据库中通过相似度算法(如余弦相似度)找到最相关的文档片段。

  • 生成增强:将检索到的文档片段作为上下文,与用户问题一起注入 Prompt,由 LLM 基于这些事实生成精准回答。

  • 1.4 RAG 的现状

    2024-2025 年,RAG 几乎是企业 AI 落地的标配方案。但进入 2026 年,随着 LLM 上下文窗口突破 1M Token、Skill/MCP 等新范式兴起,RAG 的适用边界正在被重新审视。一个值得关注的数字:2025 年一篇 arXiv 论文对政府文档 QA 任务的误差分析发现,48% 的 RAG 错误答案根源不在模型,而在检索阶段就漏掉了关键证据。

    这意味着 RAG 仍然有效,但需要更精细的优化。Spring AI Alibaba 正是在这个背景下,提供了从基础到高级的完整 RAG 解决方案。


    二、为什么会有 RAG?替代方案与场景选择

    2.1 RAG 诞生的背景

    RAG 诞生的直接原因是早期 LLM 的两个核心局限:

    局限一:上下文窗口太小

    • GPT-3 时代:4K tokens
    • 早期 GPT-4:8K tokens
    • 问题:无法容纳完整的知识库,必须先筛选再输入

    局限二:知识截止与幻觉

    • 模型训练数据有明确截止日期
    • 无法获取实时信息
    • 容易产生事实性幻觉

    RAG 的核心逻辑:先用向量检索从大规模知识库中筛选出最相关的片段,再将这些片段作为上下文喂给 LLM。

    2.2 RAG 的替代方案

    2026 年,让 LLM 获取外部能力的技术路线已经不止 RAG 一条。以下是主流方案的对比:

    方案本质解决的问题适用场景成本
    RAG 运行时检索外部知识 事实性问答、知识查询 文档问答、政策查询、FAQ 低(无需训练)
    Skill 给 AI 的标准化"操作手册" 过程性知识(Know-How) 代码迁移、部署流程、审查流程 极低(Markdown 文件)
    MCP AI 与外部系统的标准连接协议 实时数据获取、系统操作 查询实时数据、调用 API
    微调(Fine-tuning) 把知识烧入模型参数 学习特定风格、行业术语 风格迁移、术语适配 高(需要 GPU)
    长上下文 / Wiki 模式 直接全量加载到上下文窗口 小规模知识库 知识库 < 100K Token
    GraphRAG 构建知识图谱 + 图遍历检索 多跳推理、实体关系问答 跨文档关联推理

    2.3 深入理解:Skill vs RAG

    Skill 是 2025 年底由 Anthropic 发起、2026 年被 27+ 主流 AI 工具采纳的开放标准。它的核心产物是一个 SKILL.md 文件——给 AI 看的"操作手册"。

    关键区别在于知识类型:

    知识类型含义典型例子最佳载体
    陈述性知识(Know-What) 事实、数据、规则 “公司差旅报销标准是 800 元/天” RAG / 知识库
    过程性知识(Know-How) 操作步骤、工作流 “如何从零部署一个 K8s 集群” Skill
    连接性知识(Know-Where) 系统在哪、API 怎么调 “ERP 系统订单接口是 /api/v2/orders” MCP

    Skill 封装的是"怎么做",RAG 存储的是"是什么"。 两者不是替代关系,而是分工关系。

    2.4 Wiki 模式:小知识库的最优解

    2026 年 4 月,OpenAI 联合创始人 Andrej Karpathy 公开分享了自己用一组 Markdown 文件替代整个 RAG 管线的做法——没有向量数据库、没有 Embedding、没有分片。他的个人研究 Wiki 长到了 100 篇文章、40 万字,效果远超 RAG。

    核心逻辑:2026 年主流模型的上下文窗口已达 1M Token,如果你的知识库在 100K~1M Token 之间(约 200~1500 页),直接全量加载到上下文即可,召回率 85% 以上。这时候上 RAG,等于给自己制造一个 48% 概率出错的检索层。

    2.5 选型决策

    你的场景推荐方案理由
    文档问答、政策查询、FAQ RAG 最成熟、Spring AI 原生支持
    代码迁移、部署流程、审查流程 Skill 过程性知识用操作手册更合适
    实时数据查询、系统操作 MCP 需要连接外部系统
    小知识库(< 100K Token) 长上下文 / Wiki 直接塞进上下文,无需检索
    跨文档多跳推理 GraphRAG VectorRAG 天然做不到跨片段关联
    通用生产环境 RAG + Skill + MCP 组合 知识 + 能力 + 连接 互补

    工程判断:大多数团队的第一选择应该是 RAG + BM25 混合检索。GraphRAG 只在业务确实需要多跳推理时才值得投入——构建和维护成本是 VectorRAG 的 5-10 倍。


    三、RAG 全流程实现与优化——基于 Spring AI Alibaba

    Spring AI Alibaba 提供了完整的 RAG 解决方案,以 ReactAgent 为核心,支持三种架构模式,并内置了从查询转换到文档后处理的全链路优化组件。

    3.1 三种 RAG 架构模式

    模式一:两步 RAG(Two-Step RAG)

    核心特点:检索操作总是在生成之前执行,流程可预测。

    #mermaid-svg-ezZ5GDtVKt6vGBCh{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ezZ5GDtVKt6vGBCh .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ezZ5GDtVKt6vGBCh .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ezZ5GDtVKt6vGBCh .error-icon{fill:#552222;}#mermaid-svg-ezZ5GDtVKt6vGBCh .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ezZ5GDtVKt6vGBCh .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ezZ5GDtVKt6vGBCh .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ezZ5GDtVKt6vGBCh .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ezZ5GDtVKt6vGBCh .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ezZ5GDtVKt6vGBCh .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ezZ5GDtVKt6vGBCh .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ezZ5GDtVKt6vGBCh .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ezZ5GDtVKt6vGBCh .marker.cross{stroke:#333333;}#mermaid-svg-ezZ5GDtVKt6vGBCh svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ezZ5GDtVKt6vGBCh p{margin:0;}#mermaid-svg-ezZ5GDtVKt6vGBCh .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-ezZ5GDtVKt6vGBCh .cluster-label text{fill:#333;}#mermaid-svg-ezZ5GDtVKt6vGBCh .cluster-label span{color:#333;}#mermaid-svg-ezZ5GDtVKt6vGBCh .cluster-label span p{background-color:transparent;}#mermaid-svg-ezZ5GDtVKt6vGBCh .label text,#mermaid-svg-ezZ5GDtVKt6vGBCh span{fill:#333;color:#333;}#mermaid-svg-ezZ5GDtVKt6vGBCh .node rect,#mermaid-svg-ezZ5GDtVKt6vGBCh .node circle,#mermaid-svg-ezZ5GDtVKt6vGBCh .node ellipse,#mermaid-svg-ezZ5GDtVKt6vGBCh .node polygon,#mermaid-svg-ezZ5GDtVKt6vGBCh .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ezZ5GDtVKt6vGBCh .rough-node .label text,#mermaid-svg-ezZ5GDtVKt6vGBCh .node .label text,#mermaid-svg-ezZ5GDtVKt6vGBCh .image-shape .label,#mermaid-svg-ezZ5GDtVKt6vGBCh .icon-shape .label{text-anchor:middle;}#mermaid-svg-ezZ5GDtVKt6vGBCh .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ezZ5GDtVKt6vGBCh .rough-node .label,#mermaid-svg-ezZ5GDtVKt6vGBCh .node .label,#mermaid-svg-ezZ5GDtVKt6vGBCh .image-shape .label,#mermaid-svg-ezZ5GDtVKt6vGBCh .icon-shape .label{text-align:center;}#mermaid-svg-ezZ5GDtVKt6vGBCh .node.clickable{cursor:pointer;}#mermaid-svg-ezZ5GDtVKt6vGBCh .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ezZ5GDtVKt6vGBCh .arrowheadPath{fill:#333333;}#mermaid-svg-ezZ5GDtVKt6vGBCh .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ezZ5GDtVKt6vGBCh .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ezZ5GDtVKt6vGBCh .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ezZ5GDtVKt6vGBCh .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ezZ5GDtVKt6vGBCh .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ezZ5GDtVKt6vGBCh .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ezZ5GDtVKt6vGBCh .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ezZ5GDtVKt6vGBCh .cluster text{fill:#333;}#mermaid-svg-ezZ5GDtVKt6vGBCh .cluster span{color:#333;}#mermaid-svg-ezZ5GDtVKt6vGBCh div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ezZ5GDtVKt6vGBCh .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ezZ5GDtVKt6vGBCh rect.text{fill:none;stroke-width:0;}#mermaid-svg-ezZ5GDtVKt6vGBCh .icon-shape,#mermaid-svg-ezZ5GDtVKt6vGBCh .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ezZ5GDtVKt6vGBCh .icon-shape p,#mermaid-svg-ezZ5GDtVKt6vGBCh .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ezZ5GDtVKt6vGBCh .icon-shape .label rect,#mermaid-svg-ezZ5GDtVKt6vGBCh .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ezZ5GDtVKt6vGBCh .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ezZ5GDtVKt6vGBCh .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ezZ5GDtVKt6vGBCh :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    User Question

    Retrieve RelevantDocuments

    Generate Answer

    Return Answerto User

    维度表现
    控制性
    灵活性
    延迟 快且可预测
    典型场景 FAQ 系统、文档机器人

    Spring AI Alibaba 提供了 ReactAgent 配合三种钩子来实现两步 RAG:AgentHook(启动时检索一次,最省时)、MessagesModelHook(每次模型调用前检索)、ModelInterceptor(提供完整请求信息访问能力)。

    实现方式一:AgentHook(推荐,最省时)

    AgentHook 在 Agent 启动时仅执行一次检索,整个推理过程中复用检索结果,避免重复检索带来的性能开销:

    // 自定义 AgentHook:在 Agent 启动时执行一次检索
    class KnowledgeBaseHook extends AgentHook {

    private final VectorStore vectorStore;
    private final int topK;

    public KnowledgeBaseHook(VectorStore vectorStore, int topK) {
    this.vectorStore = vectorStore;
    this.topK = topK;
    }

    @Override
    public String getName() {
    return "knowledge_base_hook";
    }

    @Override
    public AgentCommand beforeAgent(OverAllState state, RunnableConfig config) {
    // 从 state 中获取用户最新问题
    List<Message> messages = state.value("messages", new ArrayList<>());
    String userQuery = extractLatestUserQuery(messages);

    if (userQuery == null || userQuery.isEmpty()) {
    return new AgentCommand(state);
    }

    // 执行向量检索(仅一次)
    List<Document> docs = vectorStore.similaritySearch(
    SearchRequest.builder().query(userQuery).topK(topK).build()
    );

    // 将检索结果拼接到系统消息中
    String context = docs.stream()
    .map(Document::getText)
    .collect(Collectors.joining("\\n\\n"));
    String systemPrompt = "你是企业知识助手。请基于以下参考资料回答用户问题:\\n\\n" + context;

    // 返回增强后的 state
    state.update("systemMessage", systemPrompt);
    return new AgentCommand(state);
    }
    }

    // 使用
    ReactAgent agent = ReactAgent.builder()
    .name("two_step_rag_agent")
    .model(chatModel)
    .instruction("你是企业知识助手,请基于参考资料回答用户问题")
    .hooks(new KnowledgeBaseHook(vectorStore, 5)) // 启动时检索一次
    .build();

    agent.invoke("公司的差旅报销标准是什么?");

    实现方式二:MessagesModelHook(每次模型调用前检索)

    MessagesModelHook 在每次模型调用前执行检索。适合需要在多轮推理中持续获取最新上下文的场景:

    // 每次模型调用前都执行检索
    @HookPositions({HookPosition.BEFORE_MODEL})
    class PerModelRetrievalHook extends MessagesModelHook {

    private final VectorStore vectorStore;
    private final int topK;

    public PerModelRetrievalHook(VectorStore vectorStore, int topK) {
    this.vectorStore = vectorStore;
    this.topK = topK;
    }

    @Override
    public String getName() {
    return "per_model_retrieval_hook";
    }

    @Override
    public AgentCommand beforeModel(List<Message> previousMessages, RunnableConfig config) {
    // 提取最新用户问题
    String userQuery = extractLatestUserQuery(previousMessages);
    if (userQuery == null || userQuery.isEmpty()) {
    return new AgentCommand(previousMessages);
    }

    // 检索相关文档
    List<Document> docs = vectorStore.similaritySearch(
    SearchRequest.builder().query(userQuery).topK(topK).build()
    );
    String context = docs.stream()
    .map(Document::getText)
    .collect(Collectors.joining("\\n\\n"));

    // 在用户消息前注入检索到的上下文
    Message contextMessage = new SystemMessage("参考资料:\\n" + context);
    List<Message> newMessages = new ArrayList<>();
    newMessages.add(contextMessage);
    newMessages.addAll(previousMessages);
    return new AgentCommand(newMessages);
    }
    }

    // 使用
    ReactAgent agent = ReactAgent.builder()
    .name("per_model_rag_agent")
    .model(chatModel)
    .instruction("你是企业知识助手")
    .hooks(new PerModelRetrievalHook(vectorStore, 5)) // 每次模型调用前检索
    .build();

    实现方式三:ModelInterceptor(完整请求信息访问)

    ModelInterceptor 提供对完整 ModelRequest 的访问能力,可以修改请求参数、注入上下文、做异常重试等:

    // 自定义 ModelInterceptor:在模型调用前注入检索结果
    class RAGContextInterceptor extends ModelInterceptor {

    private final VectorStore vectorStore;
    private final int topK;

    public RAGContextInterceptor(VectorStore vectorStore, int topK) {
    this.vectorStore = vectorStore;
    this.topK = topK;
    }

    @Override
    public String getName() {
    return "rag_context_interceptor";
    }

    @Override
    public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) {
    // 从请求中获取最新消息
    String userQuery = extractUserQueryFromRequest(request);
    if (userQuery == null || userQuery.isEmpty()) {
    return handler.call(request); // 无问题,直接放行
    }

    // 检索相关文档
    List<Document> docs = vectorStore.similaritySearch(
    SearchRequest.builder().query(userQuery).topK(topK).build()
    );
    String context = docs.stream()
    .map(Document::getText)
    .collect(Collectors.joining("\\n\\n"));

    // 构造增强后的请求:在系统消息后注入检索上下文
    ModelRequest enhancedRequest = augmentRequestWithContext(request, context);

    // 调用模型
    return handler.call(enhancedRequest);
    }
    }

    // 使用
    ReactAgent agent = ReactAgent.builder()
    .name("interceptor_rag_agent")
    .model(chatModel)
    .instruction("你是企业知识助手")
    .interceptors(new RAGContextInterceptor(vectorStore, 5)) // 使用拦截器
    .build();

    选择建议:

    钩子类型触发时机性能适用场景
    AgentHook Agent 启动时一次 最优 查询在 Agent 推理过程中保持不变(绝大多数场景)
    MessagesModelHook 每次模型调用前 中等 需要根据推理结果动态调整检索内容
    ModelInterceptor 每次模型调用前 中等 需要访问完整 ModelRequest 做更复杂的增强
    模式二:Agentic RAG

    核心特点:由 LLM 驱动的 Agent 自主决定何时、何地以及如何执行检索。

    #mermaid-svg-WY7oTtwqehwBmV28{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-WY7oTtwqehwBmV28 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-WY7oTtwqehwBmV28 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-WY7oTtwqehwBmV28 .error-icon{fill:#552222;}#mermaid-svg-WY7oTtwqehwBmV28 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WY7oTtwqehwBmV28 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-WY7oTtwqehwBmV28 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WY7oTtwqehwBmV28 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WY7oTtwqehwBmV28 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-WY7oTtwqehwBmV28 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WY7oTtwqehwBmV28 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WY7oTtwqehwBmV28 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WY7oTtwqehwBmV28 .marker.cross{stroke:#333333;}#mermaid-svg-WY7oTtwqehwBmV28 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WY7oTtwqehwBmV28 p{margin:0;}#mermaid-svg-WY7oTtwqehwBmV28 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-WY7oTtwqehwBmV28 .cluster-label text{fill:#333;}#mermaid-svg-WY7oTtwqehwBmV28 .cluster-label span{color:#333;}#mermaid-svg-WY7oTtwqehwBmV28 .cluster-label span p{background-color:transparent;}#mermaid-svg-WY7oTtwqehwBmV28 .label text,#mermaid-svg-WY7oTtwqehwBmV28 span{fill:#333;color:#333;}#mermaid-svg-WY7oTtwqehwBmV28 .node rect,#mermaid-svg-WY7oTtwqehwBmV28 .node circle,#mermaid-svg-WY7oTtwqehwBmV28 .node ellipse,#mermaid-svg-WY7oTtwqehwBmV28 .node polygon,#mermaid-svg-WY7oTtwqehwBmV28 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-WY7oTtwqehwBmV28 .rough-node .label text,#mermaid-svg-WY7oTtwqehwBmV28 .node .label text,#mermaid-svg-WY7oTtwqehwBmV28 .image-shape .label,#mermaid-svg-WY7oTtwqehwBmV28 .icon-shape .label{text-anchor:middle;}#mermaid-svg-WY7oTtwqehwBmV28 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-WY7oTtwqehwBmV28 .rough-node .label,#mermaid-svg-WY7oTtwqehwBmV28 .node .label,#mermaid-svg-WY7oTtwqehwBmV28 .image-shape .label,#mermaid-svg-WY7oTtwqehwBmV28 .icon-shape .label{text-align:center;}#mermaid-svg-WY7oTtwqehwBmV28 .node.clickable{cursor:pointer;}#mermaid-svg-WY7oTtwqehwBmV28 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-WY7oTtwqehwBmV28 .arrowheadPath{fill:#333333;}#mermaid-svg-WY7oTtwqehwBmV28 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-WY7oTtwqehwBmV28 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-WY7oTtwqehwBmV28 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WY7oTtwqehwBmV28 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-WY7oTtwqehwBmV28 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WY7oTtwqehwBmV28 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-WY7oTtwqehwBmV28 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-WY7oTtwqehwBmV28 .cluster text{fill:#333;}#mermaid-svg-WY7oTtwqehwBmV28 .cluster span{color:#333;}#mermaid-svg-WY7oTtwqehwBmV28 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-WY7oTtwqehwBmV28 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-WY7oTtwqehwBmV28 rect.text{fill:none;stroke-width:0;}#mermaid-svg-WY7oTtwqehwBmV28 .icon-shape,#mermaid-svg-WY7oTtwqehwBmV28 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WY7oTtwqehwBmV28 .icon-shape p,#mermaid-svg-WY7oTtwqehwBmV28 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-WY7oTtwqehwBmV28 .icon-shape .label rect,#mermaid-svg-WY7oTtwqehwBmV28 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WY7oTtwqehwBmV28 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-WY7oTtwqehwBmV28 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-WY7oTtwqehwBmV28 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    Yes

    No

    No

    Yes

    User Input / Question

    Agent LLM

    Need external info?

    Search using tool s

    Enough to answer?

    Generate final answer

    Return to user

    维度表现
    控制性
    灵活性
    延迟 可变
    典型场景 多工具研究助手、复杂推理任务

    实现方式是为 ReactAgent 注册多个 ToolCallback(文档搜索、网络搜索、数据库查询等),Agent 根据推理结果自主决定调用哪个工具:

    // 1. 文档检索工具
    class DocumentSearchTool {
    public Response search(Request request) {
    // 从向量存储检索相关文档
    List<Document> docs = vectorStore.similaritySearch(
    SearchRequest.builder()
    .query(request.query())
    .topK(5)
    .similarityThreshold(0.6)
    .build()
    );
    // 合并文档内容作为返回结果
    String combinedContent = docs.stream()
    .map(Document::getText)
    .collect(Collectors.joining("\\n\\n"));
    return new Response(combinedContent);
    }

    public record Request(String query) { }
    public record Response(String content) { }
    }

    // 2. 网页搜索工具
    class WebSearchTool {
    public Response search(Request request) {
    // 实际实现中调用百度/Bing API
    // 这里简化为占位实现
    return new Response("从网络搜索到的信息: " + request.query());
    }

    public record Request(String query) { }
    public record Response(String content) { }
    }

    // 3. 数据库查询工具
    class DatabaseQueryTool {
    public Response query(Request request) {
    // 实际实现中查询内部业务数据库
    return new Response("从数据库查询到的信息: " + request.query());
    }

    public record Request(String query) { }
    public record Response(String content) { }
    }

    // 构造工具实例
    DocumentSearchTool docSearchTool = new DocumentSearchTool();
    WebSearchTool webSearchTool = new WebSearchTool();
    DatabaseQueryTool dbQueryTool = new DatabaseQueryTool();

    // 4. 将工具封装为 ToolCallback
    ToolCallback documentSearchCallback = FunctionToolCallback
    .builder("document_search",
    (Function<DocumentSearchTool.Request, DocumentSearchTool.Response>)
    req -> docSearchTool.search(req))
    .description("搜索内部文档库,获取与查询相关的文档片段。适用于查询公司政策、规章制度、产品文档等私有知识。")
    .inputType(DocumentSearchTool.Request.class)
    .build();

    ToolCallback webSearchCallback = FunctionToolCallback
    .builder("web_search",
    (Function<WebSearchTool.Request, WebSearchTool.Response>)
    req -> webSearchTool.search(req))
    .description("搜索互联网获取最新信息。适用于查询新闻、实时事件、公开技术资料等。")
    .inputType(WebSearchTool.Request.class)
    .build();

    ToolCallback databaseQueryCallback = FunctionToolCallback
    .builder("database_query",
    (Function<DatabaseQueryTool.Request, DatabaseQueryTool.Response>)
    req -> dbQueryTool.query(req))
    .description("查询内部业务数据库。适用于查询订单、库存、用户信息等结构化数据。")
    .inputType(DatabaseQueryTool.Request.class)
    .build();

    // 5. 创建带多工具的 ReactAgent
    ReactAgent multiSourceAgent = ReactAgent.builder()
    .name("multi_source_rag_agent")
    .model(chatModel)
    .instruction("你可以访问多个信息源,根据用户问题选择最合适的工具组合:\\n" +
    "1. document_search – 查询公司内部文档(政策、规章、产品文档)\\n" +
    "2. web_search – 查询最新互联网信息(新闻、公开资料)\\n" +
    "3. database_query – 查询内部业务数据(订单、库存、用户)\\n" +
    "如果第一次检索结果不充分,可以继续检索其他源。")
    .tools(documentSearchCallback, webSearchCallback, databaseQueryCallback)
    .build();

    // 6. 调用 Agent
    multiSourceAgent.invoke("比较我们产品文档中的功能升级和最近一个月市场上的竞品动态");

    核心优势:

    • 高度灵活:Agent 根据推理结果自主决定检索策略,可多轮检索
    • 多源融合:可同时接入文档库、网络搜索、内部数据库等
    • 可解释:工具调用过程透明,便于调试
    模式三:混合 RAG(Hybrid RAG)

    核心特点:结合两步 RAG 的可预测性和 Agentic RAG 的灵活性,引入质量验证机制。

    #mermaid-svg-hQ64kG55iSOl7q2P{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-hQ64kG55iSOl7q2P .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-hQ64kG55iSOl7q2P .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-hQ64kG55iSOl7q2P .error-icon{fill:#552222;}#mermaid-svg-hQ64kG55iSOl7q2P .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-hQ64kG55iSOl7q2P .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-hQ64kG55iSOl7q2P .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-hQ64kG55iSOl7q2P .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-hQ64kG55iSOl7q2P .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-hQ64kG55iSOl7q2P .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-hQ64kG55iSOl7q2P .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-hQ64kG55iSOl7q2P .marker{fill:#333333;stroke:#333333;}#mermaid-svg-hQ64kG55iSOl7q2P .marker.cross{stroke:#333333;}#mermaid-svg-hQ64kG55iSOl7q2P svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-hQ64kG55iSOl7q2P p{margin:0;}#mermaid-svg-hQ64kG55iSOl7q2P .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-hQ64kG55iSOl7q2P .cluster-label text{fill:#333;}#mermaid-svg-hQ64kG55iSOl7q2P .cluster-label span{color:#333;}#mermaid-svg-hQ64kG55iSOl7q2P .cluster-label span p{background-color:transparent;}#mermaid-svg-hQ64kG55iSOl7q2P .label text,#mermaid-svg-hQ64kG55iSOl7q2P span{fill:#333;color:#333;}#mermaid-svg-hQ64kG55iSOl7q2P .node rect,#mermaid-svg-hQ64kG55iSOl7q2P .node circle,#mermaid-svg-hQ64kG55iSOl7q2P .node ellipse,#mermaid-svg-hQ64kG55iSOl7q2P .node polygon,#mermaid-svg-hQ64kG55iSOl7q2P .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-hQ64kG55iSOl7q2P .rough-node .label text,#mermaid-svg-hQ64kG55iSOl7q2P .node .label text,#mermaid-svg-hQ64kG55iSOl7q2P .image-shape .label,#mermaid-svg-hQ64kG55iSOl7q2P .icon-shape .label{text-anchor:middle;}#mermaid-svg-hQ64kG55iSOl7q2P .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-hQ64kG55iSOl7q2P .rough-node .label,#mermaid-svg-hQ64kG55iSOl7q2P .node .label,#mermaid-svg-hQ64kG55iSOl7q2P .image-shape .label,#mermaid-svg-hQ64kG55iSOl7q2P .icon-shape .label{text-align:center;}#mermaid-svg-hQ64kG55iSOl7q2P .node.clickable{cursor:pointer;}#mermaid-svg-hQ64kG55iSOl7q2P .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-hQ64kG55iSOl7q2P .arrowheadPath{fill:#333333;}#mermaid-svg-hQ64kG55iSOl7q2P .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-hQ64kG55iSOl7q2P .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-hQ64kG55iSOl7q2P .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hQ64kG55iSOl7q2P .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-hQ64kG55iSOl7q2P .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hQ64kG55iSOl7q2P .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-hQ64kG55iSOl7q2P .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-hQ64kG55iSOl7q2P .cluster text{fill:#333;}#mermaid-svg-hQ64kG55iSOl7q2P .cluster span{color:#333;}#mermaid-svg-hQ64kG55iSOl7q2P div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-hQ64kG55iSOl7q2P .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-hQ64kG55iSOl7q2P rect.text{fill:none;stroke-width:0;}#mermaid-svg-hQ64kG55iSOl7q2P .icon-shape,#mermaid-svg-hQ64kG55iSOl7q2P .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hQ64kG55iSOl7q2P .icon-shape p,#mermaid-svg-hQ64kG55iSOl7q2P .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-hQ64kG55iSOl7q2P .icon-shape .label rect,#mermaid-svg-hQ64kG55iSOl7q2P .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hQ64kG55iSOl7q2P .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-hQ64kG55iSOl7q2P .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-hQ64kG55iSOl7q2P :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    不充分

    充分

    不通过

    通过

    用户问题

    查询增强重写/变体/扩展

    检索

    检索验证结果是否充分?

    调整查询

    生成答案

    答案验证是否准确完整?

    重新检索/调整

    返回答案

    维度表现
    控制性
    灵活性
    延迟 可变
    典型场景 带质量验证的领域特定问答系统

    // 假设你已经有一个配置好的向量存储和 ChatModel

    VectorStore vectorStore = ...;

    ChatModel chatModel = ...;

    // ========== 1. 多工具检索(来自 Agentic RAG) ==========

    // 文档搜索工具

    class DocumentSearchTool {

    private final VectorStore vectorStore;

    public DocumentSearchTool(VectorStore vectorStore) {

    this.vectorStore = vectorStore;

    }

    public record Request(String query) {}

    public record Response(String content) {}

    public Response search(Request request) {

    List<Document> docs = vectorStore.similaritySearch(

    org.springframework.ai.vectorstore.SearchRequest.builder()

    .query(request.query())

    .topK(5)

    .build()

    );

    String content = docs.stream()

    .map(Document::getText)

    .collect(Collectors.joining("

    "));

    return new Response(content);

    }

    }

    // 网络搜索工具(示例)

    class WebSearchTool {

    public record Request(String query) {}

    public record Response(String content) {}

    public Response search(Request request) {

    // 实际实现中调用网络搜索 API

    return new Response("网络搜索结果: " + request.query());

    }

    }

    DocumentSearchTool docSearchTool = new DocumentSearchTool(vectorStore);

    WebSearchTool webSearchTool = new WebSearchTool();

    ToolCallback documentSearchCallback = FunctionToolCallback.builder("document_search",

    (Function<DocumentSearchTool.Request, DocumentSearchTool.Response>)

    req -> docSearchTool.search(req))

    .description("从文档库中搜索相关信息")

    .inputType(DocumentSearchTool.Request.class)

    .build();

    ToolCallback webSearchCallback = FunctionToolCallback.builder("web_search",

    (Function<WebSearchTool.Request, WebSearchTool.Response>)

    req -> webSearchTool.search(req))

    .description("从互联网搜索最新信息")

    .inputType(WebSearchTool.Request.class)

    .build();

    // ========== 2. 查询增强 Hook(来自两步 RAG) ==========

    @HookPositions({HookPosition.BEFORE_AGENT})

    class QueryEnhancementHook extends AgentHook {

    private final ChatModel chatModel;

    private static final String ENHANCED_QUERY_KEY = "enhanced_query";

    public QueryEnhancementHook(ChatModel chatModel) {

    this.chatModel = chatModel;

    }

    @Override

    public String getName() {

    return "query_enhancement";

    }

    @Override

    public CompletableFuture<Map<String, Object>> beforeAgent(OverAllState state, RunnableConfig config) {

    // 从状态中提取用户查询

    Optional<Object> messagesOpt = state.value("messages");

    if (messagesOpt.isEmpty()) {

    return CompletableFuture.completedFuture(Map.of());

    }

    @SuppressWarnings("unchecked")

    List<Message> messages = (List<Message>) messagesOpt.get();

    // 提取最后一个用户消息作为查询

    String userQuery = messages.stream()

    .filter(msg -> msg instanceof UserMessage)

    .map(msg -> ((UserMessage) msg).getText())

    .reduce((first, second) -> second) // 获取最后一个

    .orElse("");

    if (userQuery.isEmpty()) {

    return CompletableFuture.completedFuture(Map.of());

    }

    // 使用 LLM 增强查询(只执行一次,在整个 Agent 执行过程中)

    // 简化示例:实际可以使用 RewriteQueryTransformer

    String enhancedQuery = enhanceQuery(userQuery);

    // 如果查询被增强,更新消息列表

    if (!enhancedQuery.equals(userQuery)) {

    List<Message> enhancedMessages = new ArrayList<>();

    // 保留系统消息和其他消息,只替换用户消息

    for (Message msg : messages) {

    if (msg instanceof UserMessage) {

    enhancedMessages.add(new UserMessage(enhancedQuery));

    } else {

    enhancedMessages.add(msg);

    }

    }

    // 将增强后的查询存储到 metadata 中,供后续使用

    config.metadata().ifPresent(meta -> {

    meta.put(ENHANCED_QUERY_KEY, enhancedQuery);

    });

    // 返回更新后的消息列表

    return CompletableFuture.completedFuture(Map.of("messages", enhancedMessages));

    }

    return CompletableFuture.completedFuture(Map.of());

    }

    private String enhanceQuery(String query) {

    // 简化示例:实际可以使用 RewriteQueryTransformer 或调用 LLM 进行查询重写

    // 这里只是示例,实际应该调用 LLM 增强查询

    // 例如:使用 RewriteQueryTransformer.builder().chatClientBuilder(…).build().transform(query)

    return query; // 实际实现中会调用 LLM 增强查询

    }

    }

    // ========== 3. 答案验证 Interceptor(来自两步 RAG) ==========

    class AnswerValidationInterceptor extends ModelInterceptor {

    private final ChatModel chatModel;

    private static final double MIN_CONFIDENCE = 0.7;

    public AnswerValidationInterceptor(ChatModel chatModel) {

    this.chatModel = chatModel;

    }

    @Override

    public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) {

    // 先调用模型生成答案

    ModelResponse response = handler.call(request);

    // 验证答案质量(简化示例)

    AssistantMessage answer = response.getResult().getOutput();

    boolean isValid = validateAnswer(answer.getText(), request);

    if (!isValid) {

    // 如果答案质量不足,可以添加提示要求重新生成

    SystemMessage validationPrompt = new SystemMessage(

    "请重新检查你的答案,确保基于提供的上下文信息,并且准确完整。"

    );

    ModelRequest retryRequest = ModelRequest.builder(request)

    .systemMessage(validationPrompt)

    .build();

    // 可以选择重试或返回当前答案

    return handler.call(retryRequest);

    }

    return response;

    }

    private boolean validateAnswer(String answer, ModelRequest request) {

    // 简化示例:实际可以使用 LLM 验证答案与上下文的一致性

    // 检查答案长度、是否包含关键信息等

    return answer != null && answer.length() > 20; // 简单验证

    }

    @Override

    public String getName() {

    return "answer_validation";

    }

    }

    // ========== 4. 创建混合 RAG Agent ==========

    ReactAgent hybridRAGAgent = ReactAgent.builder()

    .name("hybrid_rag_agent")

    .model(chatModel)

    .instruction("""

    你是一个智能助手,可以访问多个信息源来回答问题。

    使用工具时:

    1. 优先使用 document_search 搜索文档库

    2. 如果需要最新信息,使用 web_search

    3. 基于检索到的信息生成准确、完整的答案

    4. 如果信息不足,可以多次调用工具

    """)

    .tools(documentSearchCallback, webSearchCallback)

    .hooks(new QueryEnhancementHook(chatModel))

    .interceptors(new AnswerValidationInterceptor(chatModel))

    .build();

    // ========== 5. 使用混合 RAG Agent ==========

    AssistantMessage response = hybridRAGAgent.call("Spring AI Alibaba支持哪些向量数据库?");

    System.out.println("答案: " + response.getText());

    3.2 基础 RAG 实现:从 ETL 到查询

    在深入优化之前,先用 Spring AI Alibaba 搭建一个完整的 RAG 基础流程。

    第一步:文档加载(Extract)

    Spring AI 提供了多种 DocumentReader,支持 PDF、Markdown、JSON、HTML、纯文本等格式:

    // 加载 PDF 文档(按页读取)

    PagePdfDocumentReader pdfReader = new PagePdfDocumentReader(

    new FileSystemResource("path/to/document.pdf"), // PDF 文件资源

    PagePdfDocumentReaderConfig.builder()

    // 配置 PDF 文本提取时的格式选项

    .withPageExtractedTextFormatter(new ExtractedTextFormatter.Builder()

    .withNumberOfBottomTextLinesToDelete(0) // 提取时跳过每页底部 N 行(去除页脚/页码)

    .withNumberOfTopPagesToSkipBeforeRead(0) // 从第 N 页之后开始读取(跳过封面/目录)

    .build())

    .withPagesPerDocument(1) // 每 N 页打包为一个 Document 对象(1 = 每页一个)

    .build()

    );

    List<Document> documents = pdfReader.get(); // 执行读取,返回 Document 列表

    // 加载 Markdown 文档(按结构分割)

    MarkdownDocumentReader mdReader = new MarkdownDocumentReader(

    new FileSystemResource("path/to/document.md"), // Markdown 文件资源

    MarkdownDocumentReaderConfig.builder()

    .withHorizontalRuleCreateDocument(true) // 遇到 Markdown 水平规则(—)时拆分为新 Document

    .withIncludeCodeBlock(false) // 代码块(```)是否与周围段落合并(false = 独立成块)

    .withIncludeBlockquote(false) // 引用块(>)是否与周围段落合并(false = 独立成块)

    .withAdditionalMetadata("filename", "document.md")// 为生成的每个 Document 添加自定义元数据,便于后续过滤

    .build()

    );

    List<Document> mdDocuments = mdReader.get(); // 执行读取,返回 Document 列表

    // 加载纯文本文件

    TextReader textReader = new TextReader(new FileSystemResource("path/to/document.txt"));

    // 为文档注入自定义元数据(key-value),后续可在向量检索中作为过滤条件

    textReader.getCustomMetadata().put("filename", "document.txt");

    textReader.getCustomMetadata().put("category", "policy");

    List<Document> textDocuments = textReader.get(); // 读取文本内容,返回单个 Document

    // 加载 JSON 文件(将 JSON 字段映射为 Document)

    JsonReader jsonReader = new JsonReader(

    new FileSystemResource("path/to/data.json"),

    "description", // 第一个参数:作为 metadata 的 JSON 字段名

    "content" // 第二个参数:作为 Document 内容(text)的 JSON 字段名

    );
    List<Document> jsonDocuments = jsonReader.get(); // 逐条 JSON 记录生成 Document

    常用 DocumentReader 类型一览:

    Reader适用格式主要配置项
    TextReader 纯文本 自定义元数据
    MarkdownDocumentReader Markdown 水平规则、代码块、引用块处理
    PagePdfDocumentReader PDF(按页) 每页 Document 数、跳过页
    ParagraphPdfDocumentReader PDF(按段落) 段落分割规则
    JsonReader JSON 字段映射、JSON Pointer
    HtmlReader HTML 标签过滤、CSS 选择器
    TikaDocumentReader 多格式(DOCX/PPTX/HTML) 基于 Apache Tika
    第二步:文本分割(Transform)

    将大文档切分为适合检索的文本块,Spring AI Alibaba 提供了多种分割器:

    // 方式一:基于 Token 的分割(适合英文)
    TokenTextSplitter tokenSplitter = new TokenTextSplitter(
    800, // chunkSize:每个块的目标 Token 数
    200, // chunkOverlap:相邻块重叠的 Token 数,防止关键信息被截断
    100, // minChunkSize:最小块大小,小于此值的块会被丢弃
    10000, // maxChunkSize:最大块字符数(安全上限)
    true // keepSeparators:是否保留分隔符
    );
    List<Document> tokenChunks = tokenSplitter.apply(documents);

    // 方式二:递归字符分割(推荐中文场景)
    // 分隔符优先级从高到低:段落 > 换行 > 中文句号 > 中文逗号 > 字符
    List<String> chineseSeparators = Arrays.asList(
    "\\n\\n", "\\n", "。", "!", "?", ";", ",", // 中文分隔符
    ". ", "! ", "? ", // 英文分隔符
    " ", // 空格
    "" // 最终降级:逐字符
    );
    RecursiveCharacterTextSplitter recursiveSplitter = new RecursiveCharacterTextSplitter(
    800, // chunkSize:每个块的目标字符数
    150, // chunkOverlap:相邻块重叠字符数
    chineseSeparators // 分隔符优先级列表,从高到低
    );
    List<Document> recursiveChunks = recursiveSplitter.apply(documents);

    // 方式三:基于语言模型的句子级分割(高精度场景)
    SentenceSplitter sentenceSplitter = new SentenceSplitter(
    chatModel, // 用 LLM 识别真正的句子边界
    800, // chunkSize
    150 // chunkOverlap
    );
    List<Document> sentenceChunks = sentenceSplitter.apply(documents);

    分割器选型表:

    分割器分割依据适合场景中文适配
    TokenTextSplitter Token 数量 英文文档 一般(可能乱码)
    RecursiveCharacterTextSplitter 多级分隔符递归 中文/混合文档 优秀
    SentenceSplitter LLM 识别句子边界 高精度语义分割
    自定义 TextSplitter 自定义逻辑 结构化文档(Markdown 按标题、代码按函数) 自定义

    参数调优建议:

    参数推荐值(中文)说明
    chunkSize 800~1000 字符 太小语义断裂,太大检索效率下降
    chunkOverlap 150~250 字符 保持上下文连贯,防止关键信息被截断
    分隔符优先级 段落 > 换行 > 句号 > 逗号 > 字符 优先在语义边界切分
    第三步:向量化与存储(Load)

    // 将文档块写入向量数据库(自动完成 Embedding + 存储)
    vectorStore.write(recursiveChunks);

    Spring AI Alibaba 支持多种向量数据库:Milvus、Redis、Elasticsearch、Pinecone 等。

    第四步:检索与生成

    基于 ReactAgent + AgentHook 的两步 RAG 实现(最省时方案):

    ReactAgent agent = ReactAgent.builder()
    .name("basic_rag_agent")
    .model(chatModel)
    .instruction("你是企业知识助手,请基于检索到的参考资料回答用户问题")
    .hooks(new KnowledgeBaseHook(vectorStore, 5))
    .build();

    agent.invoke("公司的差旅报销标准是什么?");

    这就是一个最基础的 RAG 系统。但在生产环境中,基础 RAG 往往不够用——用户问得模糊、检索结果不精准、答案可能遗漏关键信息。这就需要全链路优化。

    3.3 RAG 全链路优化

    Spring AI 通过模块化的 RAG 优化组件,覆盖四个阶段:

    用户提问
    → [Pre-Retrieval:查询转换/扩展] ← 优化提问
    → [Retrieval:文档检索] ← 优化检索
    → [Post-Retrieval:文档后处理] ← 优化结果
    → [Generation:查询增强/上下文注入] ← 优化生成
    → 回答

    3.3.1 Pre-Retrieval(检索前优化)

    检索前优化的核心思想是:用户的原始提问往往不是最好的检索 query。可能太模糊、可能包含代词、可能用词与文档不一致。优化提问质量,是提升 RAG 效果的第一步。

    (1)RewriteQueryTransformer——查询重写

    将过长或含代词的模糊问题,重写为具体、检索友好的 query:

    RewriteQueryTransformer rewriteTransformer = RewriteQueryTransformer.builder()
    .chatModel(chatModel)
    .build();

    // 使用示例:
    // 原始问题:"那个怎么弄来着?"
    // 重写后:"2026年最新的员工病假申请流程是什么?"
    Query originalQuery = new Query("那个怎么弄来着?");
    Query rewritten = rewriteTransformer.transform(originalQuery);
    System.out.println(rewritten.text()); // "2026年最新的员工病假申请流程是什么?"

    (2)CompressionQueryTransformer——查询压缩

    将对话历史和当前问题压缩成一个独立、自包含的 query。这是处理多轮对话中代词指代的利器:

    CompressionQueryTransformer compressionTransformer = CompressionQueryTransformer.builder()
    .chatModel(chatModel)
    .build();

    // 使用示例:
    // 历史对话:
    // 用户:"碧海湾小区的位置在哪?"
    // AI:"碧海湾小区位于XX区XX路…"
    // 当前问题:"那这个小区的二手房均价是多少?"
    //
    // 压缩后:"碧海湾小区的二手房均价是多少?"
    // ("这个小区"被替换为"碧海湾小区",检索才能命中)
    List<Message> history = List.of(
    new UserMessage("碧海湾小区的位置在哪?"),
    new AssistantMessage("碧海湾小区位于XX区XX路…")
    );
    Query currentQuery = Query.builder()
    .text("那这个小区的二手房均价是多少?")
    .history(history)
    .build();
    Query compressed = compressionTransformer.transform(currentQuery);
    System.out.println(compressed.text()); // "碧海湾小区的二手房均价是多少?"

    (3)TranslationQueryTransformer——查询翻译

    将非目标语言的查询翻译为知识库的主要语言:

    TranslationQueryTransformer translationTransformer = TranslationQueryTransformer.builder()
    .chatModel(chatModel)
    .targetLanguage("zh") // 翻译为中文
    .build();

    // 使用示例:
    // 原始问题:"What is the refund policy?"
    // 翻译后:"退款政策是什么?"
    Query englishQuery = new Query("What is the refund policy?");
    Query translated = translationTransformer.transform(englishQuery);
    System.out.println(translated.text()); // "退款政策是什么?"

    (4)MultiQueryExpander——多查询扩展

    为同一个问题生成多个不同表述的变体,多路检索后合并结果,提升召回率:

    MultiQueryExpander queryExpander = MultiQueryExpander.builder()
    .chatModel(chatModel)
    .numberOfQueries(3) // 生成 3 个变体
    .build();

    // 使用示例:
    // 原始问题:"怎么请假?"
    // 扩展为:
    // 1. "员工请假流程是什么?"
    // 2. "病假申请需要哪些材料?"
    // 3. "年假如何申请?"
    // 三路检索 → 合并去重 → 更全面的检索结果
    Query originalQuery = new Query("怎么请假?");
    ExpandedQuery expanded = queryExpander.expand(originalQuery);
    List<Query> variants = expanded.queries();
    variants.forEach(q -> System.out.println(q.text()));
    // 输出:
    // 员工请假流程是什么?
    // 病假申请需要哪些材料?
    // 年假如何申请?

    3.3.2 Retrieval(检索优化)

    检索阶段的核心组件是 VectorStoreDocumentRetriever,它负责从向量数据库中检索相关文档:

    // 构造检索器
    VectorStoreDocumentRetriever retriever = VectorStoreDocumentRetriever.builder()
    .vectorStore(vectorStore) // 向量存储实例
    .similarityThreshold(0.5) // 相似度阈值(0~1),低于此值的结果会被过滤
    .topK(5) // 返回最相关的 5 个文档
    .filterExpression("type == 'policy'") // 过滤表达式(可选)
    .build();

    // 使用
    Query query = new Query("差旅报销标准");
    List<Document> results = retriever.retrieve(query);

    检索策略对比:

    策略说明适用场景
    纯向量检索 基于语义相似度 通用场景
    纯关键词检索(BM25) 基于关键词精确匹配 精确查找(订单号、航班号)
    混合检索 向量 + BM25 结合 生产环境推荐
    3.3.3 Post-Retrieval(检索后优化)

    检索回来的文档不一定都是高质量的,可能包含重复、不相关或冗余的内容。Post-Retrieval 阶段负责"精炼"检索结果。

    (1)DocumentJoiner——文档合并器

    将多路检索的结果合并为一个文档列表,支持去重:

    // 构造文档合并器
    ConcatenationDocumentJoiner joiner = new ConcatenationDocumentJoiner();

    // 使用:多查询扩展后,三路检索的结果合并去重
    List<Query> queries = expanded.queries(); // 来自 MultiQueryExpander
    List<List<Document>> retrievedResults = queries.stream()
    .map(retriever::retrieve)
    .collect(Collectors.toList());

    List<Document> merged = joiner.join(retrievedResults);

    (2)文档重排序(Reranking)

    用更精确的模型对检索结果重新排序,将最相关的文档排到前面:

    // Spring AI Alibaba 支持 DashScope 的 Rerank 模型
    // 检索阶段先宽召回(topK=20),再用 Reranker 精排到 topK=5

    // 构造重排序模型(DashScope GTE-Rerank)
    DocumentRanker reranker = new DashScopeDocumentRanker(
    DashScopeDocumentRankerOptions.builder()
    .withModelName("gte-rerank") // 使用 GTE-Rerank 模型
    .withTopN(5) // 精排后返回前 5 个
    .build()
    );

    // 完整流程:粗召回 → 重排序
    List<Document> coarseResults = retriever.retrieve(query); // 假设返回 20 个
    List<Document> reranked = reranker.rank(query, coarseResults); // 精排到 5 个

    (3)文档压缩

    对过长的文档片段进行摘要压缩,只保留与查询相关的核心信息,减少 Token 消耗:

    // 使用 LLM 压缩文档
    DocumentCompressor compressor = new LLMCompressor(
    chatModel,
    LLMCompressorOptions.builder()
    .withMaxLength(200) // 压缩后最大字符数
    .withPreserveKeywords(true) // 保留关键词
    .build()
    );

    List<Document> compressed = compressor.compress(query, retrievedDocs);

    (4)文档去重

    多路检索或查询扩展后,不同检索路径可能返回相同的文档片段,需要去重:

    // 基于内容相似度去重
    DocumentDeduplicator deduplicator = new DocumentDeduplicator(
    DocumentDeduplicatorOptions.builder()
    .withSimilarityThreshold(0.9) // 相似度大于 0.9 视为重复
    .build()
    );
    List<Document> deduplicated = deduplicator.deduplicate(retrievedDocs);

    3.3.4 Generation(生成优化)

    生成阶段的优化主要是如何将检索到的上下文更好地注入 Prompt。

    ContextualQueryAugmenter——上下文查询增强器

    ContextualQueryAugmenter augmenter = ContextualQueryAugmenter.builder()
    .allowEmptyContext(false) // 没有检索到相关文档时,是否允许模型回答
    .build();

    // allowEmptyContext = false 时:如果检索不到任何相关文档,模型会直接回复"无法回答"
    // 而不是编造答案,这是防止幻觉的最后一道防线

    3.3.5 完整管线构建

    将上述所有优化组件组合起来,构建一个完整的 RAG 优化管线:

    @Configuration
    public class AdvancedRAGConfig {

    // Pre-Retrieval:查询压缩(处理多轮对话代词指代)
    @Bean
    public CompressionQueryTransformer compressionQueryTransformer(ChatModel chatModel) {
    return CompressionQueryTransformer.builder()
    .chatModel(chatModel)
    .build();
    }

    // Pre-Retrieval:多查询扩展(提升召回率)
    @Bean
    public MultiQueryExpander multiQueryExpander(ChatModel chatModel) {
    return MultiQueryExpander.builder()
    .chatModel(chatModel)
    .numberOfQueries(3)
    .build();
    }

    // Retrieval:向量检索
    @Bean
    public VectorStoreDocumentRetriever vectorStoreDocumentRetriever(VectorStore vectorStore) {
    return VectorStoreDocumentRetriever.builder()
    .vectorStore(vectorStore)
    .similarityThreshold(0.5)
    .topK(5)
    .build();
    }

    // Post-Retrieval:文档合并
    @Bean
    public ConcatenationDocumentJoiner concatenationDocumentJoiner() {
    return new ConcatenationDocumentJoiner();
    }

    // Post-Retrieval:文档重排序
    @Bean
    public DocumentRanker documentRanker() {
    return new DashScopeDocumentRanker(
    DashScopeDocumentRankerOptions.builder()
    .withModelName("gte-rerank")
    .withTopN(5)
    .build()
    );
    }

    // Generation:上下文增强
    @Bean
    public ContextualQueryAugmenter contextualQueryAugmenter() {
    return ContextualQueryAugmenter.builder()
    .allowEmptyContext(false)
    .build();
    }
    }

    构建 RAG 业务流:

    @Service
    public class AdvancedRAGService {

    private final CompressionQueryTransformer compressionTransformer;
    private final MultiQueryExpander queryExpander;
    private final VectorStoreDocumentRetriever retriever;
    private final ConcatenationDocumentJoiner joiner;
    private final DocumentRanker reranker;
    private final ContextualQueryAugmenter augmenter;
    private final ChatModel chatModel;

    public String query(String userQuestion, List<Message> history) {
    // 1. Pre-Retrieval:查询压缩
    Query compressedQuery = compressionTransformer.transform(
    Query.builder().text(userQuestion).history(history).build()
    );

    // 2. Pre-Retrieval:多查询扩展
    ExpandedQuery expandedQueries = queryExpander.expand(compressedQuery);

    // 3. Retrieval:多路检索
    List<List<Document>> retrievedResults = expandedQueries.queries().stream()
    .map(retriever::retrieve)
    .collect(Collectors.toList());

    // 4. Post-Retrieval:文档合并去重
    List<Document> joinedDocs = joiner.join(retrievedResults);

    // 5. Post-Retrieval:重排序精排
    List<Document> rerankedDocs = reranker.rank(compressedQuery, joinedDocs);

    // 6. Generation:上下文增强 + 生成答案
    Query finalQuery = augmenter.augment(compressedQuery, rerankedDocs);

    ChatClient chatClient = ChatClient.builder(chatModel).build();
    return chatClient.prompt()
    .user(finalQuery.text())
    .call()
    .content();
    }
    }

    完整数据流:

    #mermaid-svg-vYAobzYh5iHvtJRs{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-vYAobzYh5iHvtJRs .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vYAobzYh5iHvtJRs .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vYAobzYh5iHvtJRs .error-icon{fill:#552222;}#mermaid-svg-vYAobzYh5iHvtJRs .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vYAobzYh5iHvtJRs .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vYAobzYh5iHvtJRs .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vYAobzYh5iHvtJRs .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vYAobzYh5iHvtJRs .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vYAobzYh5iHvtJRs .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vYAobzYh5iHvtJRs .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vYAobzYh5iHvtJRs .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vYAobzYh5iHvtJRs .marker.cross{stroke:#333333;}#mermaid-svg-vYAobzYh5iHvtJRs svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vYAobzYh5iHvtJRs p{margin:0;}#mermaid-svg-vYAobzYh5iHvtJRs .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-vYAobzYh5iHvtJRs .cluster-label text{fill:#333;}#mermaid-svg-vYAobzYh5iHvtJRs .cluster-label span{color:#333;}#mermaid-svg-vYAobzYh5iHvtJRs .cluster-label span p{background-color:transparent;}#mermaid-svg-vYAobzYh5iHvtJRs .label text,#mermaid-svg-vYAobzYh5iHvtJRs span{fill:#333;color:#333;}#mermaid-svg-vYAobzYh5iHvtJRs .node rect,#mermaid-svg-vYAobzYh5iHvtJRs .node circle,#mermaid-svg-vYAobzYh5iHvtJRs .node ellipse,#mermaid-svg-vYAobzYh5iHvtJRs .node polygon,#mermaid-svg-vYAobzYh5iHvtJRs .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-vYAobzYh5iHvtJRs .rough-node .label text,#mermaid-svg-vYAobzYh5iHvtJRs .node .label text,#mermaid-svg-vYAobzYh5iHvtJRs .image-shape .label,#mermaid-svg-vYAobzYh5iHvtJRs .icon-shape .label{text-anchor:middle;}#mermaid-svg-vYAobzYh5iHvtJRs .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-vYAobzYh5iHvtJRs .rough-node .label,#mermaid-svg-vYAobzYh5iHvtJRs .node .label,#mermaid-svg-vYAobzYh5iHvtJRs .image-shape .label,#mermaid-svg-vYAobzYh5iHvtJRs .icon-shape .label{text-align:center;}#mermaid-svg-vYAobzYh5iHvtJRs .node.clickable{cursor:pointer;}#mermaid-svg-vYAobzYh5iHvtJRs .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-vYAobzYh5iHvtJRs .arrowheadPath{fill:#333333;}#mermaid-svg-vYAobzYh5iHvtJRs .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-vYAobzYh5iHvtJRs .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-vYAobzYh5iHvtJRs .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vYAobzYh5iHvtJRs .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-vYAobzYh5iHvtJRs .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vYAobzYh5iHvtJRs .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-vYAobzYh5iHvtJRs .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-vYAobzYh5iHvtJRs .cluster text{fill:#333;}#mermaid-svg-vYAobzYh5iHvtJRs .cluster span{color:#333;}#mermaid-svg-vYAobzYh5iHvtJRs div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-vYAobzYh5iHvtJRs .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-vYAobzYh5iHvtJRs rect.text{fill:none;stroke-width:0;}#mermaid-svg-vYAobzYh5iHvtJRs .icon-shape,#mermaid-svg-vYAobzYh5iHvtJRs .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vYAobzYh5iHvtJRs .icon-shape p,#mermaid-svg-vYAobzYh5iHvtJRs .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-vYAobzYh5iHvtJRs .icon-shape .label rect,#mermaid-svg-vYAobzYh5iHvtJRs .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vYAobzYh5iHvtJRs .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-vYAobzYh5iHvtJRs .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-vYAobzYh5iHvtJRs :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    用户提问:这个小区的二手房均价是多少?

    CompressionQueryTransformer

    结合对话历史压缩为:碧海湾小区的二手房均价是多少?

    MultiQueryExpander

    扩展为3个变体:1. 碧海湾小区二手房均价2. 碧海湾小区房价走势3. 碧海湾小区成交价格

    VectorStoreDocumentRetriever

    三路检索,每路 topK=5

    ConcatenationDocumentJoiner

    合并去重,得到 12 个文档片段

    DocumentRanker 重排序

    精排到 top 5 个最相关文档

    ContextualQueryAugmenter

    将文档片段注入 Prompt 上下文

    ChatModel 生成精准回答

    3.4 RAG + ChatMemory 的组合使用

    在多轮对话场景中,RAG 需要与对话记忆(ChatMemory)配合使用。关键点在于 Advisor 的执行顺序:

    ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(
    // 1. 先加载对话历史
    MessageChatMemoryAdvisor(chatMemory),
    // 2. 再执行 RAG(此时 CompressionQueryTransformer 可以看到历史)
    ragAdvisor
    )
    .build();

    为什么顺序重要? 如果 RAG Advisor 在 Memory Advisor 之前执行,CompressionQueryTransformer 就看不到对话历史,无法将"这个小区"替换为"碧海湾小区"。

    3.5 自定义 Prompt 模板

    可以通过自定义模板来控制生成行为:

    // 构建自定义 Prompt 模板:用于控制 RAG 生成时的提示词拼接方式
    PromptTemplate customPromptTemplate = PromptTemplate.builder()
    // 配置模板渲染器(基于 StringTemplate 引擎)
    .renderer(StTemplateRenderer.builder()
    .startDelimiterToken('<') // 模板变量起始分隔符:'<' (默认是 '${')
    .endDelimiterToken('>') // 模板变量结束分隔符:'>' (默认是 '}')
    .build())
    // 模板正文:必须包含 <query> 和 <question_answer_context> 两个占位符
    // – <query> 会被替换为用户原始问题
    // – <question_answer_context> 会被替换为检索到的文档片段
    .template("""
    <query>
    Context information is below.
    ———————
    <question_answer_context>
    ———————
    Given the context information and no prior knowledge, answer the query.
    Follow these rules:
    1. If the answer is not in the context, just say that you don't know.
    2. Avoid statements like "Based on the context…" or "The provided information…".
    """
    )
    .build(); // 构造 PromptTemplate 实例

    // 将自定义模板应用到 RAG Advisor
    QuestionAnswerAdvisor qaAdvisor = QuestionAnswerAdvisor.builder(vectorStore)
    .promptTemplate(customPromptTemplate) // 注入自定义 Prompt 模板
    .build();

    模板中必须包含两个占位符:

    • <query>:接收用户问题
    • <question_answer_context>:接收检索到的上下文

    3.6 完整 ETL 管道

    以下是一个从文档加载到向量存储的完整 ETL 管道:

    @Configuration
    public class RAGConfiguration {

    @Bean
    public VectorStore vectorStore(EmbeddingModel embeddingModel) {
    return new SimpleVectorStore(embeddingModel);
    }

    @Bean
    public TextSplitter textSplitter() {
    // 中文优先的递归字符分割器
    List<String> separators = Arrays.asList(
    "\\n\\n", "\\n", "。", "!", "?", ";", ",",
    ". ", "! ", "? ", " ", ""
    );
    return new RecursiveCharacterTextSplitter(800, 150, separators);
    }
    }

    文档摄入服务:

    @Service
    public class DocumentIngestionService {

    private final VectorStore vectorStore;
    private final TextSplitter textSplitter;

    public void ingestDocument(Resource resource) {
    // 1. 加载文档
    TextReader textReader = new TextReader(resource);
    textReader.getCustomMetadata().put("filename", resource.getFilename());
    List<Document> documents = textReader.get();

    // 2. 分割文档
    List<Document> chunks = textSplitter.apply(documents);

    // 3. 写入向量数据库(自动完成 Embedding)
    vectorStore.write(chunks);
    }
    }

    3.7 最佳实践总结

    文档处理:

    • 优先使用 RecursiveCharacterTextSplitter 处理中文文档
    • chunkSize 设为 800~1000 字符,chunkOverlap 设为 150~250 字符
    • 为文档添加元数据(来源、类型、时间),便于后续过滤

    检索增强策略:

    • 多轮对话场景必须使用 CompressionQueryTransformer
    • 召回率不足时使用 MultiQueryExpander
    • 生产环境推荐向量 + BM25 混合检索

    系统配置:

    • 相似度阈值建议 0.5~0.8,过低会引入噪声,过高会漏掉相关文档
    • topK 建议 5~10,需要平衡精度与 Token 消耗
    • allowEmptyContext = false 防止无检索结果时的幻觉

    钩子选择:

    • 简单两步 RAG 优先使用 AgentHook(最省时)
    • 多轮推理中需要动态调整检索时使用 MessagesModelHook 或 ModelInterceptor

    Advisor 执行顺序:

    • Memory Advisor 必须在 RAG Advisor 之前,确保查询压缩能访问对话历史

    总结

    RAG 不是万能的,但在大规模知识库问答场景中,它仍然是最成熟、最实用的方案。关键在于:

  • 选对方案:小知识库用长上下文,过程性知识用 Skill,实时数据用 MCP,事实性问答用 RAG
  • 选对架构:FAQ 用两步 RAG,复杂推理用 Agentic RAG,高质量要求用混合 RAG
  • 做好优化:查询压缩解决代词指代,多查询扩展提升召回率,重排序精炼结果,空上下文防护防止幻觉
  • 选对分割器:中文场景优先 RecursiveCharacterTextSplitter,参数需要根据实际文档调优
  • Spring AI Alibaba 的模块化设计让这些优化可以灵活组合——从最简的 AgentHook 两步 RAG,到完全定制的 RetrievalAugmentationAdvisor,开发者可以根据业务需求选择合适的复杂度。


    参考资源:

    • Spring AI Alibaba 官方文档
    • Spring AI Alibaba RAG 高级用法
    • Spring AI RAG 官方文档
    • Spring AI Alibaba Agent Framework
    赞(0)
    未经允许不得转载:171主机测评 » 一文吃透 Spring AI Alibaba RAG:三种架构模式 + 全流程优化详解
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址