feat: 新增 RAG 智能问答 POST 接口与 ChatService
- 新增 ChatService 实现 RAG 核心逻辑(向量检索 + 上下文构建 + LLM 生成) - 新增 ChatRequest/ChatResponse DTO 支持结构化请求响应 - 新增 ApiResponse 统一响应封装 - ChatController 新增 POST /api/chat RAG 问答接口(支持引用溯源) - 流式接口 GET /api/chat/ai/stream 保持不变
This commit is contained in:
parent
43891f876e
commit
710fe14d7f
195
README.md
195
README.md
@ -1,6 +1,6 @@
|
|||||||
# Spring AI Demo
|
# Spring AI Demo
|
||||||
|
|
||||||
> 🤖 一个简洁优雅的 Spring AI 对话演示项目,基于 Spring Boot 3.2.0 与 Spring AI 1.0.0-M3 构建,支持流式响应、Markdown 渲染与 RAG 文档问答。
|
> 🤖 一个简洁优雅的 Spring AI 对话演示项目,基于 Spring Boot 3.2.0 与 Spring AI 1.0.0-M3 构建,支持流式响应、Markdown 渲染与 RAG 智能问答。
|
||||||
|
|
||||||
[](https://spring.io/projects/spring-boot)
|
[](https://spring.io/projects/spring-boot)
|
||||||
[](https://spring.io/projects/spring-ai)
|
[](https://spring.io/projects/spring-ai)
|
||||||
@ -9,14 +9,15 @@
|
|||||||
|
|
||||||
## ✨ 特性
|
## ✨ 特性
|
||||||
|
|
||||||
- 🌐 **智能对话** - 基于 OpenAI/Ollama 的自然语言交互
|
- 🌐 **AI 智能对话** - 基于 Ollama 的自然语言交互
|
||||||
- 📡 **流式响应** - 实时逐字输出,体验流畅
|
- 📡 **流式响应** - 实时逐字输出,体验流畅
|
||||||
- 📝 **Markdown 支持** - 完整渲染代码块、表格、列表等格式
|
- 📝 **Markdown 支持** - 完整渲染代码块、表格、列表等格式
|
||||||
- 👨💻 **代码高亮** - Highlight.js 自动语言检测与语法着色
|
- 👨💻 **代码高亮** - Highlight.js 自动语言检测与语法着色
|
||||||
- 📚 **RAG 文档问答** - 文档向量化存储,智能语义检索
|
- 📚 **RAG 智能问答** - 基于向量检索的上下文感知回答
|
||||||
- 🗃️ **向量数据库** - Milvus 分布式向量数据库集成
|
- 🔍 **语义检索** - Milvus 向量数据库相似度搜索
|
||||||
|
- 🔗 **引用溯源** - 回答附带文档来源引用
|
||||||
|
- 🗃️ **向量数据库** - Milvus 分布式向量数据库
|
||||||
- 🔍 **Embedding 服务** - SiliconFlow BAAI/bge-large-zh-v1.5
|
- 🔍 **Embedding 服务** - SiliconFlow BAAI/bge-large-zh-v1.5
|
||||||
- 📔 **PDF 解析** - Apache PDFBox 文档处理支持
|
|
||||||
- 🎨 **精美界面** - 深色主题响应式设计
|
- 🎨 **精美界面** - 深色主题响应式设计
|
||||||
|
|
||||||
## 🚀 快速开始
|
## 🚀 快速开始
|
||||||
@ -26,7 +27,7 @@
|
|||||||
- JDK 17+
|
- JDK 17+
|
||||||
- Maven 3.8+
|
- Maven 3.8+
|
||||||
- [Ollama](https://ollama.ai/) 本地服务
|
- [Ollama](https://ollama.ai/) 本地服务
|
||||||
- Milvus 向量数据库(可选,用于 RAG)
|
- Milvus 向量数据库
|
||||||
|
|
||||||
### 1. 安装 Ollama
|
### 1. 安装 Ollama
|
||||||
|
|
||||||
@ -64,7 +65,6 @@ mvn spring-boot:run
|
|||||||
| Embedding | SiliconFlow BAAI/bge-large-zh-v1.5 | - |
|
| Embedding | SiliconFlow BAAI/bge-large-zh-v1.5 | - |
|
||||||
| 向量数据库 | Milvus | 2.3.4 |
|
| 向量数据库 | Milvus | 2.3.4 |
|
||||||
| 响应式编程 | Spring WebFlux | 3.2.0 |
|
| 响应式编程 | Spring WebFlux | 3.2.0 |
|
||||||
| 文档处理 | Apache PDFBox | 2.0.29 |
|
|
||||||
|
|
||||||
### 前端
|
### 前端
|
||||||
|
|
||||||
@ -78,27 +78,25 @@ mvn spring-boot:run
|
|||||||
|
|
||||||
```
|
```
|
||||||
springAiDemo/
|
springAiDemo/
|
||||||
├── src/
|
├── src/main/java/com/demo/
|
||||||
│ └── main/
|
│ ├── MyApplication.java # Spring Boot 启动入口
|
||||||
│ ├── java/com/demo/
|
│ ├── config/
|
||||||
│ │ ├── MyApplication.java # Spring Boot 启动入口
|
│ │ └── RagConfig.java # RAG 配置类
|
||||||
│ │ ├── config/
|
│ ├── controller/
|
||||||
│ │ │ └── RagConfig.java # RAG 文本分割配置
|
│ │ ├── ChatController.java # AI 聊天 API
|
||||||
│ │ ├── controller/
|
│ │ └── DocumentController.java # 文档导入 API
|
||||||
│ │ │ ├── ChatController.java # AI 聊天 REST API
|
│ ├── dto/
|
||||||
│ │ │ └── DocumentController.java # 文档导入 REST API
|
│ │ ├── ApiResponse.java # 通用响应封装
|
||||||
│ │ └── service/
|
│ │ ├── ChatRequest.java # 聊天请求 DTO
|
||||||
│ │ └── DocumentService.java # 文档处理服务
|
│ │ └── ChatResponse.java # 聊天响应 DTO
|
||||||
│ └── resources/
|
│ └── service/
|
||||||
│ ├── application.yaml # 应用配置
|
│ ├── ChatService.java # RAG 聊天服务
|
||||||
│ └── static/ # 前端静态资源
|
│ └── DocumentService.java # 文档处理服务
|
||||||
│ ├── index.html # 主页面
|
|
||||||
│ ├── css/style.css # 深色主题样式
|
|
||||||
│ └── js/app.js # 流式响应逻辑
|
|
||||||
├── data/
|
├── data/
|
||||||
│ └── doris_intro.md # RAG 示例文档
|
│ └── doris_intro.md # RAG 示例文档
|
||||||
├── pom.xml
|
└── src/main/resources/
|
||||||
└── README.md
|
├── application.yaml # 应用配置
|
||||||
|
└── static/ # 前端资源
|
||||||
```
|
```
|
||||||
|
|
||||||
## 💬 API 文档
|
## 💬 API 文档
|
||||||
@ -108,20 +106,22 @@ springAiDemo/
|
|||||||
| 方法 | 端点 | 描述 | 参数 |
|
| 方法 | 端点 | 描述 | 参数 |
|
||||||
|:---|:---|:---|:---|
|
|:---|:---|:---|:---|
|
||||||
| GET | `/api/chat/test` | 健康检查 | - |
|
| GET | `/api/chat/test` | 健康检查 | - |
|
||||||
| GET | `/api/chat/ai` | 同步 AI 对话 | `msg` |
|
| GET | `/api/chat/ai` | 流式 AI 对话 | `msg` |
|
||||||
| GET | `/api/chat/ai/stream` | 流式 AI 对话 | `msg` |
|
| POST | `/api/chat` | RAG 智能问答 | `{ "question": "..." }` |
|
||||||
|
|
||||||
### 文档接口
|
### 文档接口
|
||||||
|
|
||||||
| 方法 | 端点 | 描述 | 参数 |
|
| 方法 | 端点 | 描述 |
|
||||||
|:---|:---|:---|:---|
|
|:---|:---|:---|
|
||||||
| GET | `/api/documents/import` | 导入文档到向量库 | - |
|
| GET | `/api/documents/import` | 导入文档到向量库 |
|
||||||
|
|
||||||
### 请求示例
|
### 请求示例
|
||||||
|
|
||||||
**同步对话:**
|
**RAG 智能问答:**
|
||||||
```bash
|
```bash
|
||||||
curl "http://localhost:8080/api/chat/ai?msg=什么是Spring AI"
|
curl -X POST http://localhost:8080/api/chat \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"question": "Apache Doris 是什么?"}'
|
||||||
```
|
```
|
||||||
|
|
||||||
**流式对话:**
|
**流式对话:**
|
||||||
@ -134,21 +134,39 @@ curl "http://localhost:8080/api/chat/ai/stream?msg=讲一个故事"
|
|||||||
curl http://localhost:8080/api/documents/import
|
curl http://localhost:8080/api/documents/import
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 响应格式
|
||||||
|
|
||||||
|
**POST /api/chat 响应:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"answer": "Apache Doris 是一个...",
|
||||||
|
"references": ["data/doris_intro.md"],
|
||||||
|
"timestamp": 1713000000000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## 📚 RAG 文档问答
|
## 📚 RAG 文档问答
|
||||||
|
|
||||||
### 功能说明
|
### 工作原理
|
||||||
|
|
||||||
将本地文档导入 Milvus 向量数据库,实现基于语义理解的智能问答。
|
```
|
||||||
|
用户问题 → Embedding → 向量检索 → 构建上下文 → LLM 生成回答 → 返回答案+引用
|
||||||
|
```
|
||||||
|
|
||||||
|
1. **文档导入** - 将 `.md` / `.txt` 文档读取并切割成 chunks
|
||||||
|
2. **向量化** - 使用 BAAI/bge-large-zh-v1.5 生成 1024 维向量
|
||||||
|
3. **存储检索** -存入 Milvus,向量相似度搜索 top-K 结果
|
||||||
|
4. **生成回答** - 将检索结果作为上下文,LLM 生成答案
|
||||||
|
|
||||||
### 使用步骤
|
### 使用步骤
|
||||||
|
|
||||||
1. 将 `.md` 或 `.txt` 文档放入 `data/` 目录
|
1. 将文档放入 `data/` 目录
|
||||||
2. 启动应用
|
2. 调用导入接口:`curl http://localhost:8080/api/documents/import`
|
||||||
3. 调用导入接口:
|
3. 通过 POST `/api/chat` 提问
|
||||||
```bash
|
|
||||||
curl http://localhost:8080/api/documents/import
|
|
||||||
```
|
|
||||||
4. 通过聊天界面提问相关问题
|
|
||||||
|
|
||||||
### 文档处理配置
|
### 文档处理配置
|
||||||
|
|
||||||
@ -160,20 +178,6 @@ document:
|
|||||||
max-num-chunk: 10000 # 最大块数量
|
max-num-chunk: 10000 # 最大块数量
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🎨 界面预览
|
|
||||||
|
|
||||||
🧩 **现代化深色主题**
|
|
||||||
- 渐变标题与毛玻璃效果
|
|
||||||
- 流畅的消息气泡动画
|
|
||||||
- Markdown 代码块高亮
|
|
||||||
- 流式响应动画指示器
|
|
||||||
|
|
||||||
📝 **Markdown 支持**
|
|
||||||
- 代码块与行内代码
|
|
||||||
- 加粗、斜体、删除线
|
|
||||||
- 有序/无序列表
|
|
||||||
- 引用块、表格、链接
|
|
||||||
|
|
||||||
## 🛠️ 配置说明
|
## 🛠️ 配置说明
|
||||||
|
|
||||||
### AI 对话配置
|
### AI 对话配置
|
||||||
@ -182,10 +186,10 @@ document:
|
|||||||
spring:
|
spring:
|
||||||
ai:
|
ai:
|
||||||
openai:
|
openai:
|
||||||
base-url: http://localhost:11434 # Ollama 服务地址
|
base-url: http://localhost:11434
|
||||||
chat:
|
chat:
|
||||||
options:
|
options:
|
||||||
model: gpt-oss:120b-cloud # 对话模型
|
model: gpt-oss:120b-cloud
|
||||||
temperature: 0.7
|
temperature: 0.7
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -200,7 +204,6 @@ spring:
|
|||||||
base-url: https://api.siliconflow.cn
|
base-url: https://api.siliconflow.cn
|
||||||
model: BAAI/bge-large-zh-v1.5
|
model: BAAI/bge-large-zh-v1.5
|
||||||
dimensions: 1024
|
dimensions: 1024
|
||||||
enabled: true
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 向量数据库配置
|
### 向量数据库配置
|
||||||
@ -210,47 +213,44 @@ spring:
|
|||||||
ai:
|
ai:
|
||||||
vectorstore:
|
vectorstore:
|
||||||
milvus:
|
milvus:
|
||||||
host: 192.168.50.103
|
client:
|
||||||
port: 19530
|
host: 192.168.50.103
|
||||||
database-name: doris_docs
|
port: 19530
|
||||||
collection-name: vector_store
|
databaseName: doris_docs
|
||||||
embedding-dimension: 1024
|
collectionName: vector_store
|
||||||
index-type: IVF_FLAT
|
|
||||||
metric-type: COSINE
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🎬 架构图
|
## 🎬 架构图
|
||||||
|
|
||||||
```
|
```
|
||||||
┌─────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
│ Client │────▶│ ChatController │────▶│ ChatClient │
|
│ Client │
|
||||||
│ (HTTP) │ │ (Spring MVC) │ │ (Spring AI) │
|
│ ┌─────────────────┐ ┌─────────────────────────────────┐│
|
||||||
└─────────────┘ └──────────────────┘ └────────┬────────┘
|
│ │ Web UI │ │ POST /api/chat ││
|
||||||
│
|
│ │ (流式/非流式) │────▶│ { question: "..." } ││
|
||||||
┌──────────────────┐ │
|
│ └─────────────────┘ └─────────────────────────────────┘│
|
||||||
│ DocumentService │ │
|
└─────────────────────────────────────────────────────────────┘
|
||||||
│ (文档处理) │ ▼
|
│
|
||||||
└────────┬─────────┘ ┌─────────────────┐
|
▼
|
||||||
│ │ Ollama/OpenAI │
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
▼ │ (LLM Provider) │
|
│ ChatController │
|
||||||
┌──────────────────┐ └─────────────────┘
|
│ POST /api/chat → ChatService │
|
||||||
│ Milvus Store │
|
└─────────────────────────────────────────────────────────────┘
|
||||||
│ (向量存储) │
|
│
|
||||||
└──────────────────┘
|
┌───────────────┴───────────────┐
|
||||||
|
▼ ▼
|
||||||
|
┌─────────────────────────┐ ┌─────────────────────────┐
|
||||||
|
│ VectorStore │ │ ChatClient │
|
||||||
|
│ (Milvus 语义检索) │ │ (Ollama LLM) │
|
||||||
|
└─────────────────────────┘ └─────────────────────────┘
|
||||||
|
│ │
|
||||||
|
▼ ▼
|
||||||
|
┌─────────────────────────┐ ┌─────────────────────────┐
|
||||||
|
│ DocumentService │ │ SiliconFlow API │
|
||||||
|
│ (文档切割/向量化) │ │ (Embedding) │
|
||||||
|
└─────────────────────────┘ └─────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📄 预留功能
|
|
||||||
|
|
||||||
以下依赖已集成,可按需启用:
|
|
||||||
|
|
||||||
- 📔 **PDF 文档解析** - Apache PDFBox 文档处理
|
|
||||||
|
|
||||||
## 👷 已知限制
|
|
||||||
|
|
||||||
1. 暂无对话历史持久化
|
|
||||||
2. 暂未实现请求频率限制
|
|
||||||
3. Milvus 连接信息硬编码在配置中
|
|
||||||
|
|
||||||
## 📖 更新日志
|
## 📖 更新日志
|
||||||
|
|
||||||
### v1.0.0 (2026-04-19)
|
### v1.0.0 (2026-04-19)
|
||||||
@ -262,10 +262,15 @@ spring:
|
|||||||
### v1.1.0 (2026-04-19)
|
### v1.1.0 (2026-04-19)
|
||||||
- ✨ 新增 RAG 文档问答功能
|
- ✨ 新增 RAG 文档问答功能
|
||||||
- ✨ 新增 DocumentController 文档导入 API
|
- ✨ 新增 DocumentController 文档导入 API
|
||||||
- ✨ 新增 DocumentService 文档处理服务
|
|
||||||
- ✨ 配置 SiliconFlow Embedding 服务
|
- ✨ 配置 SiliconFlow Embedding 服务
|
||||||
- ✨ 集成 Milvus 向量数据库
|
- ✨ 集成 Milvus 向量数据库
|
||||||
|
|
||||||
|
### v1.2.0 (2026-04-19)
|
||||||
|
- ✨ 新增 POST /api/chat RAG 智能问答接口
|
||||||
|
- ✨ 新增 ChatService RAG 核心服务
|
||||||
|
- ✨ 新增 ChatRequest/ChatResponse/ApiResponse DTO
|
||||||
|
- ✨ 新增引用溯源功能,返回文档来源
|
||||||
|
|
||||||
## 📗 License
|
## 📗 License
|
||||||
|
|
||||||
MIT © 2026 Spring AI Demo
|
MIT © 2026 Spring AI Demo
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
package com.demo.config;
|
package com.demo.config;
|
||||||
|
|
||||||
|
import org.springframework.ai.chat.client.ChatClient;
|
||||||
|
import org.springframework.ai.chat.model.ChatModel;
|
||||||
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
|
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
@ -14,6 +16,11 @@ public class RagConfig {
|
|||||||
|
|
||||||
@Value("${document.max-num-chunk}")
|
@Value("${document.max-num-chunk}")
|
||||||
private int maxNumChunk;
|
private int maxNumChunk;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ChatClient chatClient(ChatModel chatModel){
|
||||||
|
return ChatClient.builder(chatModel).build();
|
||||||
|
}
|
||||||
@Bean
|
@Bean
|
||||||
public TokenTextSplitter tokenTextSplitter() {
|
public TokenTextSplitter tokenTextSplitter() {
|
||||||
return new TokenTextSplitter(
|
return new TokenTextSplitter(
|
||||||
|
|||||||
@ -1,20 +1,25 @@
|
|||||||
package com.demo.controller;
|
package com.demo.controller;
|
||||||
|
|
||||||
|
import com.demo.dto.ApiResponse;
|
||||||
|
import com.demo.dto.ChatRequest;
|
||||||
|
import com.demo.dto.ChatResponse;
|
||||||
|
import com.demo.service.ChatService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.ai.chat.client.ChatClient;
|
import org.springframework.ai.chat.client.ChatClient;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/chat")
|
@RequestMapping("/api/chat")
|
||||||
public class ChatController {
|
public class ChatController {
|
||||||
|
|
||||||
private final ChatClient chatClient;
|
private final ChatClient chatClient;
|
||||||
|
private final ChatService chatService;
|
||||||
|
|
||||||
public ChatController(ChatClient.Builder chatClientBuilder) {
|
public ChatController(ChatClient.Builder chatClientBuilder, ChatService chatService) {
|
||||||
this.chatClient = chatClientBuilder.build();
|
this.chatClient = chatClientBuilder.build();
|
||||||
|
this.chatService = chatService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/test")
|
@GetMapping("/test")
|
||||||
@ -31,6 +36,13 @@ public class ChatController {
|
|||||||
.content();
|
.content();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ApiResponse<ChatResponse> chat(@RequestBody ChatRequest request) {
|
||||||
|
log.info("接收到请求,用户的问题是:{}",request.getQuestion());
|
||||||
|
ChatResponse response = chatService.chat(request);
|
||||||
|
return ApiResponse.success(response);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 流式的聊天接口,要注意如果中文有乱码,就是编码得问题,需要添加produces = "text/html;charset=UTF-8
|
* 流式的聊天接口,要注意如果中文有乱码,就是编码得问题,需要添加produces = "text/html;charset=UTF-8
|
||||||
* @param msg
|
* @param msg
|
||||||
|
|||||||
46
src/main/java/com/demo/dto/ApiResponse.java
Normal file
46
src/main/java/com/demo/dto/ApiResponse.java
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package com.demo.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ApiResponse<T> {
|
||||||
|
|
||||||
|
private int code;
|
||||||
|
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
private T data;
|
||||||
|
|
||||||
|
public static <T> ApiResponse<T> success(T data) {
|
||||||
|
return ApiResponse.<T>builder()
|
||||||
|
.code(200)
|
||||||
|
.message("success")
|
||||||
|
.data(data)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ApiResponse<T> success(String message, T data) {
|
||||||
|
return ApiResponse.<T>builder()
|
||||||
|
.code(200)
|
||||||
|
.message(message)
|
||||||
|
.data(data)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ApiResponse<T> error(int code, String message) {
|
||||||
|
return ApiResponse.<T>builder()
|
||||||
|
.code(code)
|
||||||
|
.message(message)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ApiResponse<T> error(String message) {
|
||||||
|
return error(500, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/main/java/com/demo/dto/ChatRequest.java
Normal file
13
src/main/java/com/demo/dto/ChatRequest.java
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package com.demo.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ChatRequest {
|
||||||
|
|
||||||
|
@NotBlank(message = "请输入你的问题")
|
||||||
|
private String question;
|
||||||
|
private Integer topK = 3;
|
||||||
|
|
||||||
|
}
|
||||||
21
src/main/java/com/demo/dto/ChatResponse.java
Normal file
21
src/main/java/com/demo/dto/ChatResponse.java
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package com.demo.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ChatResponse {
|
||||||
|
|
||||||
|
private String answer;
|
||||||
|
|
||||||
|
private List<String> references;
|
||||||
|
|
||||||
|
private Long timestamp;
|
||||||
|
}
|
||||||
101
src/main/java/com/demo/service/ChatService.java
Normal file
101
src/main/java/com/demo/service/ChatService.java
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package com.demo.service;
|
||||||
|
|
||||||
|
import com.demo.dto.ChatRequest;
|
||||||
|
import com.demo.dto.ChatResponse;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.ai.chat.client.ChatClient;
|
||||||
|
import org.springframework.ai.chat.messages.Message;
|
||||||
|
import org.springframework.ai.chat.messages.UserMessage;
|
||||||
|
import org.springframework.ai.chat.prompt.Prompt;
|
||||||
|
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
|
||||||
|
import org.springframework.ai.document.Document;
|
||||||
|
import org.springframework.ai.vectorstore.SearchRequest;
|
||||||
|
import org.springframework.ai.vectorstore.VectorStore;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class ChatService {
|
||||||
|
@Autowired
|
||||||
|
private final VectorStore vectorStore;
|
||||||
|
@Autowired
|
||||||
|
private final ChatClient chatClient;
|
||||||
|
|
||||||
|
private static final String SYSTEM_PROMPT = """
|
||||||
|
你是一个专业的 Apache Doris 技术支持助手。
|
||||||
|
请基于以下提供的文档内容回答用户的问题。
|
||||||
|
\s
|
||||||
|
文档内容:
|
||||||
|
{context}
|
||||||
|
\s
|
||||||
|
回答要求:
|
||||||
|
1. 如果文档中有相关信息,请准确引用并详细回答
|
||||||
|
2. 如果文档中没有相关信息,请明确告知用户
|
||||||
|
3. 保持专业、准确、友好的语气
|
||||||
|
4. 使用中文回答
|
||||||
|
""";
|
||||||
|
|
||||||
|
public ChatService(VectorStore vectorStore, ChatClient.Builder chatClientBuilder) {
|
||||||
|
this.vectorStore = vectorStore;
|
||||||
|
this.chatClient = chatClientBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户的请求,查询知识库,并通过大模型回答问题。
|
||||||
|
* @param request
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public ChatResponse chat(ChatRequest request) {
|
||||||
|
|
||||||
|
// 1. 拿到用户问题,首先需要检错向量数据库,查看是否有相关的文档
|
||||||
|
// 这里是向量检索,我们拿到的用户的问题是文字,通过embedding模型进行向量化处理后,再进进行检索
|
||||||
|
List<Document> relevantDocs = vectorStore.similaritySearch(
|
||||||
|
SearchRequest.query(request.getQuestion())
|
||||||
|
.withTopK(request.getTopK())
|
||||||
|
.withSimilarityThreshold(0.5) // 【关键核心】相似度必须大于 0.75(满分通常是1.0)
|
||||||
|
);
|
||||||
|
log.info("检索到 {} 个相关文档", relevantDocs.size());
|
||||||
|
|
||||||
|
// // debug:遍历打印它们的真实得分
|
||||||
|
// for (int i = 0; i < relevantDocs.size(); i++) {
|
||||||
|
// Document doc = relevantDocs.get(i);
|
||||||
|
// // 打印每一条文档的 metadata,里面通常会有一个叫 "distance" 或 "similarity" 的字段
|
||||||
|
// log.info("第 {} 个文档的 Metadata: {}", i + 1, doc.getMetadata());
|
||||||
|
// // log.info("文档片段内容: {}", doc.getContent().substring(0, Math.min(50, doc.getContent().length())) + "...");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 2. 根据检索结果,构建上下文(请求llm的上下文)
|
||||||
|
String content = relevantDocs.stream()
|
||||||
|
.map(Document::getContent)
|
||||||
|
.collect(Collectors.joining("\n\n --- \n\n"));
|
||||||
|
|
||||||
|
// 3. 创建提示词
|
||||||
|
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(SYSTEM_PROMPT);
|
||||||
|
Message systemPrompt = systemPromptTemplate.createMessage(Map.of("context", content));
|
||||||
|
UserMessage userMessage = new UserMessage(request.getQuestion());
|
||||||
|
Prompt prompt = new Prompt(List.of(systemPrompt, userMessage));
|
||||||
|
|
||||||
|
// 4. 调用 LLM 生成答案
|
||||||
|
String answer = chatClient.prompt(prompt)
|
||||||
|
.call()
|
||||||
|
.content();
|
||||||
|
|
||||||
|
// 5. 封装返回的内容
|
||||||
|
List<String> references = relevantDocs.stream()
|
||||||
|
.map(doc -> (String) doc.getMetadata().get("source"))
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
return ChatResponse.builder()
|
||||||
|
.answer(answer)
|
||||||
|
.references(references)
|
||||||
|
.timestamp(System.currentTimeMillis())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user