Java接入DeepSeek实现流式、联网、知识库以及多轮问答

本文将详细的说明,如何使用Java、JDK8快速接入deepseek的聊天服务,包含官方的API服务,以及本地Ollama的服务。并搭建一个简单的前端界面,用于流式输出、多轮问答、联网、知识库问答的效果展示。

1. 创建spring boot应用

2. 引入pom

引入ai4j库的依赖。 AI4J是一款JavaSDK用于快速接入AI大模型应用,整合多平台大模型,如OpenAi、Ollama、智谱Zhipu(ChatGLM)、深度求索DeepSeek、月之暗面Moonshot(Kimi)、腾讯混元Hunyuan、零一万物(01)等等,提供统一的输入输出(对齐OpenAi)消除差异化,优化函数调用(Tool Call),优化RAG调用、支持向量数据库(Pinecone),并且支持JDK1.8,为用户提供快速整合AI的能力。 AI4J-GitHub

xml 复制代码
<dependency>
    <groupId>io.github.lnyo-cly</groupId>
    <artifactId>ai4j-spring-boot-starter</artifactId>
    <version>1.3.0</version>
</dependency>

3. 配置application.yml

在deepseek官网,创建API-KEY,然后在application.yml中配置

DeepSeek-APIKEY

yaml 复制代码
ai:
  deepseek:
  api-key: "sk-123456789"

4. 创建聊天服务Controller

接下来实现流式输出:

java 复制代码
@RestController
public class OpenAiController {

    // 注入Ai服务
    @Autowired
    private AiService aiService;
    
    @GetMapping("/chatStream")
    public SseEmitter getChatMessageStream(@RequestParam String question) {
        SseEmitter emitter = new SseEmitter();

        // 获取DEEPSEEK的聊天服务
        IChatService chatService = aiService.getChatService(PlatformType.DEEPSEEK);

        // 创建请求参数
        ChatCompletion chatCompletion = ChatCompletion.builder()
                .model("deepseek-chat")
                .message(ChatMessage.withUser(question))
                .build();


        Executors.newSingleThreadExecutor().submit(() -> {
            try {
                SseListener sseListener = new SseListener() {
                    @Override
                    protected void send() {
                        try {
                            emitter.send(this.getCurrData());
                            System.out.println(this.getCurrData());  // 打印当前发送的内容
                        } catch (IOException e) {
                            emitter.completeWithError(e);
                        }
                    }
                };

                emitter.onCompletion(() -> {
                    System.out.println("完成");
                    sseListener.getEventSource().cancel();

                });

                // 发送流式数据
                chatService.chatCompletionStream(chatCompletion, sseListener);

                // 完成后关闭连接
                emitter.complete();
            } catch (Exception e) {
                emitter.completeWithError(e);
            }
        });
        return emitter;
    }
}

或者

java 复制代码
@GetMapping("/chatStream")
public SseEmitter getChatMessageStream(@RequestParam String question) throws Exception {
    SseEmitter emitter = new SseEmitter();

    // 获取OLLAMA的聊天服务
    IChatService chatService = aiService.getChatService(PlatformType.DEEPSEEK);

    // 创建请求参数
    ChatCompletion chatCompletion = ChatCompletion.builder()
            .model("deepseek-chat")
            .message(ChatMessage.withUser(question))
            .build();


    SseListener sseListener = new SseListener() {
        @Override
        protected void send() {
            try {
                emitter.send(this.getCurrData());
                System.out.println(this.getCurrData());  // 打印当前发送的内容
                if ("[DONE]".equals(this.getCurrData())) {
                    emitter.complete();
                }
            } catch (IOException e) {
                emitter.completeWithError(e);
            }
        }
    };

    emitter.onCompletion(() -> {
        System.out.println("完成");
        sseListener.getEventSource().cancel();

    });

    // 发送流式数据
    sseListener.getCountDownLatch().countDown(); // 取消同步阻塞
    chatService.chatCompletionStream(chatCompletion, sseListener);


    return emitter;
}

测试流式接口如下:

5. 修改为ollama调用

