1. 环境说明
JDK17以上,SpringBoot3 以上版本
2. 引入依赖
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
注意:
如果Maven用的是国内的镜像源,如阿里云仓库,在导入依赖的时候会提示阿里云仓库没有找到 spring-ai-openai-spring-boot-starter
解决方法:在pom文件中引入如下依赖
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
同时在Maven的settings.xml进行屏蔽配置:
<mirrors>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*,!spring-milestones</mirrorOf>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
</mirrors>
至此,就能成功引入SpringAI依赖了
3. API配置
在配置之前,需要先在DeepSeek官网 https://platform.deepseek.com/api_keys 创建 API 密钥。
获取到秘钥后,在你SpringBoot项目的配置文件中进行如下配置:
spring:
ai:
openai:
api-key: 你的秘钥
base-url: https://api.deepseek.com
chat:
options:
model: deepseek-chat
解释:
- api-key: 即为在官网获取的秘钥
- model:模型名称,这里使用的是DeepSeek V 系列
- DeepSeek V 系列,V系列主要用于对话,模型名称为 deepseek-chat
- DeepSeek R 系列,R系列主要用于推理, 模型名称为 deepseek-reasoner
4. 使用示例
编写一个简单的Controller,如下:
package com.example.aitest.demos.web;
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.openai.OpenAiChatModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import java.util.Map;
@RestController
public class ChatController {
private final OpenAiChatModel chatModel;
@Autowired
public ChatController(OpenAiChatModel openAiChatModel) {
this.chatModel = openAiChatModel;
}
@GetMapping("/ai/generate")
public Map generate(@RequestParam(value = "message", defaultValue = "讲个笑话") String message) {
return Map.of("generation", this.chatModel.call(message));
}
@GetMapping("/ai/generateStream")
public Flux<ChatResponse> generateStream(@RequestParam(value = "message", defaultValue = "讲个笑话") String message) {
Prompt prompt = new Prompt(new UserMessage(message));
return this.chatModel.stream(prompt);
}
}
代码解释:
generate 方法生成的回复会被封装成 Map 对象返回给客户端
generateStream 方法使用的是一种流氏响应,服务器不会一次性把所有内容都发回给客户端,而是逐步推送数据
最后可以用Postman或Apifox等工具进行测试,这里我写了一个简单的页面用于测试
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SongChat</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #121212; /* 深色背景 */
color: #ffffff; /* 浅色文字 */
}
.chat-container {
max-width: 600px;
width: 100%; /* 使聊天容器宽度占满父容器 */
text-align: center;
background-color: #1e1e1e; /* 深色背景 */
border-radius: 8px; /* 圆角 */
padding: 20px; /* 内边距 */
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* 阴影 */
}
.header {
margin-bottom: 10px; /* 减少底部间距 */
}
.output {
margin-top: 10px; /* 减少顶部间距 */
border: 1px solid #444; /* 浅色边框 */
padding: 10px;
min-height: 100px;
background-color: #282828; /* 更深的背景 */
text-align: left;
color: #ffffff; /* 浅色文字 */
}
.footer {
margin-top: 10px; /* 减少顶部间距 */
}
.button-container {
margin-top: 10px; /* 增加按钮容器的顶部间距 */
}
button {
padding: 10px 20px;
cursor: pointer;
background-color: #4CAF50; /* 绿色背景 */
color: white;
border: none;
border-radius: 5px;
margin: 0 5px; /* 增加按钮之间的间距 */
}
button:hover {
background-color: #45a049; /* 深绿色背景 */
}
.loading {
font-size: 18px;
color: #777; /* 浅灰色文字 */
}
/* 增加输入框的样式 */
#message {
width: 100%; /* 使输入框宽度占满父容器 */
padding: 10px;
font-size: 16px;
border: 1px solid #444; /* 浅色边框 */
border-radius: 5px;
resize: vertical; /* 允许垂直调整大小 */
min-height: 50px; /* 设置最小高度 */
background-color: #1e1e1e; /* 深色背景 */
color: #ffffff; /* 浅色文字 */
}
label {
color: #ffffff; /* 浅色文字 */
}
</style>
</head>
<body>
<div class="chat-container">
<div class="header">
<h2>SongChat</h2>
</div>
<div class="output" id="outputContainer">
<p class="loading" id="loadingMessage">等待生成...</p>
</div>
<div class="footer">
<label for="message">请输入消息:</label>
<textarea id="message" name="message" placeholder="例如:讲个笑话"></textarea>
<div class="button-container">
<!-- <button id="generateButton">生成响应</button> -->
<button id="generateStreamButton">生成流式响应</button>
</div>
</div>
</div>
<script>
document.getElementById('generateStreamButton').addEventListener('click', function() {
const message = document.getElementById('message').value || '讲个笑话';
generateStreamResponse(message);
});
// 添加键盘事件监听器,按回车键自动生成流式响应
document.getElementById('message').addEventListener('keydown', function(event) {
if (event.key === 'Enter') {
event.preventDefault(); // 阻止默认的换行行为
const message = document.getElementById('message').value || '讲个笑话';
generateStreamResponse(message);
}
});
function generateResponse(message) {
document.getElementById('outputContainer').innerHTML = '<p class="loading">生成中...</p>';
fetch(`/ai/generate?message=${encodeURIComponent(message)}`)
.then(response => response.json())
.then(data => {
document.getElementById('outputContainer').innerHTML = `<p>${data.generation}</p>`;
})
.catch(error => {
console.error('生成失败:', error);
document.getElementById('outputContainer').innerHTML = '<p>生成失败,请重试。</p>';
});
}
let eventSource = null; // 用于保存当前的 EventSource 实例
let isRequesting = false; // 添加标志位防止重复请求
let isFirstMessage = true; // 添加标志位判断是否是第一次消息
function generateStreamResponse(message) {
// 如果已经有连接或正在请求,则不创建新连接
if (eventSource !== null || isRequesting) {
return;
}
isRequesting = true; // 设置请求标志位
// 显示正在等待的提示
document.getElementById('outputContainer').innerHTML = '<p class="loading">等待流式响应...</p>';
// 创建新的 EventSource
eventSource = new EventSource('/ai/generateStream?message=' + encodeURIComponent(message));
eventSource.onmessage = function(event) {
try {
// 解析服务器返回的数据
const data = JSON.parse(event.data);
// 确保数据存在并且有结果
if (data && data.results && data.results.length > 0) {
data.results.forEach(result => {
const content = result.output.content || result.output.text || '';
// 如果有有效内容,追加到输出容器
if (content.trim()) {
const outputContainer = document.getElementById('outputContainer');
if (isFirstMessage) {
// 移除“等待流式响应...”的提示
outputContainer.innerHTML = '';
isFirstMessage = false;
}
outputContainer.innerHTML += content;
}
});
} else {
document.getElementById('outputContainer').innerHTML = '<p>没有返回有效内容,请稍后再试。</p>';
}
} catch (error) {
console.error('流式响应解析错误:', error);
document.getElementById('outputContainer').innerHTML = '<p>流式响应失败。</p>';
} finally {
isRequesting = false; // 请求完成后重置标志位
}
};
eventSource.onerror = function() {
console.error('流式响应连接出错');
closeEventSource(); // 错误发生时关闭连接
};
eventSource.onclose = function() {
console.log('流式响应连接已关闭');
closeEventSource(); // 连接关闭时关闭连接
};
}
function closeEventSource() {
if (eventSource) {
eventSource.close();
eventSource = null;
isRequesting = false;
isFirstMessage = true; // 重置 isFirstMessage 标志位
}
}
</script>
</body>
</html>
如图:
5. 总结
至此,我们就实现了SpringBoot集成DeepSeek实现AI对话的功能,总体还是很简单的,现阶段 DeepSeek 服务受资源限制可能无法提供在线服务,可以本地部署一个DeepSeek 大模型进行学习和使用。