ARTICLE DETAIL

资讯详情

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

从零搭建AI增强的个人知识库:本地优先的RAG实践

从零搭建AI增强的个人知识库:本地优先的RAG实践 说实话这两年我最大的困扰不是信息太少而是存了太多却找不到。微信收藏、浏览器书签、PDF 论文、各种 Markdown 笔记散落七八个地方真到用的时候搜出来的东西不是过时就对不上。直到我动手搭了 Atlas——一套 AI 增强的个人知识与生产力系统核心思路一句话把散落的知识统一收进本地仓库用大模型做检索、问答、摘要和自动关联让知识库从死文件夹变成能对话的工作台。这篇文章就是我从零搭建的完整过程包括架构决策、代码实现、踩坑记录适合想自己动手做个人知识库、又不想被平台绑死的朋友参考。Atlas 不是什么惊天动地的产品它本质上是一套本地优先 AI 增强的私人知识基础设施。我把所有笔记统一成 Markdown 格式存放在磁盘用向量数据库做语义索引再通过 Ollama 跑本地模型完成问答和总结。整个过程不需要付费 API、不需要公网服务甚至断网也能用。我还加入了文件监视、自动切分、混合检索和对话式问答用起来像给整个笔记库请了一个随叫随到的图书管理员。这套系统解决的核心痛点是知识检索效率。传统笔记工具依赖文件夹和标签本质是人工维护索引而 Atlas 通过嵌入模型把每段文字转成向量再结合关键词检索做召回最后交给大模型综合回答。你不需要记住笔记在哪只需要描述你想知道什么。下文我会把系统拆成几个模块逐一讲解从架构设计到代码实现再到我在实际操作中遇到的坑和解决办法尽量做到你照着一步步做也能搭出一套能用的系统。1. 为什么叫 Atlas这套系统想解决的真实问题1.1 知识散落带来的效率黑洞我曾仔细统计过自己一天的信息流早上一封客户邮件上午几篇技术博客下午三五个产品文档晚上还有公众号文章和知乎回答。这些内容有价值吗有。但我回头看发现90% 的收藏夹内容从没被第二次打开。不是内容没用而是当你要解决一个具体问题时根本想不起来自己存过什么。最典型的一次我花了整整两天重新研究一个数据库性能优化方案结果第三天才发现我半年前就在笔记里整理过几乎一模一样的结论。这个问题的根源在于传统知识管理工具设计思路是存储导向的你负责分类、打标签、记忆位置工具只负责把文件放进对应文件夹。可是人的记忆是模糊的、关联的、场景化的你往往只记得好像看过一篇关于 Python 异步爬虫和协程调度的文章完全不记得它存在哪个文件夹、叫什么文件名。用文件夹和标签去匹配大脑的模糊记忆效率天然就低。我身边很多人为了对抗这个问题买了 Notion 会员、装了一堆 Obsidian 插件结果大部分时间花在了调整插件配置上而不是真正用知识。问题的本质不是工具不够多而是缺一个能理解内容的层——把你已经写下来的东西重新组织成可以被问答和推荐的形式。这就是 Atlas 最初的出发点。1.2 AI 增强的真正意义从人找知识到知识找人传统笔记做得再好也只是人找知识你主动搜索、翻文件夹、一个个打开看。而 AI 增强系统的核心变化是把存储和理解分开。存储层只管文件和向量AI 层负责理解。你可以直接提问我之前记过关于 Redis 内存碎片化的处理办法吗系统会先去向量数据库做语义匹配找到相关片段再让大模型组织成完整回答并附带来源引用。更进阶一点AI 可以主动做关联推荐。比如你正在写一封关于IM 消息推送架构的邮件系统可以将你过去三年跟推送相关的笔记、代码片段、踩坑记录自动提取出来生成一份背景摘要。这种知识找人的能力传统标签系统做不到因为标签是人为打的只能反映你想得到的维度而向量相似度计算能捕捉到文字层面的隐性关联哪怕两篇文章从没共享过同一个标签。我做 Atlas 的目标很明确不是做一个检索工具而是做一个知识助手。检索只是手段最终要的是当我想不起来、说不清楚、找不到的时候系统能给出一个让我觉得诶对就是这个的结果。这个体验一旦建立起来使用知识库的动力就完全不一样了。1.3 为什么拒绝现成方案选择自己从零搭建有人问我Notion AI、Obsidian Copilot、ChatGPT 联网搜索哪一个不比你自己搭强我承认它们各有优势但对我的核心需求来说问题同样明显。第一是数据绑定。Notion 的数据在云端导出虽然能用但格式总有些别扭Obsidian 虽然本地存储但 AI 插件大多是调第三方 API数据要传到外部服务。我希望我的知识库是一个自己完全掌控的资产放几年都不会被平台策略影响。第二是流程定制。我要的不是一个对话框而是一条管道文件进来自动切分、嵌入、建索引每天固定时间自动出summary每周汇总一次知识热点这些流程在现成工具里很难统一实现但在代码里就是一个脚本的事。第三是技术可玩性。我本身是做技术的搭 Atlas 的过程本身就在学习 RAG 架构、向量检索和模型部署的工程细节。从我个人经验看把整套系统亲手搭一遍所获得的理解远比你调一个现成插件要深得多。所以如果你也想搭一个这样的系统我会建议你不要急着一上来就上重型框架先手搓一条最小可用链路把每一步的原理搞清楚后面扩展起来会很顺。2. 核心架构与技术选型2.1 四层架构从文件收集到智能问答Atlas 的整体架构我分成了四层每一层职责单一、接口清晰层级组成部分核心职责摄取层文件夹监视、PDF/Markdown/网页解析把各种格式的原始资料统一转成 Markdown 文本存储层Markdown 文件系统、SQLite、Chroma 向量库保底数据、记录元数据、构建语义索引增强层Ollama 本地模型、嵌入模型、提示词模板切分、向量化、问答、摘要、标签生成应用层命令行工具、Web 界面、API提供提问入口和结果展示这套分层的核心思路是数据与逻辑分离。不管前端怎么变底层 Markdown 文件始终是最终事实来源不管模型怎么换存储的向量和文本都还在。我最在意的是十年后数据还能用这个底线。Markdown 是纯文本任何工具都能打开Chroma 的向量数据可以导出SQLite 数据库可以随时迁移。这三样东西组合在一起保证了整个系统没有不可替代的专有格式。还有一个常被忽略的点摄取层的统一格式转换。我原来的资料有的是 PDF、有的是网页笔记、有的是 Kindle 标注。如果不统一转成 Markdown后续切分和嵌入就很难做。所以我在摄取层做了一个很笨但很有效的函数按来源类型调用不同的解析器最终全部输出成带标题结构的 Markdown。这一步完成后后面所有模块都只需要处理纯文本逻辑瞬间简单很多。2.2 关键选型向量库、嵌入模型和 LLM 的选择逻辑选型是这种项目最花时间的环节我对比过不少方案把我的决策逻辑分享出来。向量数据库我选了 Chroma而不是 Milvus 或 Weaviate。原因很简单Atlas 是单用户系统峰值索引量在几十万条量级根本不需要分布式。Chroma 是嵌入式运行跟 SQLite 一样是一个进程内库pip 安装就能用零运维成本。相比之下Milvus 需要 Docker 起服务虽然功能强但对个人项目来说明显过度设计。如果你的笔记规模到了几百万条再考虑迁移 Qdrant 也不迟但起步阶段 Chroma 完全够用。嵌入模型我一开始试过几种包括 OpenAI 的 text-embedding-3-small效果确实好但每 100 万 token 的成本虽然不高碰到高频更新和全库重索引还是觉得没必要花这个钱。后来发现 Ollama 生态里有 nomic-embed-text 和 bge-m3 这类本地嵌入模型中文效果够用完全免费且数据不出本机。我最终用了 bge-m3它在中文长文本上的表现比 nomic 更稳维度是 1024信息密度也够。有一点要注意嵌入模型选定之后不要频繁更换因为不同模型产出的向量不在同一语义空间换了模型就得全库重新做索引。LLM 这块我走的是本地优先路线用 Ollama 跑 Qwen2.5 7B。为什么不直接调 API两个原因一是隐私我的笔记里有些客户信息和半成品想法不想经过第三方服务二是稳定性本地模型只要显存够响应速度稳定在每秒 20 token 左右不管网络好不好都能用。7B 的模型在 QA 场景下表现虽然不如 GPT-4但如果配合 RAG 把相关上下文喂进去它的回答质量足够让我满意。如果你没有 GPU也可以跑 Qwen2.5 3B或者干脆调国内大模型 API效果也完全可用。2.3 核心流程摄取、切分、向量化、检索、生成整个系统跑起来之后核心数据处理流程是固定的一条链路我把它梳理清楚文件进入监视文件夹或手动运行摄取命令解析器根据文件类型将内容转为 Markdown保存到内容库切分器按标题结构和固定窗口大小把长文切成片段chunk每个片段通过 bge-m3 转成 1024 维向量写入 Chroma 集合用户提问时先用 BM25 做关键词检索同时用向量做语义检索两种结果做加权融合取 Top-K 片段作为上下文组合提示词发送给本地大模型生成带引用的回答这一步的过程很像图书馆工作流程切分好比给一本书分章节向量化好比给每章做语义索引卡片BM25 好比按字面词查目录。大模型则是那个把索引卡片和原文对照后组织语言回答你的读者。这个类比能帮你理解为什么 RAG 能比直接问大模型更准确——因为生成回答的每一步都有实际文本作为依据而不是模型凭空发挥。我也想在初期引入 LangChain 或 LlamaIndex 这类框架但最终决定先不用。原因很简单框架封装的接口太多出了问题你根本不知道是检索失败、上下文拼错还是模型输出格式不对。手搓一遍之后每一环都心里有数再上框架反而如虎添翼。我的建议是第一版不用任何 RAG 框架直接用 Chroma 和 requests 级别的 API 搭跑通之后再决定要不要引入抽象层。3. 从零搭建配置、代码与踩坑实录3.1 环境准备Python、Ollama 与依赖安装整个系统的运行环境是 Python 3.11 Ollama依赖包控制在个位数。我是在一台 Ubuntu 22.04 的机器上搭的有 RTX 3060 12G 显卡显存刚好够跑 Qwen2.5 7B 的 Q4 量化版本。如果你没有 NVIDIA GPU 也没关系Ollama 支持 CPU 运行只是速度会慢不少建议用 3B 模型。安装步骤很简单我直接贴命令# 安装 OllamaLinux / macOS 均可 curl -fsSL https://ollama.com/install.sh | sh # 拉取需要的模型 ollama pull qwen2.5:7b # 对话/问答主模型 ollama pull bge-m3 # 嵌入模型 # Python 虚拟环境 python3 -m venv atlas_env source atlas_env/bin/activate pip install chromadb pypdf markdownify watchdog这里我特别想提醒一个我踩过的坑Ollama 拉取模型时默认会下载到~/.ollama/models目录如果你的磁盘空间比较紧张记得提前看下剩余容量。Qwen2.5 7B 的 Q4 版大约 4.7GBbge-m3 大约 1.2GB加起来差不多 6GB。还有如果你跟我一样要长时间跑服务建议设一下 Ollama 的环境变量OLLAMA_KEEP_ALIVE1h否则模型默认 5 分钟不调用就会被从内存卸载下次再问第一句话特别慢。关于 Python 版本我用的是 3.11实测 chromadb 在 3.12 下也正常但 3.10 以下可能会遇到某些依赖预编译问题不建议尝试。装完依赖后建议立刻跑一个最小测试比如ollama run qwen2.5:7b 你好确认模型能正常返回再继续往下搭避免后面排查问题时搞不清是模型的问题还是代码的问题。3.2 摄取器实现把各种格式统一成干净 Markdown摄取层是整个系统的入口它的任务很单一把不同类型的文件转化成干净的 Markdown。干净的意思是去掉广告、去重、保留标题层级、代码块完整。我最初拿 PDF 测试的时候解析出来一堆乱码和多余的换行后来发现是没把扫描版和文字版区分开。文字版 PDF 用 pypdf 解析没问题扫描版必须走 OCR目前 Atlas 暂时不做 OCR 支持遇到扫描版我会手动转成文本再放进来。Markdown 文件本身最好处理直接读取原文件就行。网页我主要使用 markdownify 把 HTML 转成 Markdown但在转之前要先提取正文。如果你也打算把网页文章收藏进知识库我建议不要直接存整个 HTML先用一个正文提取函数过滤掉导航和广告效果会好很多。这里我给一个简化的摄取器示例import os from pathlib import Path from pypdf import PdfReader from markdownify import markdownify as md def ingest_file(filepath: str) - str: filepath Path(filepath) suffix filepath.suffix.lower() if suffix .md: return filepath.read_text(encodingutf-8) if suffix .pdf: reader PdfReader(str(filepath)) text \n.join(page.extract_text() for page in reader.pages) return text if suffix in (.html, .htm): html filepath.read_text(encodingutf-8) return md(html, heading_styleATX) raise ValueError(f不支持的格式: {suffix})这段代码在生产环境上我会加一个大小限制和编码探测因为有些网页声明是 UTF-8 但实际是 GBK直接读会乱码。你用chardet或charset-normalizer做一下检测会稳很多。摄取后得到的文本我会统一写到library/目录下并按原始来源命名比如web_2025-03-15_标题.md、pdf_2025-03-15_论文名.md。这个命名规则看着简单但之后追踪来源、去重管理都会省很多事。另外一个容易被忽略但很重要的细节摄取器要记录文件的 hash 值。我用 SHA-256 对每个文本文件做指纹存进 SQLite。这样同一篇文章重复导入时能直接识别并跳过避免向量库中出现大量重复内容。对个人知识库来说重复数据不仅浪费存储还会让检索结果里出现多条几乎一样的片段严重干扰问答质量。3.3 切分器实现标题优先的分块策略切分是 RAG 系统里最影响检索质量的环节之一。我先后试过固定长度切分、递归字符切分和标题结构切分最终确定下来的方案是标题优先 大小兜底。这个策略的核心逻辑是每个 chunk 要尽量是一个语义完整的小节而不是从句子中间硬切。举个例子一篇教程的正文可能有 5000 字包含安装配置常见问题三个二级标题。如果固定按 800 字符切分很可能会把常见问题的前半段跟配置的后半段拼在一起检索时用户问配置失败怎么办模型拿到的是不完整的上下文回答自然就差。按标题结构切分则能保证每个 chunk 都对应一个完整主题。我在实现时维护了一个简单的状态机按行读 Markdown遇到#开头的标题就开启新 chunk正文行追加到当前 chunk单个 chunk 超过最大长度我设为 1500 字符则从最近的句子边界截断。这样做的好处是既能保持语义完整又不会让单个 chunk 太长导致向量化精度下降。下面是核心代码def split_markdown(text: str, max_len: int 1500) - list[str]: chunks [] current for line in text.splitlines(): if line.startswith(#) and current.strip(): chunks.append(current.strip()) current line \n else: current line \n if len(current) max_len: boundary current.rfind(。, 0, max_len) if boundary -1: boundary max_len chunks.append(current[:boundary].strip()) current current[boundary:] if current.strip(): chunks.append(current.strip()) return chunks使用这个切分器时我建议对每个 chunk 记录它的来源文件路径和原始标题路径比如## 自动配置这样后面生成回答时能明确引用。亲测这样做的好处非常明显问答结果是带着出处的我可以直接跳回原笔记核对信任度高很多。至于窗口重叠我在标题切分方案下没有额外加重叠区原因是标题结构已经天然提供上下文边界重叠反而会把相邻主题混在一起。如果你追求更平滑的语义过渡可以试试在最外层做 5% 长度的重叠但对我的使用场景来说收益不明显。3.4 向量化与检索从语义相似到混合召回切分完成后的每一段文本都要转成向量。向量化的过程我把批次大小设为 32因为 bge-m3 对小批次更友好内存占用也低。写入 Chroma 的时候我额外存了source、title、chunk三个元数据字段。chunk存的是切分后的原文这一点很重要检索命中后不需要再回头读文件直接就能把原文拿到手。检索部分我做了混合召回。只用向量检索的问题是当问题里包含精确术语比如K8s 亲和性时向量检索往往匹配不到字面完全一致但语义相近的片段而只用 BM25 关键词检索的问题是换个说法就搜不到。把两者做加权融合取 Top-K 合并去重效果明显更好。我在实现里先同时取 6 条向量结果和 6 条 BM25 结果然后按 RRFReciprocal Rank Fusion公式排序最后输出前 5 条作为上下文。RRF 排序的原理其实一句话就能说清每个文档在两种检索结果里的排名取倒数并相加排名越靠前得分越高。这样既能兼顾精确词匹配又不丢失语义变体召回。实际测试中混合召回比纯向量检索在准确率上大约提升两成左右尤其适合我这种笔记里混着中英文术语的场景。检索核心代码大致是这样import chromadb from chromadb.utils import embedding_functions client chromadb.PersistentClient(pathatlas_db) col client.get_or_create_collection( namenotes, embedding_functionembedding_functions.OllamaEmbeddingFunction( urlhttp://localhost:11434/api/embeddings, model_namebge-m3, ), metadata{hnsw:space: cosine}, ) def hybrid_search(query: str, top_k: int 5): # 语义检索 vec_res col.query(query_texts[query], n_resultstop_k * 2) # 关键词检索用 SQLite FTS5 或 Chroma 的 where 过滤做 kw_res keyword_search(query) # RRF 融合 fused rrf_merge(vec_res, kw_res, top_k) return fused关于检索我还想在实践层面分享一个细节相似度阈值。刚开始我把最小相似度阈值设成了 0.7结果发现很多真正相关的长尾笔记被过滤掉了。后来我把阈值降到 0.55并且把无阈值也作为一种模式保留。实际体验下来个人知识库的检索跟搜索引擎不太一样你更需要的是什么都翻出来一点点、让模型去判断而不是严格卡一个阈值。因为 LLM 的上下文窗口足够大只要 Top-K 选得准多个候选中自然能找到答案。盲目调高阈值等于人为制造盲区。3.5 问答生成把检索结果变成人话检索到相关片段后问答模块要做的事是把这些片段组织成最终回答。这里最关键的不是模型本身而是提示词模板。我从第一版到现在迭代了三版提示词最终稳定下来的是下面这个结构你是一个严谨的知识助手。请根据以下资料回答问题。 如果资料中没有足够信息请直接说资料中没有找到相关内容不要编造。 回答时需要标注信息来源文件名或标题。 资料 {context} 问题{question}这个提示词刻意强调了没有找到就直说。为什么因为大模型在没有足够信息时会倾向编造合理答案而在知识管理场景编造比废话更可怕。使用 RAG 的意义就在于把回答钉在已有资料上所以必须在系统层面杜绝模型自由发挥。生成部分我通过 Ollama 的 chat API 调用qwen2.5:7b设置temperature0.2。温度不要高尤其在事实问答场景高温度会让模型话语变散。我一度用默认温度 0.7回答质量明显下降比如同一个问题第二次问会给出不同表述这种不可复现性在知识问答里很让人抓狂。调到 0.2 之后答案稳定性和可预期性都好了很多。import requests def ask_ollama(messages: list[dict]) - str: resp requests.post( http://localhost:11434/api/chat, json{model: qwen2.5:7b, messages: messages, stream: False}, timeout120, ) return resp.json()[message][content] def answer(question: str): context hybrid_search(question) prompt build_prompt(context, question) return ask_ollama([{role: user, content: prompt}])你可能会发现这套问答流程没有维护历史会话。我第一版也想做得更像聊天但后来发现知识问答多是一问一答式的或者问完马上接着问下一个问题历史上下文往往还会误导模型去猜用户意图。现在我在应用层保留会话历史但在每次提问时只把最近一轮作为背景并不把整个长对话塞进上下文。效果更干净。3.6 自动标签与每日摘要AI 增强的日常应用除了问答我日常用得最多的是自动标签和每日摘要。这个功能本质上跟问答一样都是用模型理解文本但不需要向量检索是直接把全文本喂给模型。因为我的笔记每天新增量不大完全跑得动。自动标签的实现是新笔记导入两分钟后后台调一次 LLM给出 3 到 5 个标签和一句话摘要。提示词也很简单你是一个知识管理助手。请为以下文本生成 3-5 个标签和 50 字以内的摘要。 格式标签标签1、标签2摘要一句话 文本{content}摘要我会写回 Markdown 文件头部加在 YAML front matter 里方便后续筛选和列表展示。标签则存进 SQLite 的元数据表同时作为 Chroma 的元数据字段这样检索时可以按标签做精确过滤。这套机制跑起来之后我翻笔记的习惯彻底变了以前是点开文件夹一个个看现在是先看每日摘要哪条吸引了我就直接跳到原文。每日摘要则由定时任务驱动每天晚上 10 点扫描当天修改过的笔记对每篇生成一句话摘要最后汇总成一个今日知识简报。这个简报不追求全面只求回顾的时候能瞬间想起今天读过什么、写过什么。我坚持了两个月回头翻这些简报发现它们自动构成了一本个人思考流水账价值远远超出预期。后面我还打算把周报也加上但目前做法已经够用。4. 常见问题与排查技巧实录4.1 检索结果不准确问题多出在切分和模型检索不准是 RAG 系统最让人头疼的问题我遇到过三种典型情况分别对应三个不同的原因。第一种是切分粒度过细。我刚开始为了节省 token把 chunk 长度设成 300 字符结果很多语义完整的段落被拦腰切断导致检索时明明相关但片段内容不完整模型回答得支离破碎。加大到 1200-1500 字符后明显好转。你可以把自己的资料想象成书切得太碎等于把一句话从上下文里抠出来再好的检索也救不回来。第二种是嵌入模型能力不够。我之前在英文资料上用 nomic-embed-text 还好但一转到中文技术文档它经常抓不住关键实体。换到 bge-m3 之后好转非常明显。如果你的知识库以中文为主我建议直接选 bge-m3如果中英混合还可以试gte-large-zh但要留意它的显存占用。第三种是相似度算法不匹配。Chroma 默认用的是 L2 距离但我向量化时用的是余弦相似度语义两者混用会导致排序失真。我后来在创建集合时显式指定metadata{hnsw:space: cosine}结果就稳定多了。这个细节不仔细看文档真的容易踩。4.2 上下文超限与大模型答非所问RAG 的机制决定了喂给模型的上下文越多回答越准确但上下文长度是有限的。我最开始把 Top-K 设成 10 条回复每条 1500 字符加起来接近 15000 字符直接把 Qwen2.5 7B 的默认上下文窗口塞满了结果模型经常回答到一半开始胡言乱语。解决思路有两个。第一个是减少回复条数Top-K 从 10 降到 5优先用小而精的上下文。第二个是压缩回复内容对超过 2000 字符的长片段我先让模型生成一个 200 字的摘要再作为上下文使用。这个两步走做法虽然增加了一次模型调用但极大地提升了最终回答的精准度在长文档为主的笔记库里尤其有效。还有一种情况是模型偶尔会忽略上下文、直接凭记忆回答。这多半是因为提示词里根据资料回答的指令不够强。我可以分享一下我的修复方法在提示词开头把资料两个字加粗强调并且明确说如果你引用资料外的事实必须标注来自常识。这样模型会明显更收敛。4.3 性能慢、内存占用高与 Ollama 资源问题Atlas 跑在本地性能问题躲不开。主要的性能瓶颈有三个嵌入计算、模型首字延迟和并发请求。我个人对这三点都有过优化效果最明显的如下嵌入计算要批量做不要一条条调 API。单条嵌入的耗时主要是请求往返带来的固定开销批量嵌入 32 条明显比单条嵌入 32 次快得多。模型首字延迟的元凶是冷启动。Ollama 默认空闲 5 分钟卸载模型我设置OLLAMA_KEEP_ALIVE1h后短时间内的问答响应速度快了很多。如果你希望随时秒答可以设OLLAMA_KEEP_ALIVE-1常驻内存代价是显存一直被占着。多用户并发在个人场景基本遇不到但我用过一个局域网共享的版本发现 Ollama 单模型并发请求会排队。我的解决办法是前置一个简单的 token 限流队列保证同时只有一个请求在跑避免显存溢出。显存不够是我在 12G 显卡上遇到的另一个问题。跑 Qwen2.5 7B 大约占用 6GB再叠加嵌入模型 bge-m3 大约 1GB整个 Ollama 进程峰值在 7-8GB 左右。如果你的显卡是 8G 或者核显建议换 Qwen2.5 3B或者开 CPU 推理但接受速度变慢。我个人不建议在个人知识库场景同时加载多个大模型一个对话模型加一个嵌入模型已经够用。4.4 常见问题速查表现象可能原因解决办法检索命中但回答不准上下文片段不完整增大 chunk 长度检查 Top-K 的排序回答充满编造内容提示词缺少约束加资料中没有就直说指令降低 temperature导入 PDF 乱码扫描版 PDF 未做 OCR手动转录或接入 OCR 管道向量库越跑越大重复导入未去重用 SHA-256 文件指纹过滤首次提问特别慢模型被卸载设置 OLLAMA_KEEP_ALIVE1h 或 -1中文检索效果差嵌入模型不支持中文换 bge-m3 类中文嵌入模型问题带精确术语搜不到向量检索语义漂移混合 BM25 关键词召回排查问题时我的经验是先复现再定层。确定是摄取层的问题、切分层的问题还是检索层的问题分层排查比瞎试参数快得多。比如你发现一个问题问三遍答案一会儿对一会儿不对这通常是提示词不稳定或上下文拼接顺序变化导致的先固定测试集再逐层检查。有一段时间我为了验证切分策略专门准备了一个包含 20 个问题的验收集每次改动切分器就跑一遍验收集用召回率来衡量好坏。这个方法虽然笨但非常有效。5. 进一步扩展从问答到多 Agent 工作流5.1 多 Agent 协作的初步实践Atlas 现在的能力已经远不止问答。我从热词多 AI 协作和AI agent里得到启发给系统加了一层多 Agent 的调度逻辑。思路是不做一个万能机器人而是拆成几个各司其职的 Agent由调度器根据用户意图路由。我目前拆了三个 AgentKnowledgeAgent 负责检索和回答知识问题WriterAgent 负责把检索结果整理成文档草稿TaggerAgent 负责给每日新笔记打标签和写摘要。它们之间通过一个简单的消息字典通信没有上 Agent 框架。我在实现时用了一个函数route_query(query)先让一个轻量模型判断意图如果是总结类就派给 WriterAgent如果是事实类就派给 KnowledgeAgent。实测下来这种粗粒度的路由在个人场景足够用而且比单个大模型自己分心完成所有任务更稳定。这里有一个值得认真说的点本地跑多 Agent 最怕的是资源不足。我一开始试图同时加载两个模型结果系统响应速度急剧下降频繁出现显存溢出。后来我把所有 Agent 共用一个 Qwen2.5 7B把角色通过提示词切换而不是真正加载多个模型实例。这个妥协换来的稳定性和单 Agent 几乎一样但开发成本低了很多。如果未来想体验真正的多模型协作可以在服务器上加显卡再把调度逻辑抽成独立服务。5.2 自动化管道与定时任务从手动跑脚本到全自动运行是 Atlas 的一大进步。我目前实现了三个自动化管道文件监视用watchdog监听inbox/目录新文件写入后 10 秒内自动执行摄取和索引流程无需手动干预。每日摘要cron 在晚上 10 点触发daily_summary.py扫描当天变更的笔记生成摘要并保存为daily/2025-xx-xx.md。周度复盘每周日凌晨汇总本周新增笔记的标签分布和高频关键词自动生成一份本周关注方向的报告。这些管道本质上都是脚本 定时器的组合没有任何一个需要特殊框架。我认为对个人系统来说cron和watchdog已经是天花板级别的稳定方案。有人可能会推荐你上 Airflow 或者 Prefect但杀鸡用牛刀维护成本反而拖垮你。自动化管道跑通了之后你需要记得做一件事日志。我一开始没有记录日志结果有一次某个解析器报错整个批处理静默失败我隔了一周才发现索引缺失了大量笔记。现在所有管道都会把结果写入一个logs/atlas.log内容包括每篇笔记的处理时间、chunk 数量、向量写入状态。这个习惯强烈推荐哪怕只是一个简单的print加重定向。5.3 数据备份与长期维护本地系统最怕的不是坏了而是坏了之后找不回数据。Atlas 的数据由三部分组成Markdown 笔记库、Chroma 向量库、SQLite 元数据。我的备份策略是笔记库每天同步到一个私有 Git 仓库向量库每周导出一次 JSONSQLite 直接用sqlite3 .backup命令做快照。很多人忽略了一个问题向量库的文件备份如果不带元数据恢复后是无法对应到原文的。所以我每次导出向量库时会同时导出一份映射表标明每条向量对应的文件路径和 chunk 序号。这样才能保证备份是真正可恢复的而不只是一堆二进制文件。这个细节我第一次做的时候漏掉了后来模拟恢复测试才发现补上之后才安心。维护经验还用一句话总结不要把逻辑都耦合在一个大脚本里。我第一版把摄取、切分、检索全写在一个文件里虽然跑得通但后来想换掉切分器差点把整个系统掀翻。拆成独立模块之后改任何一部分都不影响其他部分。如果你打算长期维护这套系统从一开始就要注意解耦哪怕初期多写几个文件也值得。5.4 我的一些个人体会搭完 Atlas 到现在我最大的感受是AI 增强的个人知识库并不是一个终点而是一个起点。它让我彻底放下了整理强迫症——我不再需要花时间给每篇笔记找正确的文件夹因为系统本身在语义上帮我建立了连接。这种松绑感是过去用文件夹结构时完全没有的。我也意识到这类系统的价值不在于模型多强而在于你愿意往里面写什么。如果笔记质量本身很低再强的 RAG 也检索不出好东西。所以我把每日摘要和自动标签当成反向助推器因为系统会自动回顾和整理我反而更愿意认真记录形成了一种正向循环。如果你也想搭一个类似的东西我最后再分享一个小技巧不要一开始就把功能想齐全。先把一篇文章收进来能问它问题这条最小路径跑通哪怕非常简陋然后每天真实使用。慢慢地你会发现真正需要什么也许是标题切分更好一点也许是多一个浏览器收藏插件也许是摘要周报。把功能长在真实需求上这个系统才不会被你遗弃在某个 git 仓库角落。
返回列表