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

news/2025/2/21 20:38:36/

从零开始部署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 ollamacreate      Create a model from a Modelfileshow        Show information for a modelrun         Run a modelpull        Pull a model from a registrypush        Push a model to a registrylist        List modelsps          List running modelscp          Copy a modelrm          Remove a modelhelp        Help about any commandFlags:-h, --help      help for ollama-v, --version   Show version informationUse "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对话系统

flaskdeepseek_113">备注说明:选型flask及代码编写基本是由deepseek完成,本人只负责提问和修缮
1. 项目结构
deepseek-web/
├── app.py          # Flask主程序
├── templates/
│   └── index.html  # 聊天界面
2. Flask后端实现(app.py)
python"># app.py
from flask import Flask, render_template, request, Response
import ollama
import logging
from datetime import datetimeapp = 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};// 配置Markedmarked.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.ppmy.cn/news/1573976.html

相关文章

Linux-ubuntu系统移植之Uboot启动流程

Linux-ubuntu系统移植之Uboot启动流程 一&#xff0c;Uboot启动流程1.Uboot的两阶段1.1.第一阶段1.11.硬件初始化1.12.复制 U-Boot 到 RAM1.13.跳转到第二阶段 1.2.第二阶段1.21.C 语言环境初始化1.22. 硬件设备初始化1.23. 加载环境变量1.24. 显示启动信息1.25. 等待用户输入&…

详解Nginx 配置

一、Nginx 介绍 Nginx 是一款轻量级的 Web 服务器 / 反向代理服务器及电子邮件&#xff08;IMAP/POP3&#xff09;代理服务器。它由俄罗斯的程序设计师 Igor Sysoev 所开发&#xff0c;自 2004 年发布以来&#xff0c;凭借其高性能、低内存消耗、高并发处理能力等特点&#xf…

openharmony中hdf框架的设备节点(HdfDeviceNode)和设备实例(HdfDevice)的区别

设备节点&#xff08;HdfDeviceNode&#xff09;和设备实例&#xff08;HdfDevice&#xff09;的区别 在 OpenHarmony 驱动框架 HDF 中&#xff0c;设备节点&#xff08;HdfDeviceNode&#xff09; 和 设备实例&#xff08;HdfDevice&#xff09; 是两个重要的概念&#xff0c…

整合Salesmart/WhatsApp、开源Odoo模块和Deepseek AI能力,实现针对国外客户的智能客服和个性化推荐服务

一、项目背景 本文提出了一套针对软管制造公司的智能客服与个性化推荐系统实施方案&#xff0c;旨在通过整合开源Odoo模块、Salesmart/WhatsApp以及Deepseek AI能力&#xff0c;打造一个724小时不间断服务的智能化平台&#xff0c;专注于服务国外客户。方案围绕实现不间断服务…

python游戏库入门:ursina游戏引擎——复原《我的世界》

目录 &#xff1a; 主要特性 依赖库&#xff1a; 第一个 ursina代码&#xff1a; 创建一个物体&#xff1a; 代码解读&#xff1a; 复原《我的世界》&#xff1a; 总结 Ursina 是一个用于创建 3D 游戏和应用程序的 Python 游戏引擎。它基于 Panda3D 引擎构建&#xff0c…

ARM Linux平台下 OpenCV Camera 实验

一、硬件原理 1. OV2640 1.1 基本功能 OV2640 是一款低功耗、高性能的图像传感器&#xff0c;支持以下功能&#xff1a; 最高分辨率&#xff1a;200 万像素&#xff08;1600x1200&#xff09;。 输出格式&#xff1a;JPEG、YUV、RGB。 内置图像处理功能&#xff1a;自动曝…

Java Swing 学习笔记

一、什么是Swing swing是一个比较老的技术了&#xff0c;我觉得学习它还是很有必要的&#xff0c;也比较容易激发学习的兴趣。 Swing 是构建在 AWT&#xff08;Abstract Window Toolkit&#xff09;之上的轻量级 GUI 工具包&#xff0c;用于创建跨平台的图形用户界面。与 AWT…

老游戏回顾:SOR

被称为QTE之子&#xff0c;销量约130w。CE引擎打造&#xff0c;画面精美。 以罗马帝国为背景的一款动作游戏&#xff0c;游戏中玩家将化身罗马将领马略提图斯&#xff08;Marius Titus&#xff09;&#xff0c;为了替家族的死亡复仇而投身军旅&#xff0c;率领罗马战士展开一场…