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)

相关推荐
weixin_468466853 小时前
从“看懂”到“做到”:视觉-语言-动作模型如何让机器人真正执行指令
人工智能·ai·机器人·大语言模型·前沿科技
朋友圈自动点赞工具3 小时前
科学AI插件化:从蛋白质结构到古籍修复
人工智能
captain3763 小时前
MyBatis(入门5)其他查询操作
java·数据库·spring boot·mybatis
Novproxy_Z4 小时前
商用代理避坑:如何识别商家拿机房 IP 冒充住宅 IP
java·网络·tcp/ip
龙粤GEO4 小时前
实体Entity在GEO中代表什么含义
人工智能
文丑颜不良啊5 小时前
LeetCode1481~1500题目解析
java·leetcode
2601_962885725 小时前
quotes 字段全解:涨跌幅、振幅、换手率的口径与单位
java·前端·spring
前端精髓5 小时前
调用openrouter的免费API模型
java·前端·javascript
剑雨巍巍5 小时前
AI Agent面试之基础概念
人工智能
微帧Visionular5 小时前
AI视频单帧清晰,播放时却闪烁,原因是什么?
人工智能·深度学习·音视频