ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

本地AI学习软件实战:Python+Flask+Ollama搭建私有化智能助教

本地AI学习软件实战:Python+Flask+Ollama搭建私有化智能助教 三个月前我做的本地 AI 学习软件终于能稳定跑起来了。这个小工具说来也不复杂基于 Python 和 Flask 的一套本地网页应用底层调用 Ollama 加载开源大模型所有推理都在自己的电脑上完成不需要联网调任何云端接口。说白了它就是一个“完全属于自己的 AI 助教”资料放进来模型在自己机器上算聊天记录、训练数据全都留在本地。项目本身是免费开源的代码量不大每个文件都能用“一张纸讲清楚”所以也很适合当作本地 AI 应用的基础模板来研究。我平时是这样用它的备课时丢两页 PDF 进去让模型先列出核心术语遇到教材里说不清楚的概念追问时要求模型先回到原文再回答不许张口就来期末复习阶段把错题导进去让它按知识点生成同类练习题。用过云端 AI 的人可能觉得这也没什么稀奇但关键在于我不用把课堂名单、学生作业、内部讲义传到任何人的服务器上模型也不知道我是谁。冲着这一点它就值得单独写一篇完整的实操分享。这篇文章既写给想直接“抄作业”的人也写给准备自己做本地 AI 工具的同学。我会把设计思路、技术选型、完整搭建过程、调优踩坑全部摊开讲你可以照着敲也可以只挑自己需要的章节看。1. 为什么我要做“本地运行”的 AI 学习软件先聊点动机层面的东西。我见过不少想做 AI 学习工具的人第一反应都是“套一个 OpenAI 的接口就完事了”。这个方案确实快但是用在教育场景里我心里始终有几个过不去的坎。1.1 云端学习工具的三个老问题第一个是隐私。学生笔记、教案、论文初稿这些东西本质上都属于“半公开但不希望被收集”的资料。我之前用某款云端笔记 AI上传一段备课内容后过两天发现推荐流里出现了风格极其相似的内容虽然不能确定是巧合还是训练数据串了但那种“资料被拿去喂模型”的感觉非常不舒服。学习场景对隐私的敏感度比很多人想象得高。第二个是稳定性和成本。云端接口按 token 计费聊一次长文档可能几毛钱看起来不贵但真到了学期末集中复习每天几十次提问账单很快就不耐看了。而且一旦服务商调整接口、限流、停服整个工具就瘫了。我见过有人上午还在给学生演示 AI 辅助学习下午接口报错课都没法上。第三个是定制空间太窄。云端模型你只能用别人封装好的功能想做“从原文出发”“不猜测教材内容”“按教学大纲重新组织答案”这类定制几乎不可能。聪明一点的用户会写长 prompt但 system 层的东西依然捏在平台手里。1.2 本地运行反而更实在我一开始也担心本地运行会有性能瓶颈但这两年开源模型的进展比我预期快得多。现在一台普通的 16GB 内存笔记本跑一个 7B 参数的量化模型已经可以做到每秒输出六到十个字应付日常问答和资料总结足够。更关键的是本地运行带来了几个云服务给不了的特性断网可用。宿舍没网、高铁上、图书馆弱网环境我一样能查资料、问问题这对学生党是真香。零增量成本。模型参数已经下到本地问多少次都只花电费没有按 token 计费的心理负担。完全可定制。prompt、上下文逻辑、界面、导出格式全部自己说了算想怎么改都行。数据不出设备。我自己的资料、学生的错题集只存在于这台电脑的硬盘里没有第三方能看到。有人会问本地模型能力是不是比云端弱很多这个确实要看任务。复杂创意写作、多语言长文翻译本地小模型拼不过云端大模型但学习场景里大量需求是“基于给定资料解释概念、提炼要点、生成练习”这些恰恰是 7B 级别模型的强项。模型小不代表没用关键是把任务范围设计好。1.3 技术栈为什么长这样Python Flask SQLite Ollama先说背景。在这套学习软件之前我先用 Flask 做过一个轻量级网页端练手项目功能是校园失物招领的发布和匹配核心是用关键词相似度算法把“遗失物”和“招领物”自动对上数据存进轻量化数据库。那个项目让我对 Flask 的轻量模型很有好感一个小团队或个人完全能驾驭不复杂、不拖沓。所以这次做本地 AI 学习软件我几乎原封不动把网页框架的经验迁移了过来把“关键词匹配”升级成“语义理解和生成”。技术栈就四样Python生态最成熟AI 相关工具链几乎没有 Python 搞不定的写起来快。Flask极简 Web 框架没有 Django 那么重的规范个人项目拿着不累。SQLite轻量数据库比 MySQL 省事本地单机场景足够用零运维。Ollama本地模型运行和调度工具负责加载开源模型、提供 HTTP API帮我把“跑模型”这件麻烦事封装好了。选 Ollama 而不是直接用 llama.cpp 或 transformers是因为 Ollama 把量化、内存管理、模型常驻这些底层细节都处理好了我可以用十分钟内上手。对普通用户来说不需要理解 CUDA 内核也能跑起来这一点很重要。2. 功能设计与实现思路拆解明确了技术栈之后要回答的不是“能做什么”而是“该做什么”。本地 AI 学习软件如果只是做一个聊天框那和直接命令行里跑模型没区别所以我在功能上做了一轮取舍。2.1 核心功能与背后的提示词策略我保留了六个实用功能每一个都对应一个明确的学习场景功能典型场景实现策略知识问答学生问教材概念低温度先看资料原文再作答长文摘要浓缩复习资料分块总结后合并避免上下文溢出术语解释名词看不懂先下定义再用一句话举例翻译与润色外文文献阅读术语对照表限定防止乱翻错题变式题生成期末刷题分析错因后生成同类变体学习计划拆解考前突击按剩余天数倒排任务目标功能少一点没关系关键是每个都能做扎实。比如“术语解释”这个功能我专门设计了提示词要求模型先下定义再用学习场景中的例子解释最后给一个记忆口诀。这一套结构下来模型输出的内容比直接问“解释一下贝叶斯定理”要规整得多学生拿过去就能背。2.2 上下文池和会话管理不能让 AI 失忆学习场景里有个特殊需求——学生提问往往是有连续性的。上一句问“什么是梯度下降”下一句问“那它和最小二乘法有什么区别”如果模型只看到第二句很容易答偏。所以我在后端维护了一个会话池。具体逻辑是这样前端把问题发给 FlaskFlask 从 SQLite 里取出这个 session 最近的六轮对话组成 messages 数组再交给 Ollama。只取最近六轮是有原因的——本地模型上下文窗口有限塞太多历史既拖慢速度又可能让注意力涣散。六轮刚好覆盖一个持续讨论的链条又不会让内存吃不消。会话数据存在 SQLite 里表结构非常简单只有 session_id、role、content、created_at 这几个字段。用户在新一轮学习后可以清理会话记录把数据库备份带走这个我们后面实操部分再细说。2.3 免费开源的边界什么能开源什么不能既然打了免费开源的旗号就必须把边界说清楚。项目本体代码用的是 MIT 许可证意味着你可以随便改、随便用、甚至商用。但要注意项目依赖的模型权重不在开源代码范围内——Ollama 仓库里的模型各有各的开源协议比如 Qwen 系列和 Llama 系列都允许商用但如果你想换个更严格的模型就要自己去看对应协议。所以我在项目里做了一个很清晰的区隔仓库里只放源码、prompt 模板、文档和示例数据不托管任何模型文件。用户部署时自己去拉模型这样既避免了仓库体积爆炸也规避了许可证混淆。贡献者也约定了一条规则凡是涉及模型本身的问题一律去模型仓库提 issue不要污染本项目的主线。这个边界一开始就要讲清楚否则开源项目维护起来会非常累。3. 完整实操从零搭一套本地 AI 学习助手这部分我按顺序走一遍从装机环境一直到页面聊天。建议你跟着操作大概三十分钟可以跑通。3.1 环境准备Python、Ollama、模型下载基础环境是 Python 3.10 以上系统可以是 Windows、macOS 或 Linux。先建一个干净的虚拟环境避免依赖打架。mkdir ai-tutor cd ai-tutor python -m venv .venv source .venv/bin/activate # Windows 下用 .venv\Scripts\activate pip install flask requests接着安装 Ollama。你可以去官网下载对应系统的安装包装完终端里执行ollama --version确认。然后拉一个适合学习场景的中文模型我推荐 qwen2.5:7b-instruct 的 4-bit 量化版质量和资源占用平衡得不错。ollama pull qwen2.5:7b-instruct-q4_K_M拉完确认服务已经在后台运行默认端口是 11434。执行curl http://127.0.0.1:11434/api/tags能看到模型列表说明 Ollama 的 API 已经就绪了。3.2 后端核心代码用 Flask 封装本地模型调用接下来是最核心的部分一个 /api/chat 接口接收用户消息拼接历史转给 Ollama再把结果返回前端。import requests from flask import Flask, request, jsonify OLLAMA_ENDPOINT http://127.0.0.1:11434/api/chat app Flask(__name__) SYSTEM_PROMPT 你是一名严谨的本地学习助手。请始终使用中文回答。 要求 1. 如果用户给过资料先从资料中找依据再回答 2. 如果不确定明确说“根据现有资料无法确认” 3. 回答控制在 400 字以内优先用分点和短段落 4. 禁止胡编教材里不存在的结论。 app.route(/api/chat, methods[POST]) def chat(): data request.get_json(forceTrue) user_msg data.get(message, ) history data.get(history, [])[-6:] messages [{role: system, content: SYSTEM_PROMPT}] messages.extend(history) messages.append({role: user, content: user_msg}) payload { model: qwen2.5:7b-instruct-q4_K_M, messages: messages, stream: False, options: { temperature: 0.3, num_predict: 1024, num_ctx: 4096 }, } try: r requests.post(OLLAMA_ENDPOINT, jsonpayload, timeout300) r.raise_for_status() reply r.json().get(message, {}).get(content, ) return jsonify({reply: reply}) except Exception as e: return jsonify({error: f本地模型调用失败: {e}}), 502 if __name__ __main__: app.run(host127.0.0.1, port5000, debugTrue)这段代码里有几个关键参数要解释一下。temperature 设成 0.3是为了让回答更稳定、更接近事实学习场景不需要太多发散num_ctx 是模型上下文窗口设 4096 意味着模型一次最多能“看到”约四千个 token再长就只能截断num_predict 控制单次回答的最大长度1024 个 token 对学习问题来说已经够用。注意我只监听 127.0.0.1不用 0.0.0.0。这是故意的——本地学习助手只服务本机用户没必要暴露到局域网。暴露出去反而引入安全风险比如局域网里的其他人可以借用你的模型算力甚至通过对话接口消耗你的内存。3.3 前端和数据库把页面搭得像聊天软件后端接口有了前端我用了最简单的方式一个静态 HTML 页面配上一点 CSS 和 JavaScript通过 fetch 调用 /api/chat。没有引入 Vue、React 这类框架因为功能简单引入框架反而增加学习和维护成本。前端核心逻辑只有一小段async function sendMessage() { const msg document.getElementById(input).value; const history chatHistory; // 保存最近的会话历史 const resp await fetch(/api/chat, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ message: msg, history: history }) }); const data await resp.json(); appendMessage(assistant, data.reply); }聊天历史我用 JavaScript 数组存同时同步写一份到 SQLite。数据库表结构这样建CREATE TABLE IF NOT EXISTS messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT NOT NULL, role TEXT NOT NULL, content TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP );session_id 用来区分不同学习主题比如“高数期末”和“英语四级”各开一个会话互不干扰。这样能让模型在特定主题下上下文更集中不会把两个话题混在一起。3.4 把完整流程跑起来第一次提问回到终端分两步启动。先确认 Ollama 在跑再启动 Flask 应用。ollama serve # 另开一个终端进入虚拟环境 flask --app app run --port 5000浏览器打开http://127.0.0.1:5000输入“用一句话解释什么是马尔可夫链”正常情况下几秒内就能看到回答。如果页面转了半天没回应多半是模型第一次加载比较慢或者内存不够导致 swap 严重。此时可以打开终端的 Flask 日志能看到每次请求的耗时。第一次完整跑通后你会发现自己拥有了一个从“模型加载”到“对话界面”全链路可控的本地 AI 工具。那种感觉和调用别人封装好的黑盒 API 完全不同。4. 实测过程中踩到的坑与调优记录真正的干货都在踩坑里。这部分是我连着用了一个学期修修补补总结出来的每条都能省你半天时间。4.1 没有独显的设备能不能跑很多学生问的第一句话是“我笔记本没有独立显卡能跑吗”答案是能但要有合理的预期。我整理了一张常见硬件的实测参考硬件环境推荐模型量化方案预计速度内存占用8GB 内存纯 CPUqwen2.5:3bQ4_K_M8-12 token/s3-4 GB16GB 内存纯 CPUqwen2.5:7bQ4_K_M6-10 token/s5-7 GBM1/M2 Macqwen2.5:7bQ4_K_M18-25 token/s5 GB 左右8GB 显存 GPUqwen2.5:7bQ4_K_M40 token/s显存占用约 6 GB注意这些数字是近似值浮动范围很大。纯 CPU 跑 7B 模型回答 400 字大约需要 40 到 60 秒属于“能忍但不流畅”的级别。如果你的设备只有 8GB 内存建议老老实实换 3B 模型流畅度比参数大小更影响实际使用欲望。还有一个建议别追求宝塔式参数堆叠。本地学习助手要的是“够用”不是“跑分”。7B 模型在一个明确的 prompt 框架下对教材级内容的理解能力已经足够好强行上 14B 只会让你的电脑风扇起飞。4.2 中文回答质量差问题到底出在哪刚开始跑的时候中文回答经常“说不利索”要么夹英文要么用词别扭。我排查了一圈发现原因不在模型本身而在三个细节上。第一是系统提示词没有写清楚。默认不写的话模型可能根据自己的偏好输出。我后来在 SYSTEM_PROMPT 里明确写了“请始终使用中文回答”效果立竿见影。第二是温度太高。默认的 0.7 输出很“飘”学习场景下把 temperature 调到 0.2 到 0.4 之间回答会老实很多。第三是模型本身在两门语言之间切换导致混用稍微多一点约束就能把它按在中文轨道上。如果你用的模型对中文支持本身一般还有一个土办法在提示词末尾加一句“如果某个术语没有标准中文译名请保留英文并在括号里注明中文含义”。这样既不影响术语准确性又能降低中英混杂的频率。4.3 输入资料太长内存直接爆掉怎么办教材章节动辄几万字直接丢进上下文会触发两个问题Ollama 截断上下文或者内存占用飙升。我最初把一整章 PDF 灌进去结果程序直接卡死Task Manager 里看到内存占用 8GB 以上。后来我改成“分块摘要 合并总结”的做法原理很像论文里的 map-reduce先把长文本切成小块逐块让模型生成要点再把所有要点拼起来做一轮总结。分块代码很简单def split_text(text, chunk_size800, overlap100): texts [] start 0 while start len(text): end min(start chunk_size, len(text)) texts.append(text[start:end]) if end len(text): break start end - overlap return texts注意 overlap 参数。相邻块之间留一百个字符的重叠是为了避免重要内容恰好被切断。比如一句话前半段在前一块末尾、后半段在后一块开头有了重叠模型两轮滑动窗口都能看到完整语义。实践下来先让每个分块输出 3 到 5 条要点再让模型汇总效果比直接塞整篇好太多。虽然代码多绕了一步但换来的是学习资料的完整性。4.4 端口占用、模型加载慢这类“小事”该怎么处理最后说几个看起来很不起眼、但经常耽误事的杂项问题。端口冲突。Ollama 默认占用 11434如果别的程序抢了这个端口API 就调不通。排查命令是lsof -i :11434Windows 用netstat -ano | findstr 11434找到占用进程后换个端口或者改 Ollama 的配置。Flask 默认 5000 端口同理。模型加载慢。每次对话如果都要等几十秒大概率是模型没有常驻内存每个请求都在重新加载。Ollama 有一个 keep_alive 参数默认会把模型留在内存里一段时间但如果它被系统回收了就会重新加载。在项目里不要频繁用ollama run手动占着它会打乱 keep_alive 的节奏。把启动和调用分离Flask 启动后先发一个预热请求让模型常驻后续响应就快了。请求超时。我最初把 timeout 写的是 120 秒结果 CPU 机器上跑长文档偶尔会超时。后来统一改成 300 秒不会再因为慢一点就被误杀。5. 用了一学期后的真实体会工具做到能稳定用之后我开始观察学生们对它的实际使用方式有几个现象很有意思。第一个发现是学生更愿意在本地工具里问“傻问题”。用云端工具时他们可能是担心问题太简单被记录也可能是怕被平台的其他用户看到总之提问会比较收敛。本地工具没有这层心理负担问的问题很底层、很基础这恰恰是学习里最需要的部分。没有后顾之忧的提问才能真正暴露理解上的空洞。第二个发现是回答的结构比回答的内容更重要。那些把知识点拆成“定义 例子 注意事项”的回答学生留存率明显更高。这也印证了一件事本地模型能力够不够很大程度上取决于你 prompt 结构好不好。同一台电脑同一个模型换一套组织方式效果可能差一个档次。第三个发现也是最意外的一个本地工具让人更珍惜自己的资料。因为所有数据都在本地学生在导入笔记前会整理格式、清理无效信息这个动作本身就是学习。当一个工具能让你的数据“留在身边”你对待学习资料的态度会变得更认真。现在这个项目已经开源也有不少朋友在上面提交过代码改了 prompt做了更细的语言包。我个人最大的体会是本地 AI 学习软件不是一个惊天动地的技术发明它只是把“人工智能辅助学习”这件事重新放回个人手里。如果你也想做一个类似的工具我的建议是从最小的场景切入先跑通再慢慢优化。学习的壁垒不在模型大小而在你怎么设计那个“从问题到答案”的过程。
返回列表