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 :从 PDFWordMarkdownTXT 等格式加载文档
  • 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 原理

ModelInterceptorMessagesModelHook 类似,都在每次模型调用前触发。区别在于 Interceptor 可以访问更完整的请求信息(包括 systemMessagetools 等),通过 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 多次调用模型,检索也会执行多次,造成不必要的开销。

AgentHookAgent 开始时只执行一次 ,将检索结果缓存到 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)

相关推荐
Gigavision2 小时前
基于BUAA-MIHR数据集的噪声解耦对比学习算法
人工智能·python·深度学习·算法
鹿角片ljp2 小时前
LeetCode 64:最小路径和复盘|二维 DP 与 ACM 模式完整写法
java·数据结构·算法
红海云2 小时前
Kimi 双端接入 CloudBase 的工程价值
人工智能·语言模型
计算机编程-吉哥3 小时前
YOLO26 vs YOLO11 vs YOLOv8:深度学习咖啡果实成熟度分割系统【计算机毕业设计选题推荐】
人工智能·python·深度学习·yolo·django·毕业设计
冬奇Lab3 小时前
一年前没启动 AI 提效的团队,今年在付什么钱?
人工智能
泡海椒3 小时前
PDF 表格样式优化:jquick-pdf 边框、圆角、背景色
java·开发语言·pdf
NeoGressAI外贸数字化4 小时前
IOR新规9月18日生效:Form 5106六项资料自查清单
人工智能
根目录下的猫4 小时前
RK3588适配的轻量级AI模型推荐
人工智能·后端·python·目标检测
景熙55234 小时前
15.Java 8 Stream 流入门到实战
java·开发语言·数据结构