html 复制代码
@GetMapping("/chatStream")
public SseEmitter getChatMessageStream(@RequestParam String question) {
    SseEmitter emitter = new SseEmitter(-1L);


    // 获取OLLAMA的聊天服务
    IChatService chatService = aiService.getChatService(PlatformType.OLLAMA);

    // 创建请求参数
    ChatCompletion chatCompletion = ChatCompletion.builder()
            .model("deepseek-r1:1.5b")
            .message(ChatMessage.withUser(question))
            .build();


    Executors.newSingleThreadExecutor().submit(() -> {
        try {
            SseListener sseListener = new SseListener() {
                @Override
                protected void send() {
                    try {
                        emitter.send(this.getCurrData());
                        System.out.println(this.getCurrData());  // 打印当前发送的内容
                    } catch (IOException e) {
                        emitter.completeWithError(e);
                    }
                }
            };

            emitter.onCompletion(() -> {
                System.out.println("完成");
                sseListener.getEventSource().cancel();

            });

            // 发送流式数据
            chatService.chatCompletionStream(chatCompletion, sseListener);

            // 完成后关闭连接
            emitter.complete();
        } catch (Exception e) {
            emitter.completeWithError(e);
        }
    });

    return emitter;
}

修改两处即可:

  • 修改PlatformTypeOLLAMA
  • 修改modeldeepseek-r1:1.5b

6. 搭建前端界面

注意:此前端界面由AI生成,并未经过严格测试,仅供参考。

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>AI聊天助手</title>
    <!-- Font Awesome CDN -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <style>
        :root {
            --primary-color: #6366f1;
            --primary-light: #818cf8;
            --primary-dark: #4f46e5;
            --text-light: #ffffff;
            --text-dark: #1e293b;
            --bg-light: #f8fafc;
            --bg-dark: #0f172a;
            --message-user: #6366f1;
            --message-bot: #f1f5f9;
            --border-color: #e2e8f0;
        }

        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Inter', 'Arial', sans-serif;
        }

        body {
            background-color: var(--bg-light);
            color: var(--text-dark);
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            padding: 20px;
        }

        .chat-container {
            width: 100%;
            max-width: 900px;
            background: white;
            border-radius: 16px;
            box-shadow: 0 10px 25px rgba(0, 0, 0, 0.05);
            overflow: hidden;
            display: flex;
            flex-direction: column;
            height: 85vh;
            position: relative;
            border: 1px solid var(--border-color);
        }

        .chat-header {
            background: var(--primary-color);
            color: var(--text-light);
            padding: 18px 24px;
            display: flex;
            align-items: center;
            gap: 12px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            z-index: 10;
        }

        .chat-header i {
            font-size: 1.5rem;
        }

        .chat-header h1 {
            font-size: 1.3rem;
            font-weight: 600;
        }

        .chat-messages {
            flex: 1;
            overflow-y: auto;
            padding: 24px;
            display: flex;
            flex-direction: column;
            gap: 20px;
            scroll-behavior: smooth;
        }

        .message-container {
            display: flex;
            gap: 12px;
            max-width: 85%;
        }

        .user-container {
            align-self: flex-end;
            flex-direction: row-reverse;
        }

        .bot-container {
            align-self: flex-start;
        }

        .avatar {
            width: 38px;
            height: 38px;
            border-radius: 50%;
            display: flex;
            justify-content: center;
            align-items: center;
            font-size: 1.2rem;
            flex-shrink: 0;
        }

        .user-avatar {
            background: var(--primary-light);
            color: var(--text-light);
        }

        .bot-avatar {
            background: var(--primary-dark);
            color: var(--text-light);
        }

        .message {
            padding: 14px 20px;
            border-radius: 18px;
            font-size: 1rem;
            line-height: 1.6;
            position: relative;
            max-width: 100%;
        }

        .user-message {
            background: var(--message-user);
            color: var(--text-light);
            border-top-right-radius: 4px;
        }

        .bot-message {
            background: var(--message-bot);
            color: var(--text-dark);
            border-top-left-radius: 4px;
        }

        .message-time {
            font-size: 0.7rem;
            opacity: 0.7;
            margin-top: 6px;
            text-align: right;
        }

        .user-message .message-time {
            color: rgba(255, 255, 255, 0.9);
        }

        .bot-message .message-time {
            color: rgba(0, 0, 0, 0.6);
        }

        .chat-input-container {
            padding: 16px 24px;
            background: white;
            border-top: 1px solid var(--border-color);
            display: flex;
            align-items: center;
            gap: 14px;
            z-index: 10;
        }

        .chat-input {
            flex: 1;
            padding: 14px 20px;
            border: 1px solid var(--border-color);
            border-radius: 30px;
            font-size: 1rem;
            outline: none;
            transition: all 0.3s;
            background: var(--bg-light);
        }

        .chat-input:focus {
            border-color: var(--primary-color);
            box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2);
        }

        .send-button {
            width: 50px;
            height: 50px;
            border: none;
            background: var(--primary-color);
            color: var(--text-light);
            border-radius: 50%;
            cursor: pointer;
            transition: all 0.3s;
            display: flex;
            justify-content: center;
            align-items: center;
            box-shadow: 0 2px 10px rgba(99, 102, 241, 0.3);
        }

        .send-button:hover {
            background: var(--primary-dark);
            transform: translateY(-2px);
            box-shadow: 0 4px 12px rgba(99, 102, 241, 0.4);
        }

        .send-button:active {
            transform: translateY(0);
        }

        .send-button i {
            font-size: 1.2rem;
        }

        /* 滚动条样式 */
        .chat-messages::-webkit-scrollbar {
            width: 6px;
        }

        .chat-messages::-webkit-scrollbar-track {
            background: transparent;
        }

        .chat-messages::-webkit-scrollbar-thumb {
            background: #d1d5db;
            border-radius: 10px;
        }

        .chat-messages::-webkit-scrollbar-thumb:hover {
            background: #9ca3af;
        }

        /* 打字机效果 */
        .typing {
            display: flex;
            align-items: center;
            gap: 4px;
            padding: 8px 12px;
            border-radius: 18px;
            background: var(--message-bot);
            width: fit-content;
        }

        .typing-dot {
            width: 8px;
            height: 8px;
            background: var(--primary-color);
            border-radius: 50%;
            animation: typing-animation 1.4s infinite both;
        }

        .typing-dot:nth-child(2) {
            animation-delay: 0.2s;
        }

        .typing-dot:nth-child(3) {
            animation-delay: 0.4s;
        }

        @keyframes typing-animation {
            0%, 100% {
                opacity: 0.3;
                transform: scale(0.8);
            }
            50% {
                opacity: 1;
                transform: scale(1);
            }
        }

        /* 消息进入动画 */
        @keyframes message-in {
            from {
                opacity: 0;
                transform: translateY(20px);
            }
            to {
                opacity: 1;
                transform: translateY(0);
            }
        }

        .message-container {
            animation: message-in 0.3s ease-out forwards;
        }

        /* 响应式调整 */
        @media (max-width: 768px) {
            .chat-container {
                height: 90vh;
                border-radius: 12px;
            }

            .message-container {
                max-width: 90%;
            }

            .chat-header h1 {
                font-size: 1.1rem;
            }
        }

        @media (max-width: 480px) {
            .chat-container {
                height: 92vh;
                border-radius: 8px;
            }

            .message {
                padding: 12px 16px;
            }

            .avatar {
                width: 32px;
                height: 32px;
                font-size: 1rem;
            }

            .chat-input {
                padding: 12px 16px;
            }

            .send-button {
                width: 45px;
                height: 45px;
            }

            .chat-header {
                padding: 14px 20px;
            }

            .chat-messages {
                padding: 20px;
            }
        }
    </style>
