接入DeepSeek大模型?全流程一次给你讲的明明白白
第三方接口
去哪找大模型接口,这个这里不谈。
但你可以去硅基流动,有免费额度。
你注册了,我也有点免费额度。
你好,我好,大家好。
注册地址:
免费额度够用一段时间。
下面进入正题。
整体流程
从前端到后端流程如下:
- 前端使用Vue 3+Element Plus+Element Plus X;
- 中间使用Nginx进行反向代理(这个地方略有小坑);
- 后端使用Java调用大模型接口.
下面倒着来,从后端到前端.
Java后端调用DeepSeek接口
前置条件,先去申请一个大模型的KEY(此处以硅基流动为例)。
AIChatMessage(接收消息,就一个属性):
代码语言:javascript代码运行次数:0运行复制import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* AI消息
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AIChatMessage {
private String content;
}
AIChatController(处理并返回请求):
此处请求返回时,会区分thinking和result。
thinking是思考过程,result是结果。
后面前端例子的时候会讲到。
代码语言:javascript代码运行次数:0运行复制import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.URI;
import java.http.HttpClient;
import java.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
/**
* AI接口.
*
*/
@RestController
@RequestMapping("/ai-chat")
@Slf4j
public class AIChatController {
// 接口调用地址: / (硅基流动)
@Value("${ai.base-url}")
private String baseUrl;
// 接口调用key(用你自己申请的)
@Value("${ai.key}")
private String key;
/**
* AI分析
*
* @return 结果
*/
@PostMapping(value = "/chat-flux", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter aiAnalysisStream(@RequestBody AIChatMessage aiChatMessage) {
if (aiChatMessage == null || !StringUtils.hasText(aiChatMessage.getContent())) {
return null;
}
SseEmitter emitter = new SseEmitter(600_000L);
HttpClient client = HttpClient.newHttpClient();
Map<String, Object> data = new HashMap<>();
data.put("model", "deepseek-ai/DeepSeek-R1");
data.put("max_tokens", 15000);
data.put("temperature", 0.7);
data.put("stream", true);
List<Map<String, String>> messages = new ArrayList<>();
Map<String, String> userMessage = new HashMap<>();
userMessage.put("role", "user");
userMessage.put("content", aiChatMessage.getContent());
messages.add(userMessage);
data.put("messages", messages);
java.http.HttpRequest request =
java.http.HttpRequest.newBuilder()
.uri(URI.create(baseUrl + "v1/chat/completions"))
.header("Authorization", "Bearer " + key)
.header("Content-Type", "application/json")
.timeout(Duration.ofMinutes(10))
.POST(java.http.HttpRequest.BodyPublishers.ofString(JSONUtil.toJsonStr(data)))
.build();
CompletableFuture.runAsync(() -> {
try {
client.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream())
.thenAccept(response -> {
try (InputStream bodyStream = response.body()) {
BufferedReader reader = new BufferedReader(
new InputStreamReader(bodyStream, StandardCharsets.UTF_8));
String line;
while ((line = reader.readLine()) != null) {
if (line.startsWith("data: ")) {
String jsonStr = line.substring(5).trim().replaceAll("\n", "");
if ("[DONE]".equalsIgnoreCase(jsonStr)) {
emitterplete();
return;
}
JSONObject json = JSONUtil.parseObj(jsonStr);
String reasoning = json.getByPath("choices[0].delta.reasoning_content", String.class);
if (StringUtils.hasText(reasoning)) {
emitter.send(SseEmitter.event()
.name("thinking")
.data(reasoning)
.id(UUID.randomUUID().toString()));
}
String content = json.getByPath("choices[0].delta.content", String.class);
if (StringUtils.hasText(content)) {
emitter.send(SseEmitter.event()
.name("result")
.data(content)
.id(UUID.randomUUID().toString()));
}
}
}
} catch (Exception e) {
emitterpleteWithError(e);
}
});
} catch (Exception ex) {
emitterpleteWithError(ex);
}
});
return emitter;
}
}
Nginx反向代理配置
按照下面这个配置,后台接口可以正常返回推理过程。
代码语言:javascript代码运行次数:0运行复制location /ai-service/ai-chat/ {
add_header X-XSS-Protection "1;mode=block";
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
proxy_buffering off;
proxy_cache off;
proxy_set_header Connection '';
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Scheme $scheme;
proxy_ssl_session_reuse on;
proxy_redirect off;
proxy_http_version 1.1;
proxy_ignore_client_abort on;
proxy_connect_timeout 86400s;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
proxy_pass http://127.0.0.1:8089/ai-chat/;
}
前端配置
先安装好Element Plus,然后再继续下面的步骤。
安装:
代码语言:javascript代码运行次数:0运行复制npm install vue-element-plus-x
npm install @microsoft/fetch-event-source
在main.js或者main.ts中配置:
代码语言:javascript代码运行次数:0运行复制// 导入
import ElementPlusX from 'vue-element-plus-x'
// 引用
app.use(ElementPlusX)
在页面中使用,假设页面名称是AIChat.vue:
代码语言:javascript代码运行次数:0运行复制<template>
<div class="content">
<div>
<Thinking v-model="state.thinkOpenStatus" :content="state.aiAnalysisThinkContent"
:status="state.thinkStatus"
auto-collapse
button-width="250px"
max-width="100%"
/>
</div>
<div>
<Typewriter :content="state.aiAnalysisContent" :is-markdown="true" typing/>
</div>
</div>
</template>
<script lang="ts" setup>
import {fetchEventSource} from "@microsoft/fetch-event-source";
const state = reactive({
aiAnalysisContent: '',
thinkOpenStatus: true,
thinkStatus: 'start',
aiAnalysisThinkContent: '',
})
const aiAnalysis = async()=>{
await fetchEventSource(
// 换成你自己的接口地址.
`http://localhost:8081/ai-service/ai-chat/chat-flux`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({content: "你是谁?"}),
openWhenHidden: true,
onmessage: function (response) {
try {
const data = response.data;
console.log(data)
if (response.event === "thinking") {
if (state.thinkStatus != 'thinking') {
state.thinkStatus = 'thinking';
}
state.aiAnalysisThinkContent += data;
}
if (response.event === "result") {
if (state.thinkStatus != 'end') {
state.thinkStatus = 'end';
}
state.aiAnalysisContent += data;
}
} catch (e) {
if (state.thinkStatus != 'error') {
state.thinkStatus = 'error';
}
let chunk = response.data;
if (chunk.includes('<tool_response>')) {
thinkFlag = false;
}
if (!(chunk.includes('<tool_response>') || chunk.includes('</think>'))) {
if (!thinkFlag) {
state.aiAnalysisContent += chunk;
} else {
state.aiAnalysisThinkContent += chunk;
}
}
}
},
onerror: (err) => {
console.error('SSE连接异常:', err);
}
}
)
}
</script>
搞定,效果看着还行。
写代码累了,玩会游戏