Java RAG 示例:Spring AI + Milvus
Spring‑AI 0.8.1,Milvus 2.4+,JDK17,SpringBoot3.x 整体流程:文档加载 → 文本分块 → Embedding 向量化 → 存入 Milvus 向量库 → 检索向量 → 组装上下文 Prompt → LLM 生成回答。 依赖:SpringAI Milvus 向量存储、OpenAI 兼容大模型(Qwen/DeepSeek 均可)、BGE embedding(本地或 API)。
1. pom.xml 核心依赖
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.0</version>
<relativePath/>
</parent>
<groupId>com.demo</groupId>
<artifactId>spring‑ai‑rag‑milvus</artifactId>
<version>0.0.1‑SNAPSHOT</version>
<properties>
<java.version>17</java.version>
<spring-ai.version>0.8.1</spring-ai.version>
</properties>
<dependencies>
<!-- SpringBoot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring AI Milvus 向量库 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring‑ai‑milvus‑store‑spring‑boot‑starter</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<!-- SpringAI OpenAI兼容 LLM (通义千问/DeepSeek都兼容openai接口) -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring‑ai‑openai‑spring‑boot‑starter</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<!-- PDF文档解析 -->
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring‑ai‑bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
2. application.yml 配置
Milvus 先 docker 启动,默认端口
19530;示例使用通义千问兼容 OpenAI 接口,也可以换成 DeepSeek。
yaml
spring:
ai:
# LLM大模型配置,openai兼容接口
openai:
api-key: sk‑xxxx你的key
base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
chat:
options:
model: qwen-plus
temperature: 0.0
# Milvus向量数据库配置
vectorstore:
milvus:
client:
host: 127.0.0.1
port: 19530
database-name: default
collection-name: spring_ai_kb
# bge‑small‑zh 向量维度为512,务必和embedding模型保持一致!
embedding-dimension: 512
# 检索返回topN
top-k: 4
# 是否自动创建表
auto-create-collection: true
⚠️重要:
embedding‑dimension必须和 Embedding 模型输出维度完全匹配。
- bge‑small‑zh‑v1.5 → 512 维
- bge‑m3 → 1024 维
SpringAI 内置 Embedding 默认调用 OpenAI 的 text‑embedding,中文效果差;实际项目建议替换为 BGE 本地 Embedding。下面示例先用 API 快速跑通,后面给 BGE 自定义 Embedding 扩展代码。
3. 核心 RAG 服务类
java
运行
package com.demo.rag.service;
import lombok.RequiredArgsConstructor;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.document.Document;
import org.springframework.ai.reader.pdf.PagePdfDocumentReader;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@RequiredArgsConstructor
public class RagService {
private final VectorStore vectorStore;
private final ChatClient chatClient;
/**
* 1、加载PDF文档,分块,写入Milvus知识库(离线构建,执行一次)
*/
public void loadPdfToVectorStore(String pdfFilePath) {
// 读取PDF
PagePdfDocumentReader pdfReader = new PagePdfDocumentReader(pdfFilePath);
List<Document> originDocs = pdfReader.get();
// 文本分块 TokenTextSplitter
// 中文经验:chunkSize=500‑700,overlap=120‑150
TokenTextSplitter splitter = new TokenTextSplitter(700, 150, 10, 1000, true);
List<Document> splitDocs = splitter.apply(originDocs);
// 写入Milvus向量库,自动embedding
vectorStore.add(splitDocs);
System.out.println("✅文档入库完成,分块数量:" + splitDocs.size());
}
/**
* 2、RAG问答:检索Milvus + LLM生成答案
*/
public String chat(String userQuestion) {
// 向量检索,top‑k=4
List<Document> recallDocs = vectorStore.similaritySearch(
SearchRequest.builder()
.query(userQuestion)
.topK(4)
.build()
);
// 拼接检索到的知识库上下文
StringBuilder contextSb = new StringBuilder();
for (Document doc : recallDocs) {
contextSb.append(doc.getContent()).append("\n");
}
// RAG固定Prompt模板,约束不要幻觉
String promptTemplate = """
你是内部知识库问答助手,请严格依据【参考上下文】回答用户的问题。
如果参考上下文中没有相关信息,直接回复:知识库未查询到相关内容,禁止编造任何信息。
【参考上下文】
{context}
【用户问题】
{question}
""";
return chatClient.prompt()
.user(u -> u.text(promptTemplate)
.param("context", contextSb.toString())
.param("question", userQuestion))
.call()
.content();
}
/**
* 清空Milvus当前collection全部数据(测试用)
*/
public void clearKb() {
vectorStore.delete(List.of("*"));
}
}
4. 测试 Controller
java
运行
package com.demo.rag.controller;
import com.demo.rag.service.RagService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/rag")
@RequiredArgsConstructor
public class RagController {
private final RagService ragService;
// 上传PDF构建知识库,本地文件路径
@GetMapping("/load")
public String loadKb(@RequestParam String filePath) {
ragService.loadPdfToVectorStore(filePath);
return "load success";
}
// RAG问答接口
@GetMapping("/chat")
public String chat(@RequestParam String q) {
return ragService.chat(q);
}
// 清空知识库
@GetMapping("/clear")
public String clear() {
ragService.clearKb();
return "clear ok";
}
}
5. 启动类
java
运行
package com.demo.rag;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class RagMilvusApplication {
public static void main(String[] args) {
SpringApplication.run(RagMilvusApplication.class, args);
}
}
6. Milvus 快速 Docker 启动
bash
docker run -d --name milvus‑standalone \
-p 19530:19530 \
-p 9091:9091 \
milvusdb/milvus:v2.4.15‑standalone
替换为中文 BGE Embedding(生产必做)
SpringAI 默认是 OpenAI embedding,中文效果很差。 两种方案:
- 部署 bge‑small‑zh OpenAI 兼容 Embedding 服务,修改 yml 的 embedding base‑url。
- 实现
EmbeddingModel接口接入本地 BGE 模型。