20250719 spring-ai搭建
This commit is contained in:
commit
0ee73fd3de
190
README.md
Normal file
190
README.md
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
# Spring AI 聊天演示项目
|
||||||
|
|
||||||
|
这是一个基于 Spring AI 的简单聊天应用演示项目,展示了如何与大语言模型进行对话。
|
||||||
|
|
||||||
|
## 功能特性
|
||||||
|
|
||||||
|
- 🤖 支持多种大语言模型(OpenAI、Ollama)
|
||||||
|
- 💬 简洁美观的聊天界面
|
||||||
|
- 🔧 简单的配置和部署
|
||||||
|
- 📱 响应式设计,支持移动端
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
- **Spring Boot 3.2.0** - 应用框架
|
||||||
|
- **Spring AI 0.8.1** - AI 集成框架
|
||||||
|
- **Thymeleaf** - 模板引擎
|
||||||
|
- **HTML/CSS/JavaScript** - 前端界面
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 1. 环境要求
|
||||||
|
|
||||||
|
- Java 17 或更高版本
|
||||||
|
- Maven 3.6 或更高版本
|
||||||
|
|
||||||
|
### 2. 配置大语言模型
|
||||||
|
|
||||||
|
#### 使用 OpenAI(推荐)
|
||||||
|
|
||||||
|
在 `src/main/resources/application.yml` 中配置:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
ai:
|
||||||
|
openai:
|
||||||
|
api-key: your-openai-api-key-here
|
||||||
|
base-url: https://api.openai.com # 可选,默认值
|
||||||
|
```
|
||||||
|
|
||||||
|
或者通过环境变量设置:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export OPENAI_API_KEY=your-openai-api-key-here
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 使用 Ollama(本地部署)
|
||||||
|
|
||||||
|
1. 首先安装并启动 Ollama:
|
||||||
|
```bash
|
||||||
|
# 安装 Ollama
|
||||||
|
curl -fsSL https://ollama.ai/install.sh | sh
|
||||||
|
|
||||||
|
# 拉取模型(例如 llama2)
|
||||||
|
ollama pull llama2
|
||||||
|
|
||||||
|
# 启动 Ollama 服务
|
||||||
|
ollama serve
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 在配置文件中启用 Ollama:
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
ai:
|
||||||
|
ollama:
|
||||||
|
base-url: http://localhost:11434
|
||||||
|
chat:
|
||||||
|
options:
|
||||||
|
model: llama2
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 运行应用
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 编译项目
|
||||||
|
mvn clean compile
|
||||||
|
|
||||||
|
# 运行应用
|
||||||
|
mvn spring-boot:run
|
||||||
|
```
|
||||||
|
|
||||||
|
应用启动后,访问 http://localhost:8080 即可使用聊天界面。
|
||||||
|
|
||||||
|
### 4. API 接口
|
||||||
|
|
||||||
|
除了网页界面,还提供了简单的 API 接口:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 简单对话接口
|
||||||
|
curl "http://localhost:8080/simple?q=你好"
|
||||||
|
|
||||||
|
# POST 接口
|
||||||
|
curl -X POST http://localhost:8080/chat \
|
||||||
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
-d "message=介绍一下 Spring AI"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── main/
|
||||||
|
│ ├── java/com/example/springaidemo/
|
||||||
|
│ │ ├── SpringAiDemoApplication.java # 应用主类
|
||||||
|
│ │ ├── config/
|
||||||
|
│ │ │ └── AiConfig.java # AI 配置类
|
||||||
|
│ │ └── controller/
|
||||||
|
│ │ └── ChatController.java # 聊天控制器
|
||||||
|
│ └── resources/
|
||||||
|
│ ├── application.yml # 应用配置
|
||||||
|
│ └── templates/
|
||||||
|
│ └── chat.html # 聊天界面模板
|
||||||
|
```
|
||||||
|
|
||||||
|
## 配置说明
|
||||||
|
|
||||||
|
### OpenAI 配置
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
ai:
|
||||||
|
openai:
|
||||||
|
api-key: ${OPENAI_API_KEY:your-openai-api-key} # OpenAI API Key
|
||||||
|
base-url: ${OPENAI_BASE_URL:https://api.openai.com} # API 基础 URL
|
||||||
|
chat:
|
||||||
|
options:
|
||||||
|
model: gpt-3.5-turbo # 使用的模型
|
||||||
|
temperature: 0.7 # 温度参数(0-1)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ollama 配置
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
ai:
|
||||||
|
ollama:
|
||||||
|
base-url: ${OLLAMA_BASE_URL:http://localhost:11434} # Ollama 服务地址
|
||||||
|
chat:
|
||||||
|
options:
|
||||||
|
model: ${OLLAMA_MODEL:llama2} # 使用的模型
|
||||||
|
```
|
||||||
|
|
||||||
|
## 自定义和扩展
|
||||||
|
|
||||||
|
### 添加新的对话端点
|
||||||
|
|
||||||
|
在 `ChatController` 中添加新的方法:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@GetMapping("/custom")
|
||||||
|
@ResponseBody
|
||||||
|
public String customChat(@RequestParam("message") String message) {
|
||||||
|
// 自定义处理逻辑
|
||||||
|
String prompt = "请用专业的语言回答:" + message;
|
||||||
|
return chatClient.call(prompt);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 修改聊天界面
|
||||||
|
|
||||||
|
编辑 `src/main/resources/templates/chat.html` 文件来自定义界面样式和功能。
|
||||||
|
|
||||||
|
### 添加更多 AI 提供商
|
||||||
|
|
||||||
|
Spring AI 支持多种 AI 提供商,包括:
|
||||||
|
- OpenAI
|
||||||
|
- Azure OpenAI
|
||||||
|
- Hugging Face
|
||||||
|
- Ollama
|
||||||
|
- Anthropic Claude
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 1. OpenAI API Key 错误
|
||||||
|
- 确保 API Key 正确设置
|
||||||
|
- 检查 API Key 是否有足够的配额
|
||||||
|
- 验证网络连接是否正常
|
||||||
|
|
||||||
|
### 2. Ollama 连接失败
|
||||||
|
- 确保 Ollama 服务正在运行
|
||||||
|
- 检查端口 11434 是否可访问
|
||||||
|
- 确认模型已正确下载
|
||||||
|
|
||||||
|
### 3. 应用启动失败
|
||||||
|
- 检查 Java 版本是否为 17 或更高
|
||||||
|
- 确保网络连接正常
|
||||||
|
- 查看控制台错误日志
|
||||||
|
|
||||||
|
## 许可证
|
||||||
|
|
||||||
|
MIT License
|
140
pom.xml
Normal file
140
pom.xml
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
<?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 http://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.2.5</version>
|
||||||
|
<relativePath/>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<groupId>com.example</groupId>
|
||||||
|
<artifactId>spring-ai-demo</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<name>spring-ai-demo</name>
|
||||||
|
<description>Spring AI Demo Project</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>17</java.version>
|
||||||
|
<spring-ai.version>0.8.1</spring-ai.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- Spring Boot Starter Web -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Boot Starter Thymeleaf (用于前端模板) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.ai</groupId>
|
||||||
|
<artifactId>spring-ai-starter-model-openai</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.ai</groupId>
|
||||||
|
<artifactId>spring-ai-starter-model-deepseek</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.ai</groupId>
|
||||||
|
<artifactId>spring-ai-starter-vector-store-elasticsearch</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>co.elastic.clients</groupId>
|
||||||
|
<artifactId>elasticsearch-java</artifactId>
|
||||||
|
<version>8.13.3</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Spring Boot DevTools (开发工具) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-devtools</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Boot Test -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-all</artifactId>
|
||||||
|
<version>5.8.25</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.ai</groupId>
|
||||||
|
<artifactId>spring-ai-bom</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.8.1</version>
|
||||||
|
<configuration>
|
||||||
|
<source>17</source>
|
||||||
|
<target>17</target>
|
||||||
|
<encoding>UTF-8</encoding>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>spring-snapshots</id>
|
||||||
|
<name>Spring Snapshots</name>
|
||||||
|
<url>https://repo.spring.io/snapshot</url>
|
||||||
|
<releases>
|
||||||
|
<enabled>false</enabled>
|
||||||
|
</releases>
|
||||||
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<name>Central Portal Snapshots</name>
|
||||||
|
<id>central-portal-snapshots</id>
|
||||||
|
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
|
||||||
|
<releases>
|
||||||
|
<enabled>false</enabled>
|
||||||
|
</releases>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</snapshots>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
|
</project>
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.example.springaidemo;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class SpringAiDemoApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
String proxy = "127.0.0.1";
|
||||||
|
int port = 7897;
|
||||||
|
System.setProperty("proxyType", "4");
|
||||||
|
System.setProperty("proxyPort", String.valueOf(port));
|
||||||
|
System.setProperty("proxyHost", proxy);
|
||||||
|
System.setProperty("proxySet", "true");
|
||||||
|
SpringApplication.run(SpringAiDemoApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
11
src/main/java/com/example/springaidemo/bean/ChatRequest.java
Normal file
11
src/main/java/com/example/springaidemo/bean/ChatRequest.java
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package com.example.springaidemo.bean;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ChatRequest {
|
||||||
|
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
private String sessionId;
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package com.example.springaidemo.controller;
|
||||||
|
|
||||||
|
|
||||||
|
import com.example.springaidemo.bean.ChatRequest;
|
||||||
|
import com.example.springaidemo.service.DeepseekChatService;
|
||||||
|
import org.springframework.ai.chat.client.ChatClientRequest;
|
||||||
|
import org.springframework.ai.chat.model.ChatResponse;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/chatController")
|
||||||
|
public class ChatController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private DeepseekChatService deepseekChatService;
|
||||||
|
|
||||||
|
@GetMapping("/ai/chat")
|
||||||
|
public Map generate(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {
|
||||||
|
return deepseekChatService.chat(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/ai/generateStream")
|
||||||
|
public Flux<ChatResponse> generateStream(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {
|
||||||
|
return deepseekChatService.streamChat(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(value = "/ai/sseChat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||||
|
public SseEmitter streamChat(@RequestBody ChatRequest chatRequest) {
|
||||||
|
return deepseekChatService.sseChat(chatRequest.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(value = "/ai/thinkChat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||||
|
public SseEmitter thinkChat(@RequestBody ChatRequest chatRequest) {
|
||||||
|
return deepseekChatService.thinkChat(chatRequest.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,123 @@
|
|||||||
|
package com.example.springaidemo.service;
|
||||||
|
|
||||||
|
import io.micrometer.common.util.StringUtils;
|
||||||
|
import org.springframework.ai.chat.messages.UserMessage;
|
||||||
|
import org.springframework.ai.chat.model.ChatResponse;
|
||||||
|
import org.springframework.ai.chat.prompt.Prompt;
|
||||||
|
import org.springframework.ai.deepseek.DeepSeekAssistantMessage;
|
||||||
|
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||||
|
import org.springframework.ai.deepseek.DeepSeekChatOptions;
|
||||||
|
import org.springframework.ai.deepseek.api.DeepSeekApi;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class DeepseekChatService {
|
||||||
|
|
||||||
|
private final DeepSeekChatModel chatModel;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public DeepseekChatService(DeepSeekChatModel chatModel) {
|
||||||
|
this.chatModel = chatModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map chat(String message) {
|
||||||
|
return Map.of("generation", chatModel.call(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Flux<ChatResponse> streamChat(String message) {
|
||||||
|
var prompt = new Prompt(new UserMessage(message));
|
||||||
|
return chatModel.stream(prompt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SseEmitter sseChat(String message) {
|
||||||
|
SseEmitter emitter = new SseEmitter(30000L); // 30秒超时
|
||||||
|
|
||||||
|
// 异步处理
|
||||||
|
CompletableFuture.runAsync(() -> {
|
||||||
|
try {
|
||||||
|
// 使用 Spring AI 的流式调用
|
||||||
|
var prompt = new Prompt(new UserMessage(message));
|
||||||
|
Flux<ChatResponse> responseFlux = chatModel.stream(prompt);
|
||||||
|
|
||||||
|
// 处理流式响应
|
||||||
|
responseFlux.subscribe(
|
||||||
|
response -> {
|
||||||
|
try {
|
||||||
|
// 发送数据到客户端
|
||||||
|
String content = response.getResult().getOutput().getText();
|
||||||
|
emitter.send(SseEmitter.event()
|
||||||
|
.name("message")
|
||||||
|
.data(content));
|
||||||
|
} catch (IOException e) {
|
||||||
|
emitter.completeWithError(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error -> {
|
||||||
|
emitter.completeWithError(error);
|
||||||
|
},
|
||||||
|
() -> {
|
||||||
|
emitter.complete();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (Exception e) {
|
||||||
|
emitter.completeWithError(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return emitter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SseEmitter thinkChat(String message) {
|
||||||
|
SseEmitter emitter = new SseEmitter(30000L);
|
||||||
|
CompletableFuture.runAsync(() -> {
|
||||||
|
try {
|
||||||
|
DeepSeekChatOptions promptOptions = DeepSeekChatOptions.builder()
|
||||||
|
.model(DeepSeekApi.ChatModel.DEEPSEEK_REASONER.getValue())
|
||||||
|
.build();
|
||||||
|
Prompt prompt = new Prompt(message, promptOptions);
|
||||||
|
Flux<ChatResponse> responseFlux = chatModel.stream(prompt);
|
||||||
|
|
||||||
|
responseFlux.subscribe(
|
||||||
|
response -> {
|
||||||
|
try {
|
||||||
|
// 发送数据到客户端
|
||||||
|
DeepSeekAssistantMessage deepSeekAssistantMessage = (DeepSeekAssistantMessage) response.getResult().getOutput();
|
||||||
|
String reasoningContent = deepSeekAssistantMessage.getReasoningContent();
|
||||||
|
String text = deepSeekAssistantMessage.getText();
|
||||||
|
if (StringUtils.isNotEmpty(reasoningContent)) {
|
||||||
|
emitter.send(SseEmitter.event()
|
||||||
|
.name("message")
|
||||||
|
.data(reasoningContent));
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotEmpty(text)) {
|
||||||
|
emitter.send(SseEmitter.event()
|
||||||
|
.name("message")
|
||||||
|
.data(text));
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
emitter.completeWithError(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error -> {
|
||||||
|
emitter.completeWithError(error);
|
||||||
|
},
|
||||||
|
() -> {
|
||||||
|
emitter.complete();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
emitter.completeWithError(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return emitter;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package com.example.springaidemo.service;
|
||||||
|
|
||||||
|
import org.springframework.ai.vectorstore.VectorStore;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
public class VectorService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private VectorStore vectorStore;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
41
src/main/resources/application.yml
Normal file
41
src/main/resources/application.yml
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: spring-ai-demo
|
||||||
|
elasticsearch:
|
||||||
|
uris: http://154.12.80.119:9200
|
||||||
|
username: elastic
|
||||||
|
password: 123456
|
||||||
|
ai:
|
||||||
|
deepseek:
|
||||||
|
api-key: sk-3043bb4777404970a22c7544dd30aaa2
|
||||||
|
openai:
|
||||||
|
api-key: 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
|
||||||
|
# 如果您有代理服务,可以修改为代理地址
|
||||||
|
# base-url: https://your-proxy-service.com/v1
|
||||||
|
base-url: https://api.openai.com
|
||||||
|
chat:
|
||||||
|
options:
|
||||||
|
model: gpt-3.5-turbo
|
||||||
|
temperature: 0.7
|
||||||
|
# 增加超时配置
|
||||||
|
client:
|
||||||
|
connect-timeout: 30000 # 30秒连接超时
|
||||||
|
read-timeout: 60000 # 60秒读取超时
|
||||||
|
vectorstore:
|
||||||
|
elasticsearch:
|
||||||
|
initialize-schema: true
|
||||||
|
index-name: custom-index
|
||||||
|
dimensions: 1536
|
||||||
|
similarity: cosine
|
||||||
|
# ollama:
|
||||||
|
# base-url: ${OLLAMA_BASE_URL:http://localhost:11434}
|
||||||
|
# chat:
|
||||||
|
# options:
|
||||||
|
# model: ${OLLAMA_MODEL:llama2}
|
||||||
|
|
||||||
|
server:
|
||||||
|
port: 8080
|
||||||
|
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
org.springframework.ai: DEBUG
|
174
src/main/resources/templates/chat.html
Normal file
174
src/main/resources/templates/chat.html
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Spring AI 聊天演示</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Microsoft YaHei', sans-serif;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
.chat-container {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
text-align: center;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
.chat-box {
|
||||||
|
height: 400px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 15px;
|
||||||
|
overflow-y: auto;
|
||||||
|
background-color: #fafafa;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.message {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.user-message {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
text-align: right;
|
||||||
|
margin-left: 50px;
|
||||||
|
}
|
||||||
|
.ai-message {
|
||||||
|
background-color: #e9ecef;
|
||||||
|
color: #333;
|
||||||
|
margin-right: 50px;
|
||||||
|
}
|
||||||
|
.input-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
#messageInput {
|
||||||
|
flex: 1;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
#sendButton {
|
||||||
|
padding: 10px 20px;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
#sendButton:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
#sendButton:disabled {
|
||||||
|
background-color: #6c757d;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
.loading {
|
||||||
|
color: #666;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="chat-container">
|
||||||
|
<h1 class="header">🤖 Spring AI 聊天演示</h1>
|
||||||
|
<div id="chatBox" class="chat-box">
|
||||||
|
<div class="message ai-message">
|
||||||
|
<strong>AI助手:</strong> 你好!我是基于 Spring AI 的聊天助手,有什么可以帮助你的吗?
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" id="messageInput" placeholder="输入你的消息..."
|
||||||
|
onkeypress="if(event.key==='Enter') sendMessage()">
|
||||||
|
<button id="sendButton" onclick="sendMessage()">发送</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const chatBox = document.getElementById('chatBox');
|
||||||
|
const messageInput = document.getElementById('messageInput');
|
||||||
|
const sendButton = document.getElementById('sendButton');
|
||||||
|
|
||||||
|
function addMessage(content, isUser = false) {
|
||||||
|
const messageDiv = document.createElement('div');
|
||||||
|
messageDiv.className = `message ${isUser ? 'user-message' : 'ai-message'}`;
|
||||||
|
messageDiv.innerHTML = `<strong>${isUser ? '你' : 'AI助手'}:</strong> ${content}`;
|
||||||
|
chatBox.appendChild(messageDiv);
|
||||||
|
chatBox.scrollTop = chatBox.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addLoadingMessage() {
|
||||||
|
const messageDiv = document.createElement('div');
|
||||||
|
messageDiv.className = 'message ai-message loading';
|
||||||
|
messageDiv.id = 'loadingMessage';
|
||||||
|
messageDiv.innerHTML = '<strong>AI助手:</strong> 正在思考中...';
|
||||||
|
chatBox.appendChild(messageDiv);
|
||||||
|
chatBox.scrollTop = chatBox.scrollHeight;
|
||||||
|
return messageDiv;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeLoadingMessage() {
|
||||||
|
const loadingMessage = document.getElementById('loadingMessage');
|
||||||
|
if (loadingMessage) {
|
||||||
|
loadingMessage.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendMessage() {
|
||||||
|
const message = messageInput.value.trim();
|
||||||
|
if (!message) return;
|
||||||
|
|
||||||
|
// 添加用户消息
|
||||||
|
addMessage(message, true);
|
||||||
|
messageInput.value = '';
|
||||||
|
|
||||||
|
// 禁用发送按钮
|
||||||
|
sendButton.disabled = true;
|
||||||
|
|
||||||
|
// 显示加载消息
|
||||||
|
const loadingMessage = addLoadingMessage();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/chat', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
body: `message=${encodeURIComponent(message)}`
|
||||||
|
});
|
||||||
|
|
||||||
|
const aiResponse = await response.text();
|
||||||
|
|
||||||
|
// 移除加载消息
|
||||||
|
removeLoadingMessage();
|
||||||
|
|
||||||
|
// 添加AI响应
|
||||||
|
addMessage(aiResponse);
|
||||||
|
} catch (error) {
|
||||||
|
removeLoadingMessage();
|
||||||
|
addMessage('抱歉,发送消息时出现错误: ' + error.message);
|
||||||
|
} finally {
|
||||||
|
sendButton.disabled = false;
|
||||||
|
messageInput.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面加载完成后聚焦输入框
|
||||||
|
window.onload = function() {
|
||||||
|
messageInput.focus();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user