feat: 添加 RAG 文档问答功能

- 新增 DocumentController 提供文档导入 API
- 新增 DocumentService 实现文档读取、分割、向量化存储
- 新增 RagConfig 配置 TokenTextSplitter
- 添加 doris_intro.md 作为示例 RAG 文档
- 启用 milvus-sdk-java 依赖
- 配置 SiliconFlow Embedding 服务 (BAAI/bge-large-zh-v1.5)
- 配置 Milvus 向量数据库连接
This commit is contained in:
kennethcheng 2026-04-19 14:18:42 +08:00
parent 3208efe280
commit 43891f876e
7 changed files with 381 additions and 61 deletions

141
README.md
View File

@ -1,6 +1,6 @@
# Spring AI Demo
> 🤖 一个简洁优雅的 Spring AI 对话演示项目,基于 Spring Boot 3.2.0 与 Spring AI 1.0.0-M3 构建,支持流式响应与 Markdown 渲染
> 🤖 一个简洁优雅的 Spring AI 对话演示项目,基于 Spring Boot 3.2.0 与 Spring AI 1.0.0-M3 构建,支持流式响应、Markdown 渲染与 RAG 文档问答
[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.2.0-brightgreen)](https://spring.io/projects/spring-boot)
[![Spring AI](https://img.shields.io/badge/Spring%20AI-1.0.0--M3-blue)](https://spring.io/projects/spring-ai)
@ -13,7 +13,9 @@
- 📡 **流式响应** - 实时逐字输出,体验流畅
- 📝 **Markdown 支持** - 完整渲染代码块、表格、列表等格式
- 👨‍💻 **代码高亮** - Highlight.js 自动语言检测与语法着色
- 🖼️ **向量数据库** - Milvus 集成(预留 RAG 扩展能力)
- 📚 **RAG 文档问答** - 文档向量化存储,智能语义检索
- 🗃️ **向量数据库** - Milvus 分布式向量数据库集成
- 🔍 **Embedding 服务** - SiliconFlow BAAI/bge-large-zh-v1.5
- 📔 **PDF 解析** - Apache PDFBox 文档处理支持
- 🎨 **精美界面** - 深色主题响应式设计
@ -24,6 +26,7 @@
- JDK 17+
- Maven 3.8+
- [Ollama](https://ollama.ai/) 本地服务
- Milvus 向量数据库(可选,用于 RAG
### 1. 安装 Ollama
@ -58,8 +61,9 @@ mvn spring-boot:run
| 基础框架 | Spring Boot | 3.2.0 |
| AI 框架 | Spring AI | 1.0.0-M3 |
| AI 模型 | OpenAI/Ollama | - |
| Embedding | SiliconFlow BAAI/bge-large-zh-v1.5 | - |
| 向量数据库 | Milvus | 2.3.4 |
| 响应式编程 | Spring WebFlux | 3.2.0 |
| 向量数据库 | Milvus Store | 1.0.0-M3 |
| 文档处理 | Apache PDFBox | 2.0.29 |
### 前端
@ -78,21 +82,28 @@ springAiDemo/
│ └── main/
│ ├── java/com/demo/
│ │ ├── MyApplication.java # Spring Boot 启动入口
│ │ └── controller/
│ │ └── ChatController.java # AI 聊天 REST API
│ │ ├── config/
│ │ │ └── RagConfig.java # RAG 文本分割配置
│ │ ├── controller/
│ │ │ ├── ChatController.java # AI 聊天 REST API
│ │ │ └── DocumentController.java # 文档导入 REST API
│ │ └── service/
│ │ └── DocumentService.java # 文档处理服务
│ └── resources/
│ ├── application.yaml # 应用配置
│ └── static/ # 前端静态资源
│ ├── index.html # 主页面
│ ├── css/style.css # 深色主题样式
│ └── js/app.js # 流式响应逻辑
├── data/
│ └── doris_intro.md # RAG 示例文档
├── pom.xml
└── README.md
```
## 💬 API 文档
### 端点列表
### 聊天接口
| 方法 | 端点 | 描述 | 参数 |
|:---|:---|:---|:---|
@ -100,6 +111,12 @@ springAiDemo/
| GET | `/api/chat/ai` | 同步 AI 对话 | `msg` |
| GET | `/api/chat/ai/stream` | 流式 AI 对话 | `msg` |
### 文档接口
| 方法 | 端点 | 描述 | 参数 |
|:---|:---|:---|:---|
| GET | `/api/documents/import` | 导入文档到向量库 | - |
### 请求示例
**同步对话:**
@ -112,10 +129,36 @@ curl "http://localhost:8080/api/chat/ai?msg=什么是Spring AI"
curl "http://localhost:8080/api/chat/ai/stream?msg=讲一个故事"
```
### 响应格式
**导入 RAG 文档:**
```bash
curl http://localhost:8080/api/documents/import
```
- **同步**: `text/plain` - 完整回复文本
- **流式**: `text/html;charset=UTF-8` - Server-Sent Events 分块传输
## 📚 RAG 文档问答
### 功能说明
将本地文档导入 Milvus 向量数据库,实现基于语义理解的智能问答。
### 使用步骤
1. 将 `.md``.txt` 文档放入 `data/` 目录
2. 启动应用
3. 调用导入接口:
```bash
curl http://localhost:8080/api/documents/import
```
4. 通过聊天界面提问相关问题
### 文档处理配置
```yaml
document:
data-path: data # 文档目录
chunk-size: 400 # 分割块大小 (tokens)
min-chunk-size: 200 # 最小块大小
max-num-chunk: 10000 # 最大块数量
```
## 🎨 界面预览
@ -133,7 +176,7 @@ curl "http://localhost:8080/api/chat/ai/stream?msg=讲一个故事"
## 🛠️ 配置说明
### 模型配置 (`application.yaml`)
### AI 对话配置
```yaml
spring:
@ -142,14 +185,39 @@ spring:
base-url: http://localhost:11434 # Ollama 服务地址
chat:
options:
model: gpt-oss:120b-cloud # 当前模型
temperature: 0.7 # 生成温度
model: gpt-oss:120b-cloud # 对话模型
temperature: 0.7
```
**支持模型**(需在 Ollama 中预先拉取):
- `gpt-oss:120b-cloud` - 默认模型
- `kimi-k2.5:cloud` - Kimi 模型
- `gemma4:e2b` - Google Gemma 模型
### Embedding 配置
```yaml
spring:
ai:
openai:
embedding:
api-key: your-siliconflow-api-key
base-url: https://api.siliconflow.cn
model: BAAI/bge-large-zh-v1.5
dimensions: 1024
enabled: true
```
### 向量数据库配置
```yaml
spring:
ai:
vectorstore:
milvus:
host: 192.168.50.103
port: 19530
database-name: doris_docs
collection-name: vector_store
embedding-dimension: 1024
index-type: IVF_FLAT
metric-type: COSINE
```
## 🎬 架构图
@ -159,44 +227,45 @@ spring:
│ (HTTP) │ │ (Spring MVC) │ │ (Spring AI) │
└─────────────┘ └──────────────────┘ └────────┬────────┘
┌─────────────────┐
│ Ollama/OpenAI │
│ (LLM Provider) │
└─────────────────┘
┌──────────────────┐ │
│ DocumentService │ │
│ (文档处理) │ ▼
└────────┬─────────┘ ┌─────────────────┐
│ │ Ollama/OpenAI │
▼ │ (LLM Provider) │
┌──────────────────┐ └─────────────────┘
│ Milvus Store │
│ (向量存储) │
└──────────────────┘
```
## 📄 预留功能
以下依赖已集成,可按需启用:
- 🔍 **Milvus 向量数据库** - RAG 语义搜索
- 📔 **PDF 文档解析** - 文档上传与处理
- 🗨️ **Embedding 服务** - 文本向量化
- 📔 **PDF 文档解析** - Apache PDFBox 文档处理
## 👷 已知限制
1. 暂无对话历史持久化
2. 暂未实现请求频率限制
3. JUnit 版本较旧3.8.1),建议升级至 JUnit 5
3. Milvus 连接信息硬编码在配置中
## 📖 待办事项
## 📖 更新日志
- [ ] 添加全局异常处理
- [ ] 实现 RAG 文档问答
- [ ] 对话历史存储
- [ ] 动态模型切换 API
- [ ] Docker 部署支持
- [ ] 单元测试完善
## 📤 更新日志
### v1.0.0 (2026-04-17)
### v1.0.0 (2026-04-19)
- 🎉 初始版本发布
- 支持流式 AI 对话
- Markdown 渲染与代码高亮
- 深色主题 Web 界面
### v1.1.0 (2026-04-19)
- ✨ 新增 RAG 文档问答功能
- ✨ 新增 DocumentController 文档导入 API
- ✨ 新增 DocumentService 文档处理服务
- ✨ 配置 SiliconFlow Embedding 服务
- ✨ 集成 Milvus 向量数据库
## 📗 License
MIT © 2026 Spring AI Demo

98
data/doris_intro.md Normal file
View File

@ -0,0 +1,98 @@
# Apache Doris 简介
Apache Doris 是一个基于 MPP 架构的高性能、实时的分析型数据库,以极速易用的特点被人们所熟知,仅需亚秒级响应时间即可返回海量数据下的查询结果,不仅可以支持高并发的点查询场景,也能支持高吞吐的复杂分析场景。
## 核心特性
### 1. 极速易用
Apache Doris 采用了列式存储引擎通过向量化执行引擎和查询优化器能够在海量数据下实现亚秒级的查询响应。同时Doris 提供了标准 SQL 支持,用户无需学习新的查询语言,即可快速上手。
### 2. 高可用性
Doris 采用了多副本机制,数据自动备份,节点故障时可以自动切换,保证服务的高可用性。支持在线扩缩容,不影响业务运行。
### 3. 实时分析
Doris 支持实时数据导入和查询,数据导入后即可被查询,无需等待数据预处理。支持多种数据导入方式,包括 Stream Load、Broker Load、Routine Load 等。
### 4. 联邦查询
Doris 支持通过外部表的方式查询其他数据源,如 MySQL、Hive、Iceberg、Hudi 等,实现数据联邦查询,无需数据搬迁。
## 技术架构
Apache Doris 采用 MySQL 协议,高度兼容 MySQL 语法,支持标准 SQL。整体架构分为两层
### Frontend (FE)
FE 主要负责:
- 接收和解析用户请求
- 查询规划和优化
- 元数据管理
- 集群管理
FE 节点分为 Follower 和 Observer 两种角色Follower 参与选举和写入Observer 只用于扩展查询能力。
### Backend (BE)
BE 主要负责:
- 数据存储
- 查询执行
- 数据导入
BE 节点负责数据的存储和计算,采用列式存储和向量化执行引擎,提供高性能的查询能力。
## 使用场景
### 1. 报表分析
适用于各类报表场景,支持复杂的多维分析,亚秒级响应,支持高并发查询。
### 2. 即席查询
支持灵活的即席查询,用户可以快速探索数据,发现业务洞察。
### 3. 实时数仓
构建实时数据仓库,支持实时数据导入和查询,满足实时分析需求。
### 4. 日志分析
适用于日志分析场景,支持海量日志数据的快速检索和分析。
## 数据导入
Apache Doris 支持多种数据导入方式:
### Stream Load
通过 HTTP 协议导入数据,适用于实时数据导入场景。支持 CSV、JSON 等格式。
```bash
curl --location-trusted -u user:passwd \
-H "column_separator:," \
-T data.csv \
http://fe_host:http_port/api/database/table/_stream_load
```
### Broker Load
通过 Broker 进程访问外部存储系统(如 HDFS、S3导入数据适用于大批量数据导入。
### Routine Load
从 Kafka 等消息队列持续消费数据并导入,适用于实时数据流场景。
## 查询优化
Apache Doris 提供了多种查询优化手段:
1. **智能索引**支持前缀索引、Bitmap 索引、Bloom Filter 索引等
2. **分区分桶**:通过合理的分区分桶策略,减少数据扫描量
3. **物化视图**:预计算常用查询结果,加速查询响应
4. **查询缓存**:缓存查询结果,避免重复计算
## 总结
Apache Doris 是一款高性能、易用的分析型数据库,适用于多种实时分析场景。通过 MPP 架构、列式存储、向量化执行等技术,提供亚秒级的查询响应能力。

12
pom.xml
View File

@ -48,12 +48,12 @@
<artifactId>spring-ai-milvus-store-spring-boot-starter</artifactId>
</dependency>
<!-- &lt;!&ndash; milvus客户端 &ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>io.milvus</groupId>-->
<!-- <artifactId>milvus-sdk-java</artifactId>-->
<!-- <version>2.3.4</version>-->
<!-- </dependency>-->
<!-- milvus客户端 -->
<dependency>
<groupId>io.milvus</groupId>
<artifactId>milvus-sdk-java</artifactId>
<version>2.3.4</version>
</dependency>
<!-- spring中用于校验的包 -->

View File

@ -0,0 +1,27 @@
package com.demo.config;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RagConfig {
@Value("${document.chunk-size}")
private int chunkSize;
@Value("${document.min-chunk-size}")
private int minChunkSize;
@Value("${document.max-num-chunk}")
private int maxNumChunk;
@Bean
public TokenTextSplitter tokenTextSplitter() {
return new TokenTextSplitter(
chunkSize,
minChunkSize,
5,
maxNumChunk,
true
);
}
}

View File

@ -0,0 +1,31 @@
package com.demo.controller;
import com.demo.service.DocumentService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
@RestController
@RequestMapping("api/documents")
@RequiredArgsConstructor
public class DocumentController {
@Autowired
public final DocumentService documentService;
@GetMapping("import")
public String importDocument() {
try {
documentService.importDocument();
} catch (IOException e) {
return "not ok" + e.getMessage();
}
return "ok";
}
}

View File

@ -0,0 +1,82 @@
package com.demo.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.document.Document;
import org.springframework.ai.reader.TextReader;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
@Slf4j
@Service
@RequiredArgsConstructor
public class DocumentService {
@Value("${document.data-path}")
private String dataPath;
@Autowired
private final VectorStore vectorStore;
@Autowired
private final TokenTextSplitter textSplitter;
public void importDocument() throws IOException {
log.info("开始导入文档到向量数据库");
Path path = Paths.get(dataPath);
if (Files.notExists(path)) {
log.error("数据不存在:{}",dataPath);
throw new IOException("数据不存在:" + dataPath);
}
List<Document> allDocuments = new ArrayList<>();
// 读取文件
try (Stream<Path> paths = Files.walk(path)){
paths.filter(Files::isRegularFile)
.filter(p -> p.getFileName().toString().endsWith(".md")
|| p.getFileName().toString().endsWith(".txt"))
.forEach(filePath -> {
try {
log.info("开始处理 {} 文件",filePath);
TextReader reader = new TextReader(new FileSystemResource(filePath.toFile()));
List<Document> documents = reader.get();
// 切割文件生成documents
List<Document> splitDocuments = textSplitter.apply(documents);
// 添加文件元信息
splitDocuments.forEach(document -> {
document.getMetadata().put("source",filePath.getFileName().toString());
document.getMetadata().put("file_path",filePath.toString());
});
allDocuments.addAll(splitDocuments);
log.info("文件{}分割为{}个文档片段",filePath.getFileName().toString(),splitDocuments.size());
}catch (Exception e) {
log.error("处理失败",e);
e.printStackTrace();
}
});
}
if (allDocuments.size() > 0) {
log.info("开始往向量数据库中插入数据通过embedding模型进行向量化总共有{}个文档片源",allDocuments.size());
// 插入到向量数据库中
vectorStore.add(allDocuments);
}
}
}

View File

@ -15,15 +15,28 @@ spring:
# model: gemma4:e2b
# max-tokens: 10000
temperature: 0.7
# embedding:
# api-key: ollama
# base-url: http://localhost:11434
embedding:
api-key: key
base-url: https://api.siliconflow.cn
options:
model: BAAI/bge-large-zh-v1.5
dimensions: 1024
enable: true
vectorstore:
milvus:
client:
host: "192.168.50.103"
port: 19530
databaseName: "doris_docs"
collectionName: "vector_store"
embeddingDimension: 1024
indexType: IVF_FLAT
metricType: COSINE
# 增加这个配置可以让springai帮我们创建vector_store
initialize-schema: true
#spring:
# ai:
# ollama:
# base-url: http://localhost:11434
# chat:
# options:
# model: gemma4:e2b
# temperature: 0.7
document:
data-path: data
chunk-size: 400
min-chunk-size: 200
max-num-chunk: 10000