1. 从一次线上事故说起为什么工具选择策略值得单独拎出来讲去年冬天我接手了一个内部运维助手的项目功能不复杂让 Agent 根据用户自然语言指令去调用一系列内部工具完成查询、变更、通知等操作。上线第一周就炸了。用户说“帮我看看昨天订单服务的错误日志”Agent 老老实实调了日志查询工具没问题用户说“把 staging 环境重启一下”Agent 也调了重启工具也没问题。但有个用户说了句“帮我确认下线上服务是不是都正常”Agent 一口气串行调了十几个健康检查工具每个工具超时 30 秒用户等了六分多钟最后返回一句“部分服务异常”用户直接投诉。问题出在哪不是模型不够聪明也不是工具本身有问题而是工具选择策略太粗糙了。当时我的实现是最原始的硬编码路由用一堆 if-else 和关键词匹配来决定调哪个工具。这种方案在工具数量少于 5 个、用户表达高度规范的时候能跑一旦工具数量上到二三十个、用户表达千变万化就彻底失控。这件事之后我花了大概两个月时间把工具选择策略从硬编码一路迭代到动态决策中间踩了无数坑。这篇文章就是把这套演进路径完整拆开讲清楚硬编码到底什么时候能用、什么时候必须换动态决策的核心机制是什么MCP 协议在这个环节扮演什么角色以及在实际工程中怎么平衡准确率、延迟和成本。如果你正在做 Agent 开发或者正准备把原型推向生产这些经验应该能帮你少走不少弯路。2. 工具选择策略的本质一个被低估的决策问题2.1 工具选择到底在解决什么问题很多人把 Agent 的工具选择理解成“让模型选一个函数调用”这个理解太浅了。工具选择策略真正要解决的是一个在不确定输入下做多约束决策的问题。约束条件至少包括四层第一层是语义匹配。用户说“查一下昨天的错误日志”系统里可能有query_logs、search_events、fetch_metrics三个工具都能沾边选哪个取决于用户真实意图。第二层是参数完备性。有些工具需要必填参数比如时间范围、服务名用户没说全的时候是追问还是用默认值这本身就是策略。第三层是执行约束。工具可能有权限要求、速率限制、依赖关系比如重启工具必须先通过健康检查。第四层是成本与延迟。同样能完成任务的工具有的走缓存 50ms 返回有的要查冷存储 3 秒选哪个直接影响用户体验。硬编码策略通常只处理第一层而且是用关键词匹配这种极其脆弱的方式。动态决策则要把四层约束都纳入考量。这就是为什么工具数量一多、场景一复杂硬编码就撑不住了。2.2 硬编码、规则引擎、动态决策的边界在哪里我整理了一张对照表这是我实际项目中用来判断该用哪种策略的依据维度硬编码 if-else规则引擎动态决策模型驱动适用工具数量1-5 个5-20 个20 个以上用户表达规范度高度规范中等规范任意自然语言维护成本低但脆弱中等前期高后期低准确率上限约 70%约 85%约 95%延迟极低微秒级低毫秒级中百毫秒到秒级可解释性完全可解释可解释需要额外日志这张表不是绝对的但边界感很重要。我见过太多项目在工具只有 3 个的时候就上动态决策结果引入了一堆不必要的复杂度和延迟也见过工具已经 40 多个还在用 if-else 硬扛每次加工具都要改核心路由代码改一次崩一次。判断标准其实很简单当你发现新增一个工具需要修改工具选择逻辑本身时就该考虑升级策略了。硬编码的问题不在于它简单而在于它把“工具定义”和“选择逻辑”耦合在了一起。2.3 MCP 协议带来的结构性变化MCPModel Context Protocol这两年被讨论得很多但很多人只把它当成一个“工具接入标准”。在我看来MCP 对工具选择策略最大的价值是解耦。在 MCP 之前每个 Agent 框架都有自己的工具定义格式工具选择逻辑和工具实现绑死。MCP 把工具的描述、参数 schema、调用方式标准化之后工具选择策略可以独立演进。具体来说MCP 提供了几个关键能力工具的能力描述是结构化的name、description、inputSchema这意味着动态决策可以直接消费这些元数据工具可以动态注册和发现意味着策略不需要硬编码工具列表工具调用有统一的错误语义意味着策略可以根据错误类型做重试或降级。我实际用下来MCP 让工具选择策略的迭代速度至少快了一倍。以前加一个工具要改三处代码现在只需要在 MCP server 里注册Agent 侧的策略自动就能感知到。这个结构性变化是后面所有动态决策方案的基础。3. 硬编码策略什么时候它是对的什么时候是灾难3.1 硬编码的三种典型写法与各自的坑我见过和写过的硬编码策略大概有三种形态每一种都有它特定的适用场景和隐藏的坑。第一种是纯关键词匹配。代码大概长这样def select_tool(user_input): if 日志 in user_input or log in user_input: return query_logs elif 重启 in user_input or restart in user_input: return restart_service elif 监控 in user_input or metric in user_input: return fetch_metrics else: return None这种写法在 demo 阶段极其高效十分钟就能跑通。但它的坑在于关键词冲突和覆盖不全。用户说“帮我看看服务状态”既没有“日志”也没有“监控”直接返回 None。用户说“重启前先看下日志”同时命中两个关键词走第一个分支但用户真实意图是先查日志再重启需要多工具编排硬编码根本处理不了。第二种是意图分类器 映射表。先训一个小分类模型或者用规则做意图识别再把意图映射到工具。这种比纯关键词稳一些但坑在于意图和工具不是一一对应的。同一个意图可能需要不同工具组合取决于上下文。比如“查服务状态”这个意图在故障排查场景下要调健康检查工具在日常巡检场景下要调指标查询工具分类器没法区分。第三种是决策树。把选择逻辑写成一棵显式的树每个节点是一个条件判断。这种可解释性最好但坑在于树的维护成本随工具数量指数增长。我维护过一棵 30 个工具的决策树深度到了 7 层每次加工具都要重新审视整棵树的分支改一次要回归测试两天。3.2 硬编码的合理使用场景说了这么多坑但硬编码不是一无是处。在下面这些场景里硬编码反而是最优解工具数量极少且稳定比如只有“查询”和“写入”两个工具硬编码清晰直接引入动态决策纯属过度设计。高频路径需要极致延迟有些场景对延迟极其敏感比如实时对话中的简单指令硬编码的微秒级响应是动态决策给不了的。我的做法是高频路径硬编码兜底长尾路径走动态决策。安全敏感操作需要强管控涉及资金、权限变更的操作硬编码的显式判断反而更安全因为每一条路径都是人工审核过的不存在模型幻觉导致误调用的风险。作为动态决策的降级方案动态决策服务挂了或者超时了硬编码兜底能保证核心功能可用。这个后面会详细讲。我的经验是硬编码不是要被淘汰的旧方案而是动态决策体系里的一个必要组件。把它放在正确的位置它就能发挥价值。3.3 从硬编码迁移的时机判断迁移时机判断错了要么过早引入复杂度要么过晚导致线上事故。我总结了一个简单的判断清单命中任意两条就该考虑迁移工具数量超过 10 个且还在持续增加。新增工具平均需要修改工具选择逻辑 2 处以上。线上出现因工具选择错误导致的用户投诉且每月超过 3 次。用户输入的自然语言多样性明显增加关键词匹配覆盖率低于 80%。需要支持多工具编排一个请求触发多个工具按序执行。我自己的项目是在工具数量到 15 个、月投诉 5 次的时候开始迁移的。回头看其实在工具到 10 个的时候就该动了多撑的那段时间纯粹是在给自己挖坑。4. 动态决策的核心机制让 Agent 自己学会选工具4.1 动态决策的三种实现范式动态决策不是单一技术而是一类方案的统称。我实际用过和评估过的有三种范式各有取舍。第一种是纯 Prompt 驱动。把所有工具的 name、description、inputSchema 塞进 system prompt让模型直接输出要调用的工具和参数。这种实现最简单MCP 标准化之后尤其方便因为工具描述是结构化的直接序列化进 prompt 就行。坑在于工具数量一多prompt 就爆炸。我试过 40 个工具全塞进去光工具描述就占了 8000 token模型注意力被稀释选择准确率反而下降。第二种是检索增强的工具选择。先用向量检索从工具库里召回 Top-K 个候选工具再把候选塞进 prompt 让模型选。这种解决了 prompt 爆炸问题但引入了新的坑检索召回率决定了准确率上限。如果正确的工具没被召回模型再聪明也没用。我的做法是检索召回 Top-10再让模型从 10 个里选实测准确率比全量塞 prompt 高 8 个百分点。第三种是微调专用选择模型。用历史调用数据微调一个小模型专门做工具选择。这种准确率最高但成本也最高而且工具一变就要重新微调。我只在工具集极其稳定的场景下推荐这种。我最终采用的是检索增强 Prompt 驱动的混合方案兼顾了准确率、成本和灵活性。下面详细拆解这套方案的实现。4.2 工具描述的写法直接决定选择准确率这一点被严重低估。很多人写工具 description 就是一句话“查询日志”然后抱怨模型选不准。工具描述是模型做决策的唯一依据它的质量直接决定准确率。我总结了一套工具描述的写法规范实测能把选择准确率提升 15 个百分点以上描述要写“什么时候用”而不是“这是什么”。差的写法“查询日志工具”。好的写法“当用户需要排查服务错误、查看历史请求记录、分析异常行为时使用。不适用于查询实时指标用 fetch_metrics或查看服务健康状态用 health_check”。显式写出排除场景。模型很容易在相似工具间混淆主动告诉它“这个工具不用于什么”比只告诉它“用于什么”有效得多。参数描述要包含示例值。time_range参数描述写成“时间范围格式如 2024-01-01 到 2024-01-02默认最近 1 小时”比“时间范围”四个字强太多。标注工具的副作用和成本。比如“此工具会触发实际重启操作调用前需确认”、“此工具查询冷存储延迟约 3 秒”这些信息能帮模型在多个候选间做更合理的权衡。我维护了一个工具描述模板每个新工具都按这个模板写团队里谁来写都保证质量下限。4.3 多工具编排的决策逻辑单工具选择只是入门真正的难点是多工具编排。用户说“重启前先看下日志”这需要先调日志工具根据结果决定是否重启。用户说“把所有异常服务重启”这需要先调健康检查拿到异常列表再对每个异常服务调重启。动态决策处理编排有两种思路。一种是让模型输出完整的执行计划比如输出一个工具调用序列Agent 按序执行。这种适合步骤确定的场景但模型很难预判中间结果对后续步骤的影响。另一种是ReAct 式的逐步决策模型每次只决定下一步调什么工具根据上一步结果再决定下一步。这种更灵活但延迟更高因为每步都要过一次模型。我的实践是混合对于步骤数少于 3 步的编排让模型一次性输出计划超过 3 步的走 ReAct 逐步决策。这个阈值是根据实际延迟数据调出来的3 步以内一次性规划的总延迟更低超过 3 步逐步决策的准确率优势更明显。5. 实操落地一套可复现的动态决策实现5.1 整体架构与数据流先讲清楚整体架构再拆细节。我的实现分四层第一层是工具注册层基于 MCP 协议所有工具通过 MCP server 注册提供标准化的 name、description、inputSchema。第二层是检索层把工具描述向量化存入向量库用户输入来了先做向量检索召回 Top-K 候选。第三层是决策层把候选工具和用户输入组装成 prompt调模型输出工具选择和参数。第四层是执行与反馈层执行工具调用记录结果把成功和失败案例回流到检索层和决策层做优化。数据流是这样的用户输入 → 向量检索召回候选 → 组装 prompt → 模型决策 → 参数校验 → 执行 → 结果回流。每一层都有降级和兜底机制后面细讲。5.2 检索层的实现细节检索层看着简单但有几个关键参数直接影响最终效果。向量模型的选择。我试过三种通用文本向量模型、代码向量模型、以及专门微调的工具检索模型。通用模型在工具描述这种半结构化文本上表现一般代码向量模型反而更好因为工具描述里有很多参数名、格式说明和代码语义接近。最终我用的是一个在工具调用数据上微调过的向量模型召回率比通用模型高 12 个百分点。Top-K 的取值。K 太小召回不全K 太大 prompt 变长。我实测下来 K10 是个甜点召回率能到 96%prompt 长度也可控。如果工具总数少于 15 个直接全量塞 prompt 就行不用检索。检索的 query 构造。不要直接拿用户原始输入去检索先做一次轻量的意图改写。比如用户说“服务挂了帮我看看”改写成“查询服务健康状态和错误日志”检索命中率明显提升。这个改写可以用一个小模型做成本很低。5.3 决策层的 Prompt 工程决策层的 prompt 我迭代了十几版最终稳定下来的结构是这样的你是一个工具选择助手。根据用户请求从候选工具中选择最合适的工具并填充参数。 候选工具 {tools_json} 用户请求{user_input} 决策规则 1. 优先选择描述中明确匹配用户意图的工具 2. 如果多个工具都匹配选择参数最完备、副作用最小的 3. 如果用户请求需要多个工具协作输出工具调用序列 4. 如果没有任何工具匹配输出 {tool: null, reason: ...} 5. 参数缺失时使用工具描述中的默认值不要编造 输出格式严格 JSON {tool: 工具名, params: {...}, reason: 选择理由}几个关键点强制 JSON 输出保证可解析要求输出 reason方便排查和回流明确参数缺失的处理规则避免模型编造参数给出无匹配时的输出格式避免模型硬选一个。实测下来这套 prompt 在 40 个工具的场景下单工具选择准确率能到 93%多工具编排准确率约 85%。剩下的误差主要来自工具描述本身的歧义需要持续优化描述。5.4 参数校验与执行兜底模型输出的参数不能直接信。我在执行前加了一层校验类型校验参数类型必须匹配 inputSchema字符串、数字、布尔、数组、对象都要检查。必填校验必填参数缺失直接拒绝触发追问流程。枚举校验枚举类型参数必须在允许值范围内。范围校验数值参数检查上下界比如时间范围不能超过 30 天。权限校验当前用户是否有权限调用该工具。校验不通过的处理策略分两种如果是参数格式问题把校验错误信息回传给模型让它重新生成一次如果是权限或范围问题直接返回用户可读的错误提示不重试。执行层还要有超时和重试。我给每个工具设了独立的超时时间默认 10 秒查询类工具 30 秒变更类工具 60 秒。超时后根据工具类型决定是否重试查询类可以重试一次变更类绝不自动重试避免重复操作。6. 常见问题与排查技巧实录6.1 工具选择准确率上不去的排查路径准确率上不去是最常见的问题。我整理了一套排查路径按顺序检查排查项检查方法典型问题工具描述质量人工审阅所有 description描述模糊、缺少排除场景检索召回率统计正确工具是否在 Top-K 内K 太小或向量模型不匹配Prompt 结构检查决策规则是否清晰规则冲突、输出格式不明确参数 schema检查 inputSchema 是否完整缺少默认值、类型不明确模型能力换更强的模型对比模型本身推理能力不足数据分布统计错误案例的输入特征某些表达方式训练数据少我遇到最多的问题是工具描述质量。有一次准确率卡在 80% 上不去排查一圈发现是两个工具的 description 几乎一样模型根本没法区分。改完描述当天准确率就到 91%。6.2 延迟优化的几个实操手段动态决策的延迟主要来自三块向量检索、模型推理、工具执行。优化手段分别说向量检索通常几十毫秒优化空间不大但可以加缓存。相同或相似的 query 直接命中缓存省掉检索。我用的是语义缓存相似度超过 0.95 就复用结果命中率约 30%。模型推理是大头几百毫秒到几秒。优化手段有三个一是用更小的模型做初筛大模型只处理复杂 case二是流式输出边生成边解析不用等完整输出三是 prompt 压缩把工具描述精简到必要信息减少 token 数。工具执行的优化取决于工具本身但策略层可以做的是并行化。多个独立工具调用并行执行总延迟从累加变成取最大值。我有个场景串行执行 5 个工具要 15 秒并行后 3.5 秒。6.3 动态决策的安全边界动态决策最大的风险是模型幻觉导致误调用。我的防护措施分三层第一层是工具分级。把工具分成只读、写入、危险三类。只读工具随便调写入工具需要参数校验危险工具重启、删除、资金操作必须二次确认。第二层是调用频率限制。同一用户单位时间内调用危险工具的次数有上限超过直接拒绝。第三层是审计日志。所有工具调用记录完整的输入、决策理由、参数、结果出问题可以回溯。注意危险工具的二次确认不要做成简单的“是/否”要让用户看到具体的操作对象和影响范围。我见过因为确认信息太模糊用户误点确认导致误操作的事故。6.4 从硬编码迁移的实操步骤如果你决定从硬编码迁移到动态决策我建议按这个顺序来不要一步到位先标准化工具定义。把所有工具的描述、参数 schema 补齐这一步不管迁不迁移都该做。加一层检索。先不改决策逻辑只在硬编码前面加检索观察召回效果。灰度切换决策层。新老策略并行按流量比例灰度对比准确率和延迟。建立回流机制。记录所有决策结果定期分析错误案例持续优化。保留硬编码兜底。动态决策超时或失败时降级到硬编码保证核心功能可用。我整个迁移过程花了六周其中前两周都在做工具定义标准化。回头看这两周是最值得的投入后面所有优化都建立在这个基础上。7. 一些踩坑之后的个人体会工具选择策略这件事我最大的体会是不要追求一步到位的完美方案。我一开始想直接上最先进的动态决策结果工具定义一团糟再好的策略也发挥不出来。后来退回去先把工具描述和 schema 做扎实动态决策的效果自然就上来了。另一个体会是延迟和准确率永远在打架。想要准确率高就得多检索、多推理延迟就上去了想要延迟低就得简化决策准确率就下来。我的做法是分层高频简单请求走快速路径低频复杂请求走精细路径用请求特征做路由。这个分层策略让我的 P95 延迟降了 60%同时整体准确率没掉。最后说一个容易被忽略的点决策日志的价值远超你的想象。我一开始只是为了排查问题才记日志后来发现这些日志是优化策略的金矿。哪些工具经常被混淆、哪些参数经常缺失、哪些用户表达模型处理不好全在日志里。我现在的优化流程就是每周拉一次决策日志分析 Top 错误模式针对性优化。这个习惯让我的准确率在过去半年里从 85% 稳步爬到了 94%。如果你也在做 Agent 工具选择建议从第一天就把日志和回流机制建起来后面会省很多事。