老攻城狮的AI开发环境搭建全记录:从零到跑通本地大模型(一日速通版)

老攻城狮的AI开发环境搭建全记录:从零到跑通本地大模型(一日速通版)
【导航台账】老蒋的技术博客全系列文章汇总持续更新一台Windows笔记本没有独显没有云服务从拆封新机到跑通本地AI聊天应用用了我整整一天。本文记录了我作为传统制造企业IT老兵如何用一台自购设备完成AI开发环境“从零到一”的全过程。作者Javy21javy21csdn时间2026年6月21日环境华为MateBook 16S / Windows 11 家庭版 / WSL2 Ubuntu 22.04一、写在前面我是做传统企业信息化出身最熟悉的是MySQL、GaussDB和Java/Python。AI浪潮来了不想只当看客。手头没有GPU、没有云服务预算咬牙自费买了一台华为MateBook 16Si7-250H 32GB内存 1TB硬盘。一天时间从拆封新机到跑通AI聊天应用。本文就是这一天的完整记录。效果图二、环境概览项目配置笔记本华为MateBook 16S (2025款)CPUIntel Core 7 250H14核20线程内存32GB DDR5硬盘1TB NVMe SSD显卡Intel集成显卡无独显操作系统Windows 11 家庭版 (Build 26200)Windows用户名javy21目标模型Qwen2.5:7B (量化版约4.7GB)核心思路WSL2 CPU推理 内存替代显存。32GB内存 现代CPU跑7B量化模型推理速度约5-8 token/秒学习场景完全够用。三、部署全流程第一阶段系统准备与WSL2安装3.1 关于VBS基于虚拟化的安全性Windows 11默认开启VBS安全机制会消耗约10-20%的CPU性能。我的决策暂不关闭。当前阶段跑7B模型性能足够安全是底线。等真正遇到性能瓶颈再考虑关闭。powershell# 查看VBS状态Windows PowerShell管理员模式 systeminfo | findstr 虚拟化 # 如果显示正在运行——正常不用管3.2 开启WSL2以管理员身份打开PowerShell依次执行powershell# 开启Windows功能 dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart # 重启电脑后设置WSL2为默认版本 wsl --set-default-version 2注意如果wsl --set-default-version 2提示未安装WSL直接跳过下一步安装Ubuntu时会自动补全。3.3 安装Ubuntu 22.04powershell# 安装Ubuntu 22.04 LTS指定版本避免不确定性 wsl --install -d Ubuntu-22.04首次启动设置用户名和密码务必记住。安装后第一步更换APT国内镜像源清华源速度提升10倍以上bash# 进入WSL后执行 # 备份原始源 sudo cp /etc/apt/sources.list /etc/apt/sources.list.backup # 替换为清华源Ubuntu 22.04适用 sudo sed -i s|archive.ubuntu.com|mirrors.tuna.tsinghua.edu.cn|g /etc/apt/sources.list sudo apt update sudo apt upgrade -y3.4 配置WSL资源限制在Windows中创建C:\Users\javy21\.wslconfig注意javy21是你的Windows用户名ini[wsl2] memory16GB # 限制WSL最多用16GB留16GB给Windows processors12 # 留2个核心给Windows系统 swap8GB # 交换分区防止内存溢出 localhostForwardingtrue保存后执行wsl --shutdown重启WSL生效。3.5 创建系统备份安全兜底每完成一个阶段立即备份这是最重要的习惯。powershell# 查看发行版确切名称 wsl -l -v # 导出Ubuntu纯净版备份发行版名称以实际为准 wsl --export Ubuntu-22.04 D:\WSL_Backup\Ubuntu_Base.tar第二阶段Python环境与开发工具4.1 安装Windows端Python供IDE使用下载地址python.org/downloads/windows选择版本Python 3.12.10Windows installer 64-bit关键安装时务必勾选Add Python to PATH⚠️避坑AI生态LangChain、Chroma、Transformers最高稳定支持到Python 3.12。不要装3.14测试版大量库不兼容。4.2 安装PyCharm Community Edition下载地址jetbrains.com/pycharm/download选择Community版免费安装时勾选Add bin folder to PATH和Add launchers dir to the PATH版本说明Community版不支持WSL远程解释器。如果遇到此限制可改用VSCode Remote-WSL插件体验同样优秀。4.3 WSL中安装Python环境bash# 进入WSL sudo apt install -y python3 python3-pip python3-venv python-is-python3 # 验证 python --version # 应显示Python 3.10.x说明Ubuntu 22.04默认Python 3.10这是发行版的稳定策略不影响AI库使用。4.4 创建项目虚拟环境bashcd ~ mkdir ai_project cd ai_project python -m venv venv source venv/bin/activate每次开发前记得激活source ~/ai_project/venv/bin/activate第三阶段Ollama部署5.1 架构决策Ollama装在哪里方案优点缺点Ollama on Windows安装简单系统托盘管理方便需配置防火墙WSL访问需跨IPOllama on WSL推荐localhost直接访问无需额外配置需手动管理服务进程推荐直接装在WSL中本次实践采用此方案bash# 在WSL中安装Ollama curl -fsSL https://ollama.com/install.sh | sh # 设置模型存储路径建议放在WSL内部或挂载的D盘 export OLLAMA_MODELS/home/javy21/ai_models/ollama # 启动服务后台运行 ollama serve 这样后续所有代码直接用http://localhost:11434访问无需配置防火墙、无需动态探测IP。5.2 下载并测试模型bash# 拉取Qwen2.5:7B模型约4.7GB ollama run qwen2.5:7b # 测试对话 你好请介绍一下你自己 # 等待回复输入/bye退出第四阶段Python依赖安装在WSL虚拟环境中安装bashsource ~/ai_project/venv/bin/activate # 用清华源加速默认国外源极慢 pip install flask requests -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn # 永久配置PyPI镜像源 pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple第五阶段PyCharm集成WSL解释器PyCharm → File → Settings → Project → Python Interpreter点击齿轮 → Add → WSLDistribution:Ubuntu-22.04Interpreter path:/home/javy21/ai_project/venv/bin/python终端配置可选Settings → Tools → Terminal → Shell path:wsl.exe第六阶段Flask Web应用开发6.1 项目结构text~/ai_project/web_chat/ ├── app.py # Flask主程序 ├── templates/ │ └── index.html # 前端页面 └── venv/ # 共享虚拟环境6.2 核心代码带完整注解app.py—— Flask后端主程序python#!/usr/bin/env python3 # -*- coding: utf-8 -*- AI聊天应用 - Flask后端服务 功能接收前端用户输入调用本地Ollama模型生成回复 作者Javy21 环境WSL2 Ubuntu 22.04 Python 3.10 import socket import re import requests from flask import Flask, render_template, request, jsonify # 创建Flask应用实例 app Flask(__name__) def get_ollama_host(): 自动探测可用的Ollama服务地址 优先级localhost /etc/resolv.conf中的nameserver 固定IP后备 返回可用的Ollama服务IP地址 # 1. 优先尝试localhostOllama装在WSL内部时直接可用 for host in [localhost, 127.0.0.1]: try: sock socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(1) if sock.connect_ex((host, 11434)) 0: sock.close() return host sock.close() except Exception: pass # 2. 从/etc/resolv.conf获取网关IPWSL访问Windows宿主机场景 try: with open(/etc/resolv.conf, r) as f: content f.read() # 匹配nameserver行提取IP地址 ips re.findall(rnameserver\s([\d.]), content) for ip in ips: sock socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(1) if sock.connect_ex((ip, 11434)) 0: sock.close() return ip sock.close() except Exception: pass # 3. 后备方案固定IP根据实际网络环境修改 return 192.168.1.28 # 构建Ollama API地址启动时打印便于调试 OLLAMA_URL fhttp://{get_ollama_host()}:11434/api/generate print(f✅ Ollama服务地址: {OLLAMA_URL}) app.route(/) def index(): 渲染主页面 return render_template(index.html) app.route(/chat, methods[POST]) def chat(): 处理用户消息调用Ollama模型生成回复 请求体JSON格式{message: 用户输入内容} 返回JSON格式{reply: 模型回复内容} 或 {error: 错误信息} # 解析请求数据 data request.get_json() if not data: return jsonify({error: 无效的请求数据}), 400 prompt data.get(message, ).strip() if not prompt: return jsonify({error: 请输入内容}), 400 # 构造Ollama API请求体 payload { model: qwen2.5:7b, # 使用的模型名称 prompt: prompt, # 用户输入 stream: False # 非流式输出一次性返回完整结果 } try: # 调用Ollama API超时设为120秒模型推理可能需要较长时间 resp requests.post(OLLAMA_URL, jsonpayload, timeout120) resp.raise_for_status() # 非200状态码抛出异常 result resp.json() reply result.get(response, 模型未返回内容) return jsonify({reply: reply}) except requests.exceptions.Timeout: return jsonify({error: 模型推理超时请稍后重试}), 500 except requests.exceptions.ConnectionError: return jsonify({error: 无法连接到Ollama服务请检查服务是否运行}), 500 except Exception as e: return jsonify({error: f服务器内部错误: {str(e)}}), 500 if __name__ __main__: # 启动Flask服务 # host0.0.0.0 监听所有网络接口允许局域网内其他设备访问 # port5000 默认端口 # debugTrue 开发模式代码修改后自动重启 app.run(host0.0.0.0, port5000, debugTrue)templates/index.html—— 前端页面html!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title本地AI聊天/title style /* ---------- 全局重置与布局 ---------- */ * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: Segoe UI, sans-serif; background: #f0f2f5; height: 100vh; display: flex; justify-content: center; align-items: center; } .chat-container { width: 100%; max-width: 700px; height: 90vh; background: white; border-radius: 16px; box-shadow: 0 4px 20px rgba(0,0,0,0.1); display: flex; flex-direction: column; overflow: hidden; } /* ---------- 头部 ---------- */ .chat-header { padding: 20px; background: #2d5a88; color: white; text-align: center; font-size: 18px; font-weight: 600; } /* ---------- 消息区域 ---------- */ .chat-messages { flex: 1; padding: 20px; overflow-y: auto; background: #fafafa; } .message { margin-bottom: 16px; display: flex; flex-direction: column; } .message.user { align-items: flex-end; } .message.assistant { align-items: flex-start; } .message .bubble { max-width: 80%; padding: 12px 16px; border-radius: 12px; word-wrap: break-word; line-height: 1.6; } .message.user .bubble { background: #2d5a88; color: white; border-bottom-right-radius: 4px; } .message.assistant .bubble { background: #e9ecef; color: #1e1e1e; border-bottom-left-radius: 4px; } .message .label { font-size: 12px; color: #888; margin-bottom: 4px; padding: 0 4px; } /* ---------- 加载状态 ---------- */ .loading { color: #888; font-style: italic; padding: 8px 16px; background: #f0f0f0; border-radius: 12px; display: inline-block; } .error { color: #d9534f; background: #f8d7da; padding: 8px 16px; border-radius: 12px; } /* ---------- 输入区域 ---------- */ .chat-input-area { display: flex; padding: 16px 20px; border-top: 1px solid #ddd; background: white; } .chat-input-area input { flex: 1; padding: 10px 14px; border: 1px solid #ccc; border-radius: 24px; outline: none; font-size: 15px; } .chat-input-area input:focus { border-color: #2d5a88; } .chat-input-area button { margin-left: 12px; padding: 10px 24px; background: #2d5a88; color: white; border: none; border-radius: 24px; font-size: 15px; cursor: pointer; transition: background 0.2s; } .chat-input-area button:hover { background: #1f4569; } .chat-input-area button:disabled { background: #a0b8d0; cursor: not-allowed; } /style /head body div classchat-container !-- 头部 -- div classchat-header 本地 AI 助手 (Qwen2.5:7B)/div !-- 消息列表 -- div classchat-messages idmessages div classmessage assistant span classlabelAI/span div classbubble你好我是运行在你本地的 Qwen 模型有什么我可以帮你的吗/div /div /div !-- 输入区域 -- div classchat-input-area input typetext iduserInput placeholder输入你的问题... / button idsendBtn发送/button /div /div script (function() { use strict; // DOM元素引用 const messagesDiv document.getElementById(messages); const userInput document.getElementById(userInput); const sendBtn document.getElementById(sendBtn); /** * 添加一条消息到聊天区 * param {string} role - user 或 assistant * param {string} content - 消息内容 */ function addMessage(role, content) { const div document.createElement(div); div.className message ${role}; const label document.createElement(div); label.className label; label.textContent role user ? 你 : AI; const bubble document.createElement(div); bubble.className bubble; // 支持换行显示 bubble.innerHTML content.replace(/\n/g, br); div.appendChild(label); div.appendChild(bubble); messagesDiv.appendChild(div); messagesDiv.scrollTop messagesDiv.scrollHeight; } /** * 显示/隐藏思考中...加载状态 */ function showLoading() { const div document.createElement(div); div.className message assistant; div.id loadingMsg; const label document.createElement(div); label.className label; label.textContent AI; const bubble document.createElement(div); bubble.className bubble loading; bubble.textContent 思考中...; div.appendChild(label); div.appendChild(bubble); messagesDiv.appendChild(div); messagesDiv.scrollTop messagesDiv.scrollHeight; } function removeLoading() { const loading document.getElementById(loadingMsg); if (loading) loading.remove(); } /** * 发送用户消息到后端 */ async function sendMessage() { const text userInput.value.trim(); if (!text) return; // 显示用户消息 addMessage(user, text); userInput.value ; sendBtn.disabled true; userInput.disabled true; // 显示加载状态 showLoading(); try { const response await fetch(/chat, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ message: text }) }); const data await response.json(); removeLoading(); if (response.ok) { addMessage(assistant, data.reply); } else { addMessage(assistant, ❌ 错误: ${data.error || 未知错误}); } } catch (err) { removeLoading(); addMessage(assistant, ❌ 网络错误: ${err.message}); } finally { sendBtn.disabled false; userInput.disabled false; userInput.focus(); } } // ---------- 事件绑定 ---------- sendBtn.addEventListener(click, sendMessage); userInput.addEventListener(keydown, function(e) { if (e.key Enter) { sendMessage(); } }); })(); /script /body /html6.3 运行与验证bashcd ~/ai_project/web_chat source ../venv/bin/activate python app.py浏览器访问http://localhost或WSL的IP地址:5000输入问题即可对话。四、踩坑记录与解决方案问题现象原因解决方案备份失败WSL_E_DISTRO_NOT_FOUND发行版名称不匹配wsl -l -v查看确切名称后重试pip安装超慢几KB/s甚至断连默认PyPI源在国外换清华源-i https://pypi.tuna.tsinghua.edu.cn/simpleOllama连接拒绝Connection refused默认只绑定127.0.0.1设置OLLAMA_HOST0.0.0.0:11434Windows方案或直接装WSL内防火墙拦截WSL中curl超时Windows防火墙拦截添加入站规则允许TCP 11434或改用WSL内部localhostPyCharm无WSL选项Community版不支持版本限制改用VSCode Remote-WSL或升级Professional五、不足与改进方向不足改进方向未使用流式输出用SSE或WebSocket实现打字机效果无对话记忆维护消息历史实现多轮对话无模型切换前端增加下拉菜单选择不同模型六、总结一天时间从零到跑通本地AI聊天应用。这不是终点而是起点。后续可以继续探索本地PDF知识库问答RAG多模态图搜 图片描述Agent工具调用AI时代最贵的不是显卡而是行动力。从知道到做到的距离只有一步——动手。作者Javy21javy21csdn原文链接https://blog.csdn.net/javy21/article/details/xxxxxxx代码仓库https://github.com/javy21/ai-chat-demo本文采用CC BY-NC 4.0许可协议。欢迎转载请注明出处。