从零开始部署DeepSeek:基于Ollama+Flask的本地化AI对话系统

news2025/3/13 2:43:29

从零开始部署DeepSeek:基于Ollama+Flask的本地化AI对话系统


一、部署背景与工具选型

在AI大模型遍地开花的2025年,DeepSeek R1凭借其出色的推理能力和开源特性成为开发者首选。本文将以零基础视角,通过以下工具链实现本地化部署:

1.Ollama:轻量级模型管理工具,支持一键拉取、运行模型

Ollama 是一个功能强大的大语言模型管理端,专为下载、运行和调用大型语言模型(如DeepSeek)而设计。它提供了以下几个核心功能:

  • 模型下载:支持从官方仓库下载不同规模的模型。
  • 模型运行:通过API提供服务,让用户与模型进行交互。
  • 实时对话模拟:以流式方式展示模型回复。
2.DeepSeek : 平衡性能与资源消耗的中等规模模型

DeepSeek 是目标语言模型,可以根据您的硬件配置选择不同的模型规模(如7B、13B等)。它具有以下特点:

  • 可扩展性:根据内存和计算资源调整模型大小。
  • 高效训练:支持并行训练以加速模型收敛。
3.Python+Flask:构建Web交互界面,支持流式响应

Flask 是一个轻量级的Web框架,广泛应用于快速开发Web应用。结合Python,它为构建动态Web界面提供了灵活性:

  • API集成:通过Flask API调用Ollama服务。
  • 前端动态:使用JavaScript和HTML创建交互式对话框。
4.HTML+JavaScript:实现类ChatGPT的对话交互体验

HTML 是构建Web界面的基础语言,用于设计用户友好的对话框,并将其嵌入到Flask应用中。您可以通过以下方式集成:

  • 响应式布局:使用CSS样式表确保界面在不同设备上适配。
  • 动态内容展示:通过JavaScript更新页面内容,模拟模型回复,需要模拟打字机方式。

**备注说明**: 不想自己编程的话,可使用chatbox直接接入ollama

二、环境准备与模型部署

1. 安装Ollama

Windows/Mac用户
访问Ollama官网下载安装包,双击完成安装。 (github下载很慢,需要使用加速或从其他地方下载)

Linux用户

curl -fsSL https://ollama.com/install.sh | sh
sudo systemctl start ollama
2. 下载DeepSeek模型

根据硬件配置选择模型(显存要求参考):

  • 基础版(1.5B):适合笔记本(8GB内存)
  • 标准版(7B):推荐配置(16GB内存+8GB显存)
  • 加强版(70B):需专业显卡(如RTX 5090)
# 拉取7B模型
ollama run deepseek-r1:7b

上述指令会完成下载和运行两个步骤(ollama支持断线续传功能,而且我发现速度慢下来的时候,手动停止然后再启动接着下载模型的话,速度会恢复上来),成功运行后可通过命令行直接向deepseek提问,如下:

另外,也可以在命令行手动执行ollama相关指令:

  • 无参数启动
C:\Users\arbboter\Desktop>ollama
Usage:
  ollama [flags]
  ollama [command]

Available Commands:
  serve       Start ollama
  create      Create a model from a Modelfile
  show        Show information for a model
  run         Run a model
  pull        Pull a model from a registry
  push        Push a model to a registry
  list        List models
  ps          List running models
  cp          Copy a model
  rm          Remove a model
  help        Help about any command

Flags:
  -h, --help      help for ollama
  -v, --version   Show version information

Use "ollama [command] --help" for more information about a command.
命令功能示例
ollama serve启动模型ollama serve
ollama pull下载模型ollama pull deepseek-r1:7b
ollama list查看本地模型ollama list
ollama rm删除模型ollama rm deepseek-r1:1.5b
ollama cp复制模型ollama cp deepseek-r1:7b my-backup

3.配置ollama

# 限制本地访问(默认配置)
export OLLAMA_HOST=127.0.0.1

# 允许局域网访问
export OLLAMA_HOST=0.0.0.0

# 启用调试
export OLLAMA_DEBUG=1

# 自定义模型路径(可能需重启机器生效)
export OLLAMA_MODELS=~\ollama\models

三、构建Web对话系统

备注说明:选型flask及代码编写基本是由deepseek完成,本人只负责提问和修缮
1. 项目结构
deepseek-web/
├── app.py          # Flask主程序
├── templates/
│   └── index.html  # 聊天界面
2. Flask后端实现(app.py)
# app.py
from flask import Flask, render_template, request, Response
import ollama
import logging
from datetime import datetime

app = Flask(__name__)

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('chat.log'),
        logging.StreamHandler()
    ]
)

