Spring AI 携手 Milvus 构建 RAG 智能问答框架:从环境搭建到实战部署

Spring AI 携手 Milvus 构建 RAG 智能问答系统:从环境搭建到实战部署

一、技术选型与核心价值:为何选择 Spring AI + Milvus

RAG(检索增强生成)技术通过 “检索相关文档 + 大模型生成” 的模式,解决了大模型知识时效性差、幻觉输出等问题。在这一架构中,Spring AI 与 Milvus 的组合展现出独特优势:

  • Spring AI:作为 Spring 生态的 AI 集成框架,提供统一的向量嵌入、大模型调用接口,简化了与主流 AI 模型(如 OpenAI、通义千问)的对接,同时与 Spring Boot 无缝融合,降低企业级应用开发门槛。
  • Milvus:开源向量数据库,专为海量向量检索设计,支持毫秒级查询亿级向量数据,其分布式架构和丰富的索引类型(如 IVF_FLAT、HNSW),完美适配 RAG 场景中 “文档向量快速匹配” 的核心需求。

两者结合实现了 “数据接入 - 向量存储 - 检索增强 - 生成回答” 的全流程闭环,让开发者无需深入 AI 底层技术,即可快速构建生产级智能问答系统。

二、环境搭建:基础组件与依赖配置

1. 核心依赖引入

在 Spring Boot 项目的pom.xml中添加以下依赖:

<!-- Spring AI 核心依赖 -->

<dependency>

<groupId>org.springframework.ai</groupId>

<artifactId>spring-ai-openai-spring-boot-starter</artifactId>

<version>0.8.0</version>

</dependency>

<!-- Milvus 客户端 -->

<dependency>

<groupId>io.milvus</groupId>

<artifactId>milvus-sdk-java</artifactId>

<version>2.3.4</version>

</dependency>

<!-- 文档处理工具 -->

<dependency>

<groupId>org.springframework.ai</groupId>

<artifactId>spring-ai-document-reader-tika</artifactId>

<version>0.8.0</version>

</dependency>

2. 配置文件设置

在application.yml中配置大模型、Milvus 连接信息:

spring:

ai:

openai:

api-key: ${OPENAI_API_KEY}

embedding:

model: text-embedding-ada-002

chat:

model: gpt-3.5-turbo

milvus:

host: localhost

port: 19530

collection-name: rag_documents

dimension: 1536 # 与embedding模型维度一致(ada-002为1536)

三、核心实现步骤:从文档处理到智能问答

1. 文档加载与向量嵌入

首先需将原始文档(如 PDF、TXT)转换为向量并存储。Spring AI 的EmbeddingClient可简化向量生成过程:

@Service

public class DocumentService {

private final EmbeddingClient embeddingClient;

private final MilvusServiceClient milvusClient;

private final String collectionName;

// 构造函数注入依赖(省略)

// 处理文档并存储向量

public void processAndStoreDocument(MultipartFile file) throws IOException {

// 1. 加载文档(使用Tika解析多种格式)

TikaDocumentReader reader = new TikaDocumentReader(file.getInputStream());

List<Document> documents = reader.get();

// 2. 文档分块(避免超过模型上下文限制)

List<Document> chunks = new RecursiveCharacterTextSplitter(

500, // 块大小

100 // 重叠字符数

).split(documents);

// 3. 生成向量并存储

for (Document chunk : chunks) {

// 生成向量

List<Double> embedding = embeddingClient.embed(chunk.getContent());

// 转换为Milvus兼容的Float数组

float[] vector = embedding.stream()

.mapToFloat(Double::floatValue)

.toArray();

// 存储到Milvus(具体实现见下文)

saveToMilvus(chunk.getContent(), vector);

}

}

}

2. Milvus 向量存储实现

需创建集合、定义字段并实现向量插入。Milvus 的 Java SDK 提供了完整的操作接口:

private void saveToMilvus(String content, float[] vector) {

// 1. 检查集合是否存在,不存在则创建

if (!collectionExists()) {

createCollection();

}

// 2. 构造插入数据

List<Long> ids = Collections.singletonList(System.currentTimeMillis());

List<String> contents = Collections.singletonList(content);

List<float[]> vectors = Collections.singletonList(vector);

// 3. 插入数据

InsertParam insertParam = InsertParam.newBuilder()

.withCollectionName(collectionName)

.addField("id", ids)

.addField("content", contents)

.addField("vector", vectors)

.build();

milvusClient.insert(insertParam);

// 刷新集合使数据可见

milvusClient.flush(FlushParam.newBuilder()

.addCollectionName(collectionName)

.build());

}

// 创建集合(字段:id、content、vector)

