ARTICLE DETAIL

资讯详情

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

从零搭建个人知识库问答机器人:RAG与Agent实战

从零搭建个人知识库问答机器人:RAG与Agent实战 1. 为什么我要从零搭一个个人知识库问答机器人手里攒了七八年的技术笔记、PDF 文档、网页剪藏和会议纪要总量大概在 3000 多份分散在好几个文件夹和笔记软件里。以前靠全文搜索还能凑合但搜索的前提是我得记得住关键词。很多时候我脑子里只有一个模糊的问题比如之前那个处理时区转换的方案是怎么写的全文搜索根本帮不上忙。这就是我决定动手做一个个人知识库问答机器人的直接原因。这个项目的核心目标很明确把本地散落的文档喂给一个Agent让它基于RAG检索增强生成的架构用自然语言回答我的问题并且给出答案的出处。技术选型上我用了LangChain做编排、FAISS做向量检索整体跑在本地不依赖外部服务。适合谁参考如果你有一定的 Python 基础想搞明白 RAG 到底是怎么跑起来的或者你已经看过一堆概念但没真正落地过那这篇内容应该能帮你少走弯路。我先把结论摆出来一个能用的个人知识库问答机器人难点从来不在调用大模型这一步而在于文档怎么切、向量怎么存、检索怎么召回、答案怎么溯源这四个环节。大模型 API 调用是整条链路里最简单的一环真正决定效果的是前面那些看起来不起眼的工程细节。下面我按实际搭建的顺序把每个环节的取舍和踩过的坑都摊开讲。2. 文档加载与切分决定问答质量的第一道关卡2.1 我的文档类型盘点与加载器选择动手写代码之前我先花了一个下午把手里所有文档过了一遍按格式分了类。这一步很多人会跳过直接上来就写加载逻辑结果跑到一半发现某种格式解析出来全是乱码。我的文档大致分这么几类文档类型数量占比加载方式主要坑点Markdown 笔记约 45%直接读取文本代码块会被当正文切碎PDF 技术文档约 25%PDF 解析库扫描版无文字层解析为空网页剪藏 HTML约 20%HTML 解析提取正文导航栏、广告混入正文纯文本/日志约 10%直接读取编码不统一有 GBK 混入Markdown 和纯文本最好处理直接读就行。PDF 是重灾区我遇到的最大问题是扫描版 PDF 没有文字层解析出来是空的。判断方法很简单用解析库读一遍如果某份 PDF 提取出的字符数远小于预期基本就是扫描版。这类文档我最后是单独挑出来要么手动转文字要么直接放弃不要指望 OCR 能完美还原技术文档里的代码和公式。HTML 剪藏的问题在于正文提取。我一开始图省事直接读整个 HTML 的文本结果检索出来的答案里经常混着点击这里订阅相关阅读这种导航文字非常影响体验。后来我改用正文提取的方式只保留文章主体部分效果立刻好了很多。2.2 切分策略为什么我放弃了固定长度切分文档切分Chunking是 RAG 里最容易被低估的环节。我最初用的是最朴素的固定字符数切分比如每 500 个字符切一块块之间重叠 50 个字符。跑起来是能跑但检索质量很差。原因很直观固定长度切分会把一段完整的逻辑硬生生截断比如一个函数的说明被切成两半检索到上半段时下半段的关键信息就丢了。后来我换成了按语义结构切分具体做法是Markdown 文档按标题层级切一级标题下的内容作为一个大块如果超过阈值再按二级标题细分普通文本按段落切段落是最小的语义单元尽量不跨段落切代码块整体保留绝不从中间切断这里有个参数需要权衡块的大小。块太小检索精度高但上下文不足模型回答时信息不够块太大检索时容易召回一堆无关内容还会浪费 token。我实测下来中文技术文档比较合适的范围是300 到 800 字符具体取决于文档的信息密度。信息密度高的比如 API 文档可以小一点叙述性的比如教程可以大一点。还有一个细节是重叠区。块之间保留一定的重叠是为了防止关键信息正好落在切分边界上被割裂。我一般设置 10% 到 15% 的重叠比如块大小 500重叠就设 50 到 75。重叠太多会导致重复内容变多检索时同一段信息被召回好几次反而干扰排序。提示切分参数没有万能值一定要拿你自己的文档做小批量测试。我的做法是准备 20 个已知答案的问题用不同的切分参数跑一遍看哪个参数下召回的内容最完整这个土办法比任何理论都管用。2.3 元数据让答案能溯源的关键很多人做 RAG 只存文本内容不存元数据结果模型给出答案后你根本不知道它从哪来的。我在每个块上都附加了元数据包括来源文件名、原始路径、块在文档中的位置、文档修改时间。这些信息在检索时不一定用得上但在展示答案出处时是必需的。元数据的另一个用处是过滤。比如我只想在某一个项目文件夹里搜索就可以在检索时按路径前缀过滤避免召回了其他项目的无关内容。这个功能在文档量大之后特别有用相当于给检索加了一层范围限定。3. 向量化与 FAISS 索引把文本变成可检索的数字3.1 嵌入模型的选择本地还是调用文本要能被检索先得转成向量这一步靠嵌入模型Embedding Model。选型上有两条路调用外部嵌入接口或者用本地模型。我最后选了本地模型原因有三个一是文档涉及个人笔记不想往外传二是本地模型没有调用次数限制批量处理几千份文档不用担心费用三是离线可用断网也能跑。本地嵌入模型的选择上我优先考虑中文支持和模型体积的平衡。有些模型英文很强但中文拉胯检索中文文档时召回率明显下降。我的建议是拿一段中文技术文档做测试看不同模型生成的向量在相似度计算上是否合理——语义相近的句子相似度应该明显高于无关句子如果区分度不明显说明这个模型不适合你的场景。嵌入维度也是个要考虑的点。维度越高表达能力越强但存储和计算成本也越高。个人知识库这个量级几百到一千多维都够用没必要追求超高维度。3.2 FAISS 索引类型我为什么先用最朴素的FAISS是 Facebook 开源的向量检索库它的核心优势是快能在毫秒级从百万级向量里找到最相似的几个。FAISS 提供了多种索引类型从最简单的暴力检索到各种近似检索。我一开始纠结要不要直接上高级索引后来决定先用最朴素的扁平索引Flat Index。理由是这样的个人知识库的向量规模通常在几千到几万条这个量级下暴力检索完全扛得住一次查询也就几十毫秒。扁平索引的好处是结果精确不会有近似检索的精度损失。等文档量涨到几十万条、查询明显变慢时再考虑换成带量化的近似索引也不迟。过早优化索引类型反而会因为近似检索的精度损失影响体验。索引的持久化也很重要。FAISS 支持把索引存到磁盘下次启动直接加载不用重新对所有文档做嵌入。我实测下来几千份文档重新嵌入要花十几分钟而加载已有索引只要一两秒这个差距在反复调试阶段非常关键。3.3 增量更新新增文档怎么处理知识库不是一次建完就完事了我每周都会新增笔记。如果每次新增都全量重建索引既慢又浪费。我的做法是增量更新维护一个已处理文档的记录新增文档只做嵌入并追加到索引删除文档则标记失效FAISS 本身不直接支持删除我用了一个有效位标记的方式绕过。这里有个坑要提醒文档修改后要重新嵌入。我一开始只判断文件是否存在结果改了内容的老文档不会被更新检索出来的还是旧内容。后来改成用文件的修改时间加内容哈希来判断是否需要重新处理这个问题才解决。4. 检索与生成Agent 编排的核心逻辑4.1 检索策略相似度检索够不够用最基础的检索是向量相似度检索把问题转成向量在 FAISS 里找最相似的几个块。这个方式对语义匹配很有效但有个明显短板对精确关键词不敏感。比如我问某个具体的函数名怎么用向量检索可能召回一堆语义相关但没提到这个函数名的内容。我的解决方案是混合检索向量检索和关键词检索各跑一遍然后把结果融合。关键词检索用最朴素的方式就行把问题里的关键词提取出来在文本里做匹配。两路结果合并时我给向量检索的结果更高权重因为它对语义的理解更好关键词检索作为补充。检索返回的数量Top-K也需要调。K 太小可能漏掉关键信息K 太大会引入噪声还会让模型处理更长的上下文。我一般设 K 在 4 到 8 之间配合一个相似度阈值低于阈值的结果直接丢弃避免把完全不相关的内容塞给模型。4.2 用 LangChain 串起整条链路LangChain在这个项目里扮演的是胶水的角色把加载、切分、嵌入、检索、生成这几个环节串起来。它的价值在于提供了统一的抽象比如文档加载器、文本切分器、向量存储接口我不用自己从头写这些。但 LangChain 也有让人头疼的地方版本迭代快API 经常变。我踩过的坑是照着半年前的教程写代码结果一堆接口已经废弃了。我的建议是遇到报错先去看当前版本的官方文档不要死磕旧教程。另外LangChain 的抽象层有时候会掩盖细节比如它默认的切分参数可能不适合中文需要手动覆盖。整条链路的编排逻辑大致是这样用户提问 → 问题向量化 → FAISS 检索相关块 → 把问题和检索到的上下文拼成提示词 → 交给大模型生成答案 → 附上来源。这个流程用 LangChain 的链式调用可以写得很清晰但每一步的参数都要自己调不能全用默认值。4.3 提示词设计让模型基于检索内容回答提示词Prompt设计直接决定模型会不会胡说。RAG 的核心价值就是让模型基于检索到的内容回答而不是凭自己的记忆瞎编。所以提示词里必须明确要求只根据提供的上下文回答如果上下文里没有相关信息就直说不知道不要编造。我用的提示词结构大概是先给一段系统指令说明角色和回答规则然后把检索到的文档块按相关度排序贴进去每块标注来源最后是用户的问题。这里有个细节文档块之间要有清晰的分隔否则模型可能把不同块的内容混在一起理解。还有一个实用技巧要求模型在答案里标注引用来源。比如答案里提到某个信息时注明来自哪个文档。这样我一眼就能判断答案可不可信也能顺着来源去核对原文。5. 实测中暴露的问题与我的处理方式5.1 答非所问检索召回不准的排查项目跑起来后最先暴露的问题是答非所问。我问 A它回答 B。排查下来根因基本都在检索环节。我的排查链路是这样的先看检索召回了哪些块如果召回的内容本身就答非所问那问题在检索不在生成检查问题向量化是否正常有时候问题太短或太口语化向量表达不准检查切分是否合理如果关键信息被切碎检索自然召回不到检查嵌入模型是否适合中文用相似度测试验证我遇到过一次典型情况问题里有个专业术语但文档里用的是同义词向量检索没匹配上。后来我在检索前加了一步查询改写把用户问题里的口语化表达和同义词扩展一下召回率明显提升。5.2 上下文超长token 超限怎么办另一个常见问题是上下文超长。检索回来的块加起来超过了模型的上下文窗口要么报错要么被截断。我的处理方式是动态控制上下文长度先按相关度排序从最相关的块开始累加累加到接近上限就停保证最相关的信息一定在上下文里。如果单个块本身就超长那说明切分参数有问题得回去调小块大小。还有一种情况是问题本身需要跨多个文档综合这时候可以分两轮检索第一轮先定位相关文档第二轮在文档内部细查。5.3 幻觉模型编造不存在的内容即使有检索内容模型偶尔还是会编造。我遇到过模型把检索到的两个不相关事实强行关联得出一个文档里根本没有的结论。对付幻觉我的经验是提示词里反复强调只基于上下文并给出不知道的示例降低生成温度Temperature让输出更保守要求标注来源没有来源支撑的句子一眼就能看出来温度这个参数值得说一下。温度越高输出越多样但也越容易跑偏温度越低输出越稳定保守。知识库问答这种场景我一般把温度设得很低宁可答案平淡一点也不要它自由发挥。6. 关于 Agent 化的一些思考6.1 从固定链路到 Agent 的差别我最初做的是一个固定链路的问答系统问题进来检索生成结束。后来我尝试把它往Agent方向改核心差别在于 Agent 能自主决定下一步做什么。比如问题比较复杂时Agent 可以先判断需不需要检索检索一次不够可以再检索一次甚至可以把问题拆成几个子问题分别查。这个能力在简单问答上体现不出优势但遇到对比 A 和 B 两个方案的差异这类问题时Agent 可以分别检索 A 和 B 的资料再综合效果比一次性检索好很多。不过 Agent 也带来了新的复杂度决策步骤多了出错的机会也多了而且调试起来更麻烦因为每一步的决策都是模型自己做的不像固定链路那样可控。6.2 个人知识库场景下 Agent 的边界我的体会是个人知识库这个场景Agent 化要适度。不是所有问题都需要 Agent 的多步推理大部分问题一次检索加一次生成就够了。盲目上 Agent只会让系统变慢、变复杂还增加不确定性。我现在的做法是分层简单问题走固定链路快且稳复杂问题才触发 Agent 的多步流程。判断简单还是复杂可以先用一个轻量的分类步骤或者干脆让用户自己选。这个取舍没有标准答案取决于你的问题分布——如果你的问题大多是某个东西是什么固定链路足够如果经常需要综合对比、多跳推理那 Agent 才有价值。6.3 并发与性能个人场景要不要考虑热词里有个AI Agent 怎么扛并发我一开始觉得个人知识库根本用不上。但实际用下来发现并发问题在个人场景也会出现只是规模小。比如我一边在网页端提问一边在脚本里批量测试两个请求同时打到嵌入模型上如果模型不是线程安全的就会出问题。我的处理比较简单给嵌入和检索加个简单的锁保证同一时间只有一个请求在跑。个人场景下这点延迟完全可以接受没必要上复杂的并发架构。等真的有多人使用需求时再考虑把嵌入服务独立出来做并发处理也不迟。7. 我踩过的几个具体坑和绕行方案7.1 编码问题GBK 文档导致的解析失败我有一批早期的笔记是 GBK 编码的读取时直接报错。处理方式是先探测编码再读取不要假设所有文件都是 UTF-8。探测可以用现成的编码检测库读出来之后统一转成 UTF-8 再往下走。这个坑不大但如果不处理整批文档都会被跳过。7.2 索引与文档不同步前面提过我一开始没做增量更新的判断导致改了内容的文档检索出来还是旧的。后来我加了一个文档指纹机制用文件路径加修改时间加内容哈希生成一个指纹指纹变了才重新处理。这个机制还顺带解决了重复文档的问题——内容完全相同的文档只处理一次。7.3 检索结果排序不稳定FAISS 返回的相似度分数在不同查询之间不完全可比导致有时候排序看起来很奇怪。我的处理是在检索后加一层重排用一个更精细的相似度计算对候选结果重新排序。重排这一步会增加一点延迟但对最终答案质量的提升很明显值得。8. 这套东西还能怎么扩展跑通基础版本之后我陆续加了一些扩展。一个是多知识库隔离把工作笔记和个人笔记分成两个独立的索引检索时指定用哪个避免串味。另一个是对话记忆让机器人记住上下文支持追问比如我问完一个问题后接着问那它的缺点呢它能知道它指的是什么。再往深了走可以考虑知识图谱和 RAG 的结合。纯向量检索擅长语义匹配但对实体之间的关系无能为力。如果能把文档里的实体和关系抽出来建成图谱检索时就能做多跳推理回答 A 和 B 通过什么关联这类问题会强很多。不过这属于进阶方向个人知识库这个量级先把基础 RAG 打磨好收益比盲目上图谱大得多。最后分享一个我自己的使用习惯我会定期拿一批新问题去测这个机器人把答得不好的案例记下来分析是检索的问题还是生成的问题然后针对性调参。这个过程有点像养一个助手用得越多调得越准。知识库问答机器人不是搭完就完事的项目它是一个需要持续打磨的工具。
返回列表