def chat_ollama(user_message, stream):
    host = 'http://192.168.1.10:11434'
    cli = ollama.Client(host=host)
    response = cli.chat(
        model='deepseek-r1:7b',
        messages=[{'role': 'user', 'content': user_message}],
        stream=stream,
        # options={'temperature': 0.7}
    )
    return response

@app.route('/')
def index():
    return render_template('index.html')


@app.route('/api/chat', methods=['POST'])
def chat():
    """流式聊天接口"""
    def generate(user_message):
        try:
            app.logger.info(f"流式处理开始: {user_message[:50]}...")

            stream = chat_ollama(user_message, True)
            for chunk in stream:
                content = chunk['message']['content']
                if content.startswith('<think>'):
                    content = content.replace('<think>', '', 1)
                elif content.startswith('</think>'):
                    content = content.replace('</think>', '\n', 1)
                app.logger.debug(f"发送数据块: {content}")
                yield f"{content}"
                
            app.logger.info("流式处理完成")

        except Exception as e:
            app.logger.error(f"流式错误: {str(e)}")
            yield f"[ERROR] {str(e)}\n\n"

    return Response(generate(request.json['message']), mimetype='text/event-stream')

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5001, debug=True)
3. 前端实现(index.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>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css">
    <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
    <style>
        :root {
            --primary-color: #10a37f;
            --bg-color: #f0f2f5;
        }
        body {
            margin: 0;
            padding: 20px;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            background-color: var(--bg-color);
            max-width: 800px;
            margin: 0 auto;
        }
        #chat-container {
            height: 70vh;
            overflow-y: auto;
            border: 1px solid #e0e0e0;
            border-radius: 8px;
            padding: 15px;
            background: white;
            margin-bottom: 20px;
        }
        .message {
            margin: 12px 0;
            display: flex;
            gap: 15px;
        }
        .user-message {
            justify-content: flex-end;
        }
        .message-content {
            max-width: 80%;
            padding: 12px 16px;
            border-radius: 8px;
        }
        .assistant-message .message-content {
            background: #f8f9fa;
            border: 1px solid #e0e0e0;
        }
        .user-message .message-content {
            background: var(--primary-color);
            color: white;
        }
        #input-container {
            display: flex;
            gap: 10px;
        }
        #user-input {
            flex: 1;
            padding: 12px;
            border: 1px solid #e0e0e0;
            border-radius: 8px;
            resize: none;
            min-height: 44px;
        }
        button {
            background: var(--primary-color);
            color: white;
            border: none;
            padding: 0 20px;
            border-radius: 8px;
            cursor: pointer;
            transition: opacity 0.2s;
        }
        button:disabled {
            opacity: 0.6;
            cursor: not-allowed;
        }
        .typing-indicator {
            display: inline-block;
            padding: 8px 12px;
            background: #f8f9fa;
            border-radius: 8px;
            border: 1px solid #e0e0e0;
        }
        .dot {
            display: inline-block;
            width: 6px;
            height: 6px;
            margin-right: 3px;
            background: #ccc;
            border-radius: 50%;
            animation: bounce 1.4s infinite;
        }
        @keyframes bounce {
            0%, 80%, 100% { transform: translateY(0) }
            40% { transform: translateY(-6px) }
        }

        /* markdown基础样式 */
        .markdown-content {
            line-height: 1.6;
            transition: opacity 0.3s;
        }

        .markdown-content:not(.markdown-rendered) {
            opacity: 0.5;
        }

        .markdown-content h1 { font-size: 2em; margin: 0.67em 0; }
        .markdown-content h2 { font-size: 1.5em; margin: 0.83em 0; }
        .markdown-content pre { 
            background: #f5f5f5;
            padding: 1em;
            border-radius: 4px;
            overflow-x: auto;
        }
    </style>
