Agent框架重构与RAG优化实战:从大函数到可扩展系统

发布时间:2026/10/19 9:21:06

Agent框架重构与RAG优化实战:从大函数到可扩展系统 1. 为什么要把 Agent 框架重构提上日程写这个系列到第五篇前面四篇已经把 Agent 的核心链路、工具调用、提示词组织、以及最基础的 RAG 流程都跑通了。很多读者私信我说按着文章抄作业demo 确实能跑起来但一往真实场景里推就卡住了要么加一个新工具要改三四个文件要么同一个问题换一种问法答案就崩要么检索到的内容乱七八糟LLM 根本没法用。这些问题其实不是某一个环节坏了而是整体框架的“形状”出了问题。先说一个最直观的场景。早期版本里一个函数几百行把“接收用户输入-调用模型-拼接知识-解析工具结果-再生成回复”全部塞在一起。这种写法在演示阶段没问题跑通就行。但当你需要同时接入三个数据源、五个工具、两套提示词模板的时候函数之间的耦合会让你寸步难行——改一个检索逻辑生成逻辑跟着遭殃调一个工具参数输入解析也要动。时间一长你根本不敢碰代码因为每一次修改都像在拆一颗不知道有几根引线的炸弹。重构不是风格洁癖是被真实需求逼出来的。你思考的问题不再是“怎么让它再跑通一次”而是“怎么让我下次加功能的时候只动一个文件、只改一个配置”。这才是框架存在的意义。做 Agent 项目也是一样前几篇解决的是“能用”这一篇解决的是“好用、好改、好扩展”。还有一个特别容易被忽略的推手可复现性。你已经把 bug 修好了、参数调顺了但一个星期后回头看代码发现自己都看不懂当初为什么要这么写。更别提换了一台机器、换了一个模型接口之后结果完全对不上。这就说明你的框架里缺了清晰的边界和显式的状态流转记录——重构要补的正是这些东西。这一篇我会把两条线拆开讲。第一条线是框架侧的重构从分层、状态管理、工具注册到配置化第二条线是 RAG 侧的优化从分块策略、混合检索、重排序到上下文压缩。两条线看起来独立实际上在 Agent 场景里是咬合的——RAG 撑起的是 Agent 的知识底座框架撑起的是 Agent 的行动控制任何一边拖后腿整个系统都会跟着出问题。适合读这篇的人我大致归成三类一是已经写完一个能跑的 Agent 项目、想往工程化方向收敛的开发者二是做知识库问答场景、觉得现在召回结果不理想、想在检索质量上做纵深优化的人三是准备拿 Agent 作为半年期项目、需要一份相对完整的系统设计思路作为参照的人。如果你还停留在概念阶段、连第一条 Agent 链路都没跑通过建议先回过头去补前四篇不然这篇很多内容你会缺少体感。2. 框架重构把一个“大函数”拆成“一套系统”2.1 重构的第一刀先把职责边界划清楚我重构的第一个动作不是重写代码而是把原来那个几百行的主流程摊开按职责画了一张表。核心就三行谁决定下一步做什么、谁负责具体做一件事、谁负责和外部世界打交道。这三行落到代码上就是三个独立的层次。编排层负责理解用户意图、维护对话状态、决定调用哪个工具、判断是否需要检索知识以及什么时候把控制权交还给用户。执行层负责调用具体工具、执行检索、拼接上下文一切“动手”的活都在这里。接口层负责和模型、向量库、外部 API 打交道把底层实现细节屏蔽掉。这个划分看起来简单实际效果非常明显。比如我要换一家模型服务商的接口只需要改接口层那一个文件我要新增一个工具只需要在工具注册表里登记一次然后在编排层的工具列表里加一条执行层写清楚“这个东西怎么被调用”就够了。互不干扰各管一段。早期版本的“大函数”最典型的毛病是一个方法里既在判断用户情绪又在计算检索分数还在拼装系统提示词。你把这四件事混在一起每件事的测试都没法单独做。重构之后每一层都能单独 mock 掉上下游进行测试模块之间的边界也变成了自然的测试边界。一个很容易走进的误区是为了分层而分层把本来三五行就能讲清楚的逻辑硬拆成五个类。分层要服务的是“变更频率”——变更频繁的工具接入、检索策略、模型适配一定要隔离出来几乎不变的核心对话流程可以少一点抽象。判断标准很简单你问自己如果下周要加一个全新的工具你要动哪几个文件如果答案超过了两个说明边界还没划到位。2.2 状态管理别再用布尔变量和 if-else 硬撑了Demo 阶段的 Agent对话状态一般靠一个字典加上十几个 if-else 就能撑住。但这个方案在框架重构里是第一个要被换掉的。原因很实际Agent 的对话不是线性的“问一句答一句”它有分支、有等待、有并行处理还有一个很要命的场景——多轮工具调用。用户说“帮我查一下上周的销量对比一下前一周然后总结变化”这句请求需要先翻译成多个子任务子任务之间还有依赖关系。if-else 能处理几个这样的嵌套我最终采用的是轻量状态机模型。把 Agent 的状态定义成几个明确节点接收输入、意图识别、工具规划、执行工具、合并结果、生成回复、等待澄清。每个节点只做一件事节点之间的跳转由事件驱动。用户正常提问就走“接收输入 → 意图识别 → 工具规划 → 执行工具 → 合并结果 → 生成回复”用户问了一个缺少必要参数的问题就跳到“等待澄清”通过追问补齐信息再回跳。这个设计最大的好处是让 Agent 的行为变得可观测、可回放。你可以把整个对话过程的每个状态跳转都记录到日志里用户被卡在哪一步、Agent 做了哪个错误判断一眼就能定位。以前那种 if-else 写法你只能看到最终输出的错误答案完全不知道中间发生了什么。还有一个我一直强调的细节状态机里要单独设置一个“异常兜底”节点所有超时、工具返回报错、模型调用失败都汇到这个节点里去。兜底节点做的事很简单——向用户说明当前出了什么问题、系统正在尝试什么、需要用户提供什么补充信息。就这一个小小的节点能让整个产品的体感提升一个档次因为用户最怕的不是系统出错而是出错了之后系统还在假装正常地回复。2.3 工具注册化让 Agent 的能力可插拔框架重构里我认为收益最大的一项改造是把工具调用从“硬编码分支”改成“注册表驱动”。什么是硬编码分支就是你写一个超级大的 if-elif判断用户的意图关键词然后调用对应的函数。这种写法每加一个工具就要在大分支里再插入一个分支而且意图词稍微变一变匹配就失败了。注册表驱动的方式是每一个工具自带一份“身份说明”包括工具名字、功能描述、输入参数 schema、执行函数。系统启动的时候把所有工具扫描一遍形成一个工具清单。Agent 拿到这份清单后由模型自己根据用户请求和工具描述决定要不要调用、调哪个、传什么参数。这里的关键点在于——工具描述的写法要当作一等公民来对待它直接影响模型能不能正确选择工具。我平时写工具描述有一个原则“像给一个完全不了解你业务的实习生写说明书”。不要写“查询历史数据”要写“根据用户指定的日期范围和指标名称从销售数据仓库中查询历史记录。适用于用户询问过去某段时间的业绩表现。需要提供 begin_date、end_date、metric_name 三个参数”。描述里把边界也写清楚比如“如果用户没有提供日期范围调用时不要自作主张默认最近 7 天而是返回参数缺失信号”。这样模型在规划阶段就能做出更准确的选择。参数 schema 的设计同样重要。我建议把每个工具的参数定义得尽量精细可选参数要标出默认值必选参数要说明校验规则。执行层在真正调用工具之前先做一次本地校验参数不对就直接返回错误给编排层不要带着残缺参数去调用外部服务否则你排查问题的时候会分不清是模型传参错了、还是工具本身出错了。2.4 配置化把不稳定的因素全部赶出代码重构的最后一步是配置化。哪些东西属于“不稳定因素”模型名称、模型温度、系统提示词模板、向量库地址、分块大小、检索返回条数、重排序开关、日志级别……这些统统不应该写在代码里。我见过太多项目改一个检索 top_k 的数值要重新部署一次效率低得令人绝望。我现在的做法是建一个 config 目录里面按模块拆成若干 YAML 文件每个文件对应一个逻辑模块。模型配置独立一份工具清单独立一份RAG 参数独立一份。程序启动时统一加载形成全局配置对象所有模块都从这个对象里读参数。这样你调参的时候不用碰任何业务代码改完配置重启服务就能生效。这里有一个经验之谈配置里尽量不要使用深层嵌套结构。嵌套太深虽然看起来“结构化”但每次改配置都要小心翼翼地对齐缩进非常容易出错。我更喜欢扁平一点的键值对用命名规范暗示归属比如rag.chunk_size、rag.chunk_overlap、model.primary.name、model.primary.temperature。简单直观改了也不容易伤到别的模块。配置化的同时建议把关键的运行时参数一并输出到日志里。每次请求进来日志里记录这次使用的模型版本、检索参数、工具列表版本。这样出了问题你回看日志就能知道是哪一版的配置导致的而不是靠猜。3. RAG 优化让 Agent 真正“答有所依”3.1 先盘点一下你现在的 RAG 到底卡在哪很多人一上来就问我“用哪个向量库”“embedding 模型选哪个”但 RAG 效果不好的原因往往不在这些后期环节而在更上游的链路。我见过不少项目检索回来的 Top 5 文档里有两三条是纯噪音跟问题完全无关LLM 再聪明也没办法从噪音里给出正确答案。我把 RAG 链路拆成四个环节文档处理清洗、拆分、结构化、索引构建向量化、元数据标注、检索召回相似度计算、关键词匹配、混合策略、生成融合上下文选择、压缩、提示词组织。优化 RAG 不是一个动作而是一条链路一个环节一个环节排查。哪一环出了问题效果都体现为“答案不对”但原因可能天差地别。为了快速定位问题环节我在项目里加了一个“RAG 调试开关”。打开之后每次检索都会把召回结果原文、相似度分数、重排序分数完整输出到日志。不管是自己排查还是给同事看问题都有了依据。这一步极其有用很多你想象不到的 bug 都是靠这个日志现出原形的。3.2 分块策略决定 RAG 效果的第一块基石分块是整个 RAG 链路里性价比最高的优化点。同样的文档分块方式不同召回的准确率能差好几个档次。我先讲一个通用的经验分层。第一种是最省事的定长分块固定每 500 个字符切一块块与块之间重叠 50 个字符。这种方式的优点是简单、稳定适合结构化程度不高的纯文本。但缺点非常明显语义边界被硬生生切断一个完整的意思可能被劈成两半检索的时候只能召回一半内容。第二种是按自然段落分块以换行作为边界每块保留一个相对完整的语义单元。这种方式适合结构清晰的文档每段讲一个主题。第三种是结构化分块利用文档本身的层级结构来切分比如 Markdown 的标题层级每个二级标题下的内容作为一块子标题再往下细分。这是我现在最推荐的方式尤其当你的知识库里面有操作手册、产品文档这类强结构内容时。我还想强调重叠窗口的细节。有人觉得重叠纯粹是浪费存储其实重叠的作用是防止“检索时语义跨块”。打个比方一段完整的意思恰好落在前后两块的交界处如果两块各自独立检索时可能哪一块都不是最优匹配。有了重叠这段内容在两块里都完整出现命中概率大幅提升。重叠的大小一般取块长的 10% 到 20%太大则冗余太小则失去意义。我在实际项目里还增加了“小节回退”策略按二级标题分块时如果当前小节内容太短不足以下限就把它并入上一小节。这是为了平衡块长——块太短向量表示不够充分块太长噪音会变多。上下限我常用的是 300 到 800 个字符具体数字需要根据自己的文档特点试验。3.3 混合检索向量和关键词一个都不能少如果只靠向量相似度做检索你会遇到一个很经典的问题——专有名词和精确数字召回困难。“这个项目的负责人是张三”张三这个名字的 embedding 可能跟其他上下文混在一起导致精确匹配被稀释。这时候传统的关键词检索反而更可靠。所以我把向量检索与 BM25 关键词检索做成了平行通道两边同时召回最后合并。混合检索的比例调参是有讲究的。纯文本类文档向量检索权重可以高一些但知识库里有大量产品名、型号、编号这类精确信息时关键词通道权重必须拉高。我一开始用固定比例 7:3后面发现不同类型的问题最优比例不一样于是改成动态权重用户问题里如果检测到明显的专有名词就把关键词通道的权重上调否则维持默认比例。合并后的去重也需要认真处理。两路检索会召回相同或高度相似的文档片段如果不做去重生成阶段会反复看到重复内容浪费上下文窗口。最简单的做法是计算重叠度超过阈值的只保留分数更高的一路结果。另外提醒一句混合检索需要保证两路检索用的是同一套文档 ID 体系不然合并的时候对不上号后患无穷。这个看起来是常识但我确实见过因为两路索引用了不同 ID 生成逻辑导致合并结果混乱的真实案例。3.4 重排序用一个小模型解决“召回准但排序不准”的问题很多人优化 RAG 只盯着召回忽略了排序。其实初次检索返回的 Top 20 里可能已经包含正确答案了只是它的位置排在了第 15 位而你的生成环节只取了前 5 条。这个问题靠调整向量权重是解决不了的最优解是加一层重排序。重排序模型的思路很简单它对召回回来的候选文档与用户问题做深度交叉编码计算更精细的相关性分数再按这个分数重新排序。最初你可能担心这层会增加延迟实测下来候选文档控制在 20 条以内的话额外耗时通常在几百毫秒以内相比它能带来的准确性提升完全值得。在生产项目里检索召回阶段会从两路通道各取 Top 10合并去重后得到 15 到 20 条候选然后交给重排序模型打分取分数最高的 3 到 5 条进入生成阶段。这个“召回多、精排少”的两段式结构是 RAG 优化里性价比最高的手段之一。有个很容易踩的坑重排序模型的输入长度是有限制的超出长度会被截断导致长文档的关键信息被切掉。我的处理方案是先做“段落级截断”把候选段落按重排序模型的最大输入长度切分分别打一次分然后再合并排序。这样虽然多几次推理调用但不会因为截断丢失关键信息。3.5 查询改写不要让用户的问题“裸奔”进检索用户提问有一个特征口语化、省略主语、指代不明。比如用户第一轮问“帮我查一下上季度的营收情况”第二轮问“那华东区呢”这个“那华东区呢”如果直接拿去检索基本检索不到什么有效信息因为缺少“上季度”和“营收”这两个关键限定词。查询改写要解决的就是这一类问题。我的做法是维护一份对话历史摘要在发起检索之前先让模型结合最近两轮对话生成一个“检索用查询”。这个查询会把省略的限定词补全变成完整、信息充分的检索语句。注意这个改写后的查询只用于检索不用于最终回答两者各司其职。查询改写还有一种特殊形式把用户的问题拆解成多个子查询。用户的复合问题“上季度华东区和华南区的营收对比”可以拆成两个子查询分别限定华东区和华南区各自独立检索再把结果合并。这跟 Agent 工具的并行调用是同构的能显著提升复杂问题的检索覆盖度。3.6 上下文压缩与窗口预算别把 Agent 的上下文当成无限大的口袋生成阶段最大的浪费是“召回什么就全塞进上下文里”。我见过太多项目把检索回来的 Top 5 文档一字不差地全给模型结果真正有用的信息可能只有其中两句话其余全是填充内容。这既浪费上下文窗口又增加模型被无关内容带偏的风险。我采用的策略是“相关段落抽取”在检索回来的段落里进一步定位与用户问题最相关的子句只抽取这部分进入生成上下文。这个环节我称之为“上下文压缩”。上下文压缩可以靠一个小模型完成也可以靠规则实现。规则实现时通常的做法是把用户问题拆成关键字在段落中定位包含这些关键字的句子截取该句及其前后各一句。上下文预算管理也是这个环节的核心。我给每个 Agent 项目设定一个上下文预算上限比如 4000 token 用于知识上下文128 token 用于系统提示词1000 token 用于对话历史。知识部分只会在预算内挑选最有价值的检索片段多出来的宁可不要。因为对 LLM 来说无关信息的危害不仅仅是浪费额度它会主动干扰模型对有用信息的注意力——这是很多“答案看起来没道理”的根本原因之一。4. 实战中踩过的坑与排查技巧4.1 检索到了相关内容答案还是胡说八道这个问题我一开始以为是生成环节的提示词没有约束好后来写入日志仔细排查才发现是检索返回的文档 ID 和向量是对上了但文档内容在索引构建时就已经被清洗坏了——比如 HTML 标签没有剥离干净、特殊字符替代了空格、全文变成了一个超长字符串。LLM 拿到的“知识”本身就是乱的怎么可能给出正确答案。排查思路是把 RAG 调试开关打开把送入生成环节的最终上下文原文打印出来人工读一遍。如果这些内容本身读不通问题源头必然在文档处理环节不在生成环节。修复清洗流程后答案质量立竿见影地提升。4.2 检索结果总是漏掉最关键的那份文档这是混合检索里最常见的问题通常不是因为向量模型太差而是关键词通道和向量通道的分数合并方式不合理。有些文档在两个通道里的分数都不算拔尖但都排在中游合并后反而被挤出 Top N。解决方法是改用 Rank Fusion 这类基于排名而非绝对分数的方法两路检索各自输出排名再按排名倒数加权合并避免分数尺度不一致带来的偏斜。4.3 多轮对话里第二轮回答明显变差这是查询改写缺失造成的典型症状。用户在第一轮问了一个完整问题检索效果正常第二轮只说了半句话系统直接把半句话拿去做检索结果自然跑偏。给检索前加一个查询改写步骤后这个问题基本能消除。需要留意的是改写用的对话历史要控制长度太久远的历史反而会引入噪音一般取最近两轮就够。4.4 工具调用链路越来越慢响应时长不可接受排查时先拆解耗时分布不要凭感觉。我用一个简单的埋点日志记录每个环节的耗时模型调用、检索、重排序、工具执行。实测结果常常出乎意料——耗时大户往往不是模型调用而是某个外部 API 的超时重试逻辑。给所有外部调用设置合理的超时时间并增加超时后的备用路径是整个 Agent 系统稳定性的基石。4.5 重构之后功能回归原有场景跑不通重构最容易出现的问题是“拆着拆着把原来的行为逻辑拆丢了”。我会在重构前先给每个核心场景准备一套测试用例输入输出写死在测试里重构完成后先跑测试套件再对比日志中的状态流转记录逐项排查差异。这比手动点十几轮对话高效得多也更容易发现边界条件被破坏的位置。最后分享一个我在这次重构过程中最深的一点体会所有优化动作都要先有一个可观测的基线。没有日志、没有调试开关、没有数据对比所谓的“优化”就是在瞎猜。我现在每改一个参数、每调一个模块结构都会先记录改之前的效果数据改完之后再对比一轮。这样积累下来的每次变更都有明确的因果结论不仅自己能看懂将来交接给其他人也讲得清。框架重构的本质不是把代码变得漂亮而是让系统的行为变成可理解、可控制、可验证的东西。这个思路放到 RAG 优化上也一样成立——你优化的是检索质量但真正在优化的是你对一个系统行为的理解程度。
延伸阅读

