Spring AI RAG 与向量存储
Spring 官方文档在《Retrieval Augmented Generation》中,把 RAG 拆成三步:摄取(Ingestion)→ 检索(Retrieval)→ 生成(Generation)。本文是这套流程的完整落地版本:用 Markdown 文档喂入 Redis 向量库,依靠 QuestionAnswerAdvisor 在每次提问前检索上下文,再交给本地部署的 Ollama 生成答案。本文重新梳理模块结构、关键实现与运行方式,方便你快速对齐代码与文档。
示例代码库
您可以在 GitHub 仓库 中找到本文的示例代码。
架构一览
┌──────────────┐ ┌────────────┐ ┌─────────────┐ ┌────────────────────┐
│ Markdown 文档 │ --> │ 文档读取/切分│ --> │Embedding API│ --> │ Redis VectorStore │
└──────────────┘ └────────────┘ └─────────────┘ └────────────────────┘
↑
ChatClient + QuestionAnswerAdvisor ←───────────┘
- 摄取:
AiConfig在应用启动时读取src/main/resources/data/*.md,切分片段并写入 VectorStore。 - 检索:
QuestionAnswerAdvisor基于用户问题生成查询向量,调用vectorStore.similaritySearch()取回最相关的 Document。 - 生成:检索结果作为上下文拼接进 Prompt,Ollama 结合
MessageChatMemoryAdvisor输出有记忆的回答。
模块结构要点:
- AiConfig:文档摄取与切分,写入 VectorStore。
- ChatController:面向最终问答的 RAG 入口。
- EmbeddingSearchController:向量检索、重排序、查询增强与评估接口集合。
文档摄取:Markdown + 分片
AiConfig 是摄取链路的核心实现。这里先介绍 SimpleVectorStore 的手动方式,再回到本项目实际使用的自动装配方式。
方式一:SimpleVectorStore(手动 Bean)
如果只做最小化验证,可以先显式创建 SimpleVectorStore:
@Bean
VectorStore vectorStore(EmbeddingModel embeddingModel) {
return SimpleVectorStore.builder(embeddingModel).build();
}
这样做的好处是路径清晰、依赖少,适合本地验证检索流程;缺点是无法利用 Redis 之类的持久化向量库能力。
方式二:自动装配 VectorStore(本项目使用)
本模块引入 spring-ai-starter-vector-store-redis,由 Spring Boot 自动注入 VectorStore。AiConfig 只负责把文档切分并写入:
@Configuration
public class AiConfig {
@Value("classpath:/data/about.md")
private Resource aboutFile;
@Value("classpath:/data/career.md")
private Resource careerLessonsFile;
@Bean
ApplicationRunner applicationRunner(VectorStore vectorStore) {
return args -> {
loadDocument(vectorStore, aboutFile);
loadDocument(vectorStore, careerLessonsFile);
};
}
private void loadDocument(VectorStore vectorStore, Resource resource) {
DocumentReader reader = new MarkdownDocumentReader(
resource, MarkdownDocumentReaderConfig.defaultConfig());
List<Document> docs = reader.get();
TextSplitter splitter = new TokenTextSplitter();
vectorStore.accept(splitter.apply(docs));
}
}
- MarkdownDocumentReader:省去自己解析粗体/列表等格式的麻烦,直接输出结构化
Document。 - TokenTextSplitter:按 token 总量控制片段大小,防止一次查询把整篇文章塞给模型。
- VectorStore.accept:会调 Embedder(下节)为每个 chunk 生成向量并写入 Redis。
示例数据来自两个文档:
about.md:开发者履历、技术栈。career.md:15 年职业生涯心得,列出 30+ 条经验。
这种方式与 Spring 文档中的 “company knowledge base” 一致:由应用负责摄取,不需要预先离线嵌入。
嵌入模型:本地 Ollama
本模块使用本地 Ollama,同时提供聊天与嵌入模型配置:
spring.ai.ollama.base-url=http://localhost:11434
spring.ai.ollama.chat.options.model=qwen3:8b
spring.ai.ollama.embedding.options.model=nomic-embed-text:v1.5
本地启动前需要拉取对应模型,使用 Ollama 不需要配置 API Key:
ollama pull qwen3:8b
ollama pull nomic-embed-text:v1.5
如果你希望使用阿里的大模型作为可选方案,可以替换为通义千问等模型的接入方式并调整配置项,但本模块默认使用 Ollama。
application-qwen.properties 示例(通义千问 DashScope OpenAI 兼容接口),包含聊天与嵌入配置:
## 通义千问(阿里云 DashScope OpenAI 兼容)##
## 获取 API Key: https://dashscope.console.aliyun.com/
spring.ai.openai.base-url=https://dashscope.aliyuncs.com/compatible-mode/v1
spring.ai.openai.api-key=${DASHSCOPE_API_KEY}
spring.ai.openai.chat.completions-path=/chat/completions
spring.ai.openai.chat.options.model=qwen-turbo
spring.ai.openai.chat.options.temperature=0.6
spring.ai.openai.embedding.options.model=text-embedding-v2
此外,EmbeddingSearchController 提供 /api/embedding 与 /api/embedding/similarity,便于观察向量结果与相似度计算。
Redis VectorStore 与 QuestionAnswerAdvisor
Spring 文档推荐在生成阶段加入 QuestionAnswerAdvisor——它会自动:
- 从当前
Prompt中提取问题; - 查询 VectorStore(本例使用
spring-ai-starter-vector-store-redis); - 把检索到的 Document 作为上下文插入最终 Prompt。
ChatController 构造函数正是这么配置的:
this.chatClient = builder
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory).build(),
QuestionAnswerAdvisor.builder(vectorStore).build(),
new SimpleLoggerAdvisor()
)
.build();
因此前端只需调用 /api/chat,Advisor 就会在幕后完成:
- 多轮记忆(
ChatMemory.CONVERSATION_ID来自 CookieX-CONV-ID); - 基于向量检索的上下文增强;
- 最终把 Markdown 回复交给
MarkdownHelper生成 HTML。
如果你想验证检索结果,可以使用 /api/search 或更细粒度的检索接口:
curl "http://localhost:8080/api/search?query=微服务"
这些接口会直接返回 Redis 中最相近的原文片段,便于调试分词、嵌入与重排序效果。
RetrievalAugmentationAdvisor 说明
如果你希望把 RAG 流程拆成可组合的管线,RetrievalAugmentationAdvisor 是更灵活的选择。它把流程拆为多个可插拔阶段:查询变换、查询扩展、检索、后处理、文档合并与查询增强。本模块的 ChatController 中使用它构建了不同级别的 RAG 体验:
/api/chat-rag:最小链路,只做向量检索后直接拼接上下文。/api/chat-advanced:在检索后加入DocumentPostProcessor进行重排序。/api/chat-rag-modular:完整管线,包含查询改写/压缩/翻译、查询扩展、检索、后处理、文档合并与上下文增强。
这种方式适合逐步加复杂度:从最小可用的检索开始,逐步叠加 Query Transformer、Document Joiner、PostProcessor 等组件,方便验证每一步对质量的影响。
检索与评估接口说明
为便于验证检索流程,EmbeddingSearchController 提供了多组接口:
- 向量与相似度:
/api/embedding、/api/embedding/similarity。 - 基础检索:
/api/search,支持阈值、分页、来源过滤与高亮。 - 元数据过滤:
/api/search/filter,支持 source/sources、idPrefix 与任意 metadata 键值。 - 多路检索:
/api/search/multi,按来源拆分后再合并。 - 两阶段检索:
/api/search/two-stage,查询扩展 → 检索 → 合并 → 重排序。 - 重排序:
/api/search/rerank、/api/search/rerank/explain,返回融合分数与 BM25 组成项。 - 上下文增强:
/api/augment/context,基于检索结果增强 query。 - 查询变换:
/api/query/translate、/api/query/rewrite、/api/query/compress、/api/query/expand。
这些接口大部分直接返回原文片段与分数,便于观察排序变化与过滤效果。
运行步骤
启动 Redis
模块目录已提供 Compose 文件:cd 08-rag-vector-store docker compose up -d # redis/redis-stack:7.2.0-v18Spring Boot 会自动发现
org.springframework.boot.service-connection=redis标签并注入连接信息。运行应用
../mvnw spring-boot:run体验 RAG
- 打开
http://localhost:8080/。 - 问:“Summarize the author’s career experience.” 可以看到回答里引用了
about.md的具体条目。 - 再问:“What is the most important lesson from the career notes?” 模型会引用
career.md的编号列表继续回答。
- 打开
可选 API
/api/embedding、/api/embedding/similarity:观察向量与相似度。/api/search、/api/search/filter、/api/search/multi:基础检索与过滤。/api/search/two-stage、/api/search/rerank:扩展检索与重排序。/api/search/rerank/explain:BM25 解释与分数分解。/api/augment/context:上下文增强 query。/api/query/translate、/api/query/rewrite、/api/query/compress、/api/query/expand:查询变换链路。
测试脚本(bash)
一行命令一个测试,默认服务地址为 http://localhost:8080:
# 生成文本向量
curl -s "http://localhost:8080/api/embedding?message=vector%20test"
# 计算文本相似度
curl -s "http://localhost:8080/api/embedding/similarity?text1=vector&text2=embedding"
# 基础向量检索
curl -s "http://localhost:8080/api/search?query=career"
# 启用高亮的向量检索
curl -s "http://localhost:8080/api/search?query=career&highlight=true"
# 按单一来源过滤
curl -s "http://localhost:8080/api/search/filter?query=career&source=about"
# 多来源与 ID 前缀过滤
curl -s "http://localhost:8080/api/search/filter?query=career&sources=about,career&idPrefix=about-"
# 多路检索合并
curl -s "http://localhost:8080/api/search/multi?query=career"
# 两阶段检索与重排序
curl -s "http://localhost:8080/api/search/two-stage?query=career"
# 融合重排序
curl -s "http://localhost:8080/api/search/rerank?query=career"
# 重排序解释
curl -s "http://localhost:8080/api/search/rerank/explain?query=career"
# 上下文增强
curl -s "http://localhost:8080/api/augment/context?query=career"
# 查询翻译
curl -s "http://localhost:8080/api/query/translate?query=Summarize%20the%20career%20notes&target=english"
# 查询改写
curl -s "http://localhost:8080/api/query/rewrite?query=career%20notes"
# 查询压缩
curl -s "http://localhost:8080/api/query/compress?query=career%20notes&history=We%20just%20discussed%20the%20author%27s%20background"
# 查询扩展
curl -s "http://localhost:8080/api/query/expand?query=career%20notes&n=3"
小结
- 摄取(Ingestion):
DocumentReader + TextSplitter + VectorStore.accept自动将外部知识转成向量。 - 检索(Retrieval):Redis VectorStore +
QuestionAnswerAdvisor完成语义检索,并开放多种检索示例接口方便验证。 - 生成(Generation):检索上下文注入 Prompt,
MessageChatMemoryAdvisor维持会话上下文,最终由 Ollama 输出答案。 - 可扩展性:换模型、换存储都很容易——只需调整 Spring AI 的 starter 与配置即可。
如果你正在跟着 Spring 官方指南实现自己的 RAG Demo,这个模块提供了最小但完整的端到端范例。搬回你的项目,再换成真实文档,就能快速搭建企业知识库问答机器人。