</head>
<body>
    <div id="chat-container"></div>
    <div id="input-container">
        <textarea id="user-input" placeholder="输入消息..." rows="1"></textarea>
        <button id="send-btn" onclick="sendMessage()">发送</button>
    </div>

    <script>
        const chatContainer = document.getElementById('chat-container');
        const userInput = document.getElementById('user-input');
        const sendBtn = document.getElementById('send-btn');

        // 滚动到底部
        function scrollToBottom() {
            
        }

        function renderMarkdown(options = {}) {
            // 合并配置参数
            const config = {
                selector: options.selector || '.markdown-content',
                breaks: options.breaks ?? true,
                gfm: options.gfm ?? true,
                highlight: options.highlight || null
            };

            // 配置Marked
            marked.setOptions({
                breaks: config.breaks,
                gfm: config.gfm,
                highlight: config.highlight
            });

            // 渲染处理器
            const render = () => {
                document.querySelectorAll(config.selector).forEach(container => {
                    if (container.dataset.rendered) return;
                    
                    // 创建虚拟容器避免内容闪烁
                    const virtualDiv = document.createElement('div');
                    virtualDiv.style.display = 'none';
                    virtualDiv.innerHTML = container.innerHTML.trim();
                    
                    // 执行Markdown转换
                    container.innerHTML = marked.parse(virtualDiv.innerHTML);
                    container.dataset.rendered = true;
                    
                    // 添加加载动画
                    container.classList.add('markdown-rendered');
                });
            };

            // 自动执行渲染
            if (document.readyState === 'complete') {
                render();
            } else {
                document.addEventListener('DOMContentLoaded', render);
            }
        }

        // 添加用户消息
        function addUserMessage(content) {
            const messageDiv = document.createElement('div');
            messageDiv.className = 'message user-message';
            messageDiv.innerHTML = `
                <div class="message-content">${content}</div>
            `;
            chatContainer.appendChild(messageDiv);
        }

        // 添加AI消息(流式)
        async function addAssistantMessageStream() {
            const messageDiv = document.createElement('div');
            messageDiv.className = 'message assistant-message';
            messageDiv.innerHTML = `
                <div class="message-content markdown-content">
                    <div class="typing-indicator">
                        <span class="dot"></span>
                        <span class="dot" style="animation-delay: 0.2s"></span>
                        <span class="dot" style="animation-delay: 0.4s"></span>
                    </div>
                </div>
            `;
            chatContainer.appendChild(messageDiv);
            return messageDiv.querySelector('.message-content');
        }

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

            sendBtn.disabled = true;
            userInput.disabled = true;
            userInput.value = '';
            
            addUserMessage(content);
            const responseContainer = await addAssistantMessageStream();

            try {
                const response = await fetch('/api/chat', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        // 如果需要认证
                        // 'Authorization': 'Bearer YOUR_TOKEN'
                    },
                    body: JSON.stringify({ message: content })
                });

                if (!response.ok) throw new Error('请求失败');
                
                await this.createStreamTypewriter(response, responseContainer, {});
                this.scrollToBottom();
            } catch (error) {
                responseContainer.innerHTML = '❌ 请求出错: ' + error.message;
            } finally {
                sendBtn.disabled = false;
                userInput.disabled = false;
                userInput.focus();
            }
        }

        // 输入框事件处理
        userInput.addEventListener('keydown', (e) => {
            if (e.key === 'Enter' && !e.shiftKey && !e.ctrlKey) {
                e.preventDefault();
                sendMessage();
            } else if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
                userInput.value += '\n';
            }
        });

        async function createStreamTypewriter(stream, container, options = {}) {
                const config = {
                    baseSpeed: 50,
                    maxSpeedup: 3,
                    retryCount: 3,
                    ...options
                };
                this.reader = null;

                // 状态控制
                let isDestroyed = false;
                let cursorVisible = true;
                let renderQueue = [];
                let retryCounter = 0;

                // DOM元素初始化
                const cursor = document.createElement('span');
                cursor.className = 'typewriter-cursor';
                cursor.textContent = '▌';
                container.append(cursor);

                // 光标动画
                const cursorInterval = setInterval(() => {
                    cursor.style.opacity = cursorVisible ? 1 : 0;
                    cursorVisible = !cursorVisible;
                }, 600);

                // 核心渲染逻辑
                const renderEngine = () => {
                    if (renderQueue.length === 0 || isDestroyed) return;

                    // 动态调速算法
                    const speed = Math.max(
                        config.baseSpeed / config.maxSpeedup,
                        config.baseSpeed - renderQueue.length * 2
                    );

                    const fragment = document.createDocumentFragment();
                    while (renderQueue.length > 0) {
                        const char = renderQueue.shift();
                        fragment.append(document.createTextNode(char));
                    }

                    container.insertBefore(fragment, cursor);
                    setTimeout(() => requestAnimationFrame(renderEngine), speed);
                };

                // 流数据处理
                const processStream = async () => {
                    try {
                        this.reader = stream.body.getReader();
                        // Fetch模式处理
                        while (!isDestroyed) {
                            const { done, value } = await this.reader.read();
                            if (done) break;
                            renderQueue.push(...new TextDecoder().decode(value).split(''));
                            if (!renderQueue.length) continue;
                            requestAnimationFrame(renderEngine);
                        }
                    } catch (err) {
                        if (retryCounter++ < config.retryCount && !isDestroyed) {
                            processStream();
                        } else {
                            destroy();
                            throw new Error('Stream connection failed');
                        }
                    } finally {
                        container.innerHTML = marked.parse(container.textContent.replace('▌', ''));
                        destroy();
                    }
                };

                // 资源清理
                const destroy = () => {
                    if (isDestroyed) return;
                    isDestroyed = true;
                    clearInterval(cursorInterval);
                    cursor.remove();
                    if (stream.cancel) stream.cancel();
                    if (stream.close) stream.close();
                };

                // 启动引擎
                processStream();
                return { destroy };
            };
    </script>
