跳到主要内容

MCP 工具弃用:为什么模型仍然调用旧名称

· 阅读需 10 分钟
Tian Pan
Software Engineer

六周前,你将 get_user_email 重命名为 lookup_contact。新名称已发布,旧的处理程序已移除,变更日志记录了这一点,你的评估集也通过了。然而上周二,一位客户支持工程师联系了你:智能体在上周的大约 3% 的工具调用中返回了错误——tool_not_found: get_user_email。那个已被重命名的名称。那个在实时系统中已经不再对外公开的名称。

先验知识(Prior)具有粘性。你的智能体正在与之对话的模型是在一个语料库上训练的,在这个语料库中,get_user_email 是询问“这个人的电子邮件是什么”的绝大多数规范方式。即使你在推理时传递的 tools 数组中仅列出了 lookup_contact,模型偶尔——在特定的上下文条件下,特别是长追踪(long traces)或错误恢复状态下——仍会退回到它记忆中的名称。直接切换并不能消除长尾效应;它只是将软故障变成了硬故障。

这就是关于 MCP 版本控制的公开讨论中一直绕不开的失效模式。服务器动态宣告工具;客户端刷新;协议提供了 tools/list_changed 来提示过时的缓存。这些都没错。但它们都没有解决模型的先验知识问题。协议是简单的部分。困难的部分在于,JSON-RPC 管道另一端的东西是一个概率分布,它看过五年的 GitHub 代码,而你的变更日志并不在它的权重(weights)中。

直接切换模式对工具而言是错误的

大多数团队参考的心理模型是 API 弃用:宣布一个停用日期,发送六个月的 Deprecation 请求头,然后在切换日返回 410 Gone。这种模式假设调用者是一个能够读取响应代码并更新其代码的软件。它适用于人类编写的 REST 客户端,因为人类对 410 的反应是提交一个 Jira 工单。

模型不会读取 410 并提交 Jira 工单。模型收到 tool_not_found 错误后,会根据其训练分布和周围的上下文决定做什么,并且经常会用同一个名称重试。或者它会向用户道歉。或者它会幻觉出另一个同样错误的工具名称。或者它通过另一条路径成功了,而你永远不知道它曾尝试过已弃用的名称,因为错误已被吸收进了一个恢复分支中。

直接切换模式还假设弃用窗口是昂贵的——你希望窗口尽可能短,以便移除旧代码。对于模型来说,窗口是廉价的。弃用途径只是一个转发到新名称的单行垫片(shim)。真正的成本是在垫片消失那一刻你所创建的失效模式。

在 MCP 介导的智能体系统中,工具重命名不是 API 版本控制事件。它们是模型输入分布的偏移,需要像迁移数据库列时使用的那种双写(dual-write)机制。

真正有效的弃用垫片

在生产环境中经受住考验的模式包含三个部分,MCP 规范都没有强制要求这些,但协议本身都允许。

保持两个名称同时在线。 当你将 get_user_email 重命名为 lookup_contact 时,在至少一个模型生成周期和客户端更新周期(以较长者为准)内,在 tools/list 中同时宣告两者。旧工具的处理程序是对新工具的单行转发,参数保持一致。Schema 应该完全相同或更加宽泛。旧工具的描述应以弃用标记开头——[DEPRECATED: use lookup_contact]——模型有时会读取并对此做出反应,而人类在 grep 你的清单文件时也肯定能看到。

记录对已弃用名称的每一次调用。 为其标记原始会话 ID、模型版本、在追踪中的位置,以及该调用是模型的第一次尝试还是在其他失败后的重试。这是你目前缺失的数据,也是唯一能告诉你何时可以安全移除垫片的数据。没有它,你的移除决定就像是在抛硬币。

根据流量而非日期定义移除触发器。 “我们将在六周后移除 get_user_email”是错误的承诺。“一旦我们路由到的所有模型版本中,每周调用次数连续两周低于 N 次,我们就会移除它”才是正确的做法。模型正在通过实际表现告诉你它何时内化了新名称;让它自己决定。

加载中…
References:Let's stay in touch and Follow me for more thoughts and updates