这是关于如何使用 Spring Boot 调用 DeepSeek API 的第一篇博客。在本文中,我们将介绍一个简单的示例代码,展示如何通过 HTTP 请求调用 DeepSeek API 并返回完整的响应内容。同时,我们会分析当前代码的不足,并提出改进方向——例如支持流式(streaming)处理,以及使用
SseEmitter
将结果逐步发送到前端。
本工程是一个基于 Spring Boot 的简单示例,主要功能是接收用户输入的消息并调用 DeepSeek API 返回响应。以下是核心文件及其作用:
DeepseekController.java
:控制器层,处理前端请求并调用服务层。DeepseekService.java
:服务层,负责调用 DeepSeek API 客户端。DeepseekApiClient.java
:工具类,封装了对 DeepSeek API 的 HTTP 请求逻辑。index.html
:前端页面,提供用户输入框和显示响应的区域。application.properties
:配置文件,设置超时时间等参数。/deepseek/chat
接口将消息传递给后端。略。可直接参考附件的源码
DeepseekService 负责调用 DeepseekApiClient 并处理异常。
@Service
public class DeepseekService {
private final DeepseekApiClient deepseekApiClient;
public DeepseekService(DeepseekApiClient deepseekApiClient) {
this.deepseekApiClient = deepseekApiClient;
}
public String getChatResponse(String message) {
try {
return deepseekApiClient.sendDeepseekChat(message);
} catch (Exception e) {
throw new RuntimeException("Failed to get chat response from DeepSeek API", e);
}
}
}
DeepseekApiClient
封装了对 DeepSeek API 的 HTTP 请求逻辑。
public class DeepseekApiClient {
private static final Logger logger = LoggerFactory.getLogger(DeepseekApiClient.class);
private static final String DEEPSEEK_API_URL_COMPLETIONS = "https://api.deepseek.com/chat/completions"; // API地址
private static final String DEEPSEEK_API_KEY = "替换成你的key"; // 官网申请的API Key
/**
* 发送消息到 DeepSeek API 并获取响应
*
* @param message 用户输入的消息
* @return API 的完整响应内容
* @throws IOException 如果请求失败
*/
public String sendDeepseekChat(String message) throws IOException {
OkHttpClient client = createUnsafeOkHttpClient(); // 创建忽略SSL证书验证的OkHttpClient实例
// 构造请求体
JSONObject requestBody = buildRequestBody(message);
// 将JSON对象转换为RequestBody,指定媒体类型为application/json
RequestBody body = RequestBody.create(requestBody.toJSONString(), MediaType.get("application/json; charset=utf-8"));
// 构造HTTP POST请求
Request request = new Request.Builder()
.url(DEEPSEEK_API_URL_COMPLETIONS) // 设置API URL
.post(body) // 设置POST请求体
.addHeader("Authorization", "Bearer " + DEEPSEEK_API_KEY) // 添加授权头
.addHeader("Content-Type", "application/json") // 设置内容类型为JSON
.addHeader("Accept", "application/json") // 设置接受的内容类型为JSON
.build();
System.out.println("jsonBody: " + requestBody.toJSONString());
// 执行请求并处理响应
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response); // 如果响应不成功,抛出异常
}
String responseBody = response.body().string(); // 获取响应体字符串
System.out.println("responseBody: " + responseBody);
return parseResponse(responseBody); // 解析响应并返回消息内容
}
}
/**
* 解析 API 响应
*
* @param responseBody API 的响应内容
* @return 解析后的消息内容
*/
private String parseResponse(String responseBody) {
JSONObject jsonResponse = JSON.parseObject(responseBody); // 将响应体字符串解析为JSON对象
JSONArray choices = jsonResponse.getJSONArray("choices"); // 获取choices数组
if (choices != null && !choices.isEmpty()) { // 检查choices数组是否非空
JSONObject message = choices.getJSONObject(0).getJSONObject("message"); // 获取第一个choice中的message对象
return message.getString("content"); // 返回message中的content字段
}
return "模型无响应或结果解析失败"; // 如果没有有效响应,返回null
}
/**
* 构建请求体
*
* @param userMessage 用户输入的消息
* @return 请求体JSON对象
*/
private JSONObject buildRequestBody(String userMessage) {
JSONObject requestBody = new JSONObject(); // 创建一个空的JSON对象作为请求体
// 构建messages数组,包含系统消息和用户消息
JSONArray messages = new JSONArray();
messages.add(new JSONObject().fluentPut("content", "欢迎加入CSDN博客").fluentPut("role", "system").fluentPut("name", "personal"));
// fluentPut是FastJSON提供的链式方法,用于快速设置键值对
// content: 系统消息的内容
// role: 消息的角色,这里是"system"表示系统消息
// name: 消息的名称,这里是"personal"
messages.add(new JSONObject().fluentPut("content", userMessage).fluentPut("role", "user").fluentPut("name", "路人甲"));
// content: 用户输入的消息内容
// role: 消息的角色,这里是"user"表示用户消息
// name: 消息的名称,这里是"路人甲"
// 使用fluentPut方法为请求体添加多个键值对
requestBody.fluentPut("messages", messages)
.fluentPut("model", "deepseek-chat"); // 指定使用的模型名称
// model: 指定要使用的模型,这里是"deepseek-chat"
requestBody.fluentPut("frequency_penalty", 0); // 控制生成文本的重复度惩罚因子,默认为0
// frequency_penalty: 用于减少重复词的出现频率,范围通常为0到2
requestBody.fluentPut("max_tokens", 2048); // 设置最大生成token数
// max_tokens: 指定生成的最大token数量,这里是2048
requestBody.fluentPut("presence_penalty", 0); // 控制生成文本的新颖性惩罚因子,默认为0
// presence_penalty: 用于鼓励生成新的主题或概念,范围通常为0到2
requestBody.fluentPut("response_format", new JSONObject().fluentPut("type", "text")); // 指定响应格式为文本
// response_format: 指定API返回的格式,这里是"text"表示纯文本
requestBody.fluentPut("stop", null); // 指定停止序列,null表示无特定停止条件
// stop: 可以是一个字符串或字符串数组,当生成的文本遇到这些序列时停止
requestBody.fluentPut("stream", false); // 是否开启流式响应,默认为false
// requestBody.fluentPut("stream", true); // 是否开启流式响应,默认为false
// stream: true表示开启流式响应,false表示一次性返回所有结果
requestBody.fluentPut("stream_options", null); // 流式响应的选项,null表示无特定选项
// stream_options: 配置流式响应的相关参数
requestBody.fluentPut("temperature", 1); // 控制生成文本的随机性,默认为1
// temperature: 影响生成文本的创造性,值越高越随机,值越低越保守
requestBody.fluentPut("top_p", 1); // 核采样概率阈值,默认为1
// top_p: 用于控制生成文本的概率分布,值越小越集中于高概率词汇
requestBody.fluentPut("tools", null); // 自定义工具列表,null表示无工具
// tools: 可以定义一些自定义工具供模型使用
requestBody.fluentPut("tool_choice", "none"); // 工具选择策略,默认为"none"
// tool_choice: 指定如何选择工具,"none"表示不使用工具
requestBody.fluentPut("logprobs", false); // 是否返回每个token的对数概率,默认为false
// logprobs: true表示返回每个token的对数概率,false表示不返回
requestBody.fluentPut("top_logprobs", null); // 返回每个token的最高对数概率数,null表示不返回
// top_logprobs: 指定返回每个token的最高对数概率的数量
return requestBody; // 返回构建好的请求体
}
/**
* 创建忽略SSL证书验证的OkHttpClient实例
*
* @return OkHttpClient实例
*/
private OkHttpClient createUnsafeOkHttpClient() {
try {
// 创建信任所有主机的TrustManager
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[]{};
}
}
};
SSLContext sslContext = SSLContext.getInstance("TLS"); // 初始化SSL上下文
sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); // 使用信任所有证书的TrustManager初始化SSL上下文
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); // 获取SSLSocketFactory
// 忽略主机名验证
HostnameVerifier hostnameVerifier = (hostname, session) -> true;
return new OkHttpClient.Builder()
.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]) // 设置SSLSocketFactory和TrustManager
.hostnameVerifier(hostnameVerifier) // 设置主机名验证器
.connectTimeout(60, TimeUnit.SECONDS) // 设置连接超时时间为1分钟
.readTimeout(60, TimeUnit.SECONDS) // 设置读取超时时间为1分钟
.build(); // 构建并返回OkHttpClient实例
} catch (Exception e) {
throw new RuntimeException(e); // 捕获异常并抛出运行时异常
}
}
}
前端页面通过 JavaScript 发起请求,并显示响应内容。代码略。
4.1.1. 非流式处理
当前实现是同步的,即后端需要等待 API 返回完整响应后才能将其发送给前端。这种方式在处理大段文本或耗时较长的请求时,用户体验较差。
4.1.2. 缺乏实时性
如果 API 支持流式输出(如逐字返回),我们可以利用这一特性提升交互体验。然而,当前代码并未启用流式处理。
4.1.3. 未充分利用 Spring 的异步能力
Spring 提供了多种异步处理机制(如 SseEmitter 和 WebFlux),可以更好地支持流式数据传输。
4.2.1 启用流式处理
DeepSeek API 通常支持流式输出(通过 stream=true
参数)。我们可以在请求中启用该选项,并逐步接收响应数据。
4.2.2. 使用 SseEmitter
Spring 提供了SseEmitter类,可以用于服务器向客户端推送事件。我们可以将 API 的响应逐步发送到前端,从而实现更流畅的用户体验。
以下是一个改进后的流式简单处理方法:
public void streamChatResponse(String message, SseEmitter emitter) {
executorService.submit(() -> {
try {
// 假设 API 支持流式输出
String fullResponse = deepseekApiClient.sendDeepseekChat(message);
// 模拟逐字发送
for (int i = 0; i < fullResponse.length(); i++) {
char c = fullResponse.charAt(i);
emitter.send(SseEmitter.event().data(String.valueOf(c)));
Thread.sleep(50); // 模拟延迟
}
emitter.send(SseEmitter.event().data("[DONE]"));
} catch (Exception e) {
emitter.completeWithError(e);
} finally {
emitter.complete();
}
});
}
4.2.3. 前端适配
前端也需要调整为支持 SSE(Server-Sent Events)。可以通过监听 EventSource
来接收服务器推送的数据
const eventSource = new EventSource('/deepseek/stream-chat?message=' + encodeURIComponent(message));
eventSource.onmessage = function(event) {
const responseDiv = document.getElementById('response');
responseDiv.textContent += event.data;
};
eventSource.onerror = function() {
eventSource.close();
};
本文介绍了如何使用 Spring Boot 调用 DeepSeek API,并展示了非流式的实现方式。尽管当前代码能够满足基本需求,但在实际应用中仍有较大的优化空间。下一节中,我们将详细介绍如何实现流式处理,并进一步提升用户体验。
如果你对本文有任何疑问或建议,欢迎在评论区留言!
完整的代码示例已上传,可在Intellij IDEA中导入并直接运行 免费下载