这个项目说起来有点意外。我最初只是想做个“学习资料检索工具”因为平时要读的 PDF 和网课笔记太多了按文件夹翻找实在低效。后来本地大模型成熟了我就把它升级成了一个完整的本地 AI 学习软件免费、开源、本地运行所有资料和聊天记录都留在自己电脑上不上传任何服务器。它既能基于你导入的资料做问答也能生成复习卡片、整理错题、做智能摘要适合每天要读文献、刷课、整理知识体系的同学也适合对数据隐私敏感、不想把笔记交给云端的人。如果你对本地大模型、向量检索、Flask 后端这些技术感兴趣这篇文章会把我的完整思路、代码结构和踩过的坑一次性讲清楚。1. 项目整体思路为什么“本地化”值得花这么多力气1.1 核心需求资料太多缺的不是“AI”而是“组织”做这个软件之前我花了很多时间回答自己一个问题学习场景里AI 到底解决什么问题是帮你写作文、生成答案吗对学习场景来说我觉得最有价值的不是“让它直接给结论”而是“让它帮你从自己的资料里找到结论”。我电脑里有几百个 PDF 论文、几十篇 Markdown 笔记、一堆网课字幕和网页保存下来的长文。这些资料互相之间没有关联每次想找某个概念要么靠记忆翻文件夹要么用系统自带的全文搜索。全文搜索有个致命问题它只能匹配“关键词”不能理解“语义”。比如你输入“注意力机制和 Transformer 有什么关系”如果文件里写的是“自注意力”“编码器-解码器”“QKV”关键词搜索基本找不到。所以核心思路很朴素把资料做成一个本地知识库用语义检索找到相关片段再把片段交给本地大模型让它基于这些内容回答问题。1.2 为什么不用云端 AI隐私、成本、可玩性三个角度不少人问我市面上现成的 AI 学习工具那么多为什么还要自己做我用表格说一下自己的真实判断。对比项本地部署方案云端 API 方案数据隐私所有内容不出机器离线可用资料会传到第三方服务器单次使用成本0电费可忽略按 token 付费用多了很贵可控程度模型、提示词、知识库逻辑全可改受平台规则和版本更新影响部署门槛需要一定动手能力注册后即可用模型能力受限于本机硬件通常不如云端超大模型模型能力强、规模大这个表格不是劝人都用本地。如果你只是偶尔问一个问题云端方案确实方便但学习是一个长期、高频、依赖个人资料的过程。每次学习都要把几十页笔记传到别人服务器我心理上过不去。更现实的是成本一个学习季下来如果每天都问几十个问题云端的开销并不低。本地部署虽然前期折腾但后期是从“按次付费”变成“一次投入、长期免费”。1.3 技术选型Flask、ChromaDB、Ollama 怎么凑到一起整个项目我尽量选“免费、开源、生态成熟”的组件。后端没有用 FastAPI而是选了 Flask。原因很简单项目功能集中在页面渲染和几个 JSON 接口上Flask 足够轻文档多上手快排错容易。模型运行用 Ollama它把 llama.cpp 这类推理引擎封装成了非常友好的命令行工具一条命令就能下载并启动模型。向量数据库用 ChromaDB它是轻量级的嵌入式向量库可以把索引直接存在本地文件夹里不用额外部署一个数据库服务非常符合个人项目的定位。前端就三个文件HTML、CSS、JavaScript全部使用本地静态资源不依赖任何 CDN这样才能保证断网环境也能打开界面。整体流程可以概括为五步资料导入、文本分块、向量化、向量检索、大模型回答。后面每一节我都会讲清楚具体怎么做。2. 核心功能拆解本地 AI 学习软件到底能做什么2.1 资料导入PDF、Markdown、网页文章统一入库学习资料的第一道坎是格式。我的资料库里主要有三类PDF、Markdown 笔记、网页正文。PDF 处理最麻烦有的 PDF 是文字版可以直接抽取文本有的是扫描版需要 OCR。文字版 PDF 我用 PyMuPDF 抽取速度快基本能保留段落结构。扫描版 PDF 用 PaddleOCR 或 Tesseract 做识别这里我建议优先选 PaddleOCR中文识别准确率明显高于 Tesseract缺点是安装包大一些。网页文章我会用浏览器自带的“保存为完整网页”功能再用 Python 的 BeautifulSoup 提取正文标签去掉导航和广告脚本。导入阶段有两条经验很关键。第一所有文本统一转成 UTF-8否则后面中文检索会出现乱码第二保留原始文件名和来源页面的路径后续问答返回结果时可以直接告诉用户“这段内容来自哪篇文档、哪个页面”。2.2 知识问答用向量检索召回上下文再让模型“读资料回答”问答是本软件的核心功能。如果直接把用户问题发给本地大模型它也能回答但那不是“学习工具”而是“聊天机器人”。学习工具必须能指出知识来源并且只基于你给它的资料回答。我采用的方案是“RAG”也就是检索增强生成。用户提问时系统先把问题转成向量和知识库里所有文本片段的向量做相似度计算取出最相关的 5 段内容连同问题一起放进 Prompt再让本地模型只依据这些内容作答。这样做的好处是模型不需要“背下全部知识”它只需要做阅读理解因此本地小模型也能胜任。为了控制回答质量Prompt 里我做了三条硬约束一是“只能使用提供的资料作答”二是“资料不足时直接说不知道不要编造”三是“回答中需要标注引用来源编号”。最后一条对学习场景非常重要因为学习者需要知道答案来自哪里才能判断是否可信。2.3 复习辅助自动抽取闪卡、错题收集与间隔重复问答功能做到一半我发现学习工具光有问答还不够。真正的学习闭环需要“复习”。于是我又加了两个小功能闪卡抽取和错题本。闪卡抽取的原理不复杂利用本地模型读笔记片段提取“概念-解释”“问题-答案”这种结构化的知识点然后生成一组复习卡片。复习时使用间隔重复算法常见的是 SM-2也就是根据用户反馈动态调整每张卡片的复习间隔。这个算法在本地实现非常简单只需要维护一个easiness字段和next_review_date字段不需要 AI 参与每次复习提醒。错题本更像一个收藏夹。遇到做错的选择题或者理解不了的知识点可以一键把当前问答记录和上下文保存为一条“错题记录”后续按科目、时间、掌握程度三类条件筛选复习。这些数据全部存在 SQLite 里备份等于复制一个 .db 文件。2.4 本地数据存储一库一文件搬走即备份整个软件的数据层我刻意设计得特别简单只有一层 SQLite一个文件存全部业务数据ChromaDB 的向量索引放在同目录的vector_index/文件夹里。升级或搬家时把项目目录压缩复制过去就行不需要像企业软件那样做数据迁移和数据库服务配置。这个设计是被一次意外逼出来的。早期我考虑过用 PostgreSQL 存向量后来发现个人项目用数据库服务完全是自找麻烦要配置账号、管理端口、处理备份。换成 SQLite 加 ChromaDB 之后所有数据都在一个项目文件夹里测试和生产环境几乎不需要区分问题瞬间减少。3. 从零搭建的实操步骤照着做就能跑起来3.1 环境准备先装推理引擎再建 Python 虚拟环境先装 Ollama。它在 macOS、Windows、Linux 上都有安装包我以 Linux 服务器为例curl -fsSL https://ollama.com/install.sh | sh装完以后拉取一个适合学习和问答的中文模型我推荐qwen2.5:7b或qwen2.5:14b。显存高于 8GB 可以选 14b只有 8GB 就选 7b 的 Q4 量化版本ollama pull qwen2.5:7b接下来创建 Python 虚拟环境并安装依赖python3 -m venv venv source venv/bin/activate pip install flask chromadb sentence-transformers pymupdf beautifulsoup4这里有个容易踩的坑如果机器显存不宽裕尽量不要在同一个 Python 环境里既加载向量嵌入模型又加载大语言模型否则容易内存溢出。我通常把嵌入模型跑在 CPU 上只让大模型用 GPU。sentence-transformers默认会用 CPU 推理正好合适。3.2 构建知识库分块、Embedding、写入 ChromaDB知识库构建是整个项目最“魔法”的环节也是最值得优化的环节。原始文本不能直接整篇塞进向量库因为单次问答能携带的上下文有限整篇文档会超出模型窗口检索的精度也会下降。所以必须分块。我常用的分块逻辑是“分层分块”优先按 Markdown 的标题层级切没有标题的段落按固定窗口加重叠来切。固定窗口我用 500 字一个块重叠 100 字避免把一个知识点拦腰截断。分块后的代码骨架长这样from sentence_transformers import SentenceTransformer embedder SentenceTransformer(BAAI/bge-small-zh-v1.5) texts [分块后的文本1, 分块后的文本2] vectors embedder.encode(texts, normalize_embeddingsTrue)然后把文本和向量一起写入 ChromaDBimport chromadb client chromadb.PersistentClient(path./vector_index) collection client.get_or_create_collection(knowledge_base) for i, text in enumerate(texts): collection.add( ids[fchunk_{i}], documents[text], embeddings[vectors[i].tolist()], metadatas[{source: example.pdf, chunk_index: i}], )metadata一定要记录来源文件名和块序号。后面做答案溯源、定位知识块位置全靠这个字段。3.3 实现问答接口Flask 后端怎么写后端接口我设计成一个POST /api/ask接收用户问题内部做向量检索然后调用 Ollama 生成回答。简化后的接口逻辑如下from flask import Flask, request, jsonify app Flask(__name__) app.route(/api/ask, methods[POST]) def ask(): data request.get_json() question data[question] # 1. 检索相关片段 results collection.query( query_embeddings[embedder.encode(question).tolist()], n_results5 ) docs results[documents][0] sources results[metadatas][0] # 2. 组装 Prompt context \n\n.join(docs) prompt f 你是一个严谨的学习助手。请只根据以下资料回答问题。 如果资料无法回答请直接说“资料不足”不要编造。 资料 {context} 问题{question} 回答 # 3. 调用本地模型 answer ask_ollama(prompt) return jsonify({answer: answer, sources: sources})ask_ollama函数可以直接用 Ollama 的 Python 库或 HTTP 接口。 Ollama 默认监听http://localhost:11434接口路径是/api/generate具体 SDK 用法每个版本略有差异我会在项目 README 里固定一个已经测试通过的版本避免上游更新后跑不起来。3.4 前端页面和启动方式不依赖 CDN 的本地 UI前端我做得非常简单左侧是资料库列表中间是问答聊天区域右侧是检索到的引用片段。所有前端框架都用本地文件没有加载任何在线 CDN实现“拔掉网线也能打开”。本地静默资源的做法是把 Vue 或 Alpine.js 这类轻量框架的 min.js 文件下载到static/vendor/目录模板里直接引用/static/vendor/vue.global.min.js。这样虽然牺牲了一点“保持最新版”的便利但换来了离线可用和更快的加载速度。启动方式就是在项目根目录运行python app.pyFlask 默认监听 127.0.0.1:5000。如果希望同一局域网内的手机或平板访问可以启动时指定host0.0.0.0但注意这会把服务暴露到局域网中我一般只在调试时临时开启平时保持默认监听本地地址。4. 踩坑记录与问题排查我实际遇到的 6 个高频问题4.1 模型加载失败、显存不足和 OOM项目初期我用的模型是 7B 原版没有量化8GB 显存经常跑到一半就报显存不足。后来统一换成 Q4_K_M 量化版本显存占用直接少了将近一半回答速度也没有明显变差。如果你用 CPU 纯跑 7B 模型也不是不行但回答一句话可能需要十几秒只适合偶尔问答不适合连续对话。我的建议是会改代码就加一个简单的请求队列连续提问时排队处理避免多个请求同时把内存吃满。4.2 中文检索效果差问题不一定在模型一开始我用通用英文向量模型做中文 Embedding检索结果惨不忍睹查“机器学习”返回一堆不相关的内容。后来换成中文优化的bge-small-zh-v1.5准确率立刻上来了。这说明一个问题很多检索问题不是代码 bug而是 Embedding 模型没选对口。另外分块长度也很关键。分块太大检索到的内容太宽泛分块太小又会丢失完整语义。我测试下来中文场景 300 到 800 字的分块效果普遍较好具体数值需要根据自己的资料类型微调。4.3 模型回答“自由发挥”不基于你的资料编造答案这是 RAG 应用最常被吐槽的点模型明明收到了资料却还是自己编。原因通常是 Prompt 约束不够强或者是上下文里夹带了太多不相关内容模型“看着眼熟”就开始背书。解决办法我用了两层。第一层是 Prompt 里明确写“只能从资料中引用信息”并让它把引用段落编号标出来第二层是后处理如果回答里有“根据资料”但检索到的相似度很低就判断为答案不够可靠在界面上给出明显提醒。后者属于经验判断阈值可以按数据效果调整。4.4 本地 UI 加载慢或样式错乱本地页面加载慢多数是静态资源太大。我的网页主要体积来自字体文件和框架 JS解决办法是只保留用到的字体和中文字符子集。另一个问题是浏览器缓存每次更新 JS 后浏览器还用旧缓存样式错乱需要在资源文件名后加上版本号例如app.js?v20240510。4.5 资料更新后检索不到新内容有段时间我导入新笔记后问它新笔记里的内容它总说找不到。排查后发现是向量索引没有增量更新每次导入新文档只是往 ChromaDB 里追加但旧索引文件没有刷新。虽然 ChromaDB 的持久化机制会自动保存但如果程序异常退出会导致部分数据没写入。我的解决方案是专门做了一个“重建索引”按钮扫描整个资料目录比较文件修改时间只对变化部分重新分块和向量化。重建期间不对外提供问答服务因为索引写入和读取并发容易返回半新半旧的结果。4.6 硬件占用高本地学习软件真的需要好电脑吗这个问题的答案取决于你想跑多大的模型。纯 CPU 跑 7B 量化模型内存 16GB 以上可以流畅地做异步问答GPU 显存 6GB 以上体验会好很多。如果你只有 8GB 内存的旧电脑建议选择 3B 或 4B 的小模型并且把向量 Embedding 的模型也换成一个极小的miniLM版本。我也在代码里做了“低性能模式”。该模式下问答只检索知识片段并展示来源不调用大模型相当于一个语义搜索引擎。这样旧电脑也能利用语义检索只是不能自动生成答案体验也不差。4.7 常见问题速查表问题现象主要原因解决方案答非所问Embedding 模型不合适换成中文优化的 bge 系列显存不足模型未量化或选择过大换 Q4 量化版或换小规模模型回答不基于资料Prompt 约束不够强化“只根据资料回答”提示检索不到新资料索引未更新重建增量索引页面样式不生效浏览器缓存资源文件加版本号局域网无法访问服务只监听本地指定 host0.0.0.0 并注意安全OCR 出来的文字乱码编码不是 UTF-8统一转码后再写入知识库5. 实测效果、优化心得和扩展方向5.1 在真实学习场景里的使用效果我自己用了这个软件大约三个月最有价值的场景是准备技术面试。我把面试题笔记、源码解析、论坛精华帖、论文摘要都导入知识库之后刷题遇到模糊知识点直接提问“这个方案为什么是这么设计的”。和过去最大的区别是它不只是一个聊天窗口它每次回答都会把相关片段列在右侧我能立刻看到它在基于哪段笔记回答信息链路是完整的。自动闪卡功能我用得也很多。每次学完一章让模型从笔记里抽取十几个问题再导入复习流程。虽然抽取出来的卡片偶尔有表述不准的地方但“批量生成草稿卡片、人工再修订”比“从零手工做卡片”高效太多了。5.2 后续扩展多模型切换、增量导入、插件机制目前项目已经开源我收到了不少建议正在考虑三个方向。第一模型多开让用户可以在 Ollama 里部署多个模型在界面上自由切换比如摘要用小模型、深度问答用大模型。第二增量导入用户直接把整个文件夹拖进界面程序自动监听文件变动新增资料后自动索引不再需要手动点重建。第三插件机制把“闪卡生成”“错题收集”“题型分类”这些功能做成独立插件使用者可以只启用自己需要的模块代码也更好维护。如果你准备在自己的电脑上部署我的最后一条建议是不要一开始就追求“完美系统”先搭一个能用的版本跑一个星期看看哪些功能你天天用哪些实际上从来不用。我从最初只做问答到后来加入闪卡就是被日常使用推着走的结果。个人体会方面我觉得本地 AI 学习软件最有意思的地方不是“AI 有多聪明”而是“AI 终于和你的知识长在了一起”。它不替你做判断但能把你看过的东西整理得井井有条。这个过程本身就是一个比答案更有价值的学习体验。