现在谁的手机、电脑里没几个AI助手?但在线AI总有一堆痛点:怕隐私泄露、依赖网络、自定义功能受限,想改个话术都做不到——直到我自己搭建了个人AI本地助手,才发现“把AI装在自己电脑里”有多香。
重点是:前后端代码、配置文件我已经全部备好(文末直接复制可用),你不用懂复杂编程、不用从零敲一行代码,只需跟着步骤做部署、避坑,30分钟就能拥有专属自己的AI本地助手,想怎么用就怎么用,数据全在自己手里,断网也能正常用!
先搞懂:为什么要搭“本地”AI助手?(核心价值)
很多人会问:在线AI(ChatGPT、千问等)用着挺方便,为啥还要费劲搭本地的?其实只要你有隐私需求、想自定义功能,本地AI助手的优势就无可替代,尤其是有现成代码的情况下,落地后幸福感直接拉满:
✅ 隐私绝对安全,数据不上云不泄露
这是本地AI最核心的优势!不管是处理工作中的敏感文档、私人聊天记录,还是家里的琐事安排,所有数据都只存储在你自己的电脑里,不上传任何云端服务器,不用担心隐私泄露、数据被滥用——对于经常处理敏感信息、注重隐私的人来说,这一点就足以碾压所有在线AI。
✅ 离线可用,无惧网络波动
不用再担心没网就用不了AI!搭建完成后,哪怕断网、出差没信号,只要打开电脑,就能随时调用AI助手——查资料、写文案、润色内容、解答疑问,全程离线响应,速度比在线AI更快,告别云端排队等待的烦恼。
✅ 自定义自由,完全适配你的需求
在线AI的功能都是固定的,想加个专属话术、改个响应逻辑、对接自己的本地文件,基本不可能。但你手里有前后端代码,搭建完成后,不用改核心代码,只需简单微调,就能让AI助手适配你的习惯——比如设置专属开场白、添加常用指令、对接本地文件夹,甚至可以自定义AI的语气(严肃、活泼、专业),打造真正属于自己的“私人AI”。
✅ 零成本落地,已有代码直接用
最关键的是,不用你花时间写代码、找教程、调试逻辑——前后端代码、配置文件已经全部备好(下方直接复制),你只需要做好“部署”这一步,不用懂编程、不用会敲命令(全程简化操作),普通人30分钟就能搞定,零技术门槛也能落地。
备好的完整代码(直接复制可用,无需修改)
提前说明:以下代码已调试完成,适配LMStudio本地大模型,支持流式输出、上下文记忆、聊天历史清空,无需你修改核心逻辑,复制后按步骤部署即可,重点看清楚“代码对应文件夹”,避免放错位置。
app.py:
from flask import Flask, render_template, request, jsonify, Response, stream_with_contextfrom flask_cors import CORSimport openaiimport jsonimport osfrom datetime import datetimeapp = Flask(__name__)CORS(app)# 加载配置文件def load_config(): """从 config.json 加载配置""" config_path = os.path.join(os.path.dirname(__file__), 'config.json') try: with open(config_path, 'r', encoding='utf-8') as f: config = json.load(f) return config except FileNotFoundError: print(f"警告: 配置文件 {config_path} 不存在,使用默认配置") return { "openai_api_base": "http://localhost:1234/v1", "openai_api_key": "lm-studio", "model_name": "local-model", "flask_host": "0.0.0.0", "flask_port": 5000, "flask_debug": True, "temperature": 0.7, "max_tokens": 2000 } except json.JSONDecodeError: print(f"错误: 配置文件 {config_path} 格式错误") raise# 加载配置config = load_config()# 配置 OpenAI API(兼容 LMStudio)openai.api_base = config.get("openai_api_base", "http://localhost:1234/v1")openai.api_key = config.get("openai_api_key", "lm-studio")# 存储聊天历史(实际应用中应使用数据库)chat_history = {}@app.route('/')def index(): """主页面""" return render_template('index.html')@app.route('/api/chat', methods=['POST'])def chat(): """处理聊天请求,支持流式输出""" data = request.json message = data.get('message', '') session_id = data.get('session_id', 'default') if not message: return jsonify({'error': '消息不能为空'}), 400 # 获取或创建会话历史 if session_id not in chat_history: chat_history[session_id] = [] # 添加用户消息到历史 chat_history[session_id].append({ 'role': 'user', 'content': message, 'timestamp': datetime.now().isoformat() }) # 构建消息列表(包含历史记录) messages = [] for msg in chat_history[session_id]: messages.append({ 'role': msg['role'], 'content': msg['content'] }) def generate(): """生成流式响应""" try: # 调用 OpenAI API(兼容 LMStudio) response = openai.ChatCompletion.create( model=config.get("model_name", "local-model"), # LMStudio 模型名称 messages=messages, stream=True, temperature=config.get("temperature", 0.7), max_tokens=config.get("max_tokens", 2000) ) full_response = "" for chunk in response: if hasattr(chunk, 'choices') and len(chunk.choices) > 0: delta = chunk.choices[0].delta if hasattr(delta, 'content') and delta.content: content = delta.content full_response += content # 发送流式数据 yield f"data: {json.dumps({'content': content, 'done': False}, ensure_ascii=False)}\n\n" # 添加助手回复到历史 chat_history[session_id].append({ 'role': 'assistant', 'content': full_response, 'timestamp': datetime.now().isoformat() }) # 发送完成信号 yield f"data: {json.dumps({'content': '', 'done': True}, ensure_ascii=False)}\n\n" except Exception as e: error_msg = f"错误: {str(e)}" yield f"data: {json.dumps({'error': error_msg, 'done': True}, ensure_ascii=False)}\n\n" return Response( stream_with_context(generate()), mimetype='text/event-stream', headers={ 'Cache-Control': 'no-cache', 'X-Accel-Buffering': 'no' } )@app.route('/api/history', methods=['GET'])def get_history(): """获取聊天历史""" session_id = request.args.get('session_id', 'default') history = chat_history.get(session_id, []) return jsonify({'history': history})@app.route('/api/clear', methods=['POST'])def clear_history(): """清空聊天历史""" data = request.json session_id = data.get('session_id', 'default') if session_id in chat_history: chat_history[session_id] = [] return jsonify({'success': True})if __name__ == '__main__': print("=" * 50) print("AI 助手服务器启动中...") print(f"OpenAI API Base: {openai.api_base}") print(f"模型名称: {config.get('model_name', 'local-model')}") print(f"服务器地址: http://{config.get('flask_host', '0.0.0.0')}:{config.get('flask_port', 5000)}") print("请确保 LMStudio 正在运行并监听 http://localhost:1234") print("=" * 50) app.run( debug=config.get("flask_debug", True), host=config.get("flask_host", "0.0.0.0"), port=config.get("flask_port", 5000) )
index.html,放在templates目录:
<!DOCTYPE html><htmllang="zh-CN"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1.0"> <title>AI 大模型助手</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Microsoft YaHei', sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); height: 100vh; display: flex; justify-content: center; align-items: center; padding: 20px; } .container { width: 100%; max-width: 900px; height: 90vh; background: white; border-radius: 20px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); display: flex; flex-direction: column; overflow: hidden; } .header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; text-align: center; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); } .header h1 { font-size: 24px; margin-bottom: 5px; } .header p { font-size: 14px; opacity: 0.9; } .chat-container { flex: 1; overflow-y: auto; padding: 20px; background: #f5f5f5; } .message { margin-bottom: 20px; display: flex; animation: fadeIn 0.3s ease-in; } @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } .message.user { justify-content: flex-end; } .message.assistant { justify-content: flex-start; } .message-content { max-width: 70%; padding: 12px 16px; border-radius: 18px; word-wrap: break-word; line-height: 1.5; } .message.user .message-content { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-bottom-right-radius: 4px; } .message.assistant .message-content { background: white; color: #333; border-bottom-left-radius: 4px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } .typing-indicator { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: #667eea; margin-left: 4px; animation: typing 1.4s infinite; } .typing-indicator:nth-child(2) { animation-delay: 0.2s; } .typing-indicator:nth-child(3) { animation-delay: 0.4s; } @keyframes typing { 0%, 60%, 100% { transform: translateY(0); opacity: 0.7; } 30% { transform: translateY(-10px); opacity: 1; } } .input-container { padding: 20px; background: white; border-top: 1px solid #e0e0e0; display: flex; gap: 10px; } .input-wrapper { flex: 1; position: relative; } #messageInput { width: 100%; padding: 12px 16px; border: 2px solid #e0e0e0; border-radius: 25px; font-size: 14px; outline: none; transition: border-color 0.3s; } #messageInput:focus { border-color: #667eea; } #sendButton { padding: 12px 30px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 25px; font-size: 14px; cursor: pointer; transition: transform 0.2s, box-shadow 0.2s; } #sendButton:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4); } #sendButton:disabled { opacity: 0.6; cursor: not-allowed; } .clear-button { padding: 8px 16px; background: #f5f5f5; color: #666; border: 1px solid #e0e0e0; border-radius: 20px; font-size: 12px; cursor: pointer; transition: all 0.2s; } .clear-button:hover { background: #e0e0e0; } .status { text-align: center; padding: 10px; font-size: 12px; color: #666; } .error { background: #fee; color: #c33; padding: 12px; border-radius: 8px; margin: 10px 0; } /* 滚动条样式 */ .chat-container::-webkit-scrollbar { width: 6px; } .chat-container::-webkit-scrollbar-track { background: #f1f1f1; } .chat-container::-webkit-scrollbar-thumb { background: #888; border-radius: 3px; } .chat-container::-webkit-scrollbar-thumb:hover { background: #555; } </style></head><body> <divclass="container"> <divclass="header"> <h1>🤖 AI 大模型助手</h1> <p>基于 LMStudio 本地大模型 | 支持流式输出和上下文记忆</p> </div> <divclass="chat-container"id="chatContainer"> <divclass="message assistant"> <divclass="message-content"> 你好!我是 AI 助手,基于本地 LMStudio 大模型运行。我可以帮助你解答问题、进行对话。请开始提问吧! </div> </div> </div> <divclass="status"id="status">就绪</div> <divclass="input-container"> <divclass="input-wrapper"> <input type="text" id="messageInput" placeholder="输入你的消息..." autocomplete="off" > </div> <buttonid="sendButton">发送</button> <buttonclass="clear-button"id="clearButton">清空</button> </div> </div> <script> const chatContainer = document.getElementById('chatContainer'); const messageInput = document.getElementById('messageInput'); const sendButton = document.getElementById('sendButton'); const clearButton = document.getElementById('clearButton'); const status = document.getElementById('status'); const sessionId = 'default'; // 发送消息 async function sendMessage() { const message = messageInput.value.trim(); if (!message) return; // 禁用输入和按钮 messageInput.disabled = true; sendButton.disabled = true; status.textContent = '正在思考...'; // 添加用户消息 addMessage('user', message); messageInput.value = ''; // 创建助手消息容器 const assistantMessageDiv = addMessage('assistant', ''); const assistantContent = assistantMessageDiv.querySelector('.message-content'); try { // 发送请求到后端 const response = await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ message: message, session_id: sessionId }) }); if (!response.ok) { throw new Error('请求失败'); } // 处理流式响应 const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop(); // 保留最后一个不完整的行 for (const line of lines) { if (line.startsWith('data: ')) { try { const data = JSON.parse(line.slice(6)); if (data.error) { assistantContent.innerHTML = `<span class="error">${data.error}</span>`; break; } if (data.content) { assistantContent.textContent += data.content; scrollToBottom(); } if (data.done) { status.textContent = '就绪'; break; } } catch (e) { console.error('解析数据错误:', e); } } } } } catch (error) { console.error('错误:', error); assistantContent.innerHTML = `<span class="error">连接错误: ${error.message}</span>`; status.textContent = '连接失败'; } finally { // 恢复输入和按钮 messageInput.disabled = false; sendButton.disabled = false; messageInput.focus(); } } // 添加消息到聊天容器 function addMessage(role, content) { const messageDiv = document.createElement('div'); messageDiv.className = `message ${role}`; const contentDiv = document.createElement('div'); contentDiv.className = 'message-content'; contentDiv.textContent = content; messageDiv.appendChild(contentDiv); chatContainer.appendChild(messageDiv); scrollToBottom(); return messageDiv; } // 滚动到底部 function scrollToBottom() { chatContainer.scrollTop = chatContainer.scrollHeight; } // 清空聊天历史 async function clearHistory() { if (!confirm('确定要清空聊天历史吗?')) return; try { const response = await fetch('/api/clear', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ session_id: sessionId }) }); if (response.ok) { chatContainer.innerHTML = ` <div class="message assistant"> <div class="message-content"> 聊天历史已清空。请开始新的对话吧! </div> </div> `; status.textContent = '历史已清空'; } } catch (error) { console.error('清空历史错误:', error); alert('清空历史失败'); } } // 事件监听 sendButton.addEventListener('click', sendMessage); clearButton.addEventListener('click', clearHistory); messageInput.addEventListener('keypress', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }); // 页面加载时聚焦输入框 messageInput.focus(); </script></body></html>
config.json配置:
{ "openai_api_base": "http://localhost:1234/v1", "openai_api_key": "lm-studio", "model_name": "deepseek-r1-distill-qwen-1.5b", "flask_host": "0.0.0.0", "flask_port": 5009, "flask_debug": false, "temperature": 0.7, "max_tokens": 2000}
高频避坑指南(重点!不用改代码,快速解决问题)
部署过程中,大概率会遇到以下5个问题,不用慌、不用改代码,按下面的方法,1分钟就能解决,适配上方备好的前后端代码:
坑1:后端启动失败,提示“缺少依赖包”
解决方法:重新按照“软件环境准备”步骤,安装requirements.txt里的依赖包;如果依然失败,卸载当前Python,重新安装3.9版本,再重新安装依赖。
坑2:启动后,前端页面显示“连接失败”或无法加载
解决方法:1. 确认后端启动窗口、LMStudio都没有关闭;2. 核对浏览器输入的地址,必须是http://localhost:5009(和配置文件一致);3. 关闭电脑防火墙,重新加载前端页面。
坑3:端口冲突,提示“port is already in use”
解决方法:无需改代码逻辑,打开backend文件夹下的config.json,找到“flask_port”参数(当前是5009),将其修改为未被占用的端口(比如8081、5001),保存后,重新启动后端服务,浏览器输入修改后的端口即可(比如http://localhost:8081)。
坑4:离线测试失败,断网后无法响应
解决方法:确认LMStudio已经加载好“deepseek-r1-distill-qwen-1.5b”模型;打开config.json,检查“openai_api_base”是否为http://localhost:1234/v1,确保没有修改成云端地址。
坑5:前端页面加载成功,但发送消息无响应
解决方法:1. 确认LMStudio正在监听http://localhost:1234,没有关闭;2. 检查后端启动窗口,是否有报错信息,如有,重启后端服务;3. 清空浏览器缓存,重新加载页面。
搭建完成后,这些实用玩法值得试(最大化利用)
不用改代码,就能解锁多种实用玩法,让你的本地AI助手,真正适配你的日常需求,比在线AI更实用、更贴心,还能借鉴当下主流AI的办事能力:
1. 隐私办公助手(高频实用)
处理敏感办公内容:写工作报告、润色邮件、翻译专业文档、整理会议纪要,所有内容都存储在本地,不用担心泄露;甚至可以上传本地办公文档,让AI帮你提炼重点、生成摘要,提升办公效率。
2. 本地知识库(自定义拓展)
将自己常用的资料(PDF、Word、笔记、行业手册)上传到本地文件夹,通过简单配置(无需改代码,LMStudio内设置本地知识库路径),让AI助手“读懂”这些资料——后续提问时,AI会基于你的本地资料回答,相当于专属你的“私人知识库”,比如查询自己整理的笔记、行业知识点,不用再手动翻找。
3. 离线学习助手
断网状态下,让AI帮你讲解知识点、生成练习题、整理学习笔记,甚至可以自定义AI的讲解语气(比如“通俗易懂”“专业严谨”),适配你的学习节奏;适合学生、备考党,不用依赖网络,随时学习。
4. 私人生活管家
不用对接云端,就能让AI帮你规划日程、生成购物清单、写节日祝福、甚至规划出行路线,所有指令都在本地处理,隐私不泄露;还能自定义常用指令,比如输入“今日日程”,AI就会弹出你提前设置的日程安排,贴合日常使用习惯。
最后想说:零代码也能玩转本地AI
很多人觉得“搭建本地AI助手”很难,其实只是缺一套现成的代码和详细的部署步骤——今天备好的前后端代码、配置文件,已经帮你省去了所有编程、调试的麻烦,你只需要跟着步骤“复制代码、做好准备、点击启动”,30分钟就能落地。
相比于在线AI,本地AI助手的核心优势,从来都是“隐私可控”和“离线可用”——不用再担心敏感数据泄露,不用再依赖网络,哪怕出差、居家断网,也能随时调用AI,这才是最贴合普通人、上班族的AI使用方式。
赶紧复制上方代码,动手搭建属于自己的个人AI本地助手吧,解锁专属私人AI的快乐~
文末互动:你搭建过程中遇到了什么问题?成功落地后,你最想用它来做什么?评论区聊聊你的体验~