更多相关文章

2026/10/19 9:21:06

HCP-MMP1.0掩码提取:用Matlab脚本按区域名生成对齐ROI mask

简介:面向神经影像与fMRI数据分析人员的开源MATLAB实用工具,基于HCP-MMP1.0脑区图谱,可快速提取指定脑区的体积二值掩码文件。针对需要自定义ROI进行后续静息态或任务态分析的科研场景,可省略手动逐体素勾画区域的大量重复操作&am…

2026/10/19 9:21:06

YOLO26小缺陷检测升级:C3k2-EIEM融合边缘与空间特征

做工业质检目标检测的朋友,八成都被“小缺陷漏检”折磨过。这期是我在YOLO26上持续优化迭代的第33个版本,核心改动是把主干中的C3k2模块升级为“C3k2-EIEM”融合结构,在保留原有检测效率的前提下,把边缘特征和空间信息同时塞进特征…

2026/10/19 10:26:53

MySQL CASE表达式实战:从排序到行转列,一篇讲透

相信不少写SQL的朋友都遇到过这样的场景:订单表里存的是 status1/2/3 ,老板却要你在报表里显示“已支付/已发货/已取消”;统计的时候,明明只想看一行结果,却要把不同状态的数据拆成好几列;排序的时候&…

2026/10/19 10:26:53

