从零开始部署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对话系统
备注说明:选型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 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支持
五、安全注意事项
- 端口防护:
export OLLAMA_HOST=127.0.0.1 # 禁止外部访问
netstat -an | grep 11434 # 验证监听地址
- 防火墙规则:
sudo ufw deny 11434/tcp # 禁用Ollama外部端口
六、扩展应用场景
- 私有知识库:接入LangChain处理本地文档
- 自动化脚本:通过API实现代码生成/自动Debug
- 硬件控制:结合HomeAssistant实现语音智能家居
完整代码已开源(其实没有):GitHub仓库地址
通过本教程,您已掌握从模型部署到应用开发的全流程。本地化AI部署不仅降低成本,更保障了数据隐私,为开发者提供了真正的「AI自由」!