SpringBoot接入DeepSeek(硅基流动版)+ 前端页面调试
文章目录
- 前言
- 正文
- 一、项目环境
- 二、项目代码
- 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
正文
一、项目环境
- Java版本:Java1.8
- SpringBoot版本:2.7.7
- deepseek-spring-boot-starter:1.1.0
项目结构如下:
二、项目代码
2.1 pom.xml
<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>
<groupId>org.pine.ai</groupId>
<artifactId>pine-ai</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>pine-ai-demo</name>
<url>http://maven.apache.org</url>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.7.7</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.github.pig-mesh.ai</groupId>
<artifactId>deepseek-spring-boot-starter</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.7</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.7.7</version>
<configuration>
<mainClass>org.pine.ai.BootDemoApplication</mainClass>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
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<ChatCompletionResponse> 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
<?xml version="1.0" encoding="UTF-8" ?>
<configuration debug="false">
<!-- 配置控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!-- 格式化输出: %d表示日期, %thread表示线程名, %-5level: 级别从左显示5个字符宽度 %msg:日志消息, %n是换行符 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS}[%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<!-- 日志输出级别 -->
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
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
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>调用演示</title>
<style>
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;
}
</style>
</head>
<body>
<div class="form-group">
<label for="promptInput">URL + Prompt 提示词:</label>
<input type="text"
id="promptInput"
value="http://localhost:8080/deepseek/chat?prompt="
class="styled-input">
</div>
<div class="form-group">
<label for="paramInput">参数串:</label>
<input type="text"
id="paramInput"
value="model=deepseek-ai/DeepSeek-R1&temperature=0.7&frequencyPenalty=0.5&user=user&topP=0.7&maxCompletionTokens=1024"
class="styled-input">
</div>
<button onclick="start()">开始请求</button>
<button onclick="disconnectAiServer()" class="danger-button">断开连接</button>
<button onclick="tips()">参数提示</button>
<div id="output"></div>
<script>
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].delta.content === "" && data.choices[0].finish_reason === "stop") {
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`
}
</script>
</body>
</html>
三、页面调试
启动项目后,访问:http://localhost:8080/
3.1 参数提示
点击参数提示
3.2 开始请求
在【URL + Prompt参数提示】的输入框中,追加你要问的问题。然后点击【开始请求】或直接【回车】。
3.3 手动断开
点击【断开连接】。将会停止回答。