NAS入门完全指南:从硬盘RAID选型到数据备份与Docker玩法

在数据爆发的年代,手机照片、工作文档、高清电影、家庭录像散落在各个设备里,每一台设备都可能随时“背叛”你——硬盘损坏、手机丢失、某网盘限速或者关停。这时候,一个叫NAS的东西会频繁出现在数码社区、技术论坛甚至同事的聊天里。第一次听…

2026/10/19 10:26:53

数据合成工程实践:Argon模块拆解与可审计的合成数据流水线

最近在关注云上数据工程这块的时候,看到了 Google 展示的 Antigravity 平台。整个平台的功能线拉得很长,但真正让我停下来琢磨的,是其中的 Argon 数据合成模块。数据合成这四个字听起来很直白——把数据“造”出来,但它背后涉及的…

2026/10/19 10:26:53

动态非合作博弈下的电动汽车大规模实时调度Matlab仿真实践

这两年做大规模电动汽车充电调度研究,绕不开一个现实问题:电动汽车数量一多,充电行为就成了“群体行为”,怎么让几千辆甚至上万辆车的充电决策在实时环境下协调出来,比想象中要复杂得多。传统做法是假定有个中央调度中…

2026/10/19 10:26:53

智能体自动下单全链路解析:从口语指令到订单落地

