文章目录
- [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) |