
接上历史工单和知识库这个需求听起来好像就是“给Agent配个资料库”这么简单但真正落地的时候会发现问题从来不是“查不查得到”而是“查到的东西怎么用、敢不敢信”。我做了几个企业客服场景的Agent项目之后最大的感受是Agent能不能稳定给出可靠答案七成取决于你喂给它的历史工单和知识库怎么收拾、怎么检索剩下三成才轮到模型本身和Prompt设计。这篇文章就把我在实际开发中踩过的坑、试过的方法、调参的心得全部整理出来从数据准备、检索增强到工具调用和防幻觉兜底一条线给你讲清楚。这整套方案的核心思路是不指望LLM记住所有业务知识而是让它在面对相似问题时先去历史工单和知识库里检索证据再基于检索到的内容组织答案。适合正在做企业级知识问答、客服工单自动处理、内部IT支持这类场景的开发者参考哪怕你还没接触过RAG按照文章里的步骤也能搭出一个能跑通的“有依据问答”系统。1. 为什么必须让Agent“查”而不是“记”需求拆解与架构选型1.1 幻觉问题的本质LLM不知道就是不知道先说一个几乎所有Agent项目里都会遇到的老大难问题——幻觉。现象很熟悉你问Agent“上次XX系统故障怎么处理的”它给你编了一个看起来逻辑通顺、但实际从没发生过的解决方案。如果你拿这个答案发给用户轻则被质疑专业性重则误导用户做出错误操作那就不是尴尬而是事故了。幻觉的根源在于大语言模型本质上是一个“概率补全机器”。它学习的规律是“这些词接在这些词后面比较合理”而不是“这件事的真相是什么”。当它遇到训练数据里没有覆盖到的、或者出现频率很低的信息时就会启动自己最擅长的“合理脑补”模式。这里有一个区分点很多人容易忽略模型不是数据库不像MySQL那样你能精确查到一条记录就返回那条记录。哪怕模型回答错了它也绝不会告诉你“我不知道”它只会给你一个“看起来很有信心”的答案。所以对抗幻觉的第一原则是别逼模型用“记忆”回答问题而是给它“外挂”——历史工单和知识库。让模型从外部资料里去检索再基于检索到的内容进行摘要和重组。这样就算模型本身的记忆里没有这个知识它也能借助外部证据给出有出处的答案。这就像考试允许开卷你手里有参考书答题的正确率自然比空手裸考高很多。1.2 RAG、微调、长上下文三条路怎么选想让Agent“有依据”最常见的三个技术路线分别是RAG检索增强生成、微调Fine-tuning、长上下文直接投喂Long Context。我在项目里全都试过说说各自的优缺点。先说长上下文直接投喂。之前有段时间我很偷懒想把几千条历史工单全部塞进Prompt里让模型一次性读完。结果发现两个致命问题第一token费用高得离谱每问一个问题都在烧钱第二模型实际上并不会真的“读完”几万token的文本它对中间位置的注意力会大幅衰减。论文里叫“Lost in the Middle”意思就是模型对长文本首尾的内容记得清楚中间一大段基本是模糊的。你塞进去5000条工单它真正能利用的可能就头尾几十条。这个方案对一些短文档场景勉强能用但面对历史工单这种动辄上万条、单条几百上千字的场景完全不现实。再说微调。微调的本质是让模型调整自身的权重把某些知识“内化”到模型里。这个方案的问题在于第一历史工单和知识库是动态变化的今天新增一个问题明天修改一个SOP难道每次都重新微调第二微调需要高质量标注数据和时间成本对于中小团队来说投入产出比太低。第三也是最关键的即使微调得很好你依然无法控制模型“复述”出来的内容是否跟原始资料逐字一致幻觉风险只是降低了没有消除。最后说RAG。RAG的思路是“不改变模型改变模型的输入”。每次提问时先从向量数据库或搜索引擎里召回与问题最相关的几条历史工单或知识片段然后把它们作为参考上下文揉进Prompt里让模型基于这些材料给出答案。它的优势非常明显知识更新只需要替换索引里的数据不需要重新训练模型每个答案都有明确的资料出处可以追溯而且检索召回本身是一个确定性过程可以测试、可以调优、可以解释。我现在的项目基本都是这个路线核心就是“先查证后回答”。1.3 Agent历史工单知识库的整体协作逻辑明确了用RAG接下来要回答的是Agent和RAG怎么配合的问题。很多朋友理解的“RAG”是用户问一句后台把这个句子拿去检索然后把结果拼进Prompt再让模型回答。这个模式叫“单轮检索增强”能解决一部分问题但距离“有依据地查相似问题”还有差距。真正做Agent化的RAG应该让模型自己决定“要不要查”“查什么”“怎么查”。这就要用到Function Calling或者工具调用机制。举个例子用户的问题很可能是“打印机一直报错E02之前遇到过吗”你直接拿这个完整句子去检索效果可能一般。但是如果你把这个句子拆解成检索参数——问题类型“打印机报错”、故障代码“E02”、设备型号“HP LaserJet M1005”——检索出来的结果会精准得多。所以我会把整个流程设计成Agent先理解用户问题的意图判断这是否需要检索历史工单/知识库如果需要就调用一个专门设计的search_knowledge工具传入经过解析的关键词、分类字段、时间范围等参数。工具返回一批相似工单和知识文档Agent再对这些内容进行总结、推理最终给出带引用的回答。整个过程就像一个经验丰富的客服先查系统里的历史记录再结合当前情况给方案而不是凭记忆随口说。2. 数据准备把散乱的历史工单变成高质量知识资产2.1 工单清洗去掉噪声留下可检索的“答案”我接手过的历史工单少则几千条多则几十万条。乍一看数据量很大但真正清洗下来才发现大量工单是没法直接使用的“脏数据”。这里说的脏不只是格式乱更是内容本身没有检索价值。最常见的几类废数据一是内部流转信息比如“工单转给张三处理”“李四回复收到正在排查”这类信息不包含任何问题解决方案二是原始报障描述里带了大量无关的闲聊、情绪发泄、环境信息三是很多工单是操作记录而非解决方案比如“重启了服务器”“更换了网线”但没有说明“为什么要这么做”“这个操作之后现象是否消失”。如果直接把这类记录塞进知识库Agent检索到之后也无法给出有价值的答案。我的清洗思路是提炼“问题-原因-方案”三元组。拿到一条原始工单后会先拆成几个字段问题描述用户报障时说了什么、诊断过程排查步骤和关键现象、最终方案怎么解决的、关联标签涉及的系统、模块、错误码。清洗的目标不是把工单变成一篇漂亮的文章而是把它变成一段“可以被检索到、且检索到以后可以直接回答用户”的内容。这里有个实用小技巧清洗不完全靠人工我用LLM比如GPT-4级别的模型或者开源大模型做初步抽取让模型把每条工单转成结构化JSON。注意抽取的时候要给一个明确的Prompt模板比如“请从这段工单中提取问题概述、触发条件、排查过程、解决方案、涉及系统。如果某个字段不存在直接写无不要编造”。跑完之后人工抽检一部分修正格式这样清洗效率会高很多。2.2 文本切分chunk策略决定检索质量的上限清洗好的工单内容要进入检索系统面临一个绕不开的问题怎么切分文本向量检索和关键词检索的unit都是“一段文本”这段文本的大小和边界直接决定了召回的质量。我见过太多人直接拿整篇工单去做Embedding结果就是检索效果稀烂——向量只有一个但工单里可能包含排查过程和最终方案两种完全不同类型的信息语义混在一起Similarity Score上不去。切分的核心原则是“保持语义完整性”。我踩过很多坑之后总结出了一个相对实用的策略按语义段落长度上限双重控制。比如把一条工单按“问题描述”“诊断过程”“最终方案”这三段来切每段控制在200~500字左右。如果某一段特别长比如诊断过程写了2000字再按句子或子段落切分成多个chunk但要保证每个chunk单独看依然是完整的、可理解的。实际工程中很多团队喜欢直接用LangChain的TextSplitter但默认参数比如chunk_size500, chunk_overlap50不一定适合工单场景。工单数据的一个显著特点是信息密度不均衡前半段可能是详细的问题背景后半段可能就是结论。我会在设计chunk时额外添加一个“head”字段就是每个chunk的第一句或前两句话作为索引的摘要。这一步看似简单但对提高召回率帮助特别大——因为向量模型对开头部分的语义更敏感。2.3 向量化与元数据管理让检索结果更精准文本切好之后接下来是向量化。当前主流的Embedding模型比如OpenAI的text-embedding-3-small、开源的bge-large-zh、text2vec系列都能把中文文本转换成密布语义信息的向量序列。选择Embedding模型时不一定要追最新最强的模型要看它的中文语义理解和同义改写能力。实际测试中我发现企业对工单里的“黑话”比如“机子卡死”“页面白屏”“内存溢出”有不少同义词Embedding模型如果理解不了这种映射检索质量就会拉胯。我自己用的比较多的是bge-m3它对中文支持不错且支持多语言混合关键还能在CPU上跑推理部署成本很低。向量化做完之后库里的每一段文本都会对应一个向量。但向量只是一个“容器”它本身不会告诉你这段文本属于哪个工单、涉及系统是什么、发生在什么时间。所以一定要把元数据建好。以工单为例我的元数据设计长这样工单ID、工单标题、问题类型比如“打印机故障”/“网络异常”、涉及系统/设备、错误码、解决时间、人工最终方案。这些元数据表面上看起来只是“附加信息”但它们在三路召回和过滤中极其有用——后面讲检索的时候你就会体会到类型字段和错误码字段可以直接缩小搜索空间把准确率提上来不止一个档次。这里的重点是说知识库构建绝不只是“把文档丢进Embedding模型”而是一个包含清洗、切分、向量化、元数据管理的完整工程。你前面越用心后面Agent查相似问题就越准这是一个收益随投入递减的良性循环。3. 检索增强让Agent能查到“相似”的问题3.1 双路召回关键词检索与向量检索的互补很多人对RAG的刻板印象是“Embedding向量检索就是一切”。但实际生产环境里只靠向量检索往往不够——至少不能只靠一种。这里讲一个我真实踩过的坑工单里有一个很常见的字段叫“错误码”比如E02、500、timeout。向量模型在计算“E02”和“E03”的语义时会觉得它们“差不多”或“都很接近”但业务上E02和E03的处理方案完全不同。一个靠向量相似度来检索的系统能把E02的工单召回出来很可能也会把E03的工单一起拉回来结果就是答非所问。所以我采用的做法是“双路召回”向量检索关键词检索并行然后再把两路结果合并。关键词检索用的是传统的BM25算法或者ES里的Match Query它的核心是字面匹配对错误码、型号、设备编号这类精确token的识别比向量模型靠谱得多。举个例子用户说“Jetpack模块报错401”BM25能精确匹配出包含“Jetpack”“401”的历史工单而向量检索可能召回到“401未授权”相关的其他工单。两条路侧重点不同配合使用就是一个“既懂语义、又懂关键字”的组合。工程上怎么落地也简单如果你用了Elasticsearch那就用它的KNN向量检索和BM25原生能力做混合查询设置权重比如0.7向量0.3关键词如果团队用的是向量数据库独立搜索也可以在代码里写一个并行检索函数最后按相关性得分加权融合。初版可以先暴力一点把两路Top20的结果全合并进去让后面过滤器决定谁留下。3.2 重排序模块别让最强证据淹没在结果里双路召回得到的结果是“候选集”而候选集里真正有用的可能就那么一两段。这时候你需要一个重排序Rerank模块。重排序的职责是拿用户的问题对已经召回的候选文档逐一计算细粒度相关性把最相关的排到最前。我用过几种重排序方式简单介绍一下。第一种是直接用LLM做Rerank把用户问题和候选文档组合成一个Prompt让模型判断“这段内容是否与问题相关相关程度是多少”。这种方式准度高但速度慢、token花费也高适合对精度要求极端的场景。第二种是用专门的Rerank小模型比如bge-reranker-base它能在几百毫秒内对几十个候选做分数计算效果也非常好。第三种是“启发式重排”看起来不那么优雅但实际很好使给检索结果附加权重比如“错误码精确匹配80分”“标题关键词命中30分”“方案字段存在20分”“时间越近加分”这样就能把最强证据顶到前面。我现在的项目用的是“小模型Rerank启发式权重”的组合。先让bge-reranker算一遍语义分数再叠加元数据命中情况做加权修正。实际效果是Top1的命中率会比纯向量检索提升20个百分点以上。这个环节是很多人忽略但性价比最高的部分。3.3 参数调优TopK、Score阈值、窗口大小的实战设置检索环节有一堆关键参数看起来不起眼但其实对最终效果影响巨大。第一个是TopK也就是召回数量。太小了容易漏掉正确答案太大了会把噪声也带进来。以工单场景为例我通常设置TopK20也就是双路各召回20条然后Rerank后取Top3~5作为最终上下文。这个数字不是拍脑袋拍出来的。我做过实验Top1的准确率通常只有60%左右Top3能到85%Top5能到95%。但再往后多召回的文档不仅没有提升准确率反而因为占了Prompt的位置分散模型注意力轻微拉低了效果。所以TopK5是我们项目的常用配置。第二个是Score阈值。向量检索出来的相关性分数在不同Embedding模型下取值范围差异很大有的是0~1有的可能是负值。如果只设一个硬阈值很容易出现两种极端情况要么把低质量的噪声全放进来要么把一些本来可用的答案全挡在门外。我在实践中更推荐“按位次截断动态阈值”的策略先按排序取TopK再做一个最低分数线比如相似度低于0.5的直接丢弃。这个0.5也不是固定的要根据你实际数据的分布去标定。比如你已经构建了5000条向量可以抽样100条正样本、100条负样本分别统计相似度分布然后选一个能把正样本大部分留住、负样本大部分滤掉的点作为阈值。第三个是上下文窗口大小。检索回来的段落如果太长直接塞进Prompt会爆token甚至被模型当成长篇大论忽略掉。我的做法是取Rerank后的Top3~5个chunk每个chunk截取头150~300字作为参考上下文。如果这些chunk里有一致提到的同一个方案Agent的答案大概率就是这个方案。另外我强烈建议把检索到的原始文档出处也拼接进上下文里这样后续溯源和Step-by-step验证都方便。4. 工具调用与工程化落地Agent如何“有依据”地回答4.1 Tool规范一个search_knowledge函数的设计Agent能不能正确地利用历史工单和知识库关键在工具定义。从工程角度我们需要给Agent暴露几个供它思考时调用的Function其中核心的一个就是search_knowledge。这个工具的入参设计很重要因为它决定了Agent的“策略空间”。我设计的search_knowledge参数大致如下query必填用户问题转换成检索词Agent需要自己改写成简洁清晰的检索句。question_type可选问题类型如硬件故障、网络异常、账号权限Agent根据用户描述判断。error_code可选错误码比如E02、500、404如果有精确的值一定要填上。system可选涉及系统或设备比如ERP系统、打印机型号。时间范围from/to可选可以限定工单时间范围。这个工具的设计逻辑是“让参数尽可能反映业务特征”。比如用户说“我的电脑连接到公司网络之后报错提示错误代码500”Agent会调用search_knowledgequery填“电脑连接网络报错500”error_code填“500”system可能填“网络设备”这样搜索引擎召回的结果就会精准锁定在“报错500”的工单里而不是把“500万预算案审批”这种无关工单也拉出来。你可能会问为什么不直接把用户的整句话拿来做query因为在真实场景里自然语言的句子做检索噪声很大。比如“有没有人遇到过打印机卡纸和一直嗡嗡响的问题在线等急”直接拿这句话检索会匹配到一堆冗余词汇。让Agent先做意图理解和字段抽取再调用工具其实是把“语义解析”和“检索”分成两步这是Agent化RAG和普通RAG最大的区别之一。4.2 Prompt设计让Agent学会用检索结果而不是背答案有了工具还不算完另一个极其关键的点是Prompt。Agent要能意识到“我需要先查一下知识库”并且在拿到检索结果后能克制住自己“直接发挥”的冲动老老实实基于内容作答。实际开发中我会在System Prompt里明确写清楚Agent的工作流和边界比如你是一名客户服务专家。在回答任何与产品使用、故障处理相关的问题前你必须调用search_knowledge工具检索历史工单和知识库。当检索结果中包含明确答案时请优先基于检索内容回答并且在回答中引用对应工单编号或知识文档标题。当检索结果不包含答案或与问题不相关时你应该告知用户“当前知识库中没有找到准确的解决方案”并建议用户联系人工客服而不是自己猜测。回答时请使用简洁、清晰、口语化的语言适合非技术背景的用户阅读。Prompt还有一个细节容易忽略——Agent在生成回答时需要看到检索结果中“哪一条和当前问题最匹配”。我在返回给Agent的上下文里除了文档正文还会附上Rerank的分值和来源ID。比如[Doc1]相似度0.92来自工单20230415-001打印机卡纸解决方案...。这样Agent就有了判断依据优先参考高分文档低分或明显不匹配的文档可以直接忽略甚至能识别出“这条检索出来的内容虽然相关性一般但里面提到了和用户问题的共同点”。另外就是要规范“引用格式”。我通常让Agent在答案末尾输出“参考来源工单#20230415-001”这样的格式。这不仅能提高可信度还能为后续做自动化评测提供数据——比如你可以统计Agent回答里引用的工单是否正确以此衡量系统的准确率。4.3 引用溯源与兜底策略避免幻觉的最后一层防线就算检索和Prompt都做好了Agent偶尔还是会“放飞自我”。这时候就需要兜底机制来拦截。我总结下来的三层防线是第一层答案后验证。Agent生成回答之后让一个独立的验证模块或者Agent自己重新检查一遍回答里的关键信息是否都能在参考文档里找到对应文本片段。这相当于让Agent做一次“自我核对”——如果你发现它回答了一个文档里完全没有的内容就把这段标记为“疑似幻觉”并重新基于检索内容生成。第二层置信度判断。基于Rerank分数和最终答案与检索文本的字符串重合率来算一个“可信度”。如果可信度低于某个阈值就什么都不做直接降级答案为“抱歉根据历史工单和知识库暂未找到匹配的解决方案建议转人工”。在客服场景里宁可不答也不能瞎答。我真实经历里一次特别惨的教训就是因为没做这层置信度判断Agent把一条“测试工单”里的临时方案告诉用户导致用户照着操作后把系统搞挂了。从此之后我所有生产环境的Agent没有高置信度结果一律拒绝回答。第三层日志与分流。让Agent在回答时记录下它参考了哪些工单、知识库中哪些条目。一旦用户反馈“这个答案不对”可以立刻根据日志回放整个推理链路定位到底哪一步出了问题——是检索没召回正确数据、还是Rerank排错、还是Agent生成了不该有的扩展。这个可追溯性是Agent化系统的刚需也是它比普通文档检索系统更难替代的原因之一。三层防线都做到位之后Agent的幻觉率基本能被控制在一个非常低的水平。说到“有依据地查相似问题”最重要的不是“查”的动作而是“依据”的可靠性和“回答”的谨慎度——这一点希望每个做Agent的人都能真正重视起来。5. 常见问题与排查技巧实录5.1 检索结果不相关先检查你的问题拆解对不对很多初学Agent开发的朋友反馈最多的问题就是“我构建了知识库检索也能跑通但Agent回答的还是不对。”这里十有八九是检索召回出了问题但很多人第一时间去改Prompt这是方向性错误。我的排查顺序是先看Agent调用search_knowledge工具时传入的参数是否正确。比如用户说“服务器一直重启日志里出现kernel panic错误”Agent如果传的query是“服务器一直重启”那漏掉的关键信息“kernel panic”就是你召唤垃圾结果的元凶。如果工具参数没问题就看召回Top5里有没有正确文档。如果Top5里没有就是Embedding或关键词检索的匹配能力问题如果Top5里有但答案还是错那问题就出在Prompt——Agent没有正确利用文档。这里我建议你在开发阶段加一个“链路追踪中间件”把Agent每一步的思考过程、工具调用入参、检索到的Top5文档、Rerank分数都打印出来。别嫌丢人这东西就是定位问题的利器。很多项目调试困难就是因为链路是黑盒只知道结果错不知道错在哪一步。5.2 知识库更新与增量同步别让Agent用过期工单回答新问题企业内部的知识是持续变化的旧的工单会失效新的方案会覆盖旧方案。如果知识库不能及时更新Agent就会拿着两年前的老方案来指导用户后果可想而知。我见过最典型的一次事故是系统升级后新接口路径变了但知识库里还存着旧路径Agent信心满满地告诉用户旧路径结果一连接就404。增量同步有几个实用策略。第一工单入库时做“去重版本标识”。如果同一问题出现了新的工单比如同一个错误码在新版本系统里解决方式不同老的工单应该被标记为“旧方案”确保检索时优先返回新方案或者在Prompt里注明“该方案适用于旧版本系统”。第二定期比如每晚跑一遍离线任务检查知识库里的工单有没有更新约定一个简单的规则如果相似度极高比如两段文本重合度超过90%而信息有差异那就把旧doc进入“待更新”状态。第三上线前一定要做回归测试拿一批固定的测试问题集跑一轮新旧知识库的对比看看更新是否引入了新的答案偏移。5.3 性能和并发能不能撑住真实访问量AgentRAG系统上线后最现实的问题就是性能。一次完整问答涉及至少两次LLM调用一次是Agent理解意图一次是生成回答中间还要加一次向量检索和一次Rerank。如果每个请求都这么跑一遍并发一上来很容易被打崩。我的经验是三步优化。第一步缓存高频问题通常是同一个TOP问题集可以把完整回答缓存起来命中缓存直接返回省掉所有计算成本。第二步异步编排Agent的第一步意图理解可以用轻量模型只要能把用户问题转换成工具调用参数就够了真正的答案生成再用大模型。编辑项目里这个思路还能大幅降低单请求消耗。第三步限流和降级给知识库检索做一个独立的服务节点如果LLM调用失败或者超时可以让Agent直接返回检索到的Top1文档内容作为临时答案至少保证用户有一个可用的响应。如果你用的是Dify或类似平台来做RAG流水线也要注意编排里的超时设置和重试机制。很多人把整条流水线串行化某个环节一卡就全线崩溃正确做法是把检索环节和LLM环节拆开必要时熔断。5.4 实测数据参考这套方案的效果到底怎么样文章最后给一个真实项目的数据供参考。我做过一个IT运维客服Agent接入了大约3万条历史工单包含产品故障、账号权限、网络设置三类问题。上线前用200条人工标注的测试问题做评估纯向量RAG不接Rerank的Top5命中率是78%加上关键词双路召回后提到85%再加Rerank和元数据加权后达到94%。最终用户满意度调研里“答案准确”一项的评分从没有系统时的3.1分提升到了4.5分满分5分。但这里要特别说明效果不是单一技术点带来的是数据清洗、双路召回、Rerank、Agent工具调用、Prompt边界这几层共同作用的结果。单独拎出任何一块效果都不会这么明显。这也是为什么我特别不建议直接照着网上的Demo抄一套RAG就上线。我个人在实际项目里最大的体会是做“有依据”的Agent难点不在Agent而在于你给它的“依据”靠不靠谱。与其花很多时间调Prompt不如先把知识库的数据质量和检索链路打磨扎实。这两件事做好了即使换一个不那么大参数的模型效果也不会差太多。后面如果大家对工单数据清洗的Prompt模板、或者双路检索加Rerank的具体实现代码感兴趣我可以再写一篇展开讲。