SpringBoot集成本地部署的deepseek
话不多说直接上最终实现效果
一、前期准备
1. 通过Ollama安装本地deepseek。(由于我电脑配置有点低,我选择安装deepseek-R1:1.5b版本)
2. 安装IntelliJ IDEA开发工具。(我安装是IntelliJ IDEA 2023版本)
二、搭建Springboot项目
创建StringBoot项目
点击“下一步”后,我这里用的SpringBoot的版本是3.4.3,引入Spring Web、Thymeleaf和OpenAi相关的jar包。
对应pom.xml内容
org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot-starter-web org.springframework.ai spring-ai-openai-spring-boot-starter org.springframework.boot spring-boot-starter-test test
org.springframework.ai spring-ai-bom ${spring-ai.version} pom import
|
创建一个application.yml文件并进行配置本地部署的deepseek
#链接本地部署的deepseek spring: ai: openai: #key瞎写的,只要是不能为空 api-key: KEY #ollama运行地址 base-url: http://127.0.0.1:11434/ chat: options: #deepseek语言模型版本 model: deepseek-r1:1.5b |
在application.properties文件进行配置
# 设置请求超时时间
spring.mvc.async.request-timeout=360000 #可访问静态文件路径
spring.mvc.static-path-pattern=/static/** spring,resources,static-locations=classpath:static |
三、编写程序代码
1. 后端代码
1.1 编写./ai/talk的GET方式请求路径
package com.lijianqi.deepseekR1.controller.web; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.Generation; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.openai.OpenAiChatModel; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Paths; import java.util.List;
/** * 该控制器用于处理与 DeepSeek 相关的聊天请求。 * 通过 `/ai/talk` 接口,用户可以发送消息并获取 DeepSeek 的流式响应。 */ @RestController @RequestMapping("/ai") public class DeepSeekController { private final OpenAiChatModel chatModel;
public DeepSeekController(OpenAiChatModel chatModel) { this.chatModel = chatModel; } /** * 通过 DeepSeek 生成流式聊天响应。 * @param message 用户发送的消息。 * @return 返回一个 Flux 对象,包含 DeepSeek 的流式响应。 */ @GetMapping("/talk") public Flux talk(@RequestParam(value = "message") String message) { Prompt prompt = new Prompt(new UserMessage(message)); return this.chatModel.stream(prompt); } } |
1.2 在额外编写一个跳转页面的请求,用于跳转到对应html页面。(这个不是很重要)
package com.lijianqi.deepseekR1.controller.web; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; /** * 控制器类,用于处理Web页面的请求。 */ @Controller public class PageController { /** * 处理"/talk"路径的GET请求,返回聊天页面视图。 * @param model 模型对象,用于向视图传递数据 * @return 视图名称"talk",对应templates目录下的talk.html文件 */ @GetMapping("/talk") public String talk(Model model) { // 返回聊天页面视图,对应templates目录下的talk.html文件 return "talk"; } }
|
2. 前端代码
2.1 AI客服对话界面请求后端接口后响应结果处理
async function sendMessage() { //获取用户输入对话内容 const input = document.getElementById('message-input'); const message = input.value; //如果用户输入内容为空就直接返回 if (message.trim() === '') return; //将deepseek响应的对话内同步到页面上 const chatMessages = document.getElementById('chat-messages'); const newMessage = document.createElement('div'); newMessage.classList.add('message', 'customer-message'); newMessage.textContent = message; chatMessages.appendChild(newMessage); //获取到用户对话内容后,清空输入框 input.value = ''; //显示等待效果 const loadingMessage = document.createElement('div'); loadingMessage.classList.add('message', 'loading-message'); loadingMessage.textContent = '思考中...'; chatMessages.appendChild(loadingMessage); // 构建包含参数的 URL const queryParams = new URLSearchParams({ message: message }); // 请求后端GET方式的请求 const url = `./ai/talk?${queryParams.toString()}`; const eventSource = new EventSource(url); const replyMessage = document.createElement('div'); replyMessage.classList.add('message', 'service-message'); // 将 replyMessage 添加到 chatMessages 中 chatMessages.appendChild(replyMessage); //剔除思考内容 let contentThink = ''; let contentBr = '
' let contentThinkFlag = 'NULL'; let contentBrFlag = 0; //一点点将deepseek响应的内容打印到页面 eventSource.onmessage = function(event) { const data = JSON.parse(event.data); //依据deepseek响应的数据结构进行处理 let replie = data.results[0].output.text; if (replie.indexOf(contentThink)!==-1){ contentThinkFlag = contentThink; // 移除等待消息 chatMessages.removeChild(loadingMessage); } if(contentThinkFlag.indexOf(contentThink)!==-1){ // 将 \n 替换为
let formattedReplies = replie.replace(/\n/g, ' '); //清空前两个br if(formattedReplies.indexOf(contentBr)!==-1){ contentBrFlag = contentBrFlag + 2; }else{ contentBrFlag = contentBrFlag + 1; } if(contentBrFlag > 3){ // 累加内容到 replyMessage 中 replyMessage.innerHTML += formattedReplies; // 滚动到最新消息 chatMessages.scrollTop = chatMessages.scrollHeight; } } }; //出现异常时关闭连接 eventSource.onerror = function(err) { eventSource.close(); // 移除等待消息 chatMessages.removeChild(loadingMessage); //清空标志 contentThinkFlag = 'NULL'; contentBrFlag = 0; }; eventSource.onopen = function() { console.log('Connection to server opened.'); }; eventSource.onclose = function() { console.log('Connection to server closed.'); // 移除等待消息 chatMessages.removeChild(loadingMessage); // 滚动到最新消息 chatMessages.scrollTop = chatMessages.scrollHeight; }; } |
2.2 Deepseek响应内容格式说明