SpringBoot接入DeepSeek(硅基流动版)+ 前端页面调试(SSE连接模式)
文章目录
- 前言
- 正文
- 一、项目环境
- 二、项目代码
- 2.1 pom.xml
- 2.2 DeepSeekController.java
- 2.3 启动类
- 2.4 logback-spring.xml
- 2.5 application.yaml
- 2.6 index.html
- 三、页面调试
- 3.1 参数提示
- 3.2 开始请求
- 3.3 手动断开
前言
作为一个Java程序员,了解前沿科技技术,也算是份内的事了。
DeepSeek 大模型,从开源到现在,一直在🔥。各个公司也基本都部署了自己的所谓满血版DeepSeek。
虽然官方是免费使用的,但是它太忙了。因此,很多能直接使用的,不太忙的DeepSeek应运而生。
我今天使用的就是“硅基流动版”DeepSeek!
本文的主旨是,使用SpringBoot接入DeepSeek,并提供一个调试页面,用来请求,和展示结果。
硅基流动DeepSeek页面:
https://m.siliconflow.cn/playground/chat
硅基流动推理模型接口文档:
https://docs.siliconflow.cn/cn/userguide/capabilities/reasoning
所谓SSE 是使用js中的EventSource对象。
它 是 JavaScript 中用于实现 服务器发送事件(Server-Sent Events, SSE) 的接口。它允许服务器向浏览器推送实时更新,而不需要客户端频繁地轮询服务器。SSE 提供了一种单向的通信机制,即服务器可以主动向客户端推送数据,但客户端不能通过 EventSource 向服务器发送数据。
另见 WebSocket连接方式的实现:SpringBoot接入DeepSeek(硅基流动版)+ 前端页面调试(WebSocket连接模式)
正文
一、项目环境
- Java版本:Java1.8
- SpringBoot版本:2.7.7
- deepseek-spring-boot-starter:1.1.0
项目结构如下:
二、项目代码
2.1 pom.xml
4.0.0 org.pine.ai pine-ai 1.0-SNAPSHOT jar pine-ai-demo http://maven.apache.org 1.8 UTF-8 UTF-8 2.7.7 org.projectlombok lombok 1.18.34 provided true io.github.pig-mesh.ai deepseek-spring-boot-starter 1.1.0 org.springframework.boot spring-boot-starter-web 2.7.7 ch.qos.logback logback-classic 1.2.11 org.apache.maven.plugins maven-compiler-plugin 3.8.1 1.8 1.8 UTF-8 org.springframework.boot spring-boot-maven-plugin 2.7.7 org.pine.ai.BootDemoApplication true repackage repackage
2.2 DeepSeekController.java
package org.pine.ai.controller; import io.github.pigmesh.ai.deepseek.core.DeepSeekClient; import io.github.pigmesh.ai.deepseek.core.chat.ChatCompletionRequest; import io.github.pigmesh.ai.deepseek.core.chat.ChatCompletionResponse; import io.github.pigmesh.ai.deepseek.core.chat.ResponseFormatType; import lombok.extern.slf4j.Slf4j; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import reactor.core.publisher.Flux; import javax.annotation.Resource; import java.util.List; @Controller @Slf4j @RequestMapping("/deepseek") public class DeepSeekController { @Resource private DeepSeekClient deepSeekClient; @GetMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux chat(@RequestParam("prompt") String prompt, @RequestParam(value = "model", defaultValue = "deepseek-ai/DeepSeek-R1") String model, @RequestParam(value = "temperature", defaultValue = "0.7") Double temperature, @RequestParam(value = "frequencyPenalty", defaultValue = "0.5") Double frequencyPenalty, @RequestParam(value = "user", defaultValue = "user") String user, @RequestParam(value = "topP", defaultValue = "0.7") Double topP, @RequestParam(value = "maxCompletionTokens", defaultValue = "1024") Integer maxCompletionTokens) { log.info("prompt: {}", prompt); log.info("model: {}, temperature: {}, frequencyPenalty: {}, user: {}, topP: {}, maxCompletionTokens: {}", model, temperature, frequencyPenalty, user, topP, maxCompletionTokens); if (!StringUtils.hasText(prompt)) { throw new IllegalArgumentException("prompt is empty"); } ChatCompletionRequest request = ChatCompletionRequest.builder() // 添加用户输入的提示词(prompt),即模型生成文本的起点。告诉模型基于什么内容生成文本。 .addUserMessage(prompt) // 指定使用的模型名称。不同模型可能有不同的能力和训练数据,选择合适的模型会影响生成结果。 .model(model) // 是否以流式(streaming)方式返回结果。 .stream(true) // 控制生成文本的随机性。0.0:生成结果非常确定,倾向于选择概率最高的词。1.0:生成结果更具随机性和创造性。 .temperature(temperature) // 控制生成文本中重复内容的惩罚程度。0.0:不惩罚重复内容。1.0 或更高:减少重复内容,增加多样性。 .frequencyPenalty(frequencyPenalty) // 标识请求的用户。用于跟踪和日志记录,通常用于区分不同用户的请求。 .user(user) // 控制生成文本时选择词的范围。0.7:从概率最高的 70% 的词中选择。1.0:不限制选择范围。 .topP(topP) // 控制模型生成的文本的最大长度。这对于防止生成过长的文本或确保响应在预期的范围内非常有用。 .maxCompletionTokens(maxCompletionTokens) // 响应结果的格式。 .responseFormat(ResponseFormatType.TEXT) .build(); return deepSeekClient.chatFluxCompletion(request); } }
2.3 启动类
package org.pine.ai; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.CrossOrigin; @SpringBootApplication @CrossOrigin( origins = "*", allowedHeaders = "*", exposedHeaders = {"Cache-Control", "Connection"} // 暴露必要头 ) public class BootDemoApplication { public static void main(String[] args) { SpringApplication.run(BootDemoApplication.class, args); } }
2.4 logback-spring.xml
%d{yyyy-MM-dd HH:mm:ss.SSS}[%thread] %-5level %logger{50} - %msg%n
2.5 application.yaml
deepseek: # 硅基流动的url base-url: https://api.siliconflow.cn/v1 # 秘钥(自己注册硅基的账号,并申请即可) api-key: sk-ezcxadqecocxixxxxxxx spring: main: allow-bean-definition-overriding: true server: tomcat: keep-alive-timeout: 30000 # 30秒空闲超时 max-connections: 100 # 最大连接数 uri-encoding: UTF-8 servlet: encoding: charset: UTF-8 force: true enabled: true compression: enabled: false # 禁用压缩(否则流式数据可能被缓冲)
2.6 index.html
调用演示 body { padding: 20px; font-family: Arial, sans-serif; } #output { height: 400px; width: 900px; border: 2px solid #c7c1c1; padding: 10px; overflow-y: auto; margin: 20px 0; white-space: pre-wrap; box-shadow: 0 4px 6px rgba(13, 118, 231, 0.25); } button { padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; } .danger-button { background-color: #ec1b1b; } .form-group { margin: 20px 0; max-width: 900px; } label[for="promptInput"] { display: block; margin-bottom: 12px; font-size: 1.1em; color: #2c3e50; font-weight: 600; letter-spacing: 0.5px; } label[for="paramInput"] { display: block; margin-bottom: 12px; font-size: 1.1em; color: #2c3e50; font-weight: 600; letter-spacing: 0.5px; } #promptInput, #paramInput { /* 尺寸调整 */ width: 100%; padding: 14px 20px; /* 样式美化 */ border: 2px solid #007bff; border-radius: 8px; font-size: 1.1rem; background-color: #f8f9fa; transition: all 0.3s ease-in-out; box-shadow: 0 2px 4px rgba(0,0,0,0.05); color: #44878c; font-style: italic; } #promptInput, #paramInput:focus { outline: none; border-color: #0056b3; box-shadow: 0 4px 6px rgba(0,123,255,0.25); background-color: white; } /* 暗色模式适配 */ @media (prefers-color-scheme: dark) { #promptInput, #paramInput { background-color: #2d3436; border-color: #4a90e2; color: #ecf0f1; } #promptInput, #paramInput:focus { background-color: #34495e; } } /* 输入提示动画 */ @keyframes pulse-shadow { 0% { box-shadow: 0 0 0 0 rgba(0,123,255,0.4) } 100% { box-shadow: 0 0 0 10px rgba(0,123,255,0) } } .input-highlight { animation: pulse-shadow 1.5s infinite; }
URL + Prompt 提示词:参数串:开始请求 断开连接 参数提示 let eventSource; function connectAiServer(url) { eventSource = new EventSource(url); } function disconnectAiServer() { if (eventSource) { eventSource.close() } } function start() { const output = document.getElementById('output') const promptInput = document.getElementById('promptInput') const paramInput = document.getElementById('paramInput') output.textContent = '' // 清空内容 try { // 创建 EventSource 连接 connectAiServer(promptInput.value + "&" + paramInput.value) // 监听默认事件(无事件名的消息) eventSource.onmessage = (event) => { const value = event.data; const data = JSON.parse(value); if (data.choices[0].delta.reasoning_content === '') { output.textContent += '开始思考:\n\t' } if (data.choices[0].delta.content === '\n\n') { output.textContent += '思考结束!!\n\n\n\n' output.textContent += '以下是正式回答:\n\n' } if(data.choices[0].finish_reason === "stop" || data.choices[0].delta.content === "[DONE]") { output.textContent += '\r\n\r\n回答结束!!' disconnectAiServer() } // 拼接思考内容 if (data.choices[0].delta.reasoning_content) { output.textContent += data.choices[0].delta.reasoning_content } // 拼接回答内容 if (data.choices[0].delta.content) { output.textContent += data.choices[0].delta.content; } // 自动滚动到底部 output.scrollTop = output.scrollHeight }; } catch (error) { console.error('请求失败:', error) output.textContent = '请求失败: ' + error.message disconnectAiServer() } } /* 回车触发请求 */ document.querySelector('#promptInput').addEventListener('keypress', (e) => { if(e.key === 'Enter') { start() } }) function tips() { const output = document.getElementById('output') output.textContent = ` 参数说明: prompt:添加用户输入的提示词(prompt),即模型生成文本的起点。告诉模型基于什么内容生成文本。默认为:空\n model:不同模型可能有不同的能力和训练数据,选择合适的模型会影响生成结果。默认为:deepseek-ai/DeepSeek-R1\n temperature:控制生成文本的随机性。0.0:生成结果非常确定,倾向于选择概率最高的词。1.0:生成结果更具随机性和创造性。默认为:0.7\n frequencyPenalty:控制生成文本中重复内容的惩罚程度。0.0:不惩罚重复内容。1.0 或更高:减少重复内容,增加多样性。默认为:0.5\n user:标识请求的用户。用于跟踪和日志记录,通常用于区分不同用户的请求。默认为:user\n topP:控制生成文本时选择词的范围。0.7:从概率最高的 70% 的词中选择。1.0:不限制选择范围。默认为:0.7\n maxCompletionTokens:控制模型生成的文本的最大长度。这对于防止生成过长的文本或确保响应在预期的范围内非常有用。默认为:1024\n` }三、页面调试
启动项目后,访问:http://localhost:8080/
3.1 参数提示
点击参数提示
3.2 开始请求
在【URL + Prompt参数提示】的输入框中,追加你要问的问题。然后点击【开始请求】或直接【回车】。
3.3 手动断开
点击【断开连接】。将会停止回答。