private void createCollection() {

FieldType idField = FieldType.newBuilder()

.withName("id")

.withDataType(DataType.Int64)

.withPrimaryKey(true)

.withAutoID(false)

.build();

FieldType contentField = FieldType.newBuilder()

.withName("content")

.withDataType(DataType.VarChar)

.withMaxLength(2000)

.build();

FieldType vectorField = FieldType.newBuilder()

.withName("vector")

.withDataType(DataType.FloatVector)

.withDimension(dimension)

.build();

milvusClient.createCollection(CreateCollectionParam.newBuilder()

.withCollectionName(collectionName)

.addFieldType(idField)

.addFieldType(contentField)

.addFieldType(vectorField)

.withShardsNum(2)

.build());

// 创建向量索引(提升查询性能)

milvusClient.createIndex(CreateIndexParam.newBuilder()

.withCollectionName(collectionName)

.withFieldName("vector")

.withIndexType(IndexType.IVF_FLAT)

.withMetricType(MetricType.L2)

.withExtraParam("{\"nlist\": 1024}")

.build());

}

3. 检索增强生成(RAG)问答实现

核心逻辑是:用户提问→生成问题向量→检索相似文档→拼接上下文→调用大模型生成回答:

@Service

public class QAService {

private final EmbeddingClient embeddingClient;

private final MilvusServiceClient milvusClient;

private final ChatClient chatClient;

private final String collectionName;

// 构造函数注入依赖(省略)

public String answer(String question) {

// 1. 生成问题向量

List<Double> questionEmbedding = embeddingClient.embed(question);

float[] queryVector = questionEmbedding.stream()

.mapToFloat(Double::floatValue)

.toArray();

// 2. 检索相似文档(topK=3)

List<String> contexts = searchSimilarDocuments(queryVector, 3);

// 3. 构建提示词(上下文+问题)

String prompt = String.format("""

基于以下信息回答问题,不要编造内容:

%s

问题:%s

""", String.join("\n", contexts), question);

// 4. 调用大模型生成回答

return chatClient.call(

new Prompt(new UserMessage(prompt))

).getResult().getOutput().getContent();

}

// 检索相似文档

private List<String> searchSimilarDocuments(float[] queryVector, int topK) {

SearchParam searchParam = SearchParam.newBuilder()

.withCollectionName(collectionName)

.withMetricType(MetricType.L2)

.withVectorFieldName("vector")

.withVectors(List.of(queryVector))

.withLimit(topK)

.withOutputFields(List.of("content")) // 只返回内容字段

.build();

SearchResults results = milvusClient.search(searchParam);

// 解析结果并返回文档内容

return results.getResults().getScoredDocuments().stream()

.map(doc -> doc.getFields().get("content").toString())

.collect(Collectors.toList());

}

}

4. 接口层实现

通过 Controller 暴露 HTTP 接口,便于前端调用:

@RestController

@RequestMapping("/api/qa")

public class QAController {

private final DocumentService documentService;

private final QAService qaService;

@PostMapping("/upload")

public ResponseEntity<String> uploadDocument(@RequestParam("file") MultipartFile file) {

try {

documentService.processAndStoreDocument(file);

return ResponseEntity.ok("文档处理完成");

} catch (Exception e) {

return ResponseEntity.status(500).body("处理失败:" + e.getMessage());

}

}

@GetMapping("/answer")

public ResponseEntity<String> getAnswer(@RequestParam String question) {

return ResponseEntity.ok(qaService.answer(question));

}

}

四、优化策略与性能调优

  1. 文档分块优化:根据文档类型调整块大小(如技术文档用 800 字符,普通文本用 500 字符),平衡检索精度与召回率。
  1. Milvus 索引优化:百万级数据量推荐 HNSW 索引(查询速度快),亿级数据可结合 IVF_FLAT 与分区策略。
  1. 向量缓存:对高频查询的文档向量进行本地缓存(如使用 Caffeine),减少 Milvus 访问压力。
  1. 大模型参数调优:通过temperature=0.3降低随机性,maxTokens=1000控制回答长度。
  1. 异步处理:文档上传和向量生成采用异步任务(@Async),避免前端超时。

五、部署与扩展建议

  1. Milvus 部署:生产环境建议使用 Milvus 集群(至少 3 节点),配置对象存储(如 MinIO)持久化数据。
  1. 监控告警:集成 Prometheus 监控 Milvus 的 QPS、延迟,以及 Spring 应用的内存使用情况。
  1. 多源数据接入:扩展DocumentService支持数据库、API 接口等多种数据源,实现知识自动更新。
  1. 权限控制:通过 Spring Security 对文档上传和问答接口进行权限管理,确保数据安全。

六、总结

Spring AI 与 Milvus 的组合为 RAG 智能问答系统提供了高效的实现路径:Spring AI 简化了 AI 模型集成与流程编排,Milvus 则解决了向量高效存储与检索的难题。通过本文的实战步骤,开发者可在 1-2 天内搭建起基础系统,再结合业务场景进行优化,即可满足企业级知识问答、智能客服等需求。

随着大模型技术的发展,RAG 架构将成为连接私有数据与通用 AI 能力的核心范式,而 Spring AI 与 Milvus 的技术栈,无疑是这一领域值得投入的实用方案。

posted @ 2025-08-01 20:16  wzzkaifa  阅读(11)  评论(0)    收藏  举报