资讯动态

从零搭建个人知识库问答机器人:RAG与Agent实战踩坑全记录

发布时间:2026/10/5 9:30:57 来源:尧图企业网站定制
1. 为什么我第一个 Agent 项目选了个人知识库问答做 Agent 开发的人十个里有八个第一个练手项目都是知识库问答。原因不复杂它足够小能在一周内跑通闭环又足够深RAG 检索增强、工具调用、多轮对话、上下文管理这些 Agent 的核心能力全都能塞进去。我当初选它说白了就是想找一个麻雀虽小五脏俱全的场景把 LangChain 这套东西从文档里拽到真实代码里。但真正动手之后才发现网上那些十分钟搭建 RAG 知识库的教程跑通 Demo 和能用之间隔着一整条鸿沟。Demo 阶段你丢三五个 PDF 进去问一句答一句感觉良好等你把自己攒了两年的笔记、公众号文章、技术文档全灌进去问题就全冒出来了——检索召回不准、答案张冠李戴、同一个问题换个问法就答不上来、长文档切得稀碎导致上下文断裂。这些坑教程里基本不会讲。这篇东西就是把我从零搭这个个人知识库问答机器人的完整过程摊开讲。核心关键词是Agent、RAG、知识库、问答机器人、LangChain但我不打算写成 API 说明书而是按我为什么这么设计—实际怎么落地—踩了什么坑—怎么修的顺序来。适合两类人看一是刚接触 LangChain 想找个完整项目练手的二是已经跑通 Demo 但发现效果拉胯、不知道怎么优化的。如果你连向量数据库是什么都还没概念也能看我会把基础概念用生活化的方式补上。先说清楚这个项目最终长什么样一个本地运行的问答机器人我把个人知识库Markdown 笔记、PDF、网页剪藏丢进去它能基于这些内容回答问题答不出来的时候会明确说知识库里没有而不是瞎编。它支持多轮追问能记住上一轮聊了什么。整个链路是文档入库—切分—向量化—检索—重排—生成这条标准 RAG 流水线外面套一层 Agent 做意图判断和工具调度。2. 拆解 RAG 流水线每个环节到底在解决什么问题很多人一上来就抄代码结果出了问题根本不知道是哪一环的锅。我建议先把 RAG 这条流水线拆明白知道每个环节的职责边界后面调优才有方向。RAG 全称 Retrieval-Augmented Generation检索增强生成本质就是先查资料再回答跟开卷考试一个道理——模型本身的知识是闭卷的你把相关资料塞给它它就变成开卷了。2.1 文档加载与清洗脏数据是万恶之源第一环是把你散落各处的知识喂进来。我的知识库来源很杂Obsidian 里的 Markdown 笔记、下载的技术 PDF、网页剪藏、还有一些零散的 txt。LangChain 提供了对应的 LoaderMarkdown 用UnstructuredMarkdownLoaderPDF 用PyPDFLoader网页用WebBaseLoader。这里第一个坑就来了PDF 解析出来的文本经常是乱的。尤其是那种双栏排版的论文解析出来左右栏文字会交错在一起读起来前言不搭后语。我一开始没注意直接把解析结果灌进向量库结果检索出来的片段全是断句模型拿着这种上下文自然答得一塌糊涂。后来我加了一步清洗去掉多余空行、合并被硬换行拆断的句子、过滤页眉页脚。这一步看着不起眼但对最终效果的影响比我后面调的任何参数都大。提示清洗阶段一定要肉眼抽查。随机抽 10 个文档片段打印出来读一遍如果连你自己都读不通模型更读不通。2.2 文本切分Chunk 大小直接决定检索质量切分是 RAG 里最容易被低估、又最影响效果的环节。为什么要切因为模型上下文有限而且检索粒度太粗会引入大量无关信息。切分策略的核心是两个参数chunk_size每块多大和chunk_overlap块之间重叠多少。我一开始用默认的chunk_size1000效果一般。后来针对中文做了调整因为中文一个字符承载的信息量比英文单词大1000 个字符对中文来说太长了检索出来的块里一半是废话。我最终定在chunk_size500、chunk_overlap50。重叠的作用是防止一句话正好被切在中间导致语义断裂——就像撕纸条你总得让相邻两张有点重叠拼起来才连得上。但固定长度切分有个硬伤它不管语义可能把一个小节的标题和内容切开。所以我后来换成了RecursiveCharacterTextSplitter它会优先按段落、再按句子、最后才按字符切尽量保证语义完整。对于 Markdown我还专门按标题层级切让每个 chunk 尽量落在一个小节内。2.3 向量化与存储Embedding 模型怎么选切好的文本块要转成向量才能做语义检索。这一步用的是 Embedding 模型它把一段文字映射成一个高维向量语义相近的文字在向量空间里距离就近。选模型时我纠结过用 OpenAI 的text-embedding-3-small效果好但要联网、要花钱用本地的开源模型比如 BGE 系列免费、数据不出本地但效果略逊。考虑到这是个人知识库内容偏私密我最终选了本地部署的 BGE 中文模型。实测下来中文语义检索效果完全够用而且没有网络延迟检索响应基本在百毫秒级。向量库我用的 Chroma轻量、纯本地、跟 LangChain 集成顺滑适合个人项目。如果你数据量大到几十万块可以考虑 Milvus 或 Qdrant但个人知识库这个量级Chroma 绰绰有余。这里有个容易忽略的点Embedding 模型换了整个向量库必须重建。因为不同模型生成的向量空间不兼容你拿 A 模型建的库去用 B 模型查询结果全是乱的。我踩过这个坑换模型后忘了重建检索结果离谱到怀疑人生。2.4 检索与重排召回不等于精准检索环节是从向量库里找出跟问题最相关的几个块。最基础的是相似度检索按向量距离排序取 Top-K。但纯向量检索有个问题它擅长语义匹配对关键词精确匹配反而弱。比如你问一个专有名词向量检索可能召回一堆语义相近但没提到这个词的内容。我的做法是混合检索向量检索 关键词检索BM25各取一批再融合排序。这样既能抓住语义又不丢关键词。融合之后再加一层重排Rerank用一个专门的重排模型对候选块重新打分。重排模型比 Embedding 模型更精细它会把问题候选块一起输入判断相关性。加了重排之后我的检索准确率肉眼可见地提升了一截。环节我用的方案备选选择理由加载分类型 Loader 清洗统一用 Unstructured针对性处理清洗可控切分Recursive 标题感知固定长度保语义完整向量化本地 BGE 中文模型OpenAI Embedding隐私 免费 中文好存储ChromaMilvus/Qdrant轻量、本地、够用检索混合检索 重排纯向量召回更准3. 从能答到答得对Agent 层到底加了什么价值如果只是检索生成那它就是个 RAG 问答还称不上 Agent。我给这个项目加 Agent 层是因为纯 RAG 有几个绕不过去的短板而 Agent 的调度能力正好能补上。3.1 意图判断不是所有问题都该查知识库纯 RAG 的流程是死的来一个问题必查库必拿检索结果去生成。但真实使用中用户的问题分好几种。有的是知识库能答的我上次记的那个 Docker 命令是什么有的是闲聊今天天气怎么样有的是需要多步推理的对比一下我笔记里 A 方案和 B 方案的优缺点。如果所有问题都硬查库闲聊会被强行塞进一堆无关上下文答得莫名其妙多步推理则因为只查一次库信息不够。Agent 的价值就在于先判断意图再决定动作。我用 LangChain 的 Agent 框架给它配了几个工具知识库检索工具、计算工具、以及一个直接回答的兜底。Agent 会根据问题自己决定调哪个工具、调几次。3.2 多轮对话与上下文管理个人知识库问答很少是一问一答就结束的。你问我记的那个向量库方案答完你接着问它跟另一个比哪个好这里的它和另一个都依赖上一轮上下文。纯 RAG 每次都是独立查询第二轮问题里的指代就丢了。我的处理是引入对话历史管理。但直接把全部历史塞进去会爆上下文而且早期无关内容会干扰检索。所以我做了两层一是查询改写把带指代的问题它怎么样结合历史改写成完整问题Chroma 向量库怎么样再去检索二是历史摘要超过一定轮数就把早期对话压缩成摘要只保留关键信息。3.3 工具调用的边界什么时候该说不知道这是我觉得最值得强调的一点。很多 RAG 项目最大的问题不是答错而是该说不知道的时候硬答。模型拿着几条不太相关的检索结果硬生生编出一个看似合理的答案这在个人知识库场景里是灾难——你查自己的笔记结果它给你编了个你没记过的内容。我在 Agent 里加了一道判断检索结果的相关性分数低于阈值时直接返回知识库里没有相关内容而不是交给模型生成。这个阈值需要根据你的重排模型分数分布来调我一开始设太高导致很多能答的问题被拒设太低又拦不住幻觉。最后我是拿一批测试问题跑了一遍看分数分布取了个中间值。注意宁可多拒答也不要让模型编。个人知识库的可信度一旦崩了这个工具你就再也不会用了。4. 实操落地把整条链路跑起来的完整步骤前面讲的是设计思路这一节讲具体怎么落地。我按实际搭建顺序来你可以照着复现。4.1 环境准备与依赖安装我用的是 Python 3.10LangChain 生态对版本比较敏感建议用虚拟环境隔离。核心依赖就几个langchain、langchain-community、chromadb、sentence-transformers跑本地 Embedding、pypdf。如果你要用重排再加FlagEmbedding或对应的 rerank 库。python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install langchain langchain-community chromadb sentence-transformers pypdf装完之后先跑个最小验证加载一个文档、切分、向量化、检索一条确认链路通了再往下做。别一上来就写完整 Agent出问题你根本定位不到是哪一环。4.2 文档入库脚本的编写要点入库脚本我单独写成一个模块跟问答逻辑解耦。核心流程是遍历知识库目录 → 按扩展名选 Loader → 清洗 → 切分 → 向量化 → 存入 Chroma。这里有几个实操细节值得说。第一给每个 chunk 带上元数据。我存了来源文件名、标题路径、创建时间。这样检索出来之后我能知道这段内容出自哪个文件的哪一节方便溯源也方便在答案里标注引用来源。用户看到根据你的《Docker 笔记》第 3 节信任感完全不一样。第二增量更新。知识库是活的你天天往里加东西。如果每次全量重建几百个文档跑一遍要好久。我的做法是用文件哈希做去重只处理新增和修改过的文件删除的文件同步从库里删掉。这个逻辑不复杂但省下的时间很可观。import hashlib from pathlib import Path def file_hash(path): return hashlib.md5(Path(path).read_bytes()).hexdigest() # 入库时记录 hash下次比对只处理变化的文件4.3 检索链与生成链的组装LangChain 的链式组装是这个项目的骨架。我把检索和生成拆成两条链中间用重排衔接。检索链负责问题→候选块生成链负责问题候选块→答案。生成用的 Prompt 我改了好几版。第一版太简单模型容易自由发挥后来我加了明确约束只根据提供的上下文回答上下文没有的信息不要编造如果上下文不足以回答直接说不知道。 这句话看着朴素但对抑制幻觉效果显著。另外我还要求它标注引用来源方便我核对。温度参数我设得很低0.1 左右因为知识库问答要的是准确复现不是创意发挥。温度高了模型就开始润色你的笔记把原意都改了。4.4 把 Agent 串起来最后一步是把工具和 Agent 组装起来。我给 Agent 定义的工具包括search_knowledge_base查库、list_sources列出知识库有哪些文档、get_current_time时间类问题。Agent 用 ReAct 模式它会先想这个问题该用什么工具再执行再看结果决定下一步。这里有个性能考量Agent 的每一步都要调一次模型多步下来延迟会累积。对于简单问题走 Agent 反而比直接 RAG 慢。所以我在前面加了个轻量路由简单的事实查询直接走 RAG 链只有需要多步推理或工具调度的问题才交给 Agent。这个优化让平均响应时间降了不少。5. 踩坑实录那些教程不会告诉你的问题这一节是我最想写的因为前面那些流程网上都能查到但下面这些坑是我一个个撞出来的。5.1 检索召回不准的排查链路有段时间我发现明明知识库里有答案机器人就是说没有。我没有瞎调参数而是按链路一步步排查。第一步确认文档真的入库了。我直接查 Chroma看目标文档的 chunk 在不在库里。结果发现有个 PDF 根本没解析出内容——它是扫描件PyPDFLoader 读出来是空的。这是加载环节的锅跟检索无关。第二步确认检索能召回。我拿一个已知答案的问题直接调检索接口看 Top-K 里有没有正确的块。结果发现正确块排在第七八位而我只取了 Top-3自然拿不到。这是 K 值设小了。第三步确认重排没帮倒忙。我把重排前后的排序对比了一下发现重排模型把一些正确块排下去了。原因是我的重排模型跟 Embedding 模型不是一套打分尺度不一致。换成配套的重排模型后正常了。这个排查顺序很重要加载→切分→检索→重排→生成从上游往下游查。很多人一上来就怀疑模型不行其实问题往往在最上游的数据处理。5.2 中文切分的隐藏陷阱中文没有空格很多默认切分器是按空格或标点切的对中文很不友好。我遇到过一句话被从中间切开前半句在一个 chunk后半句在另一个 chunk检索时只召回前半句模型看到半句话自然答不全。解决办法是用支持中文的分隔符列表把中文标点。也加进去让切分器优先在这些位置断开。另外chunk_overlap对中文尤其重要我设了 50 个字符的重叠基本能保证跨块的句子在某一侧是完整的。5.3 多轮对话里的指代丢失前面提过查询改写这里说具体怎么踩的。我最初没做改写用户问它的优缺点是什么检索器拿着它去查库啥也查不到。后来我加了一步把当前问题和最近几轮历史一起丢给模型让它输出一个不依赖上下文的完整问题再拿这个完整问题去检索。这一步加完多轮追问的命中率提升非常明显。但改写也有副作用改写本身要调一次模型增加延迟而且改写可能引入偏差把用户原意改了。我的折中是只对短问题、含指代词的问题做改写长问题直接检索。5.4 幻觉的三种典型表现与对策幻觉是 RAG 的头号敌人。我总结了自己遇到的三种一是张冠李戴把 A 文档的内容说成 B 文档的二是无中生有上下文里没有的信息硬编三是过度概括把具体内容泛化成一句正确的废话。对策分别是张冠李戴靠元数据引用解决让模型必须标注来源无中生有靠 Prompt 约束加相关性阈值拦截过度概括靠要求模型引用原文关键句来缓解。这三种没有银弹得组合拳。幻觉类型表现我的对策张冠李戴内容对但来源错强制标注来源元数据无中生有编造不存在的内容Prompt 约束 阈值拒答过度概括具体变笼统要求引用原文关键句6. 效果调优从能用到好用的几个关键动作跑通之后就是调优。这一节讲我做过哪些真正有效的优化以及哪些是白费力气。6.1 检索质量优化的优先级排序优化要有优先级不然容易在低价值的地方耗时间。我的经验排序是数据清洗 切分策略 检索方式 重排 生成 Prompt。为什么数据清洗排第一因为垃圾进垃圾出你后面调得再花哨源头数据是乱的效果都好不了。我见过太多人跳过清洗直接调模型参数纯属浪费时间。切分排第二因为 chunk 质量直接决定检索粒度。检索方式混合检索排第三它解决的是召回覆盖问题。重排排第四它是锦上添花前提是召回本身没问题。生成 Prompt 排最后因为只要上下文给对了模型基本能答对Prompt 主要是防幻觉。6.2 用测试集量化效果别靠感觉调优最忌讳感觉好像好了一点。我建了一个小测试集30 个问题每个问题标注了正确答案应该来自哪个文档。每次改动后跑一遍看命中率。这样我能明确知道某个改动是正收益还是负收益。测试集不用大30 到 50 个就够关键是要覆盖不同类型事实查询、多跳推理、否定问题知识库里没有的、指代追问。我一开始只测事实查询结果优化完发现多跳问题反而变差了因为我的改动偏向了简单检索。6.3 响应速度与效果的平衡本地 Embedding 加本地模型效果是好了但速度是问题。尤其是重排环节它要对每个候选块跑一次模型候选多了就慢。我的优化是先用向量检索快速筛出 Top-20重排只对这 20 个跑最后取 Top-5 给生成。这样既保证了精度又控制了延迟。另外Embedding 可以缓存。同一个问题反复问没必要重复算向量。我加了个简单的查询缓存命中就直接返回省掉整个检索链路。6.4 知识库更新后的索引维护知识库是活的索引维护是个长期问题。我的方案是定时任务每天扫一遍知识库目录比对文件哈希有变化的重新入库删除的同步清理。这里要注意删除操作要彻底不光删向量元数据、缓存里的相关条目也要清不然会出现文档已删但还能检索到的诡异情况。还有一个细节更新文档时旧版本的 chunk 要先删再插不能只插不删否则同一个文档会有新旧两份内容检索时可能召回过期信息。7. 这个项目还能往哪些方向长搭完这个基础版之后我发现可扩展的方向很多这里分享几个我实际尝试过或正在做的。第一个方向是多模态。现在知识库里主要是文本但我很多笔记里有截图、有手绘的架构图。把这些图片也纳入检索需要图像 Embedding 模型让图片和文本在同一个向量空间里。这块我还在摸索难点是图文对齐。第二个方向是知识图谱融合。纯向量检索擅长找相似内容但不擅长回答关系型问题比如A 和 B 是什么关系。把知识图谱加进来用实体和关系做结构化检索跟向量检索互补能覆盖更多问题类型。第三个方向是主动追问。现在的机器人是你问它答但有时候问题本身模糊它应该反问澄清。比如你问那个方案怎么样它应该问你指的是哪个方案。这个能力需要 Agent 有主动交互的意识我还在调。最后分享一个我踩了很久才明白的道理个人知识库问答的价值不在于模型多强而在于你的知识组织得多好。我花在整理笔记结构、统一命名规范、补充元数据上的时间回报比调任何模型参数都高。你的知识库越规整机器人就越聪明。这个项目做到最后我最大的收获不是学会了 LangChain而是被迫把自己的知识体系重新梳理了一遍。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