
1. 为什么我第一个 Agent 项目选了知识库问答很多人入门 Agent 开发第一反应是搞个能自动订机票、发邮件、操作浏览器的全能助手。我一开始也这么想结果折腾了两周工具调用链路越写越长状态管理越来越乱最后连今天天气怎么样都能给我绕出三个分支。后来我换了个思路先做一个边界清晰、输入输出可控、能立刻验证效果的场景——个人知识库问答机器人。这个选择背后有三个很实际的考量。第一知识库问答是 Agent 里最小可用闭环最完整的形态。它天然包含检索、上下文组装、推理生成、结果校验这几个核心环节麻雀虽小五脏俱全。你把这套跑通了后面加工具调用、加多轮规划、加记忆管理都是在已有骨架上挂东西而不是从零搭地基。第二个人知识库的数据量可控。我自己的笔记、文档、收藏文章加起来大概两千多篇这个量级既不会小到没挑战也不会大到需要分布式集群。用一台普通笔记本就能跑完全流程调试成本极低。第三效果验证非常直观。问一个问题答案对不对、引用来源准不准一眼就能看出来。不像某些自动化任务跑完了你都不知道它到底做对没有。提示如果你也是第一次做 Agent 项目强烈建议从知识库问答切入。它的反馈回路短能让你在几天内就建立起对 RAG 全流程的肌肉记忆。这个项目最终的目标很明确把我散落在各处的个人笔记整合成一个可以自然语言提问的问答机器人问它我之前记过的那个关于向量数据库选型的对比在哪它能直接给出答案并附上原文出处。下面我把整个实践过程拆开讲包括技术选型、踩过的坑、以及那些文档里不会写的细节。2. 知识库问答的核心链路拆解2.1 从文档到可检索知识的完整流程很多人以为 RAG 就是把文档丢进向量库然后搜一下。这个理解会让你在后续调试时完全找不到问题出在哪。实际上一条完整的知识库问答链路至少包含六个环节每个环节都有独立的失败模式。文档加载把各种格式的源文件Markdown、PDF、Word、网页存档读成纯文本。这一步的坑在于编码和格式解析比如 PDF 里的双栏排版读出来会串行Markdown 里的代码块会被当成正文。文本切分把长文档切成适合检索的片段。切分策略直接决定了检索质量切太大则噪声多切太小则语义不完整。我试过固定长度切分、按段落切分、按标题层级切分三种方案最后用的是混合策略。向量化把每个文本片段转成向量。这里涉及嵌入模型的选择是本地跑还是调 API维度多少中文支持好不好。存储与索引把向量存进向量数据库建立索引以支持快速相似度检索。检索用户提问时把问题也向量化然后在库里找最相似的片段。这里可以加关键词检索做混合召回也可以加重排序模型做精排。生成把检索到的片段和用户问题一起组装成提示词交给大模型生成答案。这六个环节里任何一个出问题最终表现都是答得不对但根因可能完全不同。所以我在搭建时给每个环节都加了独立的日志和中间结果输出方便定位。2.2 为什么不能跳过切分策略这一步我见过太多人直接用一个固定长度切分器比如每 500 字切一刀然后就开始调模型参数。结果检索出来的片段经常从句子中间断开语义支离破碎。文本切分的本质是在检索粒度和语义完整性之间找平衡。切得太细每个片段信息量不足检索时容易匹配到大量无关的短片段切得太粗一个片段里混了多个主题向量表示会被平均掉检索精度下降。我最终采用的策略是优先按 Markdown 标题层级切分其次按段落切分最后对超长段落做滑动窗口切分。具体来说一级标题下的内容作为一个大块如果这个大块超过 800 字就按二级标题继续拆如果二级标题下还是太长就按段落拆单个段落超过 500 字的用 200 字窗口、50 字重叠做滑动切分。这个策略的好处是每个片段都保留了完整的语义单元同时通过重叠窗口避免了关键信息被切断。实测下来检索命中率比固定长度切分高了大概三成。2.3 嵌入模型选型本地还是 API这是很多人纠结的第一个技术决策。我的建议是先用 API 跑通流程再考虑本地化。原因很简单API 方案的嵌入质量稳定不用折腾环境能让你快速验证整条链路是否通畅。等你确认了流程没问题再换本地模型做对比测试。如果一上来就搞本地模型环境配置能吃掉你一半的精力而且你分不清到底是模型不行还是流程有问题。我用的嵌入模型维度是 1024中文语义表现不错。本地模型我试过几个开源的在中文短文本上的效果和 API 差距不大但部署和推理速度是另一个话题。对于个人知识库这种数据量API 的成本完全可以接受两千篇文档全部向量化也就几毛钱。注意嵌入模型一旦选定后续所有文档都必须用同一个模型向量化。换模型意味着整个库要重新嵌入这个成本要提前考虑。3. 技术栈选型与本地环境搭建3.1 为什么我选了这套组合技术选型这件事我的原则是成熟优先、文档优先、社区活跃优先。个人项目最怕的就是踩到一个冷门库的坑搜遍全网找不到解决方案。我的最终组合是这样的环节选型理由文档加载Python 多格式解析库支持 Markdown、PDF、HTML 等常见格式文本切分自研混合切分器通用切分器不满足按标题层级切分的需求嵌入模型云端嵌入 API稳定、免部署、中文效果好向量存储本地向量数据库轻量、支持持久化、无需额外服务大模型云端对话 API推理质量高、支持长上下文编排框架轻量 Agent 框架提供检索链和提示词模板这里重点说一下向量数据库的选择。我对比过三种方案纯内存的、需要独立服务的、以及嵌入式文件型的。纯内存的每次重启都要重新向量化不实用需要独立服务的比如要单独起一个进程的那种对个人项目来说太重最后我选了嵌入式文件型的数据直接落盘重启即用零运维。3.2 环境搭建中那些容易忽略的细节环境搭建看起来简单但有几个细节如果没处理好后面会反复出问题。Python 版本建议用 3.10 或 3.11。3.12 在某些依赖库上还有兼容性问题我踩过一次某个解析库在 3.12 上编译失败折腾了半天。虚拟环境一定要用虚拟环境别嫌麻烦。我见过有人直接在系统 Python 里装依赖结果不同项目的库版本冲突最后只能重装系统。依赖锁定把所有依赖的版本号固定下来写进 requirements 文件。Agent 相关的库更新很快今天能跑的代码明天可能就因为某个库升级而报错。API 密钥管理别把密钥硬编码在代码里。用环境变量或者配置文件并且把配置文件加入 gitignore。我就干过把密钥提交到仓库的蠢事虽然及时删了但心里一直不踏实。日志配置从第一天就把日志配好。Agent 的调试非常依赖日志你需要看到每一步的输入输出。我建议至少记录三个级别检索到的原始片段、组装后的提示词、模型的原始返回。3.3 目录结构设计一个清晰的目录结构能让你在项目变大后依然保持掌控感。我的结构是这样的knowledge-agent/ ├── data/ │ ├── raw/ # 原始文档 │ └── processed/ # 处理后的片段 ├── vectorstore/ # 向量库持久化目录 ├── src/ │ ├── loader/ # 文档加载 │ ├── splitter/ # 文本切分 │ ├── embedder/ # 向量化 │ ├── retriever/ # 检索 │ ├── generator/ # 生成 │ └── pipeline/ # 流程编排 ├── config/ │ └── settings.yaml # 配置文件 ├── logs/ # 日志 └── main.py # 入口这个结构的好处是每个环节独立成模块方便单独测试和替换。比如我想换一个嵌入模型只需要改 embedder 模块其他部分不受影响。4. 检索质量调优的实战过程4.1 第一次跑通后的答非所问流程跑通的那一刻很兴奋但紧接着就是打击。我问了一个关于向量数据库选型的问题它给我返回了一段关于数据库索引原理的内容虽然相关但完全不是我想要的。这就是典型的检索精度问题。我当时的检索策略是纯向量相似度取 Top 5 片段。问题出在两个地方一是向量相似度对选型这种带有比较意图的问题不敏感二是 Top 5 里混入了大量语义相近但主题不同的片段。4.2 混合检索向量加关键词第一个改进是引入关键词检索做混合召回。向量检索擅长语义匹配但对专有名词和精确术语不敏感关键词检索比如 BM25擅长精确匹配但不懂语义。两者结合召回质量明显提升。具体做法是向量检索取 Top 10关键词检索取 Top 10然后用倒数排名融合算法合并两个结果列表取合并后的 Top 5。这个算法不需要调参直接按排名倒数加权求和就行实测效果稳定。融合之后那个选型问题的检索结果里终于出现了包含对比选型方案这些关键词的片段。4.3 重排序让最相关的排到最前面混合检索解决了召回不全的问题但排序不准依然存在。有时候最相关的片段排在第三第四位而模型只看了前两个就生成了答案。重排序的思路是先用召回阶段拿到一个较大的候选集比如 20 个然后用一个更精细的模型对这 20 个片段重新打分排序取前 5 个给生成模型。这个精细模型可以是交叉编码器它会把问题和片段一起编码计算相关性分数精度比向量相似度高很多。我加了这个环节后检索的 Top 1 命中率从大概六成提升到了八成以上。代价是每次查询多了一次模型调用延迟增加了几百毫秒但对个人使用来说完全可以接受。4.4 查询改写让问题更容易被检索到还有一个容易被忽略的环节是查询改写。用户的问题往往是口语化的、模糊的直接拿去检索效果不好。比如用户问我之前记的那个向量库对比在哪直接检索可能匹配不到因为文档里写的是向量数据库选型分析。这时候可以用大模型先把问题改写成更适合检索的形式比如向量数据库 选型 对比 分析再去检索。我试过两种改写策略一种是让模型直接输出关键词另一种是让模型生成多个不同角度的查询。后者效果更好因为可以从不同维度召回但成本也更高。对于个人知识库我最终用的是单次改写加关键词提取的组合。提示查询改写会增加一次模型调用如果你的场景对延迟敏感可以只在检索结果置信度低时才触发改写。5. 生成环节的提示词工程5.1 为什么把片段塞进去远远不够检索做好了生成环节同样不能马虎。我最初的提示词就是简单地把检索片段和问题拼在一起结果模型经常自由发挥编造一些文档里没有的内容。这个问题的根源在于模型不知道自己的任务边界。它看到一堆文本和一个问题默认行为是尽力回答而不是只根据给定文本回答。所以提示词必须明确约束它的行为。我最终的提示词结构是这样的你是一个知识库问答助手。请严格根据下面提供的参考资料回答问题。 规则 1. 如果参考资料中没有相关信息直接说知识库中没有找到相关内容不要编造。 2. 回答时引用具体的资料编号格式为 [1] [2]。 3. 如果多个资料有冲突指出冲突并说明各自的说法。 4. 回答要简洁直接给出结论不要复述问题。 参考资料 [1] {片段1} [2] {片段2} ... 问题{用户问题}这个提示词的关键在于把不知道就说不知道变成了明确规则而不是指望模型自觉。实测下来编造内容的情况大幅减少。5.2 引用溯源让答案可验证知识库问答和普通聊天最大的区别是答案必须可验证。所以我要求模型在回答时标注引用来源这样用户可以点回去看原文。实现方式是在组装提示词时给每个片段编号然后要求模型在答案里用编号标注。生成后再把编号映射回原文的标题和路径展示给用户。这个功能看起来简单但极大提升了可信度。当模型说根据 [2]向量数据库选型要考虑三个维度时我可以直接点开 [2] 看原文确认它没有断章取义。5.3 处理知识库没有的情况这是最考验设计的地方。用户问了一个知识库里没有的问题模型如果硬答就是幻觉如果直接说不知道体验又不好。我的处理策略是分两层第一层是检索阶段如果所有召回片段的相似度都低于某个阈值直接返回知识库中没有找到相关内容不进入生成环节。第二层是生成阶段即使检索到了片段如果模型判断这些片段和问题无关也要求它明确说明。这个阈值需要根据实际数据调。我一开始设得太高导致很多能答的问题被拒设得太低又放进来一堆无关内容。最后是通过一批测试问题反复调整找到了一个平衡点。6. 那些文档里不会写的踩坑记录6.1 中文分词的隐形陷阱做中文知识库分词是个绕不过去的坎。我一开始用的是默认分词器结果发现它对专业术语的处理很差。比如向量数据库被切成向量数据库三个词检索时匹配精度大打折扣。后来我加了一个自定义词典把知识库里出现的专业术语都加进去。这个词典可以自动从文档里提取高频词组生成也可以手动维护。加了词典之后关键词检索的准确率明显提升。还有一个坑是停用词。中文里的的了是这些词如果不过滤会稀释关键词的权重。但停用词表不能照搬通用的因为有些词在你的领域里可能是关键词。我的做法是先跑一遍全量文档统计词频把那些在所有文档里都出现且没有区分度的词加入停用词表。6.2 向量库持久化的坑我用的是嵌入式向量库数据落盘。第一次重启后发现检索结果全变了排查半天才发现是持久化配置没写对数据其实没存进去每次启动都在重新向量化。这个问题的隐蔽之处在于它不会报错只是行为不一致。我的建议是第一次搭建时向量化完成后立刻重启一次验证数据是否真的持久化了。另外向量库的目录要加入版本控制的白名单别不小心被清理脚本删了。6.3 长文档处理的性能问题我有一批 PDF 文档单个文件上百页。第一次全量处理时内存直接爆了。原因是加载器把整个 PDF 读进内存切分器又在内存里做多次复制。解决办法是改成流式处理加载器逐页读取切分器逐段处理向量化逐批提交。这样内存占用从几个 G 降到了几百 M。虽然处理速度慢了一点但稳定性大幅提升。注意批处理的大小要控制好。批太大内存吃不消批太小 API 调用次数多、成本高。我最后用的批大小是 50 个片段一批兼顾了效率和稳定。6.4 模型返回格式不稳定的问题我要求模型在答案里标注引用编号但模型有时候会忘记有时候格式不对比如写成参考1而不是[1]。这导致后续的引用映射失败。处理这类问题的通用思路是不要指望模型 100% 遵守格式而是在代码里做容错解析。我用正则表达式匹配多种可能的引用格式匹配不到就降级处理至少保证答案本身能展示出来。如果格式要求特别严格可以用结构化输出功能让模型直接返回 JSON。但这会增加提示词的复杂度也可能影响生成质量需要权衡。7. 从能用到好用的进阶优化7.1 增量更新别每次都全量重建知识库是活的我每天都在往里面加新笔记。如果每次加一篇就全量重新向量化那太浪费时间了。增量更新的思路是记录每个文档的哈希值和最后修改时间每次更新时只处理新增和修改过的文档删除的文档从向量库里移除对应片段。这样日常更新只需要几秒钟。实现上需要注意两点一是文档 ID 要稳定不能每次生成新的二是删除操作要彻底否则旧片段会一直留在库里干扰检索。7.2 多轮对话的上下文管理单轮问答跑通后自然会想支持多轮对话。比如先问向量数据库有哪些选型再问第一个方案的优缺点是什么。多轮对话的难点在于指代消解。第二轮的第一个方案需要结合第一轮的上下文才能理解。我的做法是在检索前先用模型把当前问题改写成独立完整的问题再进行检索。这样检索阶段不依赖对话历史生成阶段再把历史带上。这个方案的好处是检索逻辑保持简单坏处是多了一次改写调用。对于个人使用这个成本可以接受。7.3 效果评估怎么知道改进了没有优化最怕的是感觉好像好了一点没有量化指标就没法持续改进。我建了一个小型的评估集包含 50 个问题和对应的标准答案片段。每次改动后跑一遍统计三个指标检索命中率标准片段是否在 Top 5 里、答案准确率人工判断、引用正确率引用编号是否对应正确片段。这个评估集不需要很大但必须覆盖不同类型的查询事实型、比较型、总结型、以及知识库外的问题。有了这个基准每次优化都能看到明确的数字变化。7.4 成本与延迟的平衡个人项目虽然不追求极致性能但成本和延迟还是要在意的。我统计了一下一次完整问答的 API 调用包括查询改写一次、嵌入一次、重排序一次、生成一次。如果每次都走全流程单次成本大概几分钱延迟两三秒。优化方向有几个简单问题跳过查询改写和重排序直接走向量检索加生成缓存高频问题的答案把嵌入和重排序换成更轻量的本地模型。我目前用的是按问题复杂度动态选择流程简单问题走快速通道复杂问题走完整流程。8. 关于 Agent 化的一些思考8.1 知识库问答算不算 Agent这个问题我被问过好几次。严格来说基础版的知识库问答是一个固定的检索生成流水线没有自主决策算不上 Agent。但它是一个非常好的 Agent 雏形。当你给它加上判断问题是否需要检索决定检索几次选择用哪个知识库判断答案是否足够好不够好就重新检索这些能力时它就开始具备 Agent 的特征了。我的项目目前加了一个简单的自我反思环节生成答案后让模型判断这个答案是否充分回答了问题如果不充分就换一个检索策略再试一次。8.2 从问答到知识助手的演进路径我接下来的规划是把这个问答机器人往知识助手方向演进。具体包括主动发现知识库里的关联比如两篇笔记讲了同一件事的不同侧面、定期生成知识摘要、根据我的提问历史推荐可能感兴趣的内容。这些功能的共同点是都需要 Agent 的规划能力而不是简单的检索生成。这也是我觉得知识库问答作为第一个 Agent 项目特别合适的原因它的基础扎实往上生长的空间很大。8.3 个人知识库的独特价值最后说一点感受。市面上的通用问答工具很多但个人知识库问答有它不可替代的价值它回答的是我自己记过什么而不是世界上存在什么知识。这个区别意味着答案的个性化程度极高而且随着你记录的增多它的价值会持续增长。我在实际使用中发现最有用的场景不是查具体事实而是我好像记过某个东西但想不起来在哪。这种模糊的、探索式的查询恰恰是传统搜索做不好而语义检索擅长的。每次它从几千篇笔记里精准定位到我要找的那一段那种感觉还是很爽的。如果你也在考虑做自己的知识库问答我的建议是别追求一步到位。先把最基础的链路跑通哪怕效果一般然后在使用中发现问题、逐个优化。这个过程本身就是对 RAG 和 Agent 最好的学习。