</body>
</html>

四、启动与优化

1. 运行系统
flask run --port 5001

访问 http://localhost:5001 即可开始对话 ,如下:
主页面

2. 性能优化技巧
  • 量化加速:使用ollama run deepseek-r1:7b --quantize q4_0 减少显存占用
  • GPU加速:在Ollama配置中启用CUDA支持

五、安全注意事项

  1. 端口防护
export OLLAMA_HOST=127.0.0.1  # 禁止外部访问
netstat -an | grep 11434      # 验证监听地址
  1. 防火墙规则
sudo ufw deny 11434/tcp      # 禁用Ollama外部端口

六、扩展应用场景

  1. 私有知识库:接入LangChain处理本地文档
  2. 自动化脚本:通过API实现代码生成/自动Debug
  3. 硬件控制:结合HomeAssistant实现语音智能家居

完整代码已开源(其实没有):GitHub仓库地址
通过本教程,您已掌握从模型部署到应用开发的全流程。本地化AI部署不仅降低成本,更保障了数据隐私,为开发者提供了真正的「AI自由」!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2300530.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STM32 CubeMx配置串口收发使用DMA并调用Idle模式(二)

本篇主要结合代码落实&#xff0c;之前串口已经配置好的DMA方式。 一、首先我们把串口看成一个对象&#xff0c;它有属性、私有数据和方法&#xff1b; 每个串口都有名字属性&#xff1b;有初始化、发送、接收方法&#xff1b;还有一个私有数据&#xff08;这个私有数据是每个…

使用Redis实现分布式锁,基于原本单体系统进行业务改造

一、单体系统下&#xff0c;使用锁机制实现秒杀功能&#xff0c;并限制一人一单功能 1.流程图&#xff1a; 2.代码实现&#xff1a; Service public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderSe…

Nginx内存池源代码剖析----ngx_create_pool函数

ngx_create_pool 是 Nginx 内存池 的初始化函数&#xff0c;负责创建并初始化一个内存池对象。它的作用是 为后续的内存分配操作提供统一的管理入口&#xff0c;通过预分配一块较大的内存区域&#xff0c;并基于此区域实现高效的内存分配、对齐管理和资源回收。 源代码定义&…

DeepSeek 开放平台无法充值 改用其他平台API调用DeepSeek-chat模型方法

近几天DeepSeek开放平台无法充值目前已经关闭状态&#xff0c;大家都是忙着接入DeepSeek模型 &#xff0c;很多人想使用DeepSeek怎么办&#xff1f; 当然还有改用其他平台API调用方法&#xff0c;本文以本站的提供chatgpt系统为例&#xff0c;如何修改DeepSeek-chat模型API接口…

QT基础一、学会建一个项目

注&#xff1a;因为CSDN有很多付费才能吃到的史&#xff0c;本人对此深恶痛绝&#xff0c;所以我打算出一期免费的QT基础入门专栏&#xff0c;这是QT基础知识的第一期&#xff0c;学会建一个项目&#xff0c;本专栏是适用于c / c基础不错的朋友的一个免费专栏&#xff0c;接下来…

科技引领未来,中建海龙C-MiC 2.0技术树立模块化建筑新标杆

在建筑行业追求高效与品质的征程中&#xff0c;中建海龙科技有限公司&#xff08;简称“中建海龙”&#xff09;以其卓越的创新能力和强大的技术实力&#xff0c;不断书写着装配式建筑领域的新篇章。1 月 10 日&#xff0c;由深圳安居集团规划&#xff0c;中建海龙与中海建筑共…

解锁养生秘籍,拥抱健康生活

在这个快节奏的时代&#xff0c;人们行色匆匆&#xff0c;常常在忙碌中忽略了健康。其实&#xff0c;养生并非遥不可及&#xff0c;它就藏在生活的细微之处&#xff0c;等待我们去发现和实践。 规律作息是健康的基础。日出而作&#xff0c;日落而息&#xff0c;顺应自然规律&am…

STM32 如何使用DMA和获取ADC

