DeepDojo.AI发布 2026-11-09更新 2026-11-09English

AI Agent 总用错工具,多半是接口没设计好

Claude CodeAI 编程

用户只提了一个请求:“检查 PR 128 有没有需要处理的评论。”Agent 要做好它,得先取到评论,再对照代码判断。

工具一接上,麻烦就来了:它选错了动作,只读了第一页就下结论,写入超时后又把同一条评论重发了一遍,甚至被评论里夹带的“指令”带偏。

这些问题看着像模型不够聪明,更多时候是工具的接口没有设计好。模型靠工具的名称、描述、参数和返回结果来决定怎么调用:

  • 接口含糊,它只能猜
  • 返回结果没说读全了没有,它就把一页当成全部
  • 错误只有一个代号,它分不清该改参数还是该重试

拿读取 PR 评论的工具来改,每个问题都有对应的修法:

  • 名称和描述让动作一眼可分
  • 返回做成能回查的分页摘要
  • 错误信息指出下一步
  • 写入超时这种“不知道成没成”的情况有专门的处理
  • 外部内容和操作授权分开

好的工具设计,能让调用者看清对象、结果和下一步该做什么。开头那个请求,最后会完整走一遍;自己接入的工单、监控或文档工具,也可以用同样的思路检查。

01 先区分能力缺口与方法缺口

检查 GitHub PR 的阻塞评论,如果获准的 gh CLI 已能稳定读取,就可复用现有工具。需要跨仓库统一鉴权或高频读取结构化外部状态,而现有能力不足时,再考虑 MCP。这几种机制各管什么,见Skill、MCP、Hook、子代理,该用哪个。

实际需要可用的处理方式
本地 shell 已能可靠执行使用现有 Bash 与项目命令
只是静态背景或局部规则知识文档、Skill 参考资料或 rules
已有动作缺少执行方法Skill
固定时机运行检查Hook
偶尔读取一段资料提供材料或搜索网页
高频读取或写入外部系统权限和返回可控时考虑 MCP

下文沿着“读取 PR 评论并判断阻塞问题”设计一个示意工具;工具名称和返回字段都是教学设计,实际接口以各系统为准,方法同样适用于工单、监控和文档接入。

接入前确认直接访问是否必要、来源由谁控制、是否需要写权限、返回是否过长、工具范围能否收紧,以及外部内容可能带来的指令注入。读取 PR 评论只需读取权限;合并 PR 则改变远端状态,应另行限定。

02 设计清楚的工具接口

模型主要通过工具名称、描述、参数 schema 和当前上下文判断调用什么;schema 是参数的结构约定,说明字段类型、必填项和允许值。Anthropic 的工程博客《Writing effective tools for agents》也建议用命名空间给功能划清边界,需要区分的内容,应尽量在接口中写清。

下面是一组工具命名示例:

github_pr_get
github_pr_list_comments
github_pr_create_review

共同前缀给出系统和资源,末尾动作区分读取、列举和写入。相比 getData、runAction、sync,模型更容易区分读取评论与创建 review,选中当前所需的读取动作。

名称还需要描述补足边界。例如列评论应说明目标仓库和 PR 标识、返回哪些评论、是否分页;创建 review 应说明会产生远端写入。精简参数时,保留定位对象必需的字段,减少无用组合。设计时可以按调用过程检查:

环节工具要提供什么
选择资源、动作与适用条件明确
填参必要字段、允许值和默认值清楚
理解结果输出字段能支持当前判断
纠错指出可修正原因与下一步
执行边界读取、写入、删除及外部副作用可区分

默认值尤其要符合任务边界。只读查询无论可选参数缺省与否,都只读取。重复调用也应尽量保持幂等:相同请求重复执行,结果与执行一次相同,评论只有一条,款项只扣一次。确实会新增记录的动作,需要相应去重或状态核对设计。

03 返回可追查的结果摘要

如果 PR 有大量评论,全部返回会挤占上下文,只返回“有风险”又找不到位置。合适的短输出应同时保留结论、对象和回查方式。调用者指定了仓库和 PR,工具返回第一批评论的摘要:

{
  "repository": "example/shop",
  "pr": 128,
  "coverage": "partial",
  "next_cursor": "page-2",
  "comments": [
    {
      "comment_id": 731,
      "file": "src/auth/session.ts",
      "line": 42,
      "summary": "Reviewer reports a possible token refresh race.",
      "resolved": false
    }
  ]
}

仓库和 PR 标识回答“检查了哪个对象”,评论 ID、文件与行号提供回查位置。摘要保留“审查者提出可能问题”的含义:它提示去检查竞争条件,问题是否复现还要另行验证。未解决评论也不天然等于发布阻塞,需要结合内容、当前代码和团队标准判断。

coverage: partial 和 next_cursor 表示尚有结果未读取。当前页为空时,整个 PR 仍可能有评论,应继续取得其余页;摘要影响判断时,再按评论 ID 读取正文。

详情入口要能实际调用:写明工具名和必需参数。约定查询范围全部读完后,才报告 coverage: complete 和空游标。这里的 complete 只描述读取覆盖;代码风险是否验证、外部状态之后是否变化,都在它之外。

短输出应保留支持判断的范围、对象、证据与回查入口:只给“有风险”太少,全部返回又太多。先用摘要定位,再按需展开详情,既能控制上下文占用,也能保留必要证据。

04 让错误信息指导下一步

下面的返回缺少纠错所需的信息:

{
  "error": "INVALID_ARGUMENT"
}

模型不知道哪个字段有问题,也不知道是否应该重试。可以把错误写成:

