Spring AI RAG 详解:从 Advisor 开始
Spring AI RAG 详解:从 Advisor 开始
一、先理解 Advisor 机制
在深入 RAG 相关的 Advisor 之前,需要先理解 Spring AI 中 Advisor(顾问) 是什么。
Advisor 是 Spring AI 提供的一种拦截器/增强器机制,类似于 Spring AOP 的切面概念。它可以在 AI 请求的发送前后插入自定义逻辑,比如修改请求内容、记录日志、注入上下文等。
用户输入
↓
[Advisor Chain 链式处理]
↓ ↓ ↓
A1 A2 A3 ← 多个 Advisor 按顺序执行
↓
发送给 LLM
↓
[Advisor Chain 反向处理]
↓
返回给用户Advisor 的核心接口有两个:
// 处理非流式请求
public interface CallAroundAdvisor extends Advisor {
AdvisedResponse aroundCall(AdvisedRequest request, CallAroundAdvisorChain chain);
}
// 处理流式请求
public interface StreamAroundAdvisor extends Advisor {
Flux<AdvisedResponse> aroundStream(AdvisedRequest request, StreamAroundAdvisorChain chain);
}AdvisedRequest 里包含了用户的消息、系统提示、模型参数等所有上下文,Advisor 可以对其进行读取和修改。
二、RAG 核心 Advisor:QuestionAnswerAdvisor
这是 Spring AI 内置的最重要的 RAG Advisor。
2.1 基本使用
@Bean
public ChatClient chatClient(ChatModel chatModel, VectorStore vectorStore) {
return ChatClient.builder(chatModel)
.defaultAdvisors(
new QuestionAnswerAdvisor(vectorStore)
)
.build();
}使用时:
String answer = chatClient.prompt()
.user("Spring AI 如何配置向量数据库?")
.call()
.content();2.2 它做了什么(内部流程)
1. 用户提问:"Spring AI 如何配置向量数据库?"
↓
2. 将问题向量化(Embedding)
↓
3. 去 VectorStore 相似度检索,找到最相关的文档片段
↓
4. 将检索到的内容拼接进 System Prompt(这叫做 Context Stuffing)
↓
5. 把增强后的 Prompt 发给 LLM
↓
6. LLM 基于提供的上下文来回答2.3 默认的 System Prompt 模板
QuestionAnswerAdvisor 内部有一个默认的提示词模板,大致是:
Context information is below.
---------------------
{question_answer_context}
---------------------
Given the context and provided history information and not prior knowledge,
reply to the user comment. If the answer is not in the context,
inform the user that you can't answer the question.其中 {question_answer_context} 就是从向量数据库里检索回来的文档内容,会被自动替换进去。
2.4 自定义配置
// 自定义检索参数
SearchRequest searchRequest = SearchRequest.builder()
.topK(5) // 检索前5个最相关的文档块
.similarityThreshold(0.75) // 相似度阈值,低于此值的不用
.filterExpression("category == 'java'") // 元数据过滤
.build();
QuestionAnswerAdvisor advisor = QuestionAnswerAdvisor.builder(vectorStore)
.searchRequest(searchRequest)
.userTextAdvise("请基于以下资料回答问题:{question_answer_context}") // 自定义提示词
.build();三、VectorStore(向量数据库)
RAG 的核心依赖,QuestionAnswerAdvisor 需要从它这里检索文档。
3.1 常见实现
Spring AI 支持多种向量数据库,通过统一的 VectorStore 接口抽象:
| 实现 | 说明 |
|---|---|
SimpleVectorStore | 内存实现,适合开发测试 |
PgVectorStore | PostgreSQL + pgvector 插件 |
ChromaVectorStore | Chroma 向量数据库 |
RedisVectorStore | Redis Stack |
ElasticsearchVectorStore | Elasticsearch |
MilvusVectorStore | Milvus |
PineconeVectorStore | Pinecone(云服务) |
3.2 以 PgVector 为例配置
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pgvector-store-spring-boot-autoconfigure</artifactId>
</dependency>spring:
datasource:
url: jdbc:postgresql://localhost:5432/mydb
username: postgres
password: secret
ai:
vectorstore:
pgvector:
index-type: HNSW # 索引类型
distance-type: COSINE_DISTANCE # 距离计算方式
dimensions: 1536 # 向量维度,需与 Embedding 模型一致3.3 VectorStore 的核心操作
@Autowired
VectorStore vectorStore;
// 存入文档
List<Document> docs = List.of(
new Document("Spring AI 是一个 AI 应用框架", Map.of("source", "doc1")),
new Document("它支持 OpenAI、Ollama 等模型", Map.of("source", "doc2"))
);
vectorStore.add(docs);
// 相似度检索
List<Document> results = vectorStore.similaritySearch(
SearchRequest.query("AI框架").topK(3)
);四、Document 和文档处理流水线
RAG 中,文档在存入向量库之前需要经过一系列处理,Spring AI 提供了完整的 ETL 流水线。
4.1 Document 对象
// Document 是 Spring AI 中文档的基本单元
Document doc = new Document(
"这是文档内容", // content
Map.of("source", "file.pdf", "page", 1) // metadata 元数据
);4.2 DocumentReader(读取文档)
// 读取 PDF
Resource pdfResource = new ClassPathResource("document.pdf");
DocumentReader reader = new PagePdfDocumentReader(pdfResource);
// 读取纯文本
DocumentReader txtReader = new TextReader(new ClassPathResource("doc.txt"));
// 读取 Markdown
DocumentReader mdReader = new MarkdownDocumentReader(resource);
// 读取 Tika 支持的格式(Word、PPT等)
DocumentReader tikaReader = new TikaDocumentReader(resource);
List<Document> documents = reader.get();4.3 DocumentTransformer(文档切割/转换)
这是 RAG 中非常关键的一步,文档太长需要切割(Chunking):
// Token 数量分割器(最常用)
TokenTextSplitter splitter = new TokenTextSplitter(
512, // 每个 chunk 的 token 数
128, // chunk 之间的重叠 token 数(overlap,保留上下文)
5, // 最小 chunk 长度
10000, // 最大 chunk 长度
true // 保留元数据
);
List<Document> chunks = splitter.apply(documents);为什么需要 overlap(重叠)? 因为一段话可能被切断,重叠可以保证语义的连续性,避免检索时遗漏关键信息。
4.4 完整的 ETL 流程
// 1. 读取
List<Document> docs = new TikaDocumentReader(resource).get();
// 2. 切割
List<Document> chunks = new TokenTextSplitter(512, 128).apply(docs);
// 3. 增强元数据(可选)
// 比如加上摘要、关键词等
// 4. 写入向量库(自动 Embedding)
vectorStore.add(chunks);五、EmbeddingModel(向量化模型)
VectorStore 在存入和检索时都需要将文本转成向量,这由 EmbeddingModel 完成。
// Spring AI 自动配置,通常不需要手动调用
// 但你可以这样理解它的工作:
EmbeddingModel embeddingModel; // 注入
// 将文本转成向量
EmbeddingResponse response = embeddingModel.embedForResponse(
List.of("Spring AI 是什么?")
);
float[] vector = response.getResults().get(0).getOutput();
// vector 是一个 1536 维(OpenAI ada-002)的浮点数组常见 Embedding 模型配置:
spring:
ai:
openai:
embedding:
options:
model: text-embedding-ada-002 # OpenAI
ollama:
embedding:
model: nomic-embed-text # 本地 Ollama六、进阶:RetrievalAugmentationAdvisor
Spring AI 1.0 引入了更强大的模块化 RAG Advisor,相比 QuestionAnswerAdvisor 它提供了更细粒度的控制。
RetrievalAugmentationAdvisor advisor = RetrievalAugmentationAdvisor.builder()
.documentRetriever(
VectorStoreDocumentRetriever.builder()
.vectorStore(vectorStore)
.searchRequest(SearchRequest.builder().topK(5).build())
.build()
)
.queryTransformer( // 查询转换(可选)
RewriteQueryTransformer.builder()
.chatClientBuilder(chatClientBuilder)
.build()
)
.build();6.1 QueryTransformer(查询转换)
在检索之前先对用户问题进行改写,提升检索质量:
// 查询重写:让 LLM 先优化用户的问题,使其更适合向量检索
RewriteQueryTransformer rewriter = RewriteQueryTransformer.builder()
.chatClientBuilder(chatClientBuilder)
.build();
// 查询扩展:生成多个相关查询,多路检索
MultiQueryExpander expander = MultiQueryExpander.builder()
.chatClientBuilder(chatClientBuilder)
.numberOfQueries(3) // 扩展为3个查询
.build();为什么需要查询转换? 用户的原始问题可能口语化、含糊,向量检索效果差。改写后命中率更高。
6.2 DocumentPostProcessor(文档后处理)
检索到文档后可以进一步处理,比如重排序:
// 使用 CohereRerank 对检索结果重新排序
DocumentPostProcessor reranker = CohereReranker.builder()
.cohereApi(cohereApi)
.build();七、完整实战示例
把上面所有内容串起来:
@Configuration
public class RagConfig {
// 1. 向量库配置(使用 SimpleVectorStore 做演示)
@Bean
public VectorStore vectorStore(EmbeddingModel embeddingModel) {
return new SimpleVectorStore(embeddingModel);
}
// 2. 文档加载 Bean(应用启动时加载文档)
@Bean
public ApplicationRunner documentLoader(VectorStore vectorStore) {
return args -> {
Resource resource = new ClassPathResource("knowledge.pdf");
List<Document> docs = new PagePdfDocumentReader(resource).get();
List<Document> chunks = new TokenTextSplitter(512, 128).apply(docs);
vectorStore.add(chunks);
System.out.println("知识库加载完毕,共 " + chunks.size() + " 个文档块");
};
}
// 3. ChatClient 配置,挂载 RAG Advisor
@Bean
public ChatClient chatClient(ChatModel chatModel, VectorStore vectorStore) {
SearchRequest searchRequest = SearchRequest.builder()
.topK(4)
.similarityThreshold(0.7)
.build();
return ChatClient.builder(chatModel)
.defaultSystem("你是一个专业的技术助手,请基于提供的文档内容回答问题。")
.defaultAdvisors(
new MessageChatMemoryAdvisor(new InMemoryChatMemory()), // 对话记忆
new QuestionAnswerAdvisor(vectorStore, searchRequest) // RAG
)
.build();
}
}@RestController
@RequestMapping("/api/chat")
public class ChatController {
@Autowired
private ChatClient chatClient;
@PostMapping
public String chat(@RequestBody String question) {
return chatClient.prompt()
.user(question)
.call()
.content();
}
}八、总结:RAG 完整知识图谱
用户提问
↓
[QueryTransformer] ← 可选,改写/扩展查询
↓
[EmbeddingModel] ← 将查询向量化
↓
[VectorStore] ← 相似度检索文档块
↓
[DocumentPostProcessor] ← 可选,重排序
↓
[Prompt 增强] ← 将文档塞入 System Prompt
↓
[LLM] ← 基于上下文生成回答
↓
用户得到答案关键参数调优建议:
topK通常设 3~6,太多会稀释上下文,太少可能遗漏信息similarityThreshold设 0.6~0.8,视数据质量调整chunkSize通常 256~512 tokens,overlap 设 10%~20%- Embedding 模型尽量用中文优化的版本(如
text-embedding-3-small或本地的bge-m3)
以上就是 Spring AI RAG 从 Advisor 到完整流水线的详细讲解。你想深入哪一块?比如多路检索、Hybrid Search(向量+关键词混合检索)、或者如何评估 RAG 效果?
