资讯动态

Agent运行时动态选择模型与工具:五个自制件的工程实践与避坑指南

发布时间:2026/10/13 16:37:38 来源:尧图企业网站定制
1. 为什么让 Agent 自己挑模型和工具是个真问题做过 Agent 项目的人大概都有过这种体验一开始图省事把模型和工具全写死在代码里跑起来看着挺美一旦换个任务类型就抓瞎。比如你写了个专门做代码审查的 Agent模型固定用某个擅长推理的大模型工具固定挂一个静态分析器结果某天产品经理跑来说“能不能顺便让它帮我总结一下会议纪要”你就得回去改代码、重新部署、再测一轮。这种硬编码的方式在 Demo 阶段没问题但一旦要面对真实场景里五花八门的需求维护成本会指数级上升。我前后花了大概三周时间把手上一个 Agent 框架里的五个核心组件全部重写了一遍目标就一个让 Agent 在运行时自己决定用哪个模型、调哪个工具、走哪条执行路径。这五个组件我称之为“自制件”因为它们不是现成的开源方案直接拿来用而是根据实际业务需求自己设计的。整个过程踩了不少坑也做了很多取舍有些设计决策事后看是对的有些则走了弯路。这篇文章就把这五件自制件的设计思路、实现细节和踩坑经验完整拆开讲一遍。先说一下这个 Agent 框架的基本盘。它主要处理的是多步骤任务比如“读取一份需求文档提取关键信息生成测试用例再调用接口做验证”。这类任务的特点是步骤之间有依赖关系每一步需要的模型能力和工具类型都不一样。早期版本里我是在配置文件里写死一条流水线每个节点指定模型和工具。后来发现不同项目的需求差异太大有的需要强推理有的需要快响应有的需要特定格式输出写死的方案根本扛不住。所以核心思路就变成了把模型选择和工具选择从“编译时”挪到“运行时”让 Agent 根据当前任务的上下文、历史执行结果、资源预算等因素动态做出决策。听起来简单但真做起来涉及的问题远比想象中多。下面我按五个自制件逐一展开。2. 自制件一模型路由器的设计取舍2.1 为什么不用简单的 if-else 做模型选择最开始我想得很简单搞一个函数输入任务类型输出模型名称里面就是一堆 if-else。比如任务是“代码生成”就用模型 A“文本摘要”就用模型 B“逻辑推理”就用模型 C。这个方案上线第一天就出问题了。原因是任务类型分类本身就不准确很多任务其实是混合型的比如“根据错误日志推断根因并生成修复代码”它既需要推理又需要代码生成你把它归到哪一类都不对。更麻烦的是同一个任务在不同阶段对模型的要求也不一样。比如一个长文档处理任务前期需要快速提取关键段落这时候用个轻量模型就够了后期需要对提取的内容做深度分析就得换强模型。如果按任务类型一刀切要么浪费算力要么效果打折。所以我最终设计的模型路由器输入不是单一的任务类型标签而是一个结构化的上下文对象包含当前步骤的目标描述、历史步骤的执行结果摘要、剩余预算token 数和费用、以及一个粗略的复杂度评分。输出是一个模型候选列表按推荐优先级排序而不是只给一个答案。2.2 复杂度评分怎么算复杂度评分是整个路由器的核心输入之一。我试过几种方案最后落在一个比较务实的做法上用轻量模型对当前步骤的目标描述做一个快速评估输出 1 到 5 的评分。1 代表“简单格式化或提取”5 代表“需要多步推理和领域知识”。这个评估本身消耗很少大概几十个 token但能显著提升路由准确率。具体实现上我写了一个提示词模板让轻量模型从四个维度打分语义理解难度、推理深度、输出格式复杂度、领域知识依赖度。每个维度 1 到 5 分然后取加权平均。权重是根据历史数据调出来的推理深度权重最高0.4其余三个各 0.2。这个权重不是拍脑袋定的我拿过去三个月的任务日志做了回归分析发现推理深度和最终模型选择的相关性最强。注意复杂度评分不需要非常精确它的作用是提供一个粗略的分档依据。如果你花太多精力去优化这个评分边际收益很低。我一开始就是在这个环节过度设计后来发现只要能把“简单任务”和“复杂任务”区分开路由效果就已经很好了。2.3 模型候选列表的排序逻辑拿到复杂度评分后路由器会从模型池里筛选候选。模型池里每个模型都有元数据能力标签推理、代码、摘要、翻译等、成本系数、平均响应时间、上下文窗口大小、历史成功率。排序逻辑是一个加权打分公式总分 能力匹配度 × 0.5 成本效率 × 0.2 响应速度 × 0.15 历史成功率 × 0.15能力匹配度是根据当前步骤需要的标签和模型标签做交集算出来的。成本效率是预算除以模型成本系数。响应速度是历史平均延迟的归一化值。历史成功率是这个模型在过去类似任务上的成功比例。这个公式看起来简单但每个因子的计算都有讲究。比如能力匹配度我一开始用的是二值匹配有标签就是 1没有就是 0结果发现太粗糙了。后来改成用标签的语义相似度做加权效果好很多。具体做法是把标签转成向量算余弦相似度再映射到 0 到 1 之间。2.4 踩坑记录模型切换的上下文丢失问题这是我在模型路由器上踩的最大的坑。当 Agent 在一个任务中途从模型 A 切换到模型 B 时模型 B 并不了解之前发生了什么。我一开始的做法是把历史对话完整传过去但很快发现两个问题一是 token 消耗巨大二是不同模型的对话格式不一样直接传过去模型 B 可能理解不了。后来我设计了一个“上下文摘要”机制。每次切换模型前先用当前模型对历史执行结果做一个结构化摘要包含已完成步骤、关键发现、待解决问题、当前状态。这个摘要用统一的 JSON 格式然后作为系统提示的一部分传给新模型。这样既控制了 token 消耗又保证了信息的可传递性。实测下来这个摘要机制能把切换后的任务成功率从 62% 提升到 87%。代价是每次切换多花大概 200 到 500 个 token 做摘要但相比重新跑一遍任务这个成本完全可以接受。3. 自制件二工具选择器的匹配策略3.1 工具描述的结构化改造工具选择器的核心问题是怎么让 Agent 知道有哪些工具可用以及每个工具能干什么。最原始的做法是维护一个工具列表每个工具写一段自然语言描述。但自然语言描述有个致命问题歧义。比如“搜索”这个描述可能是网页搜索、文件搜索、数据库搜索Agent 根本分不清该用哪个。我的做法是把工具描述结构化每个工具必须包含以下字段工具名称、功能分类、输入参数 schema、输出格式、适用场景描述、限制条件、调用示例。其中功能分类和适用场景描述是给 Agent 看的其余是给执行引擎用的。功能分类我用了一个两级分类体系。一级分类包括信息获取、数据处理、内容生成、外部交互、系统操作。二级分类更细比如信息获取下面有网页搜索、文件读取、数据库查询、API 调用。这个分类体系不是固定的可以根据业务需要扩展但扩展时要保持层级一致性。3.2 匹配算法从关键词到语义工具匹配我试过三种方案。第一种是关键词匹配把任务描述里的关键词和工具描述做交集。这个方案在工具数量少的时候还行超过二十个工具后准确率急剧下降。第二种是规则匹配手写一堆 if-else 规则。这个方案维护成本太高每加一个工具就要改规则。第三种是语义匹配用嵌入模型把任务描述和工具描述都转成向量算相似度。这个方案效果最好但有个前提工具描述必须写得足够清晰和区分度高。我最终用的是语义匹配为主、关键词匹配为辅的混合方案。具体流程是先用语义匹配召回 top 5 候选工具然后用关键词匹配做二次排序最后取 top 3 给 Agent 做最终选择。为什么要让 Agent 做最终选择而不是直接取 top 1因为有些工具的适用性需要结合当前上下文判断纯算法匹配做不到百分之百准确。给 Agent 两三个选项让它结合当前任务状态做决定成功率更高。3.3 工具调用的参数填充工具选对了参数填错也是白搭。我遇到过好几次 Agent 选对了工具但参数格式不对导致调用失败的情况。比如一个日期查询工具需要 ISO 格式的日期Agent 传了个“明天”这样的自然语言工具直接报错。解决方案是在工具定义里加一个参数预处理层。每个参数除了类型和描述还要指定一个“规范化函数”。这个函数负责把 Agent 输出的自然语言参数转成工具能接受的格式。比如日期参数规范化函数会调用一个日期解析器把“明天”“下周三”“2024 年底”这类表达转成标准格式。这个预处理层是工具选择器里最容易被忽视但最重要的部分。我统计过在加入参数预处理之前工具调用失败的原因里参数格式错误占了将近一半。加入之后这个比例降到了 8% 以下。3.4 踩坑记录工具冲突和循环调用工具冲突是指两个工具功能重叠Agent 不知道该用哪个。比如同时挂了两个搜索工具一个搜内部文档一个搜外部网页任务描述是“查找关于 X 的资料”Agent 可能随机选一个也可能两个都调一遍。我的处理方式是在工具元数据里加一个“优先级”字段和“互斥”字段。优先级高的工具在匹配分数相近时优先推荐互斥的工具在同一个任务步骤里只能选一个。循环调用是另一个坑。Agent 调用了一个工具拿到结果后发现不满足条件又调用同一个工具反复几次陷入死循环。我在执行引擎里加了一个调用计数器同一个工具在同一个任务步骤里最多调用三次超过就强制中断并返回错误。同时在工具选择器的输入里加入“已尝试工具列表”让 Agent 知道哪些工具已经试过了避免重复选择。4. 自制件三执行引擎的状态管理4.1 状态机的设计执行引擎是整个 Agent 的调度中心它负责按顺序执行任务步骤每一步调用模型路由器选模型、调用工具选择器选工具然后执行并记录结果。我一开始用的是一个简单的循环结构但很快发现不够用因为任务步骤之间有复杂的依赖关系有的步骤可以并行有的必须串行有的步骤失败后需要回滚。后来我把它改成了一个状态机。状态包括初始化、规划中、执行中、等待工具结果、等待模型响应、步骤完成、任务完成、任务失败、回滚中。每个状态有明确的进入条件和退出条件状态之间的转换由事件驱动。比如“执行中”状态下如果当前步骤需要调用工具就转换到“等待工具结果”工具返回后再转换回“执行中”继续下一步。这个状态机的好处是逻辑清晰每个状态只关心自己的事情不用管全局。调试的时候也方便可以清楚地看到任务卡在哪个状态。4.2 上下文窗口的管理Agent 执行多步骤任务时上下文会不断增长。如果不加管理很快就会超出模型的上下文窗口限制。我的做法是维护一个分层上下文短期上下文保存最近三步的完整信息中期上下文保存当前任务的所有步骤摘要长期上下文保存跨任务的关键知识。短期上下文直接传给模型中期上下文按需检索长期上下文存在外部存储里需要时通过检索工具获取。这个分层结构让上下文窗口的使用效率提升了很多。实测下来同样一个十步任务不分层的话到第七步就超限了分层之后可以完整跑完而且 token 消耗还少了大概 30%。4.3 错误处理和重试策略错误处理是执行引擎里最复杂的部分。错误分几类模型调用失败超时、限流、返回格式错误、工具调用失败参数错误、服务不可用、返回结果不符合预期、逻辑错误步骤依赖不满足、条件判断失败。针对不同错误类型我设计了不同的重试策略。模型调用失败如果是超时或限流等几秒后重试最多三次如果是返回格式错误把错误信息反馈给模型让它重新生成。工具调用失败如果是参数错误让 Agent 重新填充参数如果是服务不可用切换到备用工具如果是结果不符合预期把结果和预期一起反馈给 Agent让它决定下一步。提示重试策略一定要设置上限不然容易陷入无限重试。我的上限是每个步骤最多重试五次超过就标记为失败进入回滚或跳过逻辑。4.4 踩坑记录状态不一致和并发问题状态不一致是我踩过的最隐蔽的坑。有一次任务执行到一半模型路由器切换了模型但执行引擎的状态还停留在旧模型的状态上导致后续步骤全部错乱。排查了很久才发现是状态更新没有做到原子性。后来我在状态机里加了一个版本号每次状态变更都递增版本号模型路由器和工具选择器在读取状态时先检查版本号不一致就重新读取。并发问题出现在并行步骤上。有两个步骤可以并行执行但它们共享同一个上下文对象一个步骤写入的时候另一个步骤正在读取导致数据竞争。解决方案是给上下文对象加读写锁写的时候加排他锁读的时候加共享锁。这个方案会稍微降低并发性能但保证了数据一致性我觉得是值得的。5. 自制件四反馈收集器的闭环设计5.1 为什么要做反馈收集Agent 自己选模型和工具选得好不好需要有个评判机制。没有反馈路由器和选择器就没法优化。我设计的反馈收集器负责在任务执行过程中和结束后收集各类信号包括模型输出的质量评分、工具调用的成功率、任务完成时间、token 消耗、用户显式反馈如果有的话。这些信号汇总后会定期用来更新模型路由器的权重参数和工具选择器的匹配模型。这是一个闭环系统Agent 用得越多选择就越准。5.2 质量评分的自动化方案用户显式反馈很难拿到大部分时候用户不会主动评价。所以我主要依赖自动化评分。模型输出质量评分我用了一个“一致性检查”方案同一个步骤用两个不同模型各跑一遍比较输出结果的一致性。一致性高的步骤说明模型选择没问题一致性低的步骤说明可能需要换模型或者调整提示词。工具调用成功率是更直接的信号。每次工具调用都记录成功或失败以及失败原因。这些数据按工具和任务类型分组统计用来调整工具选择器的优先级。5.3 反馈数据的存储和查询反馈数据我存在一个轻量数据库里每条记录包含任务 ID、步骤 ID、模型名称、工具名称、执行时间、token 消耗、成功标志、错误信息、质量评分。查询接口支持按时间范围、模型、工具、任务类型等维度聚合。这个数据库不大但很实用。我每周会跑一次分析看看哪些模型和工具的组合表现最好哪些组合经常出问题。基于这些分析我会手动调整路由器的权重参数。未来打算把这个调整过程也自动化但目前手动调整的效果已经够用了。5.4 踩坑记录反馈偏差和过拟合反馈收集最大的坑是偏差。如果某个模型被路由到的次数多它的反馈数据就多统计上看起来更可靠路由器就更倾向于选它形成正反馈循环。这会导致其他模型得不到足够的曝光即使它们可能更适合某些任务。我的解决方案是引入“探索机制”。路由器在推荐模型时有 10% 的概率推荐一个“非最优但值得尝试”的模型。这个概率可以根据任务的重要程度调整重要任务降低探索概率普通任务保持默认。这样既保证了主要任务的稳定性又给了其他模型曝光机会。过拟合是另一个问题。如果路由器过度依赖历史数据遇到新类型的任务时可能表现很差。我的做法是定期用新数据重新训练路由器的权重同时保留一个“默认策略”作为兜底。当路由器对当前任务的置信度低于阈值时就回退到默认策略。6. 自制件五配置中心的动态管理6.1 配置项的分类和优先级配置中心管理所有可调参数包括模型池配置、工具池配置、路由器权重、选择器阈值、执行引擎超时时间、反馈收集频率等。这些配置分三个优先级系统级、任务级、步骤级。系统级配置是全局默认值任务级配置覆盖系统级步骤级配置覆盖任务级。优先级机制让配置既有统一性又有灵活性。比如系统级默认用模型 A 做推理但某个任务需要更快的响应可以在任务级配置里改成模型 B。如果某个步骤特别复杂还可以在步骤级配置里单独指定模型 C。6.2 热更新和版本管理配置中心支持热更新修改配置后不需要重启 Agent 就能生效。实现方式是用一个配置监听器定期检查配置版本号发现变化就重新加载。重新加载时采用“双缓冲”策略新配置先加载到备用缓冲区加载成功后原子性地切换主备缓冲区避免加载过程中出现配置不一致。版本管理方面每次配置修改都会生成一个新版本旧版本保留最近十个。如果新配置导致问题可以快速回滚到旧版本。这个机制在上线初期救了我好几次有一次改了一个路由器权重导致所有任务都选了最贵的模型回滚后立刻恢复正常。6.3 配置的校验和默认值配置修改最容易出的问题是格式错误或值超出范围。我在配置中心加了一个校验层每个配置项都有类型、范围、依赖关系的定义。修改配置时先过校验层不通过就拒绝并返回错误信息。默认值的设计也很重要。每个配置项都必须有默认值这样即使配置文件丢失或损坏Agent 也能用默认值启动。默认值的选择原则是“保守优先”比如超时时间默认设长一点重试次数默认设少一点避免因为配置问题导致任务大面积失败。6.4 踩坑记录配置漂移和依赖冲突配置漂移是指不同环境开发、测试、生产的配置不一致导致同一个任务在不同环境下表现不同。我的解决方案是把配置文件纳入版本控制每个环境用不同的分支合并时强制走代码审查流程。同时Agent 启动时会打印当前生效的配置摘要方便排查。依赖冲突是指两个配置项之间存在隐含的依赖关系修改一个会影响另一个。比如模型池里删除了一个模型但路由器权重里还有这个模型的权重就会导致路由失败。我在配置中心加了一个依赖检查器修改配置时自动检查是否有其他配置项依赖它有的话给出警告。7. 五个自制件的协同工作和实际效果7.1 数据流和调用关系五个自制件不是孤立的它们之间有一套清晰的调用关系。配置中心提供所有参数模型路由器和工具选择器从配置中心读取模型池和工具池。执行引擎在每一步开始时先向模型路由器请求模型候选再向工具选择器请求工具候选然后执行并收集结果。反馈收集器在执行过程中持续收集信号任务结束后汇总写入数据库。反馈数据定期用来更新路由器和选择器的参数更新后的参数写回配置中心。这个闭环让整个系统有了自我优化的能力。我统计过系统上线第一个月任务平均完成时间下降了 23%token 消耗下降了 18%任务成功率从 71% 提升到了 89%。这些提升主要来自路由器和选择器的持续优化。7.2 性能瓶颈和优化性能瓶颈主要出现在两个地方一是模型路由器的复杂度评分环节每次都要调用轻量模型增加了延迟二是反馈收集器的数据库写入高频写入时会有锁竞争。针对第一个问题我加了一个缓存层。相同或相似的任务描述复杂度评分直接读缓存不用重新计算。缓存命中率大概在 65% 左右显著降低了延迟。针对第二个问题我把反馈数据先写入内存队列然后批量写入数据库减少了锁竞争。7.3 实际案例一个多步骤任务的全流程举个实际例子。任务描述是“读取一份产品需求文档提取功能点为每个功能点生成测试用例调用接口验证测试用例的可行性”。执行引擎首先进入规划状态把任务拆成四个步骤。第一步是文档读取复杂度评分 2路由器推荐轻量模型 A 和文件读取工具。执行后拿到文档内容。第二步是功能点提取复杂度评分 3路由器推荐中等模型 B 和文本分析工具。执行后拿到功能点列表。第三步是测试用例生成复杂度评分 4路由器推荐强模型 C 和代码生成工具。执行后拿到测试用例。第四步是接口验证复杂度评分 3路由器推荐中等模型 B 和 API 调用工具。执行后拿到验证结果。整个过程中反馈收集器记录了每一步的模型、工具、耗时、token 消耗和成功标志。任务完成后这些数据写入数据库用于后续优化。7.4 踩坑记录协同工作中的信息丢失五个自制件协同工作时最容易出的问题是信息丢失。比如执行引擎把任务状态传给路由器但路由器只用了其中的一部分信息做决策导致决策不准确。或者反馈收集器收集了信号但更新参数时没有正确关联到对应的模型和工具。我的解决方案是定义一个统一的数据契约所有自制件之间的数据传递都遵循这个契约。契约里明确了每个字段的含义、类型和必填性。任何一方修改契约都要通知其他方并更新版本号。这个做法增加了前期设计成本但大幅减少了后期的调试时间。8. 常见问题速查和避坑指南8.1 模型路由相关问题现象可能原因排查方法解决方案总是选最贵的模型成本效率权重过低检查路由器权重配置提高成本效率权重或设置成本上限模型切换后任务失败上下文摘要不完整查看切换时的摘要内容完善摘要模板增加关键字段复杂度评分不准评分提示词不清晰抽样检查评分结果优化提示词增加评分示例路由延迟高缓存未命中或模型调用慢查看缓存命中率和调用耗时扩大缓存容量或改用更快的轻量模型8.2 工具选择相关问题现象可能原因排查方法解决方案选错工具工具描述区分度低检查工具描述文本重写描述增加区分性关键词参数格式错误缺少规范化函数查看工具定义为每个参数添加规范化函数工具循环调用缺少调用计数查看执行日志加调用计数器设置上限工具冲突功能重叠检查工具分类设置优先级和互斥标记8.3 执行引擎相关问题现象可能原因排查方法解决方案任务卡住不动状态机死锁查看当前状态和事件队列检查状态转换条件加超时机制上下文超限分层策略未生效查看上下文大小检查分层逻辑压缩中期上下文重试次数过多重试策略太激进查看重试日志降低重试上限区分错误类型并发数据竞争缺少锁机制查看并发步骤日志加读写锁或改为串行执行8.4 反馈收集相关问题现象可能原因排查方法解决方案反馈数据偏差正反馈循环分析模型选择分布引入探索机制增加随机性过拟合历史数据权重过高检查新任务表现定期重新训练保留默认策略数据库写入慢高频写入锁竞争查看写入延迟改用批量写入加内存队列质量评分不准一致性检查阈值不合理抽样对比评分和人工评价调整阈值增加评分维度8.5 配置中心相关问题现象可能原因排查方法解决方案配置不生效缓存未刷新检查配置版本号加配置监听器定期刷新配置冲突依赖关系未检查查看配置依赖图加依赖检查器修改时警告环境不一致配置文件未同步对比各环境配置纳入版本控制强制审查回滚失败旧版本被覆盖检查版本保留策略保留最近十个版本定期归档提示这张速查表建议打印出来贴在工位上出问题的时候按表排查能省很多时间。我自己的经验是80% 的问题都能在前三行找到答案。8.6 三条独家避坑心得第一条不要追求一次性设计完美。我一开始想把五个自制件全部设计好再写代码结果设计了两周还没动手后来改成边设计边实现先跑通最小闭环再逐步优化效率高了很多。Agent 系统本身就很复杂很多问题只有跑起来才会暴露。第二条日志要打够但不要打太多。我一开始日志打得太少出问题找不到线索后来打太多日志文件一天几十个 G排查的时候反而找不到关键信息。最后的做法是分级日志关键决策点打 INFO详细数据打 DEBUG错误打 ERROR生产环境只开 INFO 和 ERROR。第三条定期做全链路压测。五个自制件协同工作单测都过不代表整体没问题。我每周会跑一次全链路压测模拟各种任务类型和异常情况确保系统在压力下也能稳定运行。这个习惯帮我提前发现了好几个只有在高并发下才会出现的 bug。9. 后续可以继续深挖的方向这套系统跑了一个多月整体表现稳定但还有几个方向我觉得值得继续深挖。一个是模型路由器的在线学习能力目前权重调整还是半自动的未来想做成完全自动的让系统根据实时反馈自己调参。另一个是工具选择器的跨任务迁移能力目前工具匹配主要依赖当前任务的上下文如果能利用历史任务的经验匹配准确率应该还能再提升一截。还有一个方向是执行引擎的并行优化。目前只有明确标记为可并行的步骤才会并行执行未来想做成自动识别可并行步骤进一步提升执行效率。不过这个方向的风险也比较大并行执行带来的状态一致性问题比串行复杂得多需要谨慎推进。反馈收集器这块我想加入更多维度的信号比如模型输出的风格一致性、工具返回结果的信息密度等。这些信号目前还没有被充分利用如果能纳入评分体系应该能让路由和选择更精准。配置中心目前还是单机部署未来如果 Agent 要支持多实例配置中心需要改成分布式方案保证多个实例之间的配置一致性。这个改造涉及的技术点比较多暂时还在调研阶段。整体来说让 Agent 自己挑模型、自己挑工具这件事核心不在于算法有多复杂而在于工程细节有没有做到位。五个自制件每一个单独看都不算难但把它们协同起来让数据在它们之间顺畅流动让反馈闭环真正转起来这才是最花时间和精力的地方。我踩过的坑大部分都不是算法问题而是工程问题状态不一致、信息丢失、配置漂移、并发竞争。这些问题没有捷径只能靠严谨的设计和充分的测试来避免。

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

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

免费获取报价 →
↑