From 43891f876e61afe3b9845f74c1ca0f7858f70b9b Mon Sep 17 00:00:00 2001 From: kennethcheng Date: Sun, 19 Apr 2026 14:18:42 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20RAG=20=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E9=97=AE=E7=AD=94=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 DocumentController 提供文档导入 API - 新增 DocumentService 实现文档读取、分割、向量化存储 - 新增 RagConfig 配置 TokenTextSplitter - 添加 doris_intro.md 作为示例 RAG 文档 - 启用 milvus-sdk-java 依赖 - 配置 SiliconFlow Embedding 服务 (BAAI/bge-large-zh-v1.5) - 配置 Milvus 向量数据库连接 --- README.md | 157 +++++++++++++----- data/doris_intro.md | 98 +++++++++++ pom.xml | 12 +- src/main/java/com/demo/config/RagConfig.java | 27 +++ .../demo/controller/DocumentController.java | 31 ++++ .../com/demo/service/DocumentService.java | 82 +++++++++ src/main/resources/application.yaml | 35 ++-- 7 files changed, 381 insertions(+), 61 deletions(-) create mode 100644 data/doris_intro.md create mode 100644 src/main/java/com/demo/config/RagConfig.java create mode 100644 src/main/java/com/demo/controller/DocumentController.java create mode 100644 src/main/java/com/demo/service/DocumentService.java diff --git a/README.md b/README.md index ea3c947..9ab3963 100644 --- a/README.md +++ b/README.md @@ -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 | ### 前端 @@ -77,22 +81,29 @@ springAiDemo/ ├── src/ │ └── main/ │ ├── java/com/demo/ -│ │ ├── MyApplication.java # Spring Boot 启动入口 -│ │ └── controller/ -│ │ └── ChatController.java # AI 聊天 REST API +│ │ ├── MyApplication.java # Spring Boot 启动入口 +│ │ ├── 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 # 流式响应逻辑 +│ ├── 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,61 +185,87 @@ 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 +``` ## 🎬 架构图 ``` ┌─────────────┐ ┌──────────────────┐ ┌─────────────────┐ -│ Client │────▶│ ChatController │────▶│ ChatClient │ +│ Client │────▶│ ChatController │────▶│ ChatClient │ │ (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 diff --git a/data/doris_intro.md b/data/doris_intro.md new file mode 100644 index 0000000..0a1cd3f --- /dev/null +++ b/data/doris_intro.md @@ -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 架构、列式存储、向量化执行等技术,提供亚秒级的查询响应能力。 diff --git a/pom.xml b/pom.xml index 2af0b0b..5f8448b 100644 --- a/pom.xml +++ b/pom.xml @@ -48,12 +48,12 @@ spring-ai-milvus-store-spring-boot-starter - - - - - - + + + io.milvus + milvus-sdk-java + 2.3.4 + diff --git a/src/main/java/com/demo/config/RagConfig.java b/src/main/java/com/demo/config/RagConfig.java new file mode 100644 index 0000000..c3a4228 --- /dev/null +++ b/src/main/java/com/demo/config/RagConfig.java @@ -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 + ); + } +} diff --git a/src/main/java/com/demo/controller/DocumentController.java b/src/main/java/com/demo/controller/DocumentController.java new file mode 100644 index 0000000..ea883e9 --- /dev/null +++ b/src/main/java/com/demo/controller/DocumentController.java @@ -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"; + } +} + diff --git a/src/main/java/com/demo/service/DocumentService.java b/src/main/java/com/demo/service/DocumentService.java new file mode 100644 index 0000000..e2850bb --- /dev/null +++ b/src/main/java/com/demo/service/DocumentService.java @@ -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 allDocuments = new ArrayList<>(); + // 读取文件 + + try (Stream 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 documents = reader.get(); + + // 切割文件。生成documents + List 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); + } + } +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 249c1ff..35f1bf9 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -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 \ No newline at end of file +document: + data-path: data + chunk-size: 400 + min-chunk-size: 200 + max-num-chunk: 10000 \ No newline at end of file