</head>
<body>
<div class="chat-container">
    <div class="chat-header">
        <i class="fas fa-robot"></i>
        <h1>AI聊天助手</h1>
    </div>

    <div class="chat-messages" id="chat-messages">
        <div class="message-container bot-container">
            <div class="avatar bot-avatar">
                <i class="fas fa-robot"></i>
            </div>
            <div class="message-content">
                <div class="message bot-message">
                    您好!我是AI助手,很高兴为您服务。请问有什么我可以帮助您的吗?
                </div>
                <div class="message-time">
                    刚刚
                </div>
            </div>
        </div>
    </div>

    <div class="chat-input-container">
        <input type="text" class="chat-input" id="user-input" placeholder="输入您的问题..." autofocus>
        <button class="send-button" id="send-button">
            <i class="fas fa-paper-plane"></i>
        </button>
    </div>
</div>

<script>
    document.addEventListener('DOMContentLoaded', function() {
        const chatMessages = document.getElementById('chat-messages');
        const userInput = document.getElementById('user-input');
        const sendButton = document.getElementById('send-button');

        let eventSource = null;

        // 获取当前时间
        function getCurrentTime() {
            const now = new Date();
            let hours = now.getHours();
            let minutes = now.getMinutes();

            // 确保分钟为两位数
            minutes = minutes < 10 ? '0' + minutes : minutes;

            return `${hours}:${minutes}`;
        }

        // 添加用户消息
        function addUserMessage(message, time) {
            const messageContainer = document.createElement('div');
            messageContainer.className = 'message-container user-container';

            const avatarDiv = document.createElement('div');
            avatarDiv.className = 'avatar user-avatar';
            avatarDiv.innerHTML = '<i class="fas fa-user"></i>';

            const messageContentDiv = document.createElement('div');
            messageContentDiv.className = 'message-content';

            const messageDiv = document.createElement('div');
            messageDiv.className = 'message user-message';
            messageDiv.textContent = message;

            const timeDiv = document.createElement('div');
            timeDiv.className = 'message-time';
            timeDiv.textContent = time;

            messageContentDiv.appendChild(messageDiv);
            messageContentDiv.appendChild(timeDiv);

            messageContainer.appendChild(avatarDiv);
            messageContainer.appendChild(messageContentDiv);

            chatMessages.appendChild(messageContainer);
            chatMessages.scrollTop = chatMessages.scrollHeight;
        }

        // 发送消息函数
        function sendMessage() {
            const message = userInput.value.trim();
            if (!message) return;

            // 添加用户消息到聊天区域
            const time = getCurrentTime();
            addUserMessage(message, time);

            // 清空输入框
            userInput.value = '';

            // 添加机器人正在输入的指示
            const typingContainer = document.createElement('div');
            typingContainer.className = 'message-container bot-container';
            typingContainer.id = 'bot-typing';

            const avatarDiv = document.createElement('div');
            avatarDiv.className = 'avatar bot-avatar';
            avatarDiv.innerHTML = '<i class="fas fa-robot"></i>';

            const typingDiv = document.createElement('div');
            typingDiv.className = 'typing';
            typingDiv.innerHTML = '<span class="typing-dot"></span><span class="typing-dot"></span><span class="typing-dot"></span>';

            typingContainer.appendChild(avatarDiv);
            typingContainer.appendChild(typingDiv);
            chatMessages.appendChild(typingContainer);
            chatMessages.scrollTop = chatMessages.scrollHeight;

            // 更改按钮为暂停
            sendButton.innerHTML = '<i class="fas fa-pause"></i>';
            sendButton.onclick = stopStream;

            // 创建EventSource连接
            const url = `http://127.0.0.1:8080/chatStream?question=${encodeURIComponent(message)}`;
            eventSource = new EventSource(url);

            let botResponse = '';
            let responseContainer = null;

            eventSource.onmessage = function(event) {
                // 如果这是第一条消息,创建回复容器
                if (!responseContainer) {
                    // 移除打字指示器
                    const typingIndicator = document.getElementById('bot-typing');
                    if (typingIndicator) {
                        typingIndicator.remove();
                    }

                    // 创建回复容器
                    responseContainer = document.createElement('div');
                    responseContainer.className = 'message-container bot-container';
                    responseContainer.id = 'current-bot-response';

                    const avatarDiv = document.createElement('div');
                    avatarDiv.className = 'avatar bot-avatar';
                    avatarDiv.innerHTML = '<i class="fas fa-robot"></i>';

                    const messageContentDiv = document.createElement('div');
                    messageContentDiv.className = 'message-content';

                    const messageDiv = document.createElement('div');
                    messageDiv.className = 'message bot-message';
                    messageDiv.id = 'current-bot-message';

                    const timeDiv = document.createElement('div');
                    timeDiv.className = 'message-time';
                    timeDiv.textContent = getCurrentTime();

                    messageContentDiv.appendChild(messageDiv);
                    messageContentDiv.appendChild(timeDiv);

                    responseContainer.appendChild(avatarDiv);
                    responseContainer.appendChild(messageContentDiv);

                    chatMessages.appendChild(responseContainer);
                }

                // 更新回复内容
                botResponse += event.data;
                const messageDiv = document.getElementById('current-bot-message');
                if (messageDiv) {
                    messageDiv.textContent = botResponse;
                }

                // 自动滚动到底部
                chatMessages.scrollTop = chatMessages.scrollHeight;
            };

            eventSource.onerror = function() {
                // 处理完成或错误时
                completeResponse();
            };
        }

        // 停止流式响应
        function stopStream() {
            if (eventSource) {
                eventSource.close();
                completeResponse();
            }
        }

        // 完成响应处理
        function completeResponse() {
            // 关闭连接
            if (eventSource) {
                eventSource.close();
                eventSource = null;
            }

            // 移除打字指示器
            const typingIndicator = document.getElementById('bot-typing');
            if (typingIndicator) {
                typingIndicator.remove();
            }

            // 恢复发送按钮
            sendButton.innerHTML = '<i class="fas fa-paper-plane"></i>';
            sendButton.onclick = sendMessage;

            // 移除id,以便下次使用
            const currentBotResponse = document.getElementById('current-bot-response');
            if (currentBotResponse) {
                currentBotResponse.removeAttribute('id');
            }

            const currentBotMessage = document.getElementById('current-bot-message');
            if (currentBotMessage) {
                currentBotMessage.removeAttribute('id');
            }
        }

        // 设置事件监听器
        sendButton.addEventListener('click', sendMessage);

        userInput.addEventListener('keydown', function(e) {
            if (e.key === 'Enter') {
                sendMessage();
            }
        });

        // 自动聚焦到输入框
        userInput.focus();
    });
