SpringBoot集成DeepSeek实现AI对话详细教程

发布于:2025-02-27 ⋅ 阅读:(14) ⋅ 点赞:(0)

1. 环境说明

JDK17以上,SpringBoot3 以上版本


2. 引入依赖

	<dependency>
	      <groupId>org.springframework.ai</groupId>
	      <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
	 </dependency>

注意:
如果Maven用的是国内的镜像源,如阿里云仓库,在导入依赖的时候会提示阿里云仓库没有找到 spring-ai-openai-spring-boot-starter

解决方法:在pom文件中引入如下依赖

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>${spring-ai.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

同时在Maven的settings.xml进行屏蔽配置:

  <mirrors>
    <mirror>
  	<id>aliyunmaven</id>
  	<mirrorOf>*,!spring-milestones</mirrorOf>
  	<name>阿里云公共仓库</name>
  	<url>https://maven.aliyun.com/repository/public</url>
    </mirror>
  </mirrors>

至此,就能成功引入SpringAI依赖了


3. API配置

在配置之前,需要先在DeepSeek官网 https://platform.deepseek.com/api_keys 创建 API 密钥。
在这里插入图片描述

获取到秘钥后,在你SpringBoot项目的配置文件中进行如下配置:

spring:
  ai:
    openai:
      api-key: 你的秘钥
      base-url: https://api.deepseek.com
      chat:
        options:
          model: deepseek-chat

解释:

  • api-key: 即为在官网获取的秘钥
  • model:模型名称,这里使用的是DeepSeek V 系列
    • DeepSeek V 系列,V系列主要用于对话,模型名称为 deepseek-chat
    • DeepSeek R 系列,R系列主要用于推理, 模型名称为 deepseek-reasoner

4. 使用示例

编写一个简单的Controller,如下:

package com.example.aitest.demos.web;

import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import java.util.Map;

@RestController
public class ChatController {

    private final OpenAiChatModel chatModel;

    @Autowired
    public ChatController(OpenAiChatModel openAiChatModel) {
        this.chatModel = openAiChatModel;
    }

    @GetMapping("/ai/generate")
    public Map generate(@RequestParam(value = "message", defaultValue = "讲个笑话") String message) {
        return Map.of("generation", this.chatModel.call(message));
    }

    @GetMapping("/ai/generateStream")
    public Flux<ChatResponse> generateStream(@RequestParam(value = "message", defaultValue = "讲个笑话") String message) {
        Prompt prompt = new Prompt(new UserMessage(message));
        return this.chatModel.stream(prompt);
    }

}

代码解释:

  • generate 方法生成的回复会被封装成 Map 对象返回给客户端

  • generateStream 方法使用的是一种流氏响应,服务器不会一次性把所有内容都发回给客户端,而是逐步推送数据


最后可以用Postman或Apifox等工具进行测试,这里我写了一个简单的页面用于测试

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>SongChat</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            padding: 20px;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            background-color: #121212; /* 深色背景 */
            color: #ffffff; /* 浅色文字 */
        }
        .chat-container {
            max-width: 600px;
            width: 100%; /* 使聊天容器宽度占满父容器 */
            text-align: center;
            background-color: #1e1e1e; /* 深色背景 */
            border-radius: 8px; /* 圆角 */
            padding: 20px; /* 内边距 */
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* 阴影 */
        }
        .header {
            margin-bottom: 10px; /* 减少底部间距 */
        }
        .output {
            margin-top: 10px; /* 减少顶部间距 */
            border: 1px solid #444; /* 浅色边框 */
            padding: 10px;
            min-height: 100px;
            background-color: #282828; /* 更深的背景 */
            text-align: left;
            color: #ffffff; /* 浅色文字 */
        }
        .footer {
            margin-top: 10px; /* 减少顶部间距 */
        }
        .button-container {
            margin-top: 10px; /* 增加按钮容器的顶部间距 */
        }
        button {
            padding: 10px 20px;
            cursor: pointer;
            background-color: #4CAF50; /* 绿色背景 */
            color: white;
            border: none;
            border-radius: 5px;
            margin: 0 5px; /* 增加按钮之间的间距 */
        }
        button:hover {
            background-color: #45a049; /* 深绿色背景 */
        }
        .loading {
            font-size: 18px;
            color: #777; /* 浅灰色文字 */
        }
        /* 增加输入框的样式 */
        #message {
            width: 100%; /* 使输入框宽度占满父容器 */
            padding: 10px;
            font-size: 16px;
            border: 1px solid #444; /* 浅色边框 */
            border-radius: 5px;
            resize: vertical; /* 允许垂直调整大小 */
            min-height: 50px; /* 设置最小高度 */
            background-color: #1e1e1e; /* 深色背景 */
            color: #ffffff; /* 浅色文字 */
        }
        label {
            color: #ffffff; /* 浅色文字 */
        }
    </style>
