Agent 选错工具、反复调同一个工具:从描述、参数、数量三处排查

2026-07-28

数据截至 2026-07,各产品的额度与报错口径以官方最新说明为准。

这类问题最常见的归因是”模型不够聪明,换个更强的试试”,而这条路多数时候白花钱:agent 选错工具或者对着同一个工具反复调,绝大部分是它拿到的信息不足以做判断——工具之间边界没写清,它只能猜;参数没有约束,它只能试;调用完了没有明确反馈,它就以为没生效于是再调一遍。这三件事全都发生在你的工具定义里,换模型改不了。

本篇只讲排查顺序:从现象反推成因,再按成因动手。如果你是从零设计一套工具、想知道名字和描述该怎么写,去看 给 Agent 设计工具;如果你已经确认是工具挂太多引起的干扰,要在数量和上下文成本之间做取舍,去看 MCP 工具太多会让模型变笨吗。这两篇是”怎么设计”和”该挂多少”,本篇是”已经出问题了,按什么顺序查”。

一、先把现象分成三类,再谈改法

上手改之前先分清你遇到的是哪一类。三类现象的成因几乎不重叠,混着治会互相抵消。

第一类:选错。 该用 A 却用了 B,而且相当稳定地错,换任务描述也还是错。这是选择问题,根子在工具之间的区分度。

第二类:空转。 同一个工具连着调好几次,参数只差一点甚至完全一样,最后要么超轮次退出,要么给出一个和前面某次返回值一模一样的答案。这是反馈问题,根子在返回值没让模型确认状态。

第三类:该调不调。 明明挂了查询工具,它却凭训练时见过的东西直接编一个答案出来。这是触发条件问题,根子在描述里没写”什么情况下必须用”。顺带说一句,这一类和模型编造的关系很近,判断输出是否可信的方法可以参考 大模型幻觉

分清之后再看这张表。左边找现象,右边照着验证,验证通过了才动手改——跳过验证直接改描述,是这类问题最常见的时间浪费。

现象大概率成因怎么验证处置动作
稳定挑同一个错工具两个工具的名字与描述语义重叠,错的那个看起来更”万能”临时只保留正确的那一个,跑同样的任务;一次就对说明是选择问题改名做区分,在两边描述里互相写明”这种情况请用另一个”
同一工具连调多次、参数几乎不变返回值没有让模型确认这一步成了翻调用轨迹里工具返回的原文,看是不是空数组、空对象或者没有状态字段返回结构化结果并带明确状态与计数,写清”没有匹配”也是一种成功
参数总错在同一个字段schema 缺枚举、缺格式、缺示例,字段名要靠猜用同一段固定任务反复跑几遍,确认错的是同一个字段而不是随机漂;再翻这个字段在 schema 里的定义,看它是不是自由字符串、有没有格式说明和示例收紧类型与枚举,字段改成自解释的名字,能给默认值就给
工具挂少时正常、挂多才乱清单本身占位置,相似条目互相干扰按任务分组,只挂当前这一组再跑分组挂载,按阶段动态增减
该查却自己编描述里没写触发条件,模型有先验知识可用问一个不查数据就绝对答不出的问题,看它调不调描述里写清必须使用的场景,并在系统侧要求以工具返回为准
调用一直失败但 agent 还在重试传输层或鉴权问题,如 401、403、429、ETIMEDOUT、ECONNRESET绕开 agent,用 curl 直接打同一个接口先修连接和凭据,这时候改描述没有意义
只在某台机器或某个网络下失败出口代理、自签证书导致证书链校验失败对比两台机器的代理与 CA 相关环境变量把企业 CA 配进运行时信任链

最后两行值得单独强调:传输层故障伪装成”agent 变傻”的比例比你想的高。工具报错之后如果只把一句含糊的失败信息回给模型,模型看不出这是网络问题,它会认为是自己参数填错了,于是换个参数再试——你看到的就是”反复调同一个工具”。所以排查任何工具调用异常,第一步都是脱离 agent 单跑一次:

curl -sS -i -X POST http://127.0.0.1:8080/search \
  -H 'Content-Type: application/json' \
  -d '{"query":"订单超时","top_k":5}' | head -n 20

看状态码就够了。429 说明被限流了,各家的限流规则不同而且会调整,具体口径以官方最新说明为准,做法上你要在工具侧加退避重试,而不是让模型去重试。401 和 403 是凭据问题,去查密钥有没有过期或者作用域够不够。如果是证书链校验失败,通常是企业出口做了 TLS 拦截,把内部 CA 配进运行时就行:

export NODE_EXTRA_CA_CERTS=/etc/ssl/certs/corp-ca.pem   # Node 运行时
export REQUESTS_CA_BUNDLE=/etc/ssl/certs/corp-ca.pem    # Python requests

关掉证书校验当然也能跑通,但那等于把内部流量向任何能拦截你的人敞开,不要在能连到生产数据的机器上这么做。MCP 一侧更细的报错分类可以对着 MCP 调试技巧 过一遍。