目录 背景 ‌摇杆的原理 程序 端口配置 ADC 配置 DMA配置 背景 DMA是一种计算机技术&#xff0c;允许某些硬件子系统直接访问系统内存&#xff0c;而不需要中央处理器&#xff08;CPU&#xff09;的介入&#xff0c;从而减轻CPU的负担。我们可以通过DMA来从外设&#xf…

细胞计数专题 | LUNA-FX7™新自动对焦算法提高极低细胞浓度下的细胞计数准确性

现代细胞计数仪采用自动化方法&#xff0c;在特定浓度范围内进行细胞计数。其上限受限于在高浓度条件下准确区分细胞边界的能力&#xff0c;而相机视野等因素则决定了下限。在图像中仅包含少量可识别细胞或特征的情况下&#xff0c;自动对焦可能会失效&#xff0c;从而影响细胞…

蓝桥杯备考:贪心算法之纪念品分组

P1094 [NOIP 2007 普及组] 纪念品分组 - 洛谷 这道题我们的贪心策略就是每次找出最大的和最小的&#xff0c;如果他们加起来不超过我们给的值&#xff0c;就分成一组&#xff0c;如果超过了&#xff0c;就把大的单独成一组&#xff0c;小的待定 #include <iostream> #i…

Win11配置wsl、ubuntu、docker

系统要求 安装WSL。 开通虚拟化&#xff1a; 准备工作 dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestartdism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestartwsl --set-default-versi…

尚硅谷爬虫note008

一、handler处理器 定制更高级的请求头 # _*_ coding : utf-8 _*_ # Time : 2025/2/17 08:55 # Author : 20250206-里奥 # File : demo01_urllib_handler处理器的基本使用 # Project : PythonPro17-21# 导入 import urllib.request from cgitb import handler# 需求&#xff…

matlab汽车动力学半车垂向振动模型

1、内容简介 matlab141-半车垂向振动模型 可以交流、咨询、答疑 2、内容说明 略 3、仿真分析 略 4、参考论文 略

【新品解读】AI 应用场景全覆盖!解码超高端 VU+ FPGA 开发平台 AXVU13F

「AXVU13F」Virtex UltraScale XCVU13P Jetson Orin NX 继发布 AMD Virtex UltraScale FPGA PCIE3.0 开发平台 AXVU13P 后&#xff0c;ALINX 进一步研究尖端应用市场&#xff0c;面向 AI 场景进行优化设计&#xff0c;推出 AXVU13F。 AXVU13F 和 AXVU13P 采用相同的 AMD Vir…

【Linux探索学习】第二十七弹——信号(上):Linux 信号基础详解

Linux学习笔记&#xff1a; https://blog.csdn.net/2301_80220607/category_12805278.html?spm1001.2014.3001.5482 前言&#xff1a; 前面我们已经将进程通信部分讲完了&#xff0c;现在我们来讲一个进程部分也非常重要的知识点——信号&#xff0c;信号也是进程间通信的一…

redis解决高并发看门狗策略

当一个业务执行时间超过自己设定的锁释放时间&#xff0c;那么会导致有其他线程进入&#xff0c;从而抢到同一个票,所有需要使用看门狗策略&#xff0c;其实就是开一个守护线程&#xff0c;让守护线程去监控key&#xff0c;如果到时间了还未结束&#xff0c;就会将这个key重新s…

MySQL-事务隔离级别

事务有四大特性&#xff08;ACID&#xff09;&#xff1a;原子性&#xff0c;一致性&#xff0c;隔离性和持久性。隔离性一般在事务并发的时候需要保证事务的隔离性&#xff0c;事务并发会出现很多问题&#xff0c;包括脏写&#xff0c;脏读&#xff0c;不可重复读&#xff0c;…

从入门到精通:Postman 实用指南

Postman 是一款超棒的 API 开发工具&#xff0c;能用来测试、调试和管理 API&#xff0c;大大提升开发效率。下面就给大家详细讲讲它的安装、使用方法&#xff0c;再分享些实用技巧。 一、安装 Postman 你能在 Postman 官网&#xff08;https://www.postman.com &#xff09;下…

sql sqlserver的特殊函数COALESCE和PIVOT的用法分析

一、COALESCE是一个返回参数中第一个非NULL值的函数&#xff0c; 列如&#xff1a;COALESCE&#xff08;a,b,c,d,e&#xff09;;可以按照顺序取abcde&#xff0c;中的第一个非空数据&#xff0c;abcde可以是表达式 用case when 加ISNULL也可以实现&#xff0c;但是写法复杂了…

智能猫眼实现流程图

物理端开发流程图 客户端端开发流程图 用户功能开发流程图 管理员开发流程图