Agent自主选模型与工具的五个关键组件:从规则配置到运行期自决
开头想先聊个现象我把一个 Agent 从写好规则让它跑改成让它自己选模型、自己选工具的时候身边好几个朋友都劝我冷静说模型自己选不靠谱工具自己选更是不受控。结果我硬着头皮把五个自制件做完之后最大的感受反而是——问题从来不在让 Agent 选而在于你把选择的规则、描述和反馈做得够不够清楚。标题里这五个自制件是模型选择器、工具注册表、工具决策器、参数注入器和结果评估器。这篇博文会把每一件的设计取舍、实现思路和踩过的坑摊开讲。如果你是正在搭 Agent 系统、卡在模型乱选工具乱调这个阶段的人这篇应该能帮你省不少弯路。1. 为什么非要让 Agent 自己选从配置地狱到运行期自决1.1 写死规则的路走到后面全是组合爆炸最早一版我的实现很简单在入口处写一个路由函数判断用户输入里含不含天气日历发送邮件等关键词然后往对应的工具函数里扔。单看 demo 确实没问题可一旦任务变成帮我把这周的会议纪要按照公司模板转成周报再发给项目组成员关键词规则就失灵了。这个需求里同时出现了会议纪要、模板、周报、发送四个信息每个信息对应一个或者多个工具规则耦合很快就变成一层又一层的 if-else。你会发现实际问题不是要不要加规则而是规则覆盖不住这些组合。用户的一句话里可能同时包含查询、计算、格式化、发送四个动作动作之间还有顺序关系。用写死规则维护这种组合配置量是爆炸的而且每个新工具上线都得回去改路由逻辑改完还要担心影响旧场景。1.2 我的答案把选择权交给运行期但外面加约束框既然规则维护不起来那就换方向让 Agent 在运行期自己看任务、自己选。我做的第一个关键决策是不把选择权完全交给模型裸奔而是给模型提供一份清晰的候选清单和选择依据。模型不直接调工具它只负责做选择真正的执行链路仍然由代码控制。这个思路落到架构上就成了五个件模型选择器负责决定这个任务该由哪个模型来理解工具注册表维护所有可用工具的结构化描述工具决策器根据注册表决定调哪个工具、按什么顺序调参数注入器解决模型给的是自然语言工具要的是强类型参数这个断层最后结果评估器检查工具执行结果是不是真的解决了用户的问题。五件连起来就是一条完整的自主闭环。1.3 明确优先级精度第一位成本第二位延迟第三位设计任何一件之前都得先把目标定清楚。我在这次项目里把优先级定为结果精度 调用成本 响应延迟。原因很朴素——一个工具选错了后续所有步骤都建立在错误基础上返工成本远高于多付几次模型调用的钱。这个优先级直接影响后面每一件的实现方式比如模型选择器里会更倾向给复杂任务分配更强的模型而不是最便宜的模型结果评估器宁可多花一次调用来确认也不接受一个看起来成功其实全错的返回。2. 模型选择器让 Agent 判断该用谁而不是永远用最贵的2.1 为什么不能一刀切用旗舰模型搭 Agent 早期最贵的教训就是让所有任务都走同一个最强模型。当时想的是反正模型强一点工具调用准确率就高一点结果一个月下来的账单很难看而且部分简单任务用旗舰模型并没有带来精度提升。举个例子像把这段文本里的日期提取出来这种简单结构化任务用轻量模型和旗舰模型的准确率差距不到两个百分点成本却差了近十倍。所以就做了第一个自制件模型选择器。它的输入是任务分析结果任务类型、输入长度、是否需要视觉、是否需要长上下文、涉及工具复杂度输出是选用哪个候选模型。它是第一个被调用件但逻辑不应该复杂——前面先用硬性规则把肯定不合适的模型过滤掉比如任务输入超过 32k 上下文、必须支持图像输入、需要特定模态输出这类场景直接锁定候选模型不做智能判断。2.2 让模型选模型的正确姿势先建模型档案再做匹配规则过滤完之后剩下的候选集通常有两到三个模型这时候让一个轻量的决策模型做匹配就够了。我给每个候选模型都建了结构化的档案字段大致是这样{ model_id: light_model, capabilities: [text_generation, function_calling, extraction], max_context: 32000, cost_per_1k_tokens: 0.002, strengths: [结构化抽取, 工具调用参数生成], weaknesses: [复杂推理, 长文总结], recommended_for: [信息提取, 参数填充, 短文本分类], not_recommended_for: [多步推理, 长文档综述] }关键就在recommended_for和not_recommended_for这两个字段。让模型选择模型的时候它不是靠谁更强来选而是靠谁更适合当前任务来选。决策提示词里明确告诉它你是调度员不是使用者你的目标是性价比和成功率最高而不是选择最强的模型。实测下来这个引导非常有效模型自选时消费金额能降一半左右错误率没有明显上升。2.3 踩坑记录模型自评置信度完全不可信第一次把模型选择器放上线我犯过一个错误——让候选模型先自评我这个模型能不能处理这个任务给出 0 到 1 的置信度然后选置信度最高的那个。这个设计看着合理实际跑起来简直是灾难。几乎所有模型都会给自己打 0.8 以上的高分即使任务根本不是它的强项。后来想明白了模型没有自我认知它的自评本质上还是语言生成它会倾向于生成我可以的文本这不是它骗你是训练目标决定的。这个坑的解法是放弃自评改成让一个小模型给任务打标签标签直接映射到推荐模型。比如任务标签是extraction就推荐轻量模型标签是multi_step_reasoning推荐复杂推理模型。这本质上是把模型选择从感性的自我评价改成了基于文本分类的确定性映射效果稳定得多。2.4 成本护栏就算模型想选贵的也要有兜底还有一个细节容易被忽略模型选择器本身也是模型调用它可能受输入影响产生随机性。为了不让随机性影响预算我加了一个简单的成本护栏——如果本次任务估算 token 数超过预设阈值强制降级到成本更低的候选模型并记录一条降级日志。这里有个经验护栏条件宁可写宽松一点也不要写死因为有些任务确实是需要高成本的强行降级会导致结果不可用。护栏的作用是挡住极端情况不是限制正常路径。3. 工具注册表把工具描述成人话Agent 才不会选错3.1 工具描述是给模型看的产品说明书第二个自制件是工具注册表。一开始我觉得工具列表不就是把函数名传进 prompt 里嘛但真要落地就发现完全不是这样。模型没有能力看你的函数源码它只能靠注册表里的一段文字和参数结构来理解这个工具是干嘛的。你的字段写得越清楚它选错的概率就越低写糊了再强的模型也救不回来。我用的工具注册字段经过几次迭代稳定下来的版本大致是这样的name唯一标识不能和别的工具重名description描述工具做什么、典型使用场景、不做什么parametersJSON Schema 格式的参数定义必须有类型和必填标记output_schema工具返回值的预期结构tags能力标签方便决策器做候选过滤constraints调用约束比如仅在有用户明确授权时调用不支持批量3.2 踩坑记录description 的措辞陷阱导致语义抢占最经典的一个错我给它起名叫语义抢占。当时系统里有两个工具一个叫发送邮件另一个叫发送通知。为了省 token我把发送通知的描述写成了给用户或群组发送一条消息内容。结果模型在处理给项目组发送周报通知这种任务时选通知不选邮件因为它看到发送消息内容就觉得匹配上了哪管你是邮件还是 IM 通知。这类问题的根因不是模型笨而是描述里缺少区分性信息。后来我统一改成了描述模板 负面提示的结构每个工具的描述都分成用途典型场景不适用场景三段。比如发送邮件的不适用场景写明如果需求是即时聊天通知请使用发送通知工具反过来也一样。加了负面提示之后语义抢占的问题基本消失。3.3 token 长度和描述质量的拉锯你可能会想描述写得越详细当然越好。但这里有个硬约束——工具多了以后注册表全部塞进上下文里是要花 token 的。我实际项目里接了十几个工具完整描述塞进去要几千 token每轮调用都带着成本压力很大。做过一轮实验把描述精简掉一半工具选择准确率直接掉了接近十个点。最后还是找到了一个平衡点完整描述只给到决策阶段一旦工具被选定进入执行阶段就不再重复加载所有工具描述只加载被选中那一个的结构化参数。这样主要是为了节省上下文长度效果也挺好。对参数校验需求高的工具描述写完整一点对非常简单、一眼就懂的工具描述压到一行。3.4 注册表上线前的自检清单这个清单是我后来每次接新工具都要过一遍的列出来供你参考。工具的 description 是否包含不适用场景参数是否都是强类型枚举字段是否写全了所有合法值工具之间是否有描述语义重叠如果有是否加了互相指向的排除说明该工具是否需要有前置工具输出作为输入如果有是否在约束里写明白了这套自检看着基础但每一条我都真实踩出过问题。尤其最后一条工具依赖如果不在注册表里声明决策器再聪明也只能靠猜。4. 工具决策器候选工具怎么排序才能从能用变该用4.1 决策不是选一个而是排一条链路很多第一版 Agent 的设计是让模型直接选工具。我也这么做过结果模型在一个需求里同时输出调用工具 A、B、C、D完全不管顺序。比如先发邮件再生成周报等周报生成完邮件已经发出去了里面还是空的。后来我把工具决策器从主 Agent 里拆了出来它负责两件事选哪些工具以及按什么顺序执行。工具决策器的输入是用户目标解析结果输出是工具链它是一个有序列表每个元素包含工具 ID 和预期输入来源。决策器本身用模型实现但它不直接生成工具调用参数只输出 JSON 格式的计划比如[{tool: generate_report, input_from: user_data}, {tool: send_email, input_from: generate_report.output}]。这个结构让后面的参数注入器有了明确的依赖依据。4.2 排序策略读操作永远排在写操作前面在给工具链排序的时候我发现了一个相对普适的经验只读工具优先副作用工具靠后。这个逻辑有点类似于操作系统的权限设计——查询类工具不会改状态先执行没关系但发送邮件、修改记录这类写操作必须等所有数据准备完成之后才能执行。把动作类型放进了注册表的 constraints 里标注为read/write/append/delete。工具决策器在生成计划时会默认把 write 和 delete 类安排在所有 read 类的后面同时禁止删除类工具在没有二次确认的情况下出现在自动链路里。这条规则从设计上拦截了一大批潜在事故。4.3 踩坑记录模型把查询当创建的幂等性灾难说一个印象特别深的线上问题系统里有一个创建任务工具和一个查询任务工具两者并行作用在同一个项目里。某次用户说帮我看看昨天的任务列表决策器在解析时把看这个意图匹配到了创建任务的 description 上原因是我在创建任务描述里写了创建一个新任务而不是向系统添加一条新的待办记录。模型把看理解为新建一个视图之类的动作随即真的调用了创建接口往系统里写了一条脏数据。这个问题的教训是双层的注册表描述要包含本工具的副作用是什么决策器在生成计划前要增加一个意图-动作一致性校验。我后来在校验逻辑里写了一条硬规则用户提问如果是疑问句或查询性质工具链里不允许出现 write/delete 类工具。这个规则不需要模型判断用简单的句式分类就能实现但它会挡住绝大多数误写场景。4.4 互斥工具与依赖工具工具之间还有两类关系需要决策器特别处理互斥与依赖。互斥的典型例子是使用模板 A 生成周报和使用模板 B 生成周报两者只能选其一依赖的典型例子是发送邮件需要生成周报的输出作为正文来源。我在注册表 models 之外加了一张工具关系表专门声明exclusive_with和depends_on。决策器生成计划之前代码会先对这层关系做一次硬校验如果不满足就直接让模型重写计划而不是让错误计划带到下一环节。这里我强调一下凡是可以用确定性代码校验的关系就不要丢给概率性的模型判断这是整个系统稳定性的基础。5. 参数注入器九成调用失败出在这一层别怪模型笨5.1 自然语言和强类型接口之间的断层工具链确定之后面临的是最后一个也比较容易出问题的一步把用户需求转成工具需要的实际参数。模型天然输出自然语言工具接口却要求严格的类型、枚举和格式。就拿日期字段来举例用户说帮我查一下上周的数据模型给你的参数可能直接是上周而工具接口要的是2024-12-02这样的具体日期字符串直接传上去必炸。这个件做的东西很具体接收工具链计划 用户原始上下文按 JSON Schema 生成符合格式的参数对每个参数做类型转换、枚举合法值校验、必填项检查校验不通过则自动把错误信息拼接回上下文让模型重新生成限制最多重试三次。它的核心价值是把模型生成参数从自由文本变成了受约束填充强类型边界的兜底全部落在代码上而不是寄托于模型的临场发挥。5.2 几种最普遍的类型陷阱我列几个反复踩到的参数类型问题你可以直接对照自己的场景看。异常情况场景还原处理方式日期相对表达用户说下周一模型传 下周抽取后交给日期解析函数结合今天推断具体日期再转目标格式空值沉默用户没说收件人模型生成空字符串必填参数缺失时回传错误信息要求模型补充三次后转人工澄清字符串和数组混淆工具要数组模型传 a, b, c按 schema 检测类型不匹配尝试安全转换无法转换则报错重试枚举值幻觉状态字段只允许 pending/done模型传 completed做合法值映射表能映射的映射映射不了的回传重试这些陷阱光靠 prompt 是止不住的因为它们源于模型对世界理解的模糊性而不是指令理解不到位。参数注入器最大的意义就是把这部分模糊性在运行时显式暴露出来再通过重试机制收敛。5.3 重试风暴必须掐住必须重点说一下重试循环。第一次做参数注入器的时候我让它校验失败就回到模型重新生成结果有一个任务因为某个枚举值一直生成不合法模型快速重试了十几次token 费用飙升任务还是没通过。主要原因是我没设最大重试次数模型每次都轻微调整措辞、换各种近义词就是不敢承认我不确定这个字段该怎么填。后来给重试逻辑加了两个收敛手段一是最大重试次数为三次超过就短路直接进入向用户澄清通道二是把上一次失败的具体原因拼进重试 prompt 里告诉模型具体是哪个字段为什么失败不要改动其他字段只修正这个字段。第二个手段特别有效准确率显著提升因为模型的精力被聚焦到一个具体错误上了。5.4 参数注入器要和工具执行器解耦最后还有一个架构上的经验参数注入器只负责生成并校验参数不负责调用工具。工具调用放在一个独立的执行器里执行器拿到的已经是校验过的强类型参数。做这个解耦的原因很现实一旦工具调用失败你才能明确区分是参数错了还是工具本身错了。如果是参数的问题重试参数注入流程如果是工具的错误考虑换工具或者标记工具不可用。这两者混在一起的时候排查会非常痛苦我第一版就是混在一起的线上问题定位花了好几次。6. 结果评估器让 Agent 知道自己搞砸了才有机会纠错6.1 HTTP 200 不代表任务完成这一件是我最早觉得没必要、最后发现是最值得做的。最开始的系统里工具调用只要返回没有抛异常链路就往下走了。直到有一次发送邮件工具返回成功但实际上收件人地址被模型拼错了邮件发到了一个不存在的邮箱地址调用接口照样返回 200。用户不会说你接口调成功了用户只会说周报没收到。所以第五个自制件——结果评估器的任务是检查工具执行结果是否真的满足用户原始需求而不是只看有没有异常。它的输出是结构化的状态success、partial_success、failed、need_clarification四种。每个状态后面必须跟一段原因说明这段说明会回到决策器供它决定下一步走换工具重试、换模型重试还是向用户确认。6.2 评估信号从哪里来做结果评估器过程中会最先遇到的问题通常是怎么判断结果好不好完全靠大模型做开放式判断肯定不行。我实际用的信号分三层第一层是硬校验比对输出结构是否完整比如周报生成结果里有没有标题、有没有日期范围、有没有正文段落第二层是启发式规则比如返回体为空、返回时长异常、包含错误关键字、数据条数为 0第三层才是模型判断——把用户原始需求和工具输出一起交给评估模型让它做一致性打分。这三层的执行顺序是从便宜到昂贵。硬校验和启发式规则是代码实现几乎没有成本模型判断放在最后只处理前两层没判断出来的模糊情况。这样设计的原因依然是成本控制不是所有调用都需要模型重新读一遍用户需求。6.3 踩坑记录把结果评估逻辑写进主 Agent 的 prompt最早一版我想省事直接在主 Agent 的 prompt 里加了一句调用完工具后请检查结果是否满足用户需求。结果非常讽刺模型几乎总是回答已经满足哪怕结果明显不对。后来我意识到这不是模型不听话而是它把检查当成了一种对话礼节它倾向于给你一个让你满意的肯定答复。就像你问朋友我这件事办得怎么样大部分人会先说挺好然后才补充细节。独立性是这个件的核心。评估器我单独用一个轻量模型跑prompt 里明确说你的任务是质检不是执行。你必须指出不留情面的事实错误。即便是同一个底层模型能力独立上下文也确实比混在主链路里的判断要客观得多这一点经过多轮对比验证。6.4 失败状态怎么驱动下一步评估器输出failed之后后续路径值得说一下。我实际跑下来发现评估失败之后不存在一个通用的最优方案得细分场景如果失败原因是参数错误回参数注入器修正如果是工具本身返回异常或者内容明显不对回工具决策器换候选工具如果两次重试还是失败降级到简单模型重新跑一遍完整链路这个动作在部分场景下反而能成功大概率是因为简单模型生成结果更保守不会写一些自以为合理的内容。这一整条失败处理链路是让 Agent 系统从demo 可用变成真实可用的关键分水岭。7. 五件联动复盘一次会议纪要转周报并发送的完整链路7.1 场景设定与预期路径前面几件都是单独讲的合在一起看它们怎么配合可能更有体感。这里用一个最常见的场景走一遍完整链路。用户输入是把昨天的项目例会纪要按照周报模板整理一下发给项目群里所有人。没有模板的话用默认格式就行。这个需求在旧规则系统里要写好几条 if-else在自决链路里会拆得很清晰。预期路径是模型选择器判定这是一个中等复杂任务需要文本理解和工具调用不需要视觉分配给中档推理模型工具决策器从注册表里锁定会议纪要解析周报生成联系人查询消息发送四个工具并且把联系人查询放在消息发送前面参数注入器从用户上下文里抽出时间范围昨天、模板偏好默认格式、收件人范围项目群所有人执行器依次调用最终评估器检查周报完整性和发送状态。7.2 每步流转的具体状态变化实际跑起来每步之间流转的是带状态的数据结构而非游离的自然语言。模型选择器输出selected_models工具决策器输出tool_chain包含每个工具的前置依赖参数注入器为每个工具生成injected_arguments附上校验状态执行器返回每个工具的raw_output评估器最后输出final_status和issues。数据结构的标准化是后面能顺利排查问题的基础每步都有日志可回溯。这次 demo 过程中故意埋了一个坑用户说昨天但项目例会实际是上周五昨天是周末。参数注入器严格按字面把日期解析成了昨天周报生成出来内容自然是空的。如果是第一版系统到这里就算完成但评估器在做完整性检查时发现周报主体为空于是判断为partial_success并把原因回传。工具决策器收到反馈后决定追加一次会议纪要列表查询发现有上周五的会议记录于是推翻原日期参数用新日期重新生成整个链路又走了一遍这次评估通过。7.3 从结果倒推线上问题应该从哪个环节查这轮演练也让我总结了一套排查顺序。线上出现 Agent 行为异常时别一上来就看模型输出我现在的习惯是从后往前查先看评估器给的final_status和issues它里面已经区分了失败类别再看参数注入器的校验日志比如参数缺了哪些、哪些字段是重试出来的再看工具决策器的计划检查工具顺序和依赖是否合理最后才回到模型选择器看是不是模型分配错了导致推理能力不足。这个顺序能帮你快速定位八成问题也避免把一个参数问题反复归因到模型能力上。顺带说一下排查时需要重点关注的日志字段每个环节都记录input_source输入来源、decision_reason决策原因、revision_count重试次数、degraded_flag是否降级。decision_reason尤其重要因为模型决策是概率性的只有把决策原因落下来你事后才有办法分析它为什么选错。我在第一版里没记这个字段导致很多问题只能靠猜。8. 一点实操体会先跑通闭环再加判断力整套五件自制件做完之后如果再让我重来一次我大概率会反过来做。第一版系统其实没必要一上来就把模型选择器和评估器做得那么完善完全可以先用固定模型、固定工具链把闭环跑通然后逐步放开自主决策。这样做的最大好处是你可以先积累一批正常路径的日志再拿这些日志去调模型选择器和工具决策器比一上来就全自主、全靠肉眼盯日志要踏实得多。8.1 给 Agent 留一条逃走通道再提一个容易被忽视的设计要允许 Agent 在拿不准的时候主动停下来问人而不是硬着头皮继续跑。我给系统加了一个need_clarification状态一旦参数注入器三轮重试失败或者评估器发现多个冲突信号系统会直接向用户提问而不是继续消耗 token 瞎猜。表面上看这会打断流程实际上它大幅提高了整体成功率因为很多失败链路的起点就是模型在一个本当澄清的地方做了一个自信的错误假设。8.2 日志系统是你唯一可信赖的同事如果你也要做类似系统我建议从第一天就把决策日志当作第一等公民来对待——不要等出了线上事故再补。我后来复盘时发现大多数坑都非常直白就一个原因某个环节的决策理由没有记录下来。只要理由落盘了坑就不再是坑顶多是下一次要改进的参数。加上这五个件本身是解耦的日志清晰之后每次迭代都变成局部的不会把整个系统重写一遍。如果你正准备让 Agent 自己挑模型、自己挑工具我的建议很简单先做工具注册表和参数注入器它们是整个地基再做结果评估器和工具决策器它们负责堵住模型的盲目自信最后加模型选择器做成本优化。按这个顺序来每一层都是在给下一层铺路而不是推倒重来。