Spring AI Alibaba 1.x 系列【73】两步 RAG

文章目录

  • [1. RAG 概述](#1. RAG 概述)
    • [1.1 什么是 RAG](#1.1 什么是 RAG)
    • [1.2 核心流程](#1.2 核心流程)
    • [1.3 三种 RAG 架构](#1.3 三种 RAG 架构)
    • [1.4 构建模块](#1.4 构建模块)
  • [2. 两步 RAG](#2. 两步 RAG)
    • [2.1 原理](#2.1 原理)
    • [2.2 三种实现方式](#2.2 三种实现方式)
  • [3. 方式一:MessagesModelHook 实现](#3. 方式一:MessagesModelHook 实现)
    • [3.1 原理](#3.1 原理)
    • [3.2 实现代码](#3.2 实现代码)
    • [3.3 Agent 配置](#3.3 Agent 配置)
    • [3.4 调用示例](#3.4 调用示例)
  • [4. 方式二:ModelInterceptor 实现](#4. 方式二:ModelInterceptor 实现)
    • [4.1 原理](#4.1 原理)
    • [4.2 实现代码](#4.2 实现代码)
    • [4.3 Agent 配置](#4.3 Agent 配置)
    • [4.4 调用示例](#4.4 调用示例)
  • [5. 方式三:AgentHook 实现(性能优化)](#5. 方式三:AgentHook 实现(性能优化))
    • [5.1 原理](#5.1 原理)
    • [5.2 AgentHook 实现](#5.2 AgentHook 实现)
    • [5.3 配套的 RagContextInterceptor](#5.3 配套的 RagContextInterceptor)
    • [5.4 Agent 配置](#5.4 Agent 配置)
    • [5.5 调用示例](#5.5 调用示例)
  • [6. 三种方式对比总结](#6. 三种方式对比总结)

1. RAG 概述

1.1 什么是 RAG

大型语言模型(LLM)虽然强大,但有两个关键限制:

  • 有限上下文 ------ 无法一次性摄取整个语料库
  • 静态知识 ------ 训练数据在某个时间点被冻结

检索增强生成 (Retrieval-Augmented Generation,RAG) 通过在查询时获取相关的外部知识来解决这些问题:使用特定上下文信息来增强 LLM 的回答。

1.2 核心流程

复制代码
用户查询 → 检索相关文档 → 构建上下文 → 注入 LLM → 生成回答

每个组件都是模块化的:可以独立替换加载器、分割器、嵌入模型或向量存储。

1.3 三种 RAG 架构

架构 描述 控制性 灵活性 延迟 场景
两步 RAG 检索总是在生成之前执行 高 低 快 FAQ、文档问答
Agentic RAG Agent 自主决定何时检索 低 高 可变 多工具研究助手
混合 RAG 结合两者,含验证步骤 中 中 可变 高精度领域问答

1.4 构建模块

Spring AI Alibaba 基于 Spring AI 提供了完整的 RAG 组件栈:

复制代码
┌─────────────────────────────────────────┐
│                ETL Pipeline               │
│  DocumentReader → TextSplitter → VectorStore │
├─────────────────────────────────────────┤
│              检索层                       │
│  VectorStore.similaritySearch()           │
├─────────────────────────────────────────┤
│              Agent 集成层                 │
│  MessagesModelHook / ModelInterceptor    │
│  AgentHook / ToolCallback                │
└─────────────────────────────────────────┘
  • DocumentReader :从 PDF、Word、Markdown、TXT 等格式加载文档
  • TextSplitter:将大文档分割为适合上下文窗口的块
  • EmbeddingModel :将文本转为向量(如 DashScope text-embedding-v4)
  • VectorStore :存储和检索向量(SimpleVectorStore / Milvus / Elasticsearch 等)
  • Hook / Interceptor :在 Agent 生命周期中注入检索逻辑

2. 两步 RAG

2.1 原理

两步 RAG 是最简单、最可预测的 RAG 架构:

复制代码
用户查询 → [Step 1: 检索文档] → [Step 2: 增强上下文 → 生成回答] → 返回结果

检索步骤总是在生成步骤之前执行。这种模式适合大多数场景,如 FAQ 机器人、文档问答等。

2.2 三种实现方式

Spring AI Alibaba 提供了三种在 Agent 中实现两步 RAG 的方式:

方式 执行时机 检索次数 适用场景
MessagesModelHook 每次模型调用前 每次 reasoning 循环 需要根据每次推理动态检索
ModelInterceptor 每次模型调用前 每次 reasoning 循环 需要访问完整请求信息
AgentHook Agent 开始时 只检索一次 查询不变时优化性能

3. 方式一:MessagesModelHook 实现

3.1 原理

MessagesModelHook 在每次模型调用前触发,可以修改发送给 LLM 的消息列表。我们将检索到的文档内容注入为 SystemMessage。

复制代码
ReAct 循环:
  [Think] → [MessagesModelHook: 检索文档 → 注入 SystemMessage] → [LLM 调用] → [Act] → ...

3.2 实现代码

java 复制代码
public class RagMessagesHook extends MessagesModelHook {

    private final VectorStore vectorStore;
    private final int topK;

    @Override
    public AgentCommand beforeModel(List<Message> previousMessages, RunnableConfig config) {
        // 1. 提取用户问题
        String userQuestion = extractUserQuestion(previousMessages);

        // 2. 检索相关文档
        List<Document> relevantDocs = vectorStore.similaritySearch(
                SearchRequest.builder().query(userQuestion).topK(topK).build());

        // 3. 构建上下文
        String context = relevantDocs.stream()
                .map(Document::getText)
                .collect(Collectors.joining("\n\n"));

        // 4. 构建增强的消息列表(SystemMessage + 原有消息)
        List<Message> enhancedMessages = new ArrayList<>();
        enhancedMessages.add(new SystemMessage("基于以下上下文回答问题:\n" + context));
        enhancedMessages.addAll(previousMessages);

        return new AgentCommand(enhancedMessages, UpdatePolicy.REPLACE);
    }
}

3.3 Agent 配置

java 复制代码
ReactAgent agent = ReactAgent.builder()
        .name("two-step-rag-hook")
        .model(chatModel)
        .hooks(new RagMessagesHook(vectorStore, topK))
        .build();

3.4 调用示例

bash 复制代码
curl "http://localhost:8088/rag/two-step/messages-hook?query=Spring AI Alibaba支持哪些向量数据库?"

4. 方式二:ModelInterceptor 实现

4.1 原理

ModelInterceptor 与 MessagesModelHook 类似,都在每次模型调用前触发。区别在于 Interceptor 可以访问更完整的请求信息(包括 systemMessage、tools 等),通过 ModelRequest.Builder 修改请求。

复制代码
ReAct 循环:
  [Think] → [ModelInterceptor: 检索文档 → 增强 systemPrompt] → [LLM 调用] → [Act] → ...

4.2 实现代码

java 复制代码
public class RagModelInterceptor extends ModelInterceptor {

    private final VectorStore vectorStore;
    private final int topK;

    @Override
    public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) {
        // 1. 提取用户查询
        String userQuery = extractUserQuery(request);

        // 2. 检索相关文档
        List<Document> relevantDocs = vectorStore.similaritySearch(
                SearchRequest.builder().query(userQuery).topK(topK).build());

        // 3. 构建 RAG 上下文
        String context = relevantDocs.stream()
                .map(Document::getText)
                .collect(Collectors.joining("\n\n"));

        // 4. 增强 systemPrompt(合并原有 prompt + RAG 上下文)
        String ragPrompt = "上下文:\n" + context;
        SystemMessage enhancedMessage = request.getSystemMessage() == null
                ? new SystemMessage(ragPrompt)
                : new SystemMessage(request.getSystemMessage().getText() + "\n\n" + ragPrompt);

        // 5. 构建增强请求
        ModelRequest enhancedRequest = ModelRequest.builder(request)
                .systemMessage(enhancedMessage)
                .build();

        return handler.call(enhancedRequest);
    }
}

4.3 Agent 配置

java 复制代码
ReactAgent agent = ReactAgent.builder()
        .name("two-step-rag-interceptor")
        .model(chatModel)
        .interceptors(new RagModelInterceptor(vectorStore, topK))
        .build();

4.4 调用示例

bash 复制代码
curl "http://localhost:8088/rag/two-step/model-interceptor?query=什么是两步RAG?"

5. 方式三:AgentHook 实现(性能优化)

5.1 原理

前两种方式在 每次 ReAct reasoning 循环 中都执行检索。如果 Agent 多次调用模型,检索也会执行多次,造成不必要的开销。

AgentHook 在 Agent 开始时只执行一次 ,将检索结果缓存到 RunnableConfig.metadata 中。后续的 ModelInterceptor 直接从缓存读取,避免重复检索。

复制代码
AgentHook (只执行一次):
  检索文档 → 缓存到 config.metadata["rag_context"]
      │
      ▼
ReAct 循环:
  [Think] → [RagContextInterceptor: 读取缓存] → [LLM 调用] → [Act] → ...
             ↑ 不再检索,直接读取

5.2 AgentHook 实现

java 复制代码
@HookPositions({HookPosition.BEFORE_AGENT})
public class QueryEnhancementHook extends AgentHook {

    public static final String RAG_CONTEXT_KEY = "rag_context";

    @Override
    public CompletableFuture<Map<String, Object>> beforeAgent(
            OverAllState state, RunnableConfig config) {
        // 1. 提取用户问题
        String userQuery = extractUserQuery(state);

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

        // 3. 缓存到 config metadata
        config.metadata().ifPresent(meta -> meta.put(RAG_CONTEXT_KEY, context));

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

5.3 配套的 RagContextInterceptor

java 复制代码
public class RagContextInterceptor extends ModelInterceptor {

    @Override
    public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) {
        // 从 metadata 读取缓存的上下文(不再检索)
        String ragContext = (String) request.getContext()
                .get(QueryEnhancementHook.RAG_CONTEXT_KEY);

        if (ragContext == null || ragContext.isEmpty()) {
            return handler.call(request);
        }

        // 注入上下文到 systemPrompt
        String ragPrompt = "上下文:\n" + ragContext;
        SystemMessage enhancedMessage = new SystemMessage(ragPrompt);

        ModelRequest enhancedRequest = ModelRequest.builder(request)
                .systemMessage(enhancedMessage)
                .build();
        return handler.call(enhancedRequest);
    }
}

5.4 Agent 配置

java 复制代码
ReactAgent agent = ReactAgent.builder()
        .name("two-step-rag-agent-hook")
        .model(chatModel)
        .hooks(new QueryEnhancementHook(vectorStore, topK))
        .interceptors(new RagContextInterceptor())
        .build();

5.5 调用示例

bash 复制代码
curl "http://localhost:8088/rag/two-step/agent-hook?query=Spring AI Alibaba有哪些核心特性?"

6. 三种方式对比总结

维度 MessagesModelHook ModelInterceptor AgentHook
检索频率 每次 LLM 调用 每次 LLM 调用 Agent 开始时 1 次
性能 一般 一般 最优
灵活性 可动态调整检索 可访问完整请求 上下文固定
适合场景 推理中检索需求变化 需要修改 tools 等 查询不变,追求性能
实现复杂度 低 中 中(需配合 Interceptor)

相关推荐
橘和柠1 分钟前
RAG检索增强实战:原理、七步链路与最小可用代码
人工智能
龙腾-虎跃1 分钟前
AI-Vue3-python-flask-Blog 全栈博客项目深度解析:从零搭建你的 AI 博客
人工智能·python·flask
沐风___2 分钟前
AI 开发 iOS 的 7 个步骤:从想法到上线
人工智能
大强同学5 分钟前
Muse 搭配 Tailscale!让 AI 钻进家里内网,直接访问你电脑里的素材!
人工智能
水圈模拟6 分钟前
世界模型:让 AI 预判水怎么流
人工智能·机器人
nm_zfyjt6 分钟前
GEO 实战指南:面向 RAG 链路的企业品牌内容优化方法
人工智能·搜索引擎优化·品牌知识库
鲜于言悠9057 分钟前
Cosmos Curator踩坑:只传1条视频,模型却全部加载
人工智能
2401_8322981011 分钟前
以智赋能,向善而行——理性拥抱人工智能新时代
人工智能
johnsong11 分钟前
AI前沿日报 2026-10-10:边缘融合
人工智能
陈天伟教授16 分钟前
CAD中关于坡度的标记的技巧
人工智能·阅读·具身智能·论文写法·文字排版