用 MCP 把 Agent 的工具调用做成可回放的实验

举报
yd_277423341 发表于 2026/11/13 09:18:57 2026/11/13
【摘要】 为什么工具调用总是"这次可以、下次不行"做工具型 Agent 的时候,最难受的不是模型答错,而是同一个问题连着跑三次,行为三次都不一样,而你没有任何证据说明差异从哪来。日志里只有一行 tool_call: search_docs,参数被截断,返回被丢掉,重试次数没记,耗时没记。等你想复现,只能靠运气。前一段时间我负责的一个内部知识库问答助手上线后,这类问题占了绝大部分线上工单。后来我们把每...

为什么工具调用总是"这次可以、下次不行"

做工具型 Agent 的时候,最难受的不是模型答错,而是同一个问题连着跑三次,行为三次都不一样,而你没有任何证据说明差异从哪来。日志里只有一行 tool_call: search_docs,参数被截断,返回被丢掉,重试次数没记,耗时没记。等你想复现,只能靠运气。

前一段时间我负责的一个内部知识库问答助手上线后,这类问题占了绝大部分线上工单。后来我们把每一次工具调用都记录成一个可回放的实验单元,"玄学"才变成可以归因的数据。下面这套做法和平台无关,只是在 MCP 上落地成本最低。

一、把埋点放在 MCP Server 侧,而不是 Agent 侧

最初我们在 Agent 框架里加日志,很快就乱了:框架换了、prompt 改了、工具换了三种语言实现,埋点逻辑要跟着改三遍。

MCP 的好处是它天然是一个边界——所有工具调用都从同一个 JSON-RPC 入口进出。只要在 server 的 dispatch 层包一层,就能拿到完整的 (tool, arguments, result, error, latency)。

一个最小的记录结构:

{
  "span_id": "7f3c...",
  "trace_id": "b21e...",
  "tool": "search_docs",
  "args_digest": "sha256:9a1c...",
  "args_preview": {"q": "如何配置日志采集", "top_k": 5},
  "status": "ok",
  "latency_ms": 412,
  "result_bytes": 18342,
  "result_digest": "sha256:5bd0...",
  "retry": 0,
  "ts": "2026-11-13T01:12:44.318+08:00"
}

注意两个字段:digest 和 preview。参数和结果都可能很大,全量存会让日志系统先崩掉,所以正文进对象存储,索引里只留摘要和大小。后来正是 result_bytes 这个不起眼的字段帮我们定位了一个 bug:某次检索返回了 2.4MB,因为分页参数被模型写成了 top_k=500,后续 prompt 被截断,答案自然就"变傻"了。

二、录制而不是采样

可回放的前提是数据完整。很多团队默认开 5% 采样,结果出事的那一次永远在采样之外。

我们的做法是:正常流量只存摘要与 digest;一旦某个 trace 被标记为"不满意"(用户点踩、或评测集命中),就把这个 trace 关联的原始请求与响应完整落盘。这样存储可控,问题 trace 又 100% 可复现。

回放时 Agent 侧只做一件事:把 MCP client 的 transport 换成 fixture 传输层,按 (tool, args_digest) 命中录制结果直接返回。于是"模型换了一版还会不会犯同样的错"这个问题第一次变得可以回答——因为工具侧完全冻结,变量只剩模型。

三、只重跑规划,不重跑工具

调试工具型 Agent 最贵的是外部副作用:写库、发消息、下单。回放模式下必须把这些工具标记为只读,否则一次调试就是一次事故。

我们在 MCP server 的 tool 定义里补了注解:

readOnlyHint: true | false
idempotentHint: true | false

回放器见到 readOnlyHint=false 就直接抛 ReplayBlocked,并在 trace 里标注"该步骤被阻断",而不是伪造一个成功返回。伪造成功会掩盖真实的依赖关系,让结论偏向乐观——这是我们踩过的坑。

四、四个能直接看出问题的指标

有了 span 之后,日常只看四个数:

  1. 工具选择准确率:评测集里期望工具与首轮选择一致的比例。低于 85% 通常说明工具描述写得含糊。
    1. 参数合法率:第一次调用就通过 schema 校验的比例。它比"最终成功"更敏感,能提前发现模型在瞎猜字段。
      1. 重试率:同一 span 内 retry>0 的占比。超过 15% 基本可以断定某个工具的错误信息不可读。

        1. P95 单步时延:工具慢会把整条链路拖垮,而且用户感知上像是"模型在思考"。

        2. 这四个数不需要看板,每天一条消息推送就够。它们的价值在于趋势:某天参数合法率从 96% 掉到 88%,大概率是有人改了某个工具的 schema 却没同步描述。

      2. 五、几个容易忽略的细节

      • 时间戳统一用带时区的 RFC3339,别用本地时间。跨时区排查时你会感谢自己。

        • 结果摘要不要截断在字符中间,UTF-8 多字节字符被切断会让下游解析器直接抛异常。
          • args_digest 要把 key 排序后再算,否则 {"a":1,"b":2} 和 {"b":2,"a":1} 会被当成两次不同调用。
            • 幂等标记要真的验证过再写。我们曾经把一个"创建工单"接口标成幂等,重放时造出了 47 张重复工单。

              • 别在 span 里塞 prompt 全文。它变化频繁、体积大,还会把敏感信息带进日志。

              • 六、落地顺序

            • 如果要从零开始,我建议的顺序是:先统一入口(在 MCP dispatch 包一层),再定 span 结构,然后做"问题 trace 全量落盘",最后才做回放器。反过来的顺序也能做,但你会发现自己花了两周写回放器,却没有一条可回放的数据。

          • 工具调用从"不可解释"变成"可实验",中间隔的不是更聪明的模型,而是一层诚实的记录。

        以上是我在自己项目里的实践,MCP 只是让这层记录更容易标准化。如果你们也在做类似的事,欢迎在评论区交流更细的指标口径。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。