上篇写了 Spring AI 多模型切换,这篇补上后半段------RAG 知识库 。不用 Python、不用 LangChain、不用 Milvus,纯 Spring AI + Java 一把梭。 代码已更新 👉 spring-ai-qa-service
整体流程
css
上传 PDF/MD/TXT
↓
PagePdfDocumentReader / 纯文本读取
↓
TokenTextSplitter 分块(按 Token 数切分)
↓
DashScopeEmbeddingModel 向量化
↓
SimpleVectorStore 存储(本地 JSON 文件)
↓
用户提问 → QuestionAnswerAdvisor 检索 Top-K → 注入 Prompt → 大模型回答
1. 文档上传与解析
java
public synchronized KnowledgeDocumentInfo upload(MultipartFile file) throws IOException {
// 校验文件类型:只允许 txt / md / pdf
String extension = extractExtension(originalFilename);
if (!ALLOWED_EXTENSIONS.contains(extension)) {
throw new IllegalArgumentException("不支持的文件类型");
}
// 保存到本地
Path storedPath = storageDirectory.resolve(storedFilename);
file.transferTo(storedPath);
// 按类型解析文档
List<Document> sourceDocuments = readDocuments(storedPath, extension);
// 分块
List<Document> chunks = textSplitter.apply(sourceDocuments);
// 写入向量库
vectorStore.add(chunks);
}
PDF 用 Spring AI 自带的 PagePdfDocumentReader,纯文本文档直接按段落切分。
2. 向量化(Embedding)
用了通义千问的 DashScope Embedding,和 Chat 共用同一个 API Key,不用额外注册:
java
@Bean
@ConditionalOnNonEmptyApiKey(property = "spring.ai.dashscope.api-key", env = "AI_DASHSCOPE_API_KEY")
DashScopeEmbeddingModel ragEmbeddingModel(...) {
DashScopeApi api = DashScopeApi.builder()
.apiKey(resolved.apiKey())
.baseUrl(resolved.baseUrl())
.build();
return DashScopeEmbeddingModel.builder()
.dashScopeApi(api)
.build();
}
3. 向量存储
选的是 SimpleVectorStore------纯内存 + JSON 文件持久化,零外部依赖:
java
@Bean
SimpleVectorStore vectorStore(DashScopeEmbeddingModel embeddingModel, RagProperties props) {
SimpleVectorStore store = SimpleVectorStore.builder(embeddingModel).build();
// 启动时从文件恢复
Path storePath = Path.of(props.storagePath()).resolve("vector-store.json");
if (Files.exists(storePath)) {
store.load(storePath.toFile());
}
return store;
}
生产环境可以换成 Elasticsearch / PostgreSQL pgvector / Milvus,接口不变,改个 Bean 就行。
4. 检索增强(最核心的一行)
java
private ChatClient.CallResponseSpec buildPrompt(ChatClient chatClient, ChatRequest request) {
var spec = chatClient.prompt()
.system(isRagMode ? RAG_SYSTEM_PROMPT : DEFAULT_SYSTEM_PROMPT)
.messages(toMessages(request.history(), request.message()));
// 如果用户开启了知识库模式,注入 QuestionAnswerAdvisor
if (isRagMode) {
VectorStore store = vectorStore.getIfAvailable();
if (store != null) {
spec.advisors(new QuestionAnswerAdvisor(
store,
SearchRequest.builder().topK(4).build()
));
}
}
return spec;
}
QuestionAnswerAdvisor 是 Spring AI 内置的 RAG Advisor------它自动做三件事:
- 把用户问题用同一个 Embedding 模型向量化
- 在 VectorStore 里检索 Top-K 相似文档块
- 把检索结果拼到 Prompt 里作为上下文
你完全不需要写检索逻辑。
5. System Prompt 区分
java
// 普通模式
"你是一个有帮助的 AI 助手。用简洁、准确的中文回答。"
// RAG 模式
"你是企业规则问答助手。请优先依据检索到的知识库内容回答。
若文档中没有相关信息,请明确说明「未在已导入的规则文档中找到依据」,不要编造。"
这是 RAG 落地最关键的一步------告诉模型「不知道就说不知道」,否则它会在检索不到的时候瞎编。
6. 前端知识库面板
Vue 3 写的侧边栏组件 KnowledgePanel.vue:
- 启动时调
/api/rag/status检查 RAG 是否可用(没配 API Key 就隐藏面板) - 支持拖拽/选择文件上传,实时显示文档列表和分块数
- 聊天框新增「知识库模式」开关,开启后 AI 用 RAG System Prompt
踩坑
- SimpleVectorStore 的 save/load :需要手动在启动时
load(),关闭时save()。不 save 的话重启后向量库就没了 - PDF 解析的依赖 :
PagePdfDocumentReader需要pdfbox,pom.xml 里别忘了加 - Embedding 和 Chat 共用一个 API Key:DashScope 的 Embedding 和 Chat 都走同一个 Key,但自动配置里它们是分开的------Embedding 的自动配置也要排除,自己手动装配
下一步
- 支持更多文档格式(Word / HTML)
- 向量库切换为 PostgreSQL pgvector(SimpleVectorStore 适合 Demo,生产量大了内存扛不住)
- 对话中展示检索到的参考段落
GitHub: github.com/zgqstory/sp...