</script>
</body>
</html>

7. 多轮对话

只需要简单修改,即可携带历史上下文进行对话:

java 复制代码
private List<ChatMessage> history = new ArrayList<>(); // 1. 创建历史消息列表

@GetMapping("/chatStream")
public SseEmitter getChatMessageStream(@RequestParam String question) {
    // ......
    history.add(ChatMessage.withUser(question)); // 2. 向历史中添加用户输入
    // 创建请求参数
    ChatCompletion chatCompletion = ChatCompletion.builder()
            .model("deepseek-chat")
            .messages(history) // 3. 添加完整历史消息
            .build();

    // ......        
    Executors.newSingleThreadExecutor().submit(() -> {
        try {

            // ......
            emitter.onCompletion(() -> {
                System.out.println("完成");
                history.add(ChatMessage.withAssistant(sseListener.getOutput().toString())); // 4. 向历史中添加AI回复
                sseListener.getEventSource().cancel();
            });
            // ......
        } catch (Exception e) {
            emitter.completeWithError(e);
        }
    });
    // ......
}

8. 联网对话

  1. 配置application.yml中的searxng,将其中的url替换为你已经部署的searxng服务的地址。
yaml 复制代码
ai:
  websearch:
    searxng:
      url: "http://127.0.0.1:29080/search"
      nums: 10
  1. 修改代码,添加联网对话的功能:
java 复制代码
@GetMapping("/chatStream")
public SseEmitter getChatMessageStream(@RequestParam String question) {
    // ......

    // 获取DEEPSEEK的聊天服务
    IChatService chatService = aiService.webSearchEnhance(aiService.getChatService(PlatformType.DEEPSEEK)); // 1. 使用webSearchEnhance对原本chat服务增加联网功能,该联网服务使用的为searxng

    // ......
    // ......

}
相关推荐
Knight_AL3 小时前
浅拷贝与深拷贝详解:概念、代码示例与后端应用场景
android·java·开发语言
DolphinScheduler社区3 小时前
# 3.1.8<3.2.0<3.3.1,Apache DolphinScheduler集群升级避坑指南
java·大数据·开源·apache·任务调度·海豚调度
Le1Yu3 小时前
黑马商城微服务项目准备工作并了解什么是微服务、SpringCloud
java·微服务·架构
ZhengEnCi3 小时前
🚀创建第一个 SpringBoot 应用-零基础体验开箱即用的神奇魅力
java·spring boot
宠友信息3 小时前
仿小红书短视频APP源码:Java微服务版支持小程序编译的技术解析
java·微服务·音视频
努力努力再努力wz3 小时前
【C++进阶系列】:万字详解智能指针(附模拟实现的源码)
java·linux·c语言·开发语言·数据结构·c++·python
敲代码的嘎仔3 小时前
JavaWeb零基础学习Day2——JS & Vue
java·开发语言·前端·javascript·数据结构·学习·算法
夜晚中的人海4 小时前
【C++】智能指针介绍
android·java·c++
正在走向自律4 小时前
RSA加密从原理到实践:Java后端与Vue前端全栈案例解析
java·前端·vue.js·密钥管理·rsa加密·密钥对·aes+rsa