“让智能体替我把外卖点了”这句话,我在很多场合都当段子讲过。直到我真的把一个叫 Meta Muse 的智能体项目跑通、让它从一句口语指令一路执行到购物车结算,才发现这中间的链路远比“AI帮我下单”这六个字复杂得多。 这篇文章不是产品发布会&#xff0c…

2026/10/19 10:21:47

Win11安装npm全攻略:Node.js安装与环境配置详解

1. 环境准备与核心认知1.1 为什么Win11安装npm要先装Node.js很多第一次接触前端开发的朋友,在Windows 11上敲下npm install却收到“npm不是内部或外部命令”的提示,第一反应往往是“我明明安装了npm啊”。实际上这里有个很常见的误区:npm本身…

2026/10/18 0:01:50

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

2026/10/19 0:04:56

悟空开源无代码平台实战:从实体建模到应用设计避坑指南

简介:基于悟空开源无代码平台的多种应用设计源码,面向希望在不编写代码的情况下快速搭建CRM、HRM、财务系统、ERP、SRM等业务应用的企业与技术人员,通过拖放式界面和内置模板即可完成应用定制与部署。压缩包共697个文件,约10.74MB…

2026/10/19 0:04:56

基于.NET9的多租户权限系统:数据权限与动态API落地实践

简介:这份资源是一套面向 .NET 与 Vue 全栈开发者的中台权限管理系统,后端基于 .Net9,前端采用 Vue3 Element Plus,并配套 uniapp uviewui 移动端方案,实现前后端分离。系统覆盖多租户隔离、数据权限、动态 Api、任务…

2026/10/19 0:04:56

场地预约小程序毕设实战:SSM+微信小程序+MySQL数据库设计到避坑

简介:面向毕业设计场景的场地预约小程序完整项目,基于微信小程序SSMMySql技术栈开发,适合计算机相关专业学生作为选题参考、功能扩展或二次开发起点。系统实现场地类型管理、场地管理、预约管理、取消申请与押金退回等功能,分为管…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