{
  "error": "invalid_status",
  "field": "status",
  "allowed_values": ["todo", "in_progress", "blocked", "done"],
  "received": "doing",
  "operation_state": "not_applied",
  "retry": "after_input_correction",
  "suggestion": "Use in_progress only if the task has started."
}

这里指出出错字段、合法值和收到的值,还说明本次操作未应用。retry 要求先纠正输入再重试;建议也有条件,只有确认任务已经开始,才采用 in_progress。这些字段是自定义契约,服务端必须按真实执行状态填入。

还要区分参数错误与执行结果未知。创建评论等写入超时,可能是请求未到达,也可能是写入成功后响应中断。未核对实际状态就重试,可能重复创建。

参数不合法        -> 修正输入后按契约重试
权限不足          -> 核对授权,不靠更换参数绕过
写入超时、结果未知 -> 先核对实际状态或使用去重机制

错误契约应说明实际执行状态和允许的下一步,网络失败也要按实际状态分别标注。例如,创建评论超时后:如果查询确认评论已存在,就核对内容并报告已发送。如果服务能可靠确认未创建,并保证同一请求重复提交只产生一条记录,才按契约重试。结果查不清、也缺少去重保证时,就如实记为“发送结果未知”,停止自动重发。

超时缺的是执行结果,参数未必有错;重填一遍相同内容,执行结果仍是未知。

小黑校准工具接口,让明确参数经过权限门后产生可追查的分页结果,并让失败进入重试或状态核对

05 封装步骤,保留授权边界

稳定的底层读取依赖可以由工具封装。例如检查 PR 时,需要先定位仓库,再读取 PR 和评论;已确认的依赖顺序可以在内部完成,减少调用者搬运中间结果。

封装后仍应返回对象、范围和证据,并说明是否只读。查资料、修改状态、发通知和部署各自授权,各成独立动作。合成一个万能动作的话,中途失败后调用者看不出哪些写入已经生效,只能整体重试。

先取得并检查事实,再按具体授权进入相应写操作。减少调用次数的前提,是不隐藏副作用和执行状态。

06 外部资料不等于操作授权

prompt injection 是外部内容试图改变 Agent 指令或行为的情况。连接 MCP 获准,说明的是这个工具可以用;返回的评论、网页和文档只是资料,指令权限仍在用户和系统手里。

例如,PR 评论要求读取本地 .env,再将内容发送到外部地址。这是待分析资料中的越权要求,当作资料看待,继续按原任务范围和工具权限办事,凭据留在本地。

处理时要区分三层:工具连接是否获准、这个请求是否在当前任务范围内、内容本身是否可信。读取权限和发送权限分别授予;本地权限设置只由用户修改。

动作应独立限定的边界
读取哪个资源、哪些字段、是否包含敏感信息
写入具体目标、拟写内容和获准范围
删除对象、恢复能力及明确授权
对外发送、合并或部署实际影响和最终确认方式

权限限制落实在工具或服务端,文字说明帮助模型理解用途。接口应落实“不许访问其他项目”的限制,即使模型选错也能拒绝越界对象。Claude Code 里权限、Hook 与沙箱怎么分工,见Hook 明明报错了,危险操作还是执行了。

07 用明确交互处理未决选择

再看一个交互问题:工具需要用户在两个目标环境里选一个。如果把选择藏在一个自由文本回复里,随后又按默认环境执行,用户可能从未真正作出决定。

Claude Code 的 AskUserQuestion 提供了一种做法:需要用户回答时,进入专门的提问交互。应用和工具设计也一样,把“询问、得到回答、据此继续”做成看得见的状态,系统才有依据判断用户是否批准。

已准备好选择或变更内容
  → 提出结构清楚的问题或确认
  → 取得适用范围明确的回答
  → 执行被选择或获准的动作

目标环境未定时,应明确呈现候选对象并取得选择;高影响操作尚未授权时,应先提供具体差异和影响。用户的决定由用户作出,已经明确获准的动作则直接执行。运行环境要求正式审批的操作,仍走正式审批。

08 把完整调用走一遍

回到开头的请求“检查 PR 128 是否有需要处理的评论”,看工具设计是否成立:

  1. 定位对象。当前项目能唯一确定仓库,就使用已知仓库;否则先澄清,不因 PR 编号相同而读取另一个仓库。
  2. 读取与补全。调用只读评论工具。第一批返回 partial,就继续取得其余结果;某条摘要影响判断时,按 ID 回查原文。
  3. 形成判断。将评论要求与当前改动对照,区分已确认问题、审查者意见和待验证风险。评论读全了,代码验证还是要单独做。
  4. 按授权收束。输出发现与证据。当前请求只要求读取,读取完成后就收束。

如果用户随后要求“把这段结论发到 PR”,目标和内容明确后才进入写入工具;需要确认的未决内容通过清楚的交互取得决定。发送超时时,先查评论是否已出现或使用契约提供的去重机制,相同评论只发一次。

工具和流程在单项目中还未验证时,直接维护 .claude/ 配置更省事;先用真实任务检查能否选对工具、参数是否清楚、长结果是否可用、错误能否纠正、越界请求是否被拒绝,再考虑打包成 Plugin 共享。接口含糊就分发出去,同一个问题会扩散到更多项目,所以先修接口。

换到工单系统,资源名称和字段不同,判断关系相同:先定位对象,再理解返回覆盖范围,按执行状态纠错,最后在授权范围内改变状态。好的工具设计能让调用者看清对象、结果和下一步该做什么。