开发者

JAVA集成本地部署的DeepSeek的图文教程

目录
  • 一、下载部署DeepSeek
    • 1.下载ollama
    • 2.下载DeepSeek-R1模型并启动
  •  二、Java项目接入DeepSeek

    一、下载部署DeepSeek

    1.下载ollama

    前往 Ollama 主页。

    JAVA集成本地部署的DeepSeek的图文教程

    点击下载,选择对应操作系统,点击下载。

    JAVA集成本地部署的DeepSeek的图文教程

    下载完成后打开直接无脑下一步就行。

    安装完成之后打开CMD页面,输入ollama,如果有输出就代表安装成功,如果显示"不是内部命令"就需要配置一下环境变量。

    JAVA集成本地部署的DeepSeek的图文教程

    ###配置环境变量以及更改ollama模型路径###

    win10系统为例,右键此电脑>高级系统设置>高级>环境变量

    ollama 默认会安装到 C:\Users\XX\AppData\Local\Programs\ollama 下,将此文件全部移到其他盘。

    选择系统变量Path>编辑>新建>输入移动后的ollama地址

    JAVA集成本地部署的DeepSeek的图文教程

    在系统变量中点击新建,输入变量名OLLAMA_MODELS,变量值D:\ollama\models(希望ollama模型放入哪个位置)

    JAVA集成本地部署的DeepSeek的图文教程

    配置完成后重启电脑,打开CMD页面再次输入ollama,查看是否有输出

    2.下载DeepSeek-R1模型并启动

    打开 Ollama 首页,点击 DeepSeek-R1

    JAVA集成本地部署的DeepSeek的图文教程

    在这里有模型各种参数大小

    JAVA集成本地部署的DeepSeek的图文教程

    DeepSeek-R1模型有1.5b、7b、8b、14b、32b、70b和671b的参数量区别,B代表十亿的意思,1.5b代表15亿参数量的意思。671B是基础大模型,1.5B、7B、8B、14B、32B、70B是蒸馏后的小模型,它们的区别主要体现在参数规模、模型容量、性能表现、准确性、训练成本、推理成本和不同使用场景:

    DeepSeek模型版本参数量特点适用场景硬件配置
    DeepSeek-R1-1.5B1.5B轻量级模型,参数量少,模型规模小适用于轻量级任务,如短文本生成、基础问答等4核处理器、8G内存,无需显卡
    DeepSeek-R1-7B7B平衡型模型,性能较好,硬件需求适中适合中等复杂度任务,如文案撰写、表格处理、统计分析等8核处理器、16G内存,Ryzen7或更高,RTX 3060(12GB)或更高
    DeepSeek-R1-8B8B性能略强于7B模型,适合更高精度需求适合需要更高精度的轻量级任务,比如代码生成、逻辑推理等8核处理器、16G内存,Ryzen7或更高,RTX 3060(12GB)或4060
    DeepSeek-R1-14B14B高性能模型,擅长复杂的任务,如数学推理、代码生成可处理复杂任务,如长文本生成、数据分析等i9-13900K或更高、32G内存,RTX 4090(24GB)或A5000
    DeepSeek-R1-32B32B专业级模型,性能强大,适合高精度任务适合超大规模任务,如语言建模、大规模训练、金融预测等Xeon 8核、128GB内存或更高,2-4张A100(80GB)或更高
    DeepSeek-R1-70B70B顶级模型,性能最强,适合大规模计算和高复杂任务适合高精度专业领域任务,比如多模态任务预处理。这些任务对硬件要求非常高,需要高端的 CPU 和显卡,适合预算充足的企业或研究机构使用Xeon 8核、128GB内存或更高,8张A100/H100(80GB)或更高
    DeepSeek-R1-671B671B超大规模模型,性能卓越,推理速度快,适合极高精度需求适合国家级 / 超大规模 AI 研究,如气候建模、基因组分析等,以及通用人工智能探索

    64核、512GB或更高,8张A100/H100

    根据自己电脑的配置选择对应大小的模型,复制安装命令,在cmd中执行命令

    JAVA集成本地部署的DeepSeek的图文教程

    JAVA集成本地部署的DeepSeek的图文教程

    等待安装完成后会自己启动。

    JAVA集成本地部署的DeepSeek的图文教程

    退出 DeepSeek 输入 /bye 或者 Ctrl+D

    ollama 常用命令

    ollama list :查看已安装的模型

    ollama ps:正在运行的模型

    ollama run 模型名称:启动模型

    ollama rm 模型名称:卸载模型

    下次启动 DeepSeek 只需要先 ollama list 查看已安装的模型,ollama run 模型名称启动即可。

     二、JAVA项目接入DeepSeek

    创建一个是 Springboot 项目引入 okhttp,lombok 等依赖

            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>com.squareup.okhttp3</groupId>
                <artifactId>okhttp</artifactId>
                <version>4.10.0</version>
            </dependency>

    配置文件中配置好本地 DeepSeek 的访问URL,ollama启动的 DeepSeek 端口默认为 11434

    deepseek:
      api:
        url: "http://127.0.0.1:11434/api/generate"
        model: "deepseek-r1:1.5b"

    编写 DeepSeekController

    @RestController
    @RequestMapping("/deepSeek")
    public class DeepSeekController {
    
        @Autowired
        private DeepSeekService deepSeekService;
    
        @GetMapping
        @RequestMapping("/chat")
        public String chat(String question) {
            try {
                return deepSeekService.chat(question);
            } catch (IOException e) {
                return "服务器繁忙,请稍后重试!";
            }
        }
    }

    编写DeepSeekRequest实体类

    @Data
    @Builder
    public class DeepSeekRequest {
        /**
         * 消息列表,包含对话中的消息对象
         */
        private String prompt;
    
        /**
         * 模型名称,指定要使用的模型
         */
        private String model;
    
    }

    请求参数有很多,只列举最主要的两个参数

    编写DeepSeekResponse实体类

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @TableName("deepseek_response")
    public class DeepSeekResponse {
    
        private String id;
    
        private String questionId;
    
        //模型名称
        private String model;
    
        //创建时间
        @jsONField(name = "created_at")
        private String createdAt;
    
        //响应内容
        private String response;
    
        //
        private boolean done;
    
        //
        @JSONField(name = "done_reason")
        private String doneReason;
    
        //
        @TableField(typeHandler = JacksonTypeHandler.class)
        private Integer[] context;
    
        //
        @JSONField(name = "total_duration")
        private Long totalDuration;
    
        //
        @JSONField(name = "load_duration")
        private Long loadDuration;
    
        //
        @JSONField(name = "prompt_eval_count")
        private Long promptEvalCount;
    
        //
        @JSONField(name = "prompt_eval_duration")
        private Long promptEvalDuration;
    
        //
        @JSONField(name = "eval_count")
        private Long evalCount;
    
        //
        @JSONField(name = "eval_duration")
        private Long evalDuration;
    }

    编写DeepSeekService

    @Service
    public class DeepSeekService {
    
        @Value("${deepseek.api.url}")
        private String url;
    
        @Value("${deepseek.api.model}")
        private String model;
    
        private final OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(30, TimeUnit.SECONDS)
                .readTimeout(60, TimeUnit.SECONDS)
                .build();
    
        public String chat(String question) throws IOException {
    
            DeepSeekRequest requestBody = DeepSeekRequest.builder()
                    .model(model)
                    .prompt(question)
                    .build();
            // 创建HTTP请求
            Request request = new Request.Builder()
                    .url(url)
                    .post(RequestBody.create(JSON.toJSONString(requestBody), MediaType.get("application/json")))
                    .build();
            // 发送请求并处理响应
            try {
                Response response = client.newCall(request).execute();
                if (!response.isSuccessful()) {
                    throw new IOException("Unexpected code " + response);http://www.devze.com
                }
                DeepSeekResponse deepSeekResponse = JSON.parseobject(response.body().string(), DeepSeekResponse.class);
                return  deepSeekResponse.getResponse();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return "服务器繁忙!";
        }

    随便写一个前端页面使用axIOS访问刚才暴露出来的接口就可以啦

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>智能 AI</title>
        <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css" rel="external nofollow" >
        <link rel="stylesheet" href="/css/self.css" rel="external nofollow" >
    </head>
    <body>
    <div id="app">
        <div class="chat-container">
            <div v-if="showTitle" class="welcome-title">你好!我是CloseAI,有什么可以帮助你的吗?</div>
            <div class="chat-messages">
                <transition-group name="list">
                    <div v-for="message in messages" :key="message.id" class="message-wrapper"
                         :class="{'user-message-wrapper': message.sender === 'user', 'ai-message-wrapper': message.sender === 'ai'}">
                        <img :src="message.sender === 'user' ? '/images/user.png' : '/images/ai.png'" class="avatar"/>
                        <div class="message"
                             :class="{'user-message': message.sender === 'user', 'ai-message': message.sender === 'ai'}">
                            <span v-if="message.sender === 'user'">{{ message.text }}</span>
                            <div v-else v-html="message.formattedText || message.text"></div>
                            <div v-if="message.sender === 'ai' && message.isLoading" class="loading-icon"></div>
    
                            <!-- 调整停止按钮容器位置 -->
                            <div v-if="(isThinking || animationFrameId) && message.sender === 'ai' && message.isActive"
                                 class="stop-container">
                                <button @click="stopThinking" class="stop-btn" title="JAVA集成本地部署的DeepSeek的图文教程">
                                    <svg width="24" height="24" viewBox="0 0 24 24">
                                        <rect x="6" y="4" width="4" height="16" rx="1"/>
                                        <rect x="14" y="4" width="4" height="16" rx="1"/>
                                    </svg>
                                </button>
                            </div>
                        </div>
                    </div>
                </transition-group>
            </div>
            <div class="chat-input">
                <input type="text" v-model="userInput" @keyup.enter="sendMessage" placeholder="给 CloseAI 发送消息..."/>
                <button @click="sendMessage">Send</button>
            </div>
        </div>
    </div>
    <script src="/js/vue.js"></script>
    <script src="/js/axios.min.js"></script>
    <script src="/js/method.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/AJAX/libs/dompurify/3.0.5/purify.min.js"></script>
    <script>
        function debounce(func, wait) {
            let timeout;
            return function (...args) {
                clearTimeout(timeout);
                timeout = setTimeout(() => func.apply(this, args), wait);
            };
        }
    
        new Vue({
            el: '#app',
            data: {
                userInput: '',
                messages: [],
                showTitle: true, // 添加一个新属性来控制标题的显示
                performanceConfig: {
                    maxMessageLength: 1000,  // 超过该长度启用优化
                    chunkSize: window.innerWidth < 768 ? 3 : 5 // 响应式分段
                },
                // 新增控制变量
                isThinking: false,
                currentController: null,  // 请求中止控制器
                animationFrameId: null    // 动画帧ID
            },
            methods: {
                sendMessage() {
                    if (this.userInput.trim() !== '') {
                        this.showTitle = false; // 用户开始提问时隐藏标题
                        const userMessage = {
                            id: Date.now(),
                            sender: 'user',
                            text: this.userInput
                        };
                        this.messages.push(userMessage);
                        this.userInput = '';
    
                        // 停用所有旧消息
                        this.messages.forEach(msg => {
                            if (msg.sender === 'ai') {
                                this.$set(msg, 'isActive', false);
                            }
                        });
    
                        const aiMessage = {
                            id: Date.now() + 1,
                            sender: 'ai',
                            text: '思考中 ',
                            isLoading: true,
                            isThinking: true, // 新增状态字段
                            isActive: true  // 新增激活状态字段
                        };
                        this.messages.push(aiMessage);
    
                        // 在请求前重置状态
                        t编程客栈his.isThinking = true;
                        // 使用 axios 的取消令牌
                        const controller = new AbortController();
                        this.currentController = controller;
    
                        axios.get('/deepSeek/chat?question=' + userMessage.text, {
                            signal: controller.signal
                        }).then(response => {
                            aiMessage.isLoading = false;
                            aiMessage.text = ''
                            aiMessage.rawContent = response.data; // 新增原始内容存储
    
                            let index = 0;
                            let chunk = '';
                            const chunkSize = this.performanceConfig.chunkSize;
                            let lastUpdate = 0;
                            let lastFullRender = 0;
    
                            const processChunk = () => {
                                // 分块处理逻辑
                                const remaining = response.data.length - index;
                                const step = remaining > chunkSize * 5 ? chunkSize * 5 : 1;
                                chunk += response.data.slice(index, index + step);
                                index += step;
    
                                // 增量更新文本
                                aiMessage.text = chunk;
                                this.safeParseMarkdown(aiMessage, true); // 实时解析
                                this.scrollToBottom();
                            };
    
                            const animate = (timestamp) => {
                                // 时间片控制逻辑
                                if (timestamp - lastUpdate > 16) { // 约60fps
                                    processChunk();
                                    lastUpdate = timestamp;
                                }
                                // 完整渲染控制(每100ms强制更新一次)
                                if (timestamp - lastFullRender > 100) {
                                    this.$forceUpdate();
                                    lastFullRender = timestamp;
                                }
    
                                if (timestamp - lastUpdate > 20) {
                                    const remainingChars = response.data.length - index;
                                    const step = remainingChars > chunkSize ? chunkSize : 1;
    
                                    aiMessage.text += response.data.substr(index, step);
                                    index += step;
    
                                    this.safeParseMarkdown(aiMessage);
                                    this.scrollToBottom();
    
                                    lastUpdate = timestamp;
                                }
    
                                if (index < response.data.length) {
                                    this.animationFrameId = requestAnimationFrame(animate);
                                } else {
                                    this.$set(aiMessage, 'isActive', false); // 关闭激活状态
    
                                    this.$set(aiMessage, 'formattedText',
                                        DOMPurify.sanitize(marked.parse(aiMessage.text))
                                    );
                                    this.animationFrameId = null; // 新增重置
                                    this.isThinking = false; // 在此处更新状态
                                    this.finalizeMessage(aiMessage);
                                }
                            };
                            // 修改动画启动方式
                            this.animationFrameId = requestAnimationFrame(animate);
                        }).catch(error => {
                            if (error.name !== 'CanceledError') {
                                console.error('Error:', error);
                            }
                        }).finally(() => {
                            this.animationFrameId = null;
                            // 新增消息状态更新
                            this.messages.forEach(msg => {
                                if (msg.sender === 'ai') {
                                    msg.isLoading = false;
                                    msg.isThinking = false;
                                }
                            });
                        });
                    }
                },
                // 新增滚动方法
                scrollToBottom() {
                    const container = this.$el.querySelector('.chat-messages');
                    container.scrollTop = container.scrollHeight;
                },
    
                // 优化的Markdown解析方法
                safeParseMarkdown: debounce(function (aiMessage, immediate = false) {
                    if (immediate) {
                        // // 快速解析基础格式(粗体、斜体等)
                        // const quickFormatted = aiMessage.text
                        //     .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
                        //     .replace(/\*(.*?)\*/g, '<em>$1</em>');
    
                        // 完整Markdown解析
                        this.$set(aiMessage, 'formattedText',
                            DOMPurify.sanitize(marked.parse(aiMessage.text))
                        );
                    } else {
                        // 完整Markdown解析
                        this.$set(aiMessage, 'formattedText',
                            DOMPurify.sanitize(marked.parse(aiMessage.text))
                        );
                    }
                }, 50), // 完整解析保持50ms防抖
    
                stopThinking() {
                    // 只停止当前活动消息
                    const activeMessage = this.messages.find(msg =>
                        msg.sender === 'ai' && msg.isActive
                    );
    
                    if (activeMessage) {
                        // 更新消息状态
                        this.$set(activeMessage, 'text', activeMessage.text + '\n(响应已停止)');
                        this.$set(activeMessage, 'isLoading', false);
                        this.$set(activeMessage, 'isActive', false);
                        this.safeParseMarkdown(activeMessage);
                    }
    
                    // 取消网络请求
                    if (this.currentController) {
                        this.currentController.abort()
                    }
    
                    // 停止打印渲染
                    if (this.animationFrameId) {
                        cancelAnimationFrame(this.animationFrameId);
                        this.animationFrameId = null;
                    }
    
                    // 更新所有相关消息状态
                    this.messages.forEach(msg => {
                        if (msg.sender === 'ai' && (msg.isLoading || msg.isThinking)) {
                            msg.text += '\n(响应已停止)';
                            msg.isLoading = false;
                            msg.isThinking = false;
                            this.safeParseMarkdown(msg);
                        }
                    });
    
                    this.isThinking = false;
                    this.animationFrameId = null; // 确保重置
                },
    
                // 新增最终处理函数
                finalizeMessage(aiMessage) {
                    this.$set(aiMessage, 'formattedText',
                        DOMPurify.sanitize(marked.parse(aiMessage.text))
                    );
                    this.$set(aiMessage, 'isActive', false);
                    this.animationFrameId = null;
                    this.isThinking = false;
                    this.$forceUpdate();
                },
            }
        });
    </script>
    </body>
    </html>
    
    body {
        font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        margin: 0;
        padding: 0;
        display: Flex;
        justify-content: center;
        align-items: center;
        height: 100vh;
        background-color: #f0f0f0;
    }
    
    .chat-container {
        width: 100%;
        /*max-width: 800px;*/
        background-color: #fff;
        border-radius: 12px;
        box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
        overflow: hidden;
        display: flex;
        flex-direction: column;
    }
    
    .welcome-title {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 100%;
        text-align: center;
        font-size: 24px;
        font-weight: bold;
    }
    /* 新增停止按钮样式 */
    .stop-btn {
        background-color: #ff4d4f;
        color: white;
        margin-left: 8px;
        transition: all 0.3s;
    }
    
    .stop-btn:hover {
        background-color: #f5f7fa;
    }
    .avatar {
        width: 40px;
        height: 40px;
        border-radius: 50%;
        margin-right: 10px;
        vertical-align: middle;
    }
    
    
    .chat-messages {
        /*flex: 1;*/
        overflow-y: auto;
        padding: 20px;
        /*display: flex;*/
        flex-direction: column-reverse;
        height: 70vh;
        width: 500px;
        scrollbar-width: none;
        scroll-behavior: smooth; /* 平滑滚动效果 */
        contain: strict; /* 限制浏览器重排范围 */
        transform: translateZ(0);
    }
    
    .message {
        margin-bottom: 15px;
        padding: 12px;
        border-radius: 12px;
        max-width: 70%;
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
        transition: all 0.3s ease;
        /*align-self: flex-end; !* 默认消息靠右对齐 *!*/
        display: table;
    }
    
    .message-wrapper {
        display: flex;
        max-width: 80%;
        margin: 10px;
        align-items: flex-start;
        will-change: transform, opacity; /* 启用GPU加速 */
    }
    
    /*.user-message-wrapper {*/
    /*    justify-content: flex-end;*/
    /*}*/
    
    .ai-message-wrapper {
        justify-content: flex-start;
        position: relative; /* 为绝对定位的停止按钮提供参考系 */
        margin-bottom: 50px; /* 给停止按钮留出空间 */
    }
    
    .avatar {
        width: 40px;
        height: 40px;
        border-radius: 50%;
        margin-right: 10px;
        vertical-align: middle;
    }
    
    .message {
        margin-bottom: 0;
    }
    
    .user-message {
        background-color: #007bff;
        color: #fff;
        margin-left: auto; /* 用户消息靠右对齐 */
    }
    
    
    .ai-message {
        background-color: #f0f2f5; /* AI消息背景色 */
        margin-right: auto; /* 消息内容靠左 */
        position: relative; /* 添加相对定位 */
        padding-bottom: 40px; /* 为按钮预留空间 */
    }
    
    /* 在self.css中添加 */
    .user-message-wrapper {
        flex-direction: row-reverse; /* 反转排列方向 */
        margin-left: auto; /* 让用户消息靠右 */
    }
    
    .user-message-wrapper .avatar {
        margin: 0 0 0 15px; /* 调整头像右边距 */
    }
    
    .chat-input {
        display: flex;
        align-items: center;
        padding: 15px;
        background-color: #f8f9fa;
    }
    
    .chat-input input {
        flex: 1;
        padding: 12px;
        border: 1px solid #ccc;
        border-radius: 6px;
        margin-right: 15px;
        transition: border-color 0.3s ease;
    }
    
    .chat-input input:focus {
        border-color: #007bff;
    }
    
    .chat-input button {
        padding: 12px 24px;
        border: none;
        border-radius: 6px;
        background-color: #007bff;
        color: #fff;
        cursor: pointer;
        transition: background-color 0.3s ease;
    }
    
    .message {
        display: flex;
        align-items: center;
    }
    
    /* 调整停止按钮定位 */
    .stop-container {
        position: absolute;
        bottom: 10px;
        right: 10px;
        z-index: 10;
    }CSBARwMYp
    
    
    .stop-btn {
        background: #ff4757;
        border: none;
        border-radius: 50%;
        width: 32px;
        height: 32px;
        display: flex;
        align-items: center;
        justify-content: center;
        cursor: pointer;
        box-shadow: 0 2px 5px rgba(0,0,0,0.2);
        transition: transform 0.2s;
    }
    
    .stop-btn:hover {
        transform: scale(1.1);
    }
    
    .stop-btn svg {
        fill: white;
        width: 16px;
        height: 16px;
    }
    
    .ai-message pre {
        background: #f5f7fa;
        padding: 15px;
        border-radius: 6px;
       http://www.devze.com overflow-x: auto;
    }
    
    .ai-message code {
        font-family: 'Courier New', monospace;
        font-size: 14px;
    }
    
    .ai-message table {
        border-collapse: collapse;
        margin: 10px 0;
    }
    
    .ai-message td, .ai-message th {
        border: 1px solid #ddd;
        padding: 8px;
    }
    
    .loading-icon {
        width: 20px;
        height: 20px;
        border: 2px solid #ccc;
        border-top-color: #007bff;
        border-radius: 50%;
        /*animation: spin 1s linear infinite;*/
        margin-left: 10px;
        /* 替换为CSS动画实现的加载效果 */
        /*background: linear-gradient(90deg, #eee 25%, #ddd 50%, #eee 75%);*/
        animation: spin 1s infinite linear;
    }
    
    @keyframes spin {
        0% {
            transform: rotate(0deg);
        }
        100% {
            transform: rotate(360deg);
        }
    }
    
    .chat-input button:hover {
        background-color: #0056b3;
    }
    
    .list-enter-active, .list-leave-active {
        transition: all 0.5s ease;
    }
    
    .list-enter, .list-leave-to /* .list-leave-active in <2.1.8 */ {
        opacity: 0;
    www.devze.com    transform: translateY(30px);
    }
    
    

    我的这个页面有bug,只供参考!!!

    JAVA集成本地部署的DeepSeek的图文教程

    JAVA集成本地部署的DeepSeek的图文教程

    到此这篇关于JAVA集成本地部署的DeepSeek的图文教程的文章就介绍到这了,更多相关JAVA集成本地部署DeepSeek内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