二、改法一:工具描述里写边界,不是写功能

确认是选择问题之后,先动描述。这里最容易犯的错是”把描述写得很全面”——每个工具都写一段漂亮的功能介绍,结果每个都看起来能干这件事,模型的区分难度反而变大了。

描述真正要承载的是边界,也就是这三句话:什么情况用我、什么情况别用我、别用我的时候该用谁。第三句尤其有用,它相当于给模型一张路牌。两个功能相邻的工具,各自描述里都点一句对方的名字,选错率会明显下降。

第二个要写进描述的是前置依赖。如果 B 必须拿到 A 的返回结果才能调,就在 B 的描述里写明”需要先调 A 取到标识”。不写这句,模型会自己造一个看起来合理的标识填进去,然后你在日志里看到一串不存在的 ID。

第三个是副作用的强弱。只读的查询和会改动数据的写入,在模型眼里如果长得一样,它就会用同样的随意程度去试。写入类工具的描述里应当明确它会产生持久变更、失败可能留下半成品状态。这不是给人看的免责声明,是给模型的行为约束——它读到这句之后确实会更保守。

还有一个纯机械但有效的自查:把工具清单导出成 JSON,算一下两两之间的词面重叠,重叠高的先改名。

python - <<'PY'
import json, itertools, re

tools = json.load(open('tools.json'))          # [{"name": ..., "description": ...}, ...]

def bag(t):
    text = (t['name'] + ' ' + t['description']).lower()
    return set(re.findall(r'[a-z][a-z0-9_]+', text))

for a, b in itertools.combinations(tools, 2):
    wa, wb = bag(a), bag(b)
    if not wa or not wb:
        continue
    j = len(wa & wb) / len(wa | wb)
    if j > 0.4:
        print(round(j, 2), a['name'], '<->', b['name'])
PY

这段只看英文词面,中文描述可以换成按字切分,思路一样。它找不出语义相似但用词不同的那类重叠,所以只当粗筛用。真正的判断还得靠你自己回答一句:如果我是第一天来的人,只看这份清单,我能分清这两个吗。

三、改法二:参数约束当护栏,返回值当反馈

参数出错和空转,这两件事往往是同一个原因的两面:模型对”我刚才那一步到底成了没有”没有把握。

参数侧的收紧有几个固定动作。能枚举的一律枚举,不要写成自由字符串再在实现里校验——校验只能拒绝,枚举能引导。时间参数明确格式并说清时区,否则你会看到本地时间和 UTC 混着传。分页和数量类参数给默认值,让模型可以不填。字段名换成自解释的写法,代码里叫 uid 没关系,暴露给模型的那一层写成 user_id 更省事。互斥的参数不要放进同一个工具,拆成两个,或者用一个枚举把模式选出来。

返回值侧的原则更简单:每一次调用都要能让模型明确知道结果是成功、失败、还是成功但没有数据。空转最典型的触发方式,就是查询没命中时返回一个空数组。模型看到空数组无法区分”确实没有”和”我姿势不对”,于是换个说法再查一次,再查一次,直到轮次用完。改法是返回里带上状态和计数,并把”没有匹配”当成一个正常结果明确说出来,顺带把已经用过的查询条件回显一遍。

失败也一样。工具报错时回给模型的内容,应当分清这是它能修的还是它修不了的:参数不合法属于它能修,应当直接指出哪个字段不合法、合法取值是什么;上游超时或者被限流属于它修不了,应当明说不要重试、由系统侧处理。把这两类混成一句”调用失败”,模型只能选择重试。

写入类工具再加一层幂等。让调用方传一个幂等键,服务端见过就直接返回上次的结果。这样即便模型判断失误重复调了,也不会产生两条数据。重复调用带来的浪费和排查方法,Agent 反复失败与重试 里讲得更细。

四、改法三:数量与轮次控制,把空转的成本按住

前两处改完,剩下的问题基本都跟规模有关:工具清单越长,选择越容易出偏差;单次任务允许的轮次越宽松,空转的代价越大。

数量上的做法就三种,按代价从小到大:分组挂载,按任务类型只挂当前需要的那一组;分阶段挂载,检索阶段只给读的工具,落地阶段再放开写的;合并同类,把几个只差一个参数的工具合成一个带枚举的工具。第三种最省清单空间,但要小心别合出一个参数组合爆炸的万能工具——那会把选择问题变成参数问题,治好一个又长出一个。

轮次上要设三道闸。一是单次任务的总调用次数上限,到了就停下来把当前状态交还给人,而不是继续试。二是同一工具连续相同参数的调用直接短路,第二次就返回一句”你刚才用完全相同的参数调过,结果如下”,把重复挡在工具层而不是指望模型自己发现。三是把任务拆小,长任务里模型对早期结果的记忆会衰减,衰减之后它会重新查一遍已经查过的东西——这一类可以配合 Agent 上下文管理 里的做法处理。

