
我一直有个烦恼收藏夹里躺了几百篇公众号文章真到想用的时候什么都搜不到。印象笔记的全局搜索偶尔能命中几个关键词但我想问“上次见到的那篇关于提示词工程的七条原则后来有人补充了哪三条”它就彻底罢工了。所以从去年开始我就在研究怎么搞一个“属于自己的知识库问答机器人”。这不是普通的关键词搜索也不是把文章丢给大模型随便问。它要解决的问题是把散落在微信公众号、Obsidian笔记、PDF里的信息统一变成能被检索、被引用、被追问的私有知识库再通过AI Agent把“查资料”和“回答”这两个动作连起来让机器人像用过你所有笔记的同事一样回答问题。这篇是“Agent实践”系列的第一篇我会把选型逻辑、完整搭建过程、参数细节和踩坑记录都放出来。适合想自己搭个人知识库Agent、又不想看一堆云里雾里文档的人。1. RAG、KG、结构化知识库的选型逻辑个人场景为什么先推RAG1.1 三种知识库的本质差异在动手之前我先把知识库分了个类。这个分类很重要因为很多人一上来就搜“知识库搭建教程”结果把RAG检索增强生成、KG图知识库、结构化数据库混着看最后什么都搭不起来。维度向量/文本知识库RAG图知识库KG结构化知识库数据形态非结构化文本笔记、文章、PDF实体关系三元组表格、字段、记录查询方式语义相似度检索图遍历、多跳推理精确匹配、SQL聚合构建成本低切块向量化即可高需要设计本体、抽取实体关系中需要建模与清洗擅长场景开放式问答、文章总结、灵感检索关系发现、因果链、多跳问答指标查询、账目统计、配置查询典型工具Chroma、Milvus、Dify知识库Neo4j、GraphRAGMySQL、SQLite、Supabase维护难度低高中个人场景里90%的资料是文章、笔记、PDF全是非结构化文本我们想要的回答也大多是模糊语义问答比如“我之前记录的关于RAG切分策略是什么”。所以RAG是默认选项这几乎是社区共识。KG看着高级但个人维护一套本体哪些是实体、哪些是关系、怎么避免关系爆炸成本太高我见过不少个人项目在知识图谱上投入三个月最后跑通的还是RAG。结构化数据库适合存账单、书单、看片记录这类有固定字段的东西可以作为Agent可调用的工具之一但不建议拿来存公众号文章——存进去也搜不出来。1.2 个人知识管理的主战场是半结构化文本个人笔记有个特点它不是纯文本而是带标题、带列表、带链接的半结构化Markdown。这个“半结构化”特性很关键因为RAG切块时如果能把标题保留下来当作元数据后面召回质量会明显好过一刀切。我试过两种做法对比一种是把Obsidian的MD直接按字数切另一种是按Markdown的#标题层级切。后者切出来的每个块自带“所在章节”上下文比如一个块标题是## RAG的切分策略那这个块里的内容就算单独被召回模型也知道它属于“RAG的切分策略”这一节而不是一个孤零零的碎片。这种带结构信息的切分其实就是在利用你现有笔记的“半结构化”优势——这也是我后来坚决不用纯文本格式管笔记的原因。1.3 为什么Agent让RAG更值得用纯RAG的局限很明显单次检索拼接答案问题稍微绕一点就断。比如你问“我收藏的Agent文章里哪三篇反复提到tool use”纯RAG一次检索只会命中“tool use”这个关键词然后把最接近的三块拼在一起它不会先全库扫一遍再归纳。Agent在这里提供了三个增量。第一是意图路由它先判断你是在闲聊、查知识库、还是让它执行某个操作。第二是工具调度它决定要不要检索知识库、检索几次、要不要再查一下SQLite里的账单。第三是多轮规划它能把“先检索→再对比→最后总结”这个链路拆开执行。同样是上面的问题Agent会先检索一次拿到候选文章列表再针对每篇做第二轮检索确认是否提到tool use最后把结论组织给你。这个“先查再验再答”的动作就是Agent区别于普通RAG的核心价值。2. 从公众号文章到可检索片段知识库流水线的完整闭环2.1 微信公众号文章的高效收藏与清洗很多人卡在第一步公众号文章怎么进知识库复制粘贴肯定不行太累而且会带入大量导航、广告、无关样式。我现在的流程是看到好文章先用浏览器剪藏插件简悦或印象笔记剪藏一键存成Markdown存到本地目录命名规则统一为YYYY-MM-DD-标题.md。存下来之后有三件清洗工作必须做。第一删掉文章底部那些“推荐阅读”“广告”“关注公众号”之类的噪音段落这些不删后面切块检索会当成正文喂给模型。第二检查标题公众号的标题经常是“震惊不看后悔”这种我会在MD文件里补一行# 原标题正经版避免模型回答时把“震惊体”当事实。第三补元数据在文件顶部YAML区域写下原始链接、发布日期、我的标签。这个习惯救了我很多次——后来问答时问“那篇关于RAG的文章是哪天收藏的”模型直接读YAML就能答出来。2.2 以Obsidian为枢纽统一管理文档清洗完的文章统一进Obsidian库原因有三本地Markdown文件路径清晰可控文件名和YAML元数据都可以直接程序读取Obsidian的双链和Tags能保留文章之间的关联关系。这比把html网页直接喂给知识库好太多——网页里有大量标签噪音切块后混进向量库召回时模型会读到一堆导航链接很影响回答质量。如果只是个人用这套“公众号剪藏→存成MD→Obsidian管理”的流程就够稳了。我见过有人直接用RSS订阅公众号再自动下载全文也能跑通但公众号的反爬机制会让这个方案偶尔断流不如剪藏插件稳。剪藏之后顺手在Obsidian里打两个标签成本很低收益却很大因为Tag本身可以成为RAG的过滤字段。2.3 切分策略按标题结构切比按字数切更稳切块Chunk是RAG最影响体验的环节。很多人以为随便切一切就行实际上切错了召回跟着错模型给出的答案就算字句通顺也是错的。我早期用固定字数切一篇文章按512个字一刀切下去结果某篇文章讲“RAG四要素”第二要素的结尾被切到上一块第三要素开头落在下一块。用户问“第二要素是什么”召回块里只有一半内容模型只好靠猜。换成按Markdown标题层级切之后这个问题基本消失。场景切分方式chunk大小overlap公众号长文按H2切500-800字50-100字碎片笔记、卡片整篇一切不限0技术文档按H2/H3800-1000 token100 token聊天记录清洗后按日期切300-500字30-50字overlap为什么要设因为切分边界总会截断某些句子重叠一点可以让边界内容在后一块里再出现一次降低信息损失。但这个值别太大overlap超过150字会让同一个内容出现在两个块里检索时会重复召回同一段话白白浪费上下文窗口。这个参数我也是试了七八轮才定下来的。3. LangChain、Dify、CrewAI哪个框架适合搭个人知识库Agent3.1 三个框架的核心差异这个选择题每个搭Agent的人都会遇到。我三个都上手过直接说结论没有所谓的“最好”只有“按你的落地方式选”。框架上手难度灵活性可视化适合人群LangChain高高低开发者想自己控制每一步细节的人Dify低中高想快速落地、重视知识库管理和调参的人CrewAI中中低需要多个Agent角色协作的人LangChain的问题是“太自由”。它像一块乐高任何环节你都要自己拼Embedding用什么、向量库用哪个、Retriever怎么配、Prompt怎么写。好处是你完全知道每一环在干什么排错时可以钻进任意一层去看。坏处是新手很容易被文档绕晕尤其是版本更新频繁网上教程经常过时。Dify这两年火得不行热词里“dify知识库流水线”被搜到爆是原因的。它把知识库创建、文档切分、Embedding、检索、Rerank、Agent节点全部做成了可视化编排改一个参数立刻能看到效果不需要改代码重新部署。我见过完全不会写代码的人用Dify两天搭出一个可用的知识库问答机器人。CrewAI定位更偏“多角色Agent”你可以定义一个研究Agent、一个写作Agent、一个审查Agent让它们像团队一样协作。个人知识库问答用不太上但你如果准备做“检索Agent回答Agent行动Agent”这种架构可以考虑它。我自己的实践是先用LangChain写脚本弄清原理再切到Dify做日常调优两个搭配效率最高。3.2 我的选型倾向Dify做流水线LangChain做深度定制如果你是第一次搭我建议直接上Dify别犹豫。原因是知识库问答机器人里最大的工作量根本不在Agent逻辑而在“切分参数怎么调、TopK选多少、Score阈值设多高、Rerank要不要加”。这些在Dify里都是可视化选项改完立刻能对比效果。LangChain里要写一堆代码才能看到同样的结果。那LangChain什么时候有价值当你想把Agent嵌入自己的完整工具链时。比如我从Dify验证好参数后想把同样的逻辑放进一个Python脚本让机器人每天自动读取Obsidian里新增笔记并增量入库这时候用LangChain写一条自动化流水线比Dify的定时任务更顺手。两种手段不冲突先用Dify快速验证思路再用LangChain脚本化落地这算是我踩完坑之后觉得最舒服的路线。3.3 Agent在问答环节真正的价值这部分我得说点实在的。很多人把“Agent知识库”理解成“把知识库检索结果丢给模型”这其实只是RAGAgent的价值被浪费了一半。我实践下来Agent至少在三处起作用。第一是意图路由用户说“你好”Agent不会去检索知识库用户说“我收藏里关于LangChain的笔记有哪些”它才触发检索用户说“帮我把上个月的文章整理成一份摘要”它会先检索、再调用摘要工具、最后组织答案。第二是工具调度它可以决定调用knowledge_base_search这一个工具也可以决定再加一个structured_database_query去查SQLite里的账单记录甚至连续查两次。第三是溯源回答时会主动把内容对应的来源链接列出来这个动作如果是固定Prompt也可以做但Agent会在多轮对话里一直记住“每次回答都要带来源”不需要每轮重复提醒。举一个真实例子我收藏过一篇讲Dify流水线的文章又在Obsidian里写过一段关于“流水线步骤”的口诀笔记后来两个信息出现矛盾。普通RAG会把两段文本都检索出来拼在一起让用户自己判断Agent会把两段内容都读一遍发现矛盾后明确告诉你“文章里写的是A但你6月的笔记里写的是B两者不一致”并且给出两边的来源。这就是“工具调用推理”和“检索拼接”的差距。4. 我的实现路线本地Embedding Agent对话脚本的最小闭环4.1 整体架构我最终落地的最小闭环长这样Obsidian的MD库 → 本地Python脚本切块 → Ollama跑Embedding模型bge-m3→ Chroma向量库 → LangChain Agent脚本Ollama跑qwen2.5→ 命令行/API问答选本地部署有两个原因。一是私密个人笔记没必要传到第三方服务。二是省钱省心很多人用云端知识库服务时碰到过“知识库排队中”个人批量导入几百篇文章时尤其明显本地Embedding完全不受这个限制。如果你愿意接受云端API性能会更好但本地部署的逻辑和云端完全一样迁移成本很低。4.2 知识库构建脚本切块和入库部分用LangChain写非常直接。我的脚本大概长这样from langchain_community.document_loaders import DirectoryLoader, TextLoader from langchain.text_splitter import MarkdownHeaderTextSplitter from langchain_ollama import OllamaEmbeddings from langchain_chroma import Chroma # 1. 把Obsidian库里的所有Markdown装进来 loader DirectoryLoader(./notes, glob**/*.md, loader_clsTextLoader) docs loader.load() # 2. 按Markdown标题层级切分保留标题作为上下文 headers_to_split_on [ (#, H1), (##, H2), (###, H3), ] splitter MarkdownHeaderTextSplitter( headers_to_split_onheaders_to_split_on, strip_headersFalse ) chunks [] for doc in docs: # 把文件路径和原始内容切成带结构的块 for chunk in splitter.split_text(doc.page_content): chunk.metadata[source] doc.metadata[source] chunks.append(chunk) # 3. 本地Embedding先用ollama pull bge-m3 embeddings OllamaEmbeddings(modelbge-m3) # 4. 写入Chroma持久化目录 vectorstore Chroma.from_documents( documentschunks, embeddingembeddings, persist_directory./kb_db )几个细节说一下。MarkdownHeaderTextSplitter会把H1/H2/H3标题写进每个chunk的metadata里后续检索时你甚至可以直接看metadata知道这个chunk属于哪一章。strip_headersFalse我故意留着让标题文本也进入向量因为标题本身就是很强的检索信号。至于bge-m3它在中文语义检索上的表现在同尺寸模型里是很能打的个人场景完全够用。4.3 Agent问答脚本索引建好之后真正问答环节由Agent驱动。我用的方式是给Agent挂一个检索工具让模型自己决定什么时候调用from langchain.agents import create_tool_calling_agent, AgentExecutor from langchain_community.tools import Tool from langchain_core.prompts import ChatPromptTemplate from langchain_ollama import ChatOllama # 本地对话模型先用ollama pull qwen2.5:14b llm ChatOllama(modelqwen2.5:14b) # retriever配置TopK5和Score阈值的互动后面讲 retriever vectorstore.as_retriever( search_kwargs{k: 5} ) def search_kb(query: str) - str: docs retriever.invoke(query) if not docs: return 知识库中没有找到相关内容。 return \n\n.join( f[来源: {d.metadata.get(source)}]\n{d.page_content} for d in docs ) retrieve_tool Tool( namepersonal_kb_search, funcsearch_kb, description个人知识库检索工具。当用户问题涉及已收藏的公众号文章、Obsidian笔记、阅读摘要时必须先调用这个工具。 ) prompt ChatPromptTemplate.from_messages([ (system, 你是一个个人知识库助手。回答问题时必须以知识库检索结果为依据并附上来源。 如果知识库中没有相关资料明确告诉用户没有找到不要编造。), (placeholder, {chat_history}), (human, {input}), (placeholder, {agent_scratchpad}), ]) agent create_tool_calling_agent( llmllm, tools[retrieve_tool], promptprompt ) agent_executor AgentExecutor(agentagent, tools[retrieve_tool], verboseTrue) # 用起来 while True: question input( ) if question.lower() in (quit, exit): break result agent_executor.invoke({input: question}) print(result[output])这个脚本跑通之后你就有最基础的个人知识库问答机器人了。要注意的是create_tool_calling_agent要求模型支持工具调用tool callingqwen2.5系列支持得很好老一些的模型比如qwen1.5容易出现格式错误。如果换成其他模型先确认它支持OpenAI风格工具调用。4.4 两个需要注意的版本细节LangChain版本更新快网上教程经常因为版本差异跑不通。我踩过的两个坑一是langchain_community.document_loaders和langchain_ollama这些包需要单独pip install新版本里Ollama模块从langchain.llms挪到了langchain_ollama照旧版教程会直接ImportError。二是Chroma的persist_directory参数在新版本里有一部分被废弃如果你用的是最新Chroma请改用Chroma(collection_name..., client...)方式初始化。遇到报错别慌看报错指向哪个包基本都能在官方文档里搜到替代写法。5. 实测踩坑切分粒度、召回质量与幻觉控制5.1 问题一固定字数切分把语义拆碎这是我最开始踩的坑前面也提到过。我再补一个细节公众号文章经常有“总-分-总”结构开头引言非常关键如果固定字数切分引言可能单独成块后面分论点各自成块广告又混进另一块。检索时最理想的情况是模型把“引言某个分论点”放在一起但固定字数切分做不到因为块间没有层级关联。换MarkdownHeaderTextSplitter之后广告段落在正文外的部分会被正文标题自动隔开至少不会和正文混杂在同一块里。配合overlap 100字文章中间被截断的句子后一块还能补齐前文。当时我拿同一批数据对比过问答效果按标题切的做法让“答案缺失”明显减少尤其当问题精确到“某小节里提到的方法论”时。5.2 问题二TopK和Score阈值不会调TopK是“每次检索召回多少块”Score阈值是“低于多少相似度的块直接扔掉”这两个参数是召回质量的命门。我的经验TopK从3开始试小一点保证噪音少如果发现答案常常不全再往上加到5、7。我自己的库大概600篇文章TopK5已经够用超过7就会出现好几个块讲同一件事、互相矛盾的情况。Score阈值方面Dify里可以直接设LangChain的as_retriever没有直接暴露Score阈值但你可以用vectorstore.similarity_search_with_relevance_scores()自己过滤。通常0.3-0.5是个合理区间低于0.3说明检索结果可能完全不相关高于0.5又会把一些表述不同但语义相关的块误杀。中文语义里同义表达很多阈值拍太高往往召回不足。5.3 问题三模型一本正经地编造幻觉是知识库问答最气人的问题。模型明明没检索到还是会“很合理”地编一段。治本很难但治标有几个有效的招。第一System Prompt里写明“只能依据检索内容回答检索信息不足时直接说没有找到”。第二把工具返回的空结果处理成语义明确的“知识库中没有相关内容”而不是返回空字符串让模型知道这不是没检索而是确实没有。第三让模型在回答末尾自动附带来源。我的Prompt里就有一句“在回答最后列出你参考的来源文件”这样就算模型答错你也能马上顺着来源找到原文验证不会真信了。这招不是为了消灭幻觉而是为了让你能快速识别幻觉。5.4 问题四多轮对话中的上下文丢失多轮对话是Agent问答里最容易被忽略的点。第一轮问“RAG是什么”第二轮问“那它跟KG比呢”如果第二轮的检索query还是“它跟KG比”向量库根本不知道“它”指RAG召回结果会偏。解决办法是“检索前先重写query”。Dify在知识检索节点里有多轮增强选项把对话历史合并进去生成一个新的检索词。LangChain里用create_history_aware_retriever原理一样先把聊天历史和当前问题拼在一起让LLM生成一个不含代词的独立检索式再做向量检索。这个重写步骤非常管用没有它第二轮问题基本就是废的。5.5 一次完整的排错案例最后分享一个典型排错过程。有段时间我启动Agent后只要问题一涉及知识库就报Agent execution terminated due to error。看verboseTrue的输出Agent确实调用了personal_kb_search但工具返回的内容格式解析失败模型也再没生成后续文本。我的排查链路是这样。第一步确认是不是检索工具本身坏了直接写一行脚本调search_kb(LangChain Agent)发现返回正常所以问题不在知识库侧。第二步检查是不是模型不支持工具调用换qwen2.5:14b跑同一个问题错误消失说明之前用的7B模型对工具调用格式支持不稳。第三步再验证一次把工具描述写得更明确加一句“用户问题涉及已收藏的文章时必须先调用”后续稳定触发。结论这类“terminated due to error”多半不是知识库的问题而是LLM侧的工具调用不稳定优先换更支持tool calling的模型再回来检查工具描述。这个排查思路值得复制先分离“检索层”和“Agent层”用脚本单独测检索再用简单问题测模型工具调用哪个出问题就定位在哪一层不要一上来就怀疑向量库。6. 下一阶段让Agent具备记忆、工具边界与多Agent协作6.1 给Agent补上短期与长期记忆热词里“agent记忆”一直很火因为它直接决定Agent是不是“用过你笔记的同事”。短期记忆就是对话历史这个大多数框架自带比如Dify的会话变量。长期记忆则要自己设计把用户偏好比如“回答尽量简短”“带来源链接”、常用术语、甚至“最近一个月新增了哪些主题文章”存成结构化片段在新会话开始时注入Prompt。我现在是把长期记忆存成一小段YAML每次问答前先读入效果很直接。6.2 工具调用安全边界Agent可以调用工具之后安全问题就来了。个人用的知识库机器人问题不大但习惯要养成工具白名单机制只允许它调用你明确列出的工具API Key一律走环境变量不写进Knowledge或Prompt里涉及删除、写入等敏感操作时加上人工确认步骤。我在工具描述里还会加限制语比如“只用于读取不要修改或删除任何知识库内容”让模型在意图判断时就自我约束。6.3 Obsidian Trae笔记自动同步进知识库知识库最怕“建完就不更新”。我现在用Obsidian管理笔记TraeAI原生IDE作为辅助开发工具把“新笔记→自动入库”这条流水线自动化了Obsidian某个文件夹里每新增或修改一个MD文件触发一个Python脚本做增量切块和向量更新只处理变更文件不用全量重建。这样公众号文章剪藏进Obsidian后几分钟内就会被Agent检索到。数据库那边的逻辑是把mtime大于上次同步时间的文件挑出来重新处理这个判断每次入库前比对一下文件时间戳即可几十行代码搞定。6.4 多Agent协作的实践方向CrewAI和Dify的多Agent模式让我看到了下一步扩展的空间拆一个“检索Agent”专门负责访问知识库、整理候选材料一个“问答Agent”负责组织语言和溯源一个“行动Agent”负责把有价值的问答结果写回Obsidian形成新的笔记。三个Agent各管一段比一个全能Agent在出错时更好定位问题。这是“Agent实践”系列后面准备展开的内容我从个人知识库起步下一步就是让Agent不只是回答还能主动“整理知识”。写到这儿我回顾了一下整个搭建过程最值钱的不是最后跑通的那几分钟而是中间为了搞清楚“为什么召回出来的是那几篇”而做的那些调整。现在这套机器人已经成了我每周读公众号文章后的固定终点读完、剪藏、入库、问两句不满意就调整切分再入库。下一步我打算把微信读书的高亮也接进来让“我的知识库”范围从公众号再往外扩一大圈。如果你也在搭自己的Agent建议先别碰复杂框架把“一篇公众号文章从收藏到被回答”跑通再谈优化。有卡住的点欢迎留评论我尽量把后续实践写成新一篇。