</head>
<body>

<div class="chat-container">
    <div class="header">
        <h2>SongChat</h2>
    </div>

    <div class="output" id="outputContainer">
        <p class="loading" id="loadingMessage">等待生成...</p>
    </div>

    <div class="footer">
        <label for="message">请输入消息:</label>
        <textarea id="message" name="message" placeholder="例如:讲个笑话"></textarea>

        <div class="button-container">
            <!-- <button id="generateButton">生成响应</button> -->
            <button id="generateStreamButton">生成流式响应</button>
        </div>
    </div>
</div>

<script>
    document.getElementById('generateStreamButton').addEventListener('click', function() {
        const message = document.getElementById('message').value || '讲个笑话';
        generateStreamResponse(message);
    });

    // 添加键盘事件监听器,按回车键自动生成流式响应
    document.getElementById('message').addEventListener('keydown', function(event) {
        if (event.key === 'Enter') {
            event.preventDefault(); // 阻止默认的换行行为
            const message = document.getElementById('message').value || '讲个笑话';
            generateStreamResponse(message);
        }
    });

    function generateResponse(message) {
        document.getElementById('outputContainer').innerHTML = '<p class="loading">生成中...</p>';

        fetch(`/ai/generate?message=${encodeURIComponent(message)}`)
            .then(response => response.json())
            .then(data => {
                document.getElementById('outputContainer').innerHTML = `<p>${data.generation}</p>`;
            })
            .catch(error => {
                console.error('生成失败:', error);
                document.getElementById('outputContainer').innerHTML = '<p>生成失败,请重试。</p>';
            });
    }

    let eventSource = null; // 用于保存当前的 EventSource 实例
    let isRequesting = false; // 添加标志位防止重复请求
    let isFirstMessage = true; // 添加标志位判断是否是第一次消息

    function generateStreamResponse(message) {
        // 如果已经有连接或正在请求,则不创建新连接
        if (eventSource !== null || isRequesting) {
            return;
        }

        isRequesting = true; // 设置请求标志位

        // 显示正在等待的提示
        document.getElementById('outputContainer').innerHTML = '<p class="loading">等待流式响应...</p>';

        // 创建新的 EventSource
        eventSource = new EventSource('/ai/generateStream?message=' + encodeURIComponent(message));

        eventSource.onmessage = function(event) {
            try {
                // 解析服务器返回的数据
                const data = JSON.parse(event.data);

                // 确保数据存在并且有结果
                if (data && data.results && data.results.length > 0) {
                    data.results.forEach(result => {
                        const content = result.output.content || result.output.text || '';

                        // 如果有有效内容,追加到输出容器
                        if (content.trim()) {
                            const outputContainer = document.getElementById('outputContainer');
                            if (isFirstMessage) {
                                // 移除“等待流式响应...”的提示
                                outputContainer.innerHTML = '';
                                isFirstMessage = false;
                            }
                            outputContainer.innerHTML += content;
                        }
                    });
                } else {
                    document.getElementById('outputContainer').innerHTML = '<p>没有返回有效内容,请稍后再试。</p>';
                }
            } catch (error) {
                console.error('流式响应解析错误:', error);
                document.getElementById('outputContainer').innerHTML = '<p>流式响应失败。</p>';
            } finally {
                isRequesting = false; // 请求完成后重置标志位
            }
        };

        eventSource.onerror = function() {
            console.error('流式响应连接出错');
            closeEventSource(); // 错误发生时关闭连接
        };

        eventSource.onclose = function() {
            console.log('流式响应连接已关闭');
            closeEventSource(); // 连接关闭时关闭连接
        };
    }

    function closeEventSource() {
        if (eventSource) {
            eventSource.close();
            eventSource = null;
            isRequesting = false;
            isFirstMessage = true; // 重置 isFirstMessage 标志位
        }
    }
</script>

</body>
</html>

如图:
在这里插入图片描述


5. 总结

至此,我们就实现了SpringBoot集成DeepSeek实现AI对话的功能,总体还是很简单的,现阶段 DeepSeek 服务受资源限制可能无法提供在线服务,可以本地部署一个DeepSeek 大模型进行学习和使用。