顺便提一句成本口径:空转烧掉的量往往比人估计的高,因为每一轮都要重新把工具清单和历史带一遍。要不要装监控看你的规模,但至少应当能回答”上周哪个任务的调用次数异常”。各家的计费与额度规则不同且会调整,以官方最新说明为准,不要照着别人的经验值给自己定阈值。

五、什么情况下别再折腾

调工具定义是有收益上限的,越过这条线继续投入就是纯亏。下面几种情况建议直接换路子。

改了三轮描述,选错率没有明显变化。 这说明区分度问题不在描述层面,而是两个工具在业务上本来就重叠。把它们合并,或者干脆在流程里固定顺序:这一步只能调这一个,不给模型选择空间。工具选择本来就不是必须交给模型的决策。

同一个环节反复失败,而这个环节的正确做法你自己完全清楚。 那就别让 agent 探索了,写成固定流程,把 agent 用在真正需要判断的地方。用规则能表达的东西交给规则,既省时间也省钱。

任务本身要求高确定性,而失败的后处理成本很高。 涉及资金、对外发送、生产环境变更这一类,正确的做法是在动作前插一道人工确认,而不是把描述写得更严。人在环路的设计 里的判断依据可以直接照搬。

回滚点这件事要提前准备,不要临时想。 让 agent 动代码之前先落一个干净提交,出问题直接丢弃工作区:

git status --short          # 先看清它改了什么
git stash push -u -m agent  # 想留证据就 stash,不想留用下一条
git checkout -- .           # 丢弃已跟踪文件的改动

如果它把冲突标记 <<<<<<<=======>>>>>>> 留在了文件里,说明它在合并或应用补丁时半途而废,不要让它接着改,回到干净状态重来更快。

最后一种情况:你怀疑是模型能力上限。 这时候换模型确实有意义,但要先做对照——同一套工具、同一段任务,只换模型跑几遍。没有对照的换模型只是换了个心情。这里要说清楚一件事:部分海外 agent 工具与模型的官方服务对中国大陆有区域限制、不支持直连,把它当备选方案时要把可用性算进去;市面上存在第三方中转,我不背书也不给渠道,合规与数据风险你自己评估。

六、避坑清单

每条都写清为什么会踩、怎么避。

只改描述不看轨迹。 会踩是因为改描述的反馈最快、心理成本最低,而看轨迹要一条条读工具返回的原文,枯燥。避法是把顺序固定死:先看轨迹里最后三次调用的入参和返回原文,确认现象归到哪一类,再决定动哪里。

把描述写成功能宣传。 会踩是因为写文档的习惯让人倾向于把能力写全,而模型需要的是边界。避法是每个描述里必须有一句”什么情况别用我”,写不出来就说明这个工具和别的重叠了。

用空返回值表示没找到。 会踩是因为在代码里空数组表示没结果是天经地义的,而模型收到的空数组和”调用出了点问题”长得一样。避法是返回里必须有状态字段,没有匹配也要明确说出来。

给模型看原始堆栈。 会踩是因为直接把异常转成字符串最省事。堆栈里没有可行动的信息,模型只能重试。避法是把错误分成”你能修”和”你别管”两类,前者指出具体字段,后者明说不要重试。

在实现里默默改参数。 会踩是因为容错看起来很贴心:模型传错了范围,你悄悄裁到合法区间。结果模型永远学不到正确用法,而且返回值和它以为的请求不一致,它会以为工具没生效。避法是要么报错并说明合法值,要么在返回里明确回显实际使用的参数。

一次挂上所有工具。 会踩是因为”先都挂上省得来回配”。清单长了之后相似条目互相干扰,而且每一轮都要带一遍。避法是按任务分组,新增工具时问一句现有工具能不能覆盖。

拿别人的轮次上限和参数直接抄。 会踩是因为看到经验值就想省事。任务复杂度、工具数量、模型都不一样,阈值不可移植。避法是自己压测:同一段任务跑几遍,记录正常完成需要多少轮,在这个基础上留一点余量。

不留回滚点。 会踩是因为顺利跑通过几次就放松了。避法是把”动手前先提交”写进流程,别依赖记性。

收个尾

排查这类问题的顺序基本是固定的:先脱离 agent 单跑一次接口排掉传输层,再看调用轨迹把现象归成选错、空转、该调不调,然后依次动描述的边界、参数的约束与返回值的反馈、工具数量与轮次的上限。改完每一处都用同一段任务复跑,才知道是哪一处起了作用。

下面这几条可以在下次出问题时照着过:

  • 现象归类了吗,是选错、空转,还是该调不调
  • 脱离 agent 单跑过接口吗,状态码是什么
  • 出错的两个工具,各自描述里有没有写”什么情况别用我”
  • 空结果和失败结果,模型能分清吗
  • 写入类工具有幂等键吗
  • 同参数连续重复调用,工具层挡住了吗
  • 单次任务有总轮次上限吗,到上限是停下来交人还是继续试
  • 动手前有干净提交可以回滚吗

改完这一轮如果选错率还是没动,那就不是工具定义的问题,回到第五节那几条判断,该固定流程就固定流程,该插人工确认就插人工确认。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。