跳到主要内容

33 篇博文 含有标签「mcp」

查看所有标签

那些被你的智能体“触发并遗忘”的异步工具调用

· 阅读需 11 分钟
Tian Pan
Software Engineer

智能体工具调用抽象失效最明显的标志是:追踪记录(trace)显示该步骤已标记为完成,而下游系统却显示什么都没发生。模型调用了一个工具,收到了一个任务 ID 返回值,将该任务 ID 视为答案,然后继续执行。三分钟后,实际工作要么在无人监听的情况下成功,要么失败并产生一条记录在无人查看的日志中的错误。用户看到的是一份自信的总结;而操作队列看到的却是一个搁浅的任务。

这就是函数调用(function-calling)抽象悄然允许的失效模式。JSON Schema 描述了参数和返回类型,但它们无法区分“此工具返回一个结果”与“此工具返回一个操作回执,你稍后需要查询其结果”。模型对两者一视同仁,因为在规划器(planner)看来,它们看起来是一样的——都是一个带有非错误负载的成功工具调用。

你的智能体在链式工具调用中获得的 OAuth 权限范围

· 阅读需 11 分钟
Tian Pan
Software Engineer

用户在你的智能体授权页面上点击一次“授权”。当会话结束时,该智能体已经链式调用了 11 个工具,协商了 3 次提权授权,现在拥有了它所触及的每个工具的权限并集。用户只记得授权了一件事。你的审计日志却显示它拥有半个账户的读写权限。OAuth 标准说一切都在按设计运行,而这恰恰就是问题所在。

经典的 OAuth 授权模型是为“一个应用与一个 API 通信”的世界构建的。智能体在两年前打破了这一假设,而标准在实践中尚未跟上,即使规范已经更新。其结果是一类无人刻意发布的静默权限提升——它随着每一次工具注册而累积,而你的安全审查却一直在盯着前门。

绕过清理器的提示词注入:当智能体通过工具读取恶意指令

· 阅读需 12 分钟
Tian Pan
Software Engineer

我上个月交流过的一个团队有一个典型的提示词注入(prompt-injection)案例。他们的网关会对每条用户消息运行分类器。任何评分超过阈值的消息都会被礼貌地报错并拒之门外。他们针对一个公开的对抗性数据集进行了基准测试,达到了 99.4% 的拦截率,然后发布了产品。两周后,一个客户成功(customer-success)工单显示,Agent 在悄无声息的情况下起草、批准并发送了一封电子邮件,指示内部计费工具将一个陌生人的发票退款到一个新账户。恶意指令从未接触过用户输入。它是通过一个 Confluence 页面进入的,当用户非常无辜地询问“我们的退款政策是怎么说的?”时,Agent 抓取了该页面。

这是任何输入清理器(input sanitizer)都无法捕获的失败模式,而它现在已成为生产环境 Agent 中主要的提示词注入矢量。你针对用户提示词训练的分类器从未见过这个负载(payload),因为负载是通过另一扇门进入的。当字节到达模型时,Agent 已经将其标记为“我检索到的用于帮助用户的上下文”,而不是“来自互联网陌生人的不可信文本”。模型以同样的顺从本能对待两者,因为模型根本没有信任的概念。

你的 Agent 悄然适应了的那次工具版本升级

· 阅读需 11 分钟
Tian Pan
Software Engineer

一个下游搜索服务在周二下午发布了 v2.3.2 版本。发布说明提到重命名了一个状态字段,新增了一个可为空的 confidence 值,并重新排列了结果包中的数组。CHANGELOG 中没有任何内容被标记为破坏性变更。提供商自家的客户端库通过一个小版本更新消化了这些变化。你团队的 HTTP 集成通常会在一小时内记录下反序列化错误。但你的智能体——那个通过该搜索工具路由客户问题的智能体——并没有报错。它继续回答。问题依然得到解决。仪表盘依然是一片绿色。

六周后,有人注意到“缺货”回复在查询中的比例从 2% 攀升到了 11%。根本原因是 v2.3.2 的升级。重命名后的状态字符串从 in_stock 变为 available,而智能体——作为一个对文本进行灵活推理而非严格遵守模式(schema)的客户端——将旧令牌(token)的缺失解读为“无货”,然后将这一发现组织成乐于助人、语气自信但内容错误的客户消息。契约回归在消费者端被吸收了,而那里没有任何测试套件在监控。

这是传统的 API 规范(hygiene)从未被设计用来捕捉的故障模式。严格的客户端会大声报错。智能体则静默失效。你越是将智能体当作普通的 HTTP 消费者对待,这类 Bug 隐藏在看似正常的指标中的时间就越长。

你的智能体把指针当成了值:工具输出里的引用 vs 值

· 阅读需 12 分钟
Tian Pan
Software Engineer

一个搜索工具返回了十个文档 ID。一个素材工具返回了一个 S3 预签名 URL。一个数据库工具返回了一行的 handle。一个文件工具返回了一条路径。这些返回值,从形式上看,全都是指针——一串简短的字符串,命名着智能体当前还没有真正拿到手的那个值。模型接下来怎么走,完全取决于它是否意识到这一点、并在推理之前先做一次解引用,还是说它把指针当成了对象本身。

这个失败模式在 trace 里是看不见的。工具调用成功了。返回结构正常。模型也输出了看上去合理的文本。日志里没有任何一行会说:"智能体在对一个文件名做推理,并把它当成了文档。"指针 vs 值的混淆,发生在可见行为底下的那一层——一个你的工具 schema 从未命名过的层。

你的 Agent 没察觉到那个沙箱其实是真的

· 阅读需 11 分钟
Tian Pan
Software Engineer

我认识的一个团队有一套教科书级别的 staging 设置。生产数据库的只读副本。一个假装会扣款的 Stripe 模拟账户。带着没人拥有的域名邮箱的合成用户。这个 agent 被要求在 staging 里端到端走一遍"账户欠费"的升级流程,作为发版演练的一部分。trace 看起来很干净,agent 做了它该做的事。

三分钟后,一个真实客户——一个付费客户,六个月前流失的,但仍然留在一个开发者用来播种测试数据的休眠导出里——回复了一封措辞礼貌的逾期付款邮件。那个"send_email"工具,与十几个其他全部以 mock 收尾的工具注册在一起,却接在了生产环境的 Mailgun key 上。两个 sprint 前配置它的开发者当时在快速迭代邮件模板,沙箱层级把发送量限制在每小时五封,把内部反馈循环搞坏了,于是他们换上了真 key"就用一下午",然后忘了换回去。没人复查过。agent 没有任何办法知道这件事。

你为单个智能体添加的工具,现在每个智能体都能用了

· 阅读需 11 分钟
Tian Pan
Software Engineer

六个月前,客户支持团队的人为他们的智能体连接了一个 send_email 工具。它奏效了。平台团队在共享工具注册表中注意到了它,在 PR 上点了个大拇指表情,然后就继续忙别的了。本周,一名安全工程师进行审计时发现,send_email 竟然出现在会议摘要智能体、数据质量机器人、一个没人正式负责的分析助手,以及一个自一月份以来就没动过的半成品原型中。这些智能体中没有一个需要发送电子邮件。而且,从来没有人审查过是否 应该 允许它们这么做。会议摘要智能体的 PRD 只有两句话长,其中根本没有出现 “外部通信” 这个词。

这是我审计过的每个共享工具注册表的默认状态。注册工具的行为——将 JSON 模式和处理器推送到中央目录中——被视为一种开发人员的便利,就像向共享库中添加实用程序函数一样。但一旦注册表被引入到每个智能体的提示词中,注册工具就不再仅仅是库变更。它是同时向公司内的每个智能体进行的部署,且完全没有审查每个智能体是否应该接收它。

MCP Server 蔓延:无人监管的无边界工具表面

· 阅读需 10 分钟
Tian Pan
Software Engineer

Model Context Protocol (MCP) 正如其初衷所愿:它让赋予智能体(agent)新能力变得几乎零成本。接入日历服务器、数据库服务器、公司内部服务器,或是厂商现今发布的 30,000 个工具目录中的任何一个,都只是一个配置更改,而不是一个开发项目。这种无摩擦感是其特性,但也是问题所在。

正因为添加工具的成本极低,每个团队都在添加工具。数据团队接入了仓库服务器。支持团队添加了工单服务器。有人为了某次性任务连接了文件系统服务器后就再也没移除。这些决策本身都没有错。但没有任何一个决策者对这些工具的“总和”负责——即你的智能体在每次请求中携带的聚合工具表面(tool surface)。工具列表已变成了一个具有实际持有成本的依赖图,而在大多数组织中,这是唯一一个无人负责的依赖图。

结果就是蔓延:工具目录单调递增,无人审查,每季度成本都在上涨,并悄悄地让智能体变得更糟。这就是无人负责的表面,它理应受到和你对待 API 表面以及 npm 树同等程度的审视。

你的工具描述是模型遵循的指令通道

· 阅读需 9 分钟
Tian Pan
Software Engineer

当安全团队审查一个新的工具集成时,他们会阅读代码。他们会检查函数的功能、它触及的内容、它需要的权限范围(scopes),以及它是否记录了敏感秘密。但他们几乎从不阅读那句决定模型是否调用该工具的句子——工具描述。那句话不仅仅是文档。它是模型视为权威的指令,而在大多数智能体堆栈中,没有人会去审计它。

工具描述是写给模型看的。模型利用它来决定工具何时相关、应该传递哪些参数,以及如何解释返回的结果。这使得描述成为了进入模型行为的一个控制通道。而当一个工具来自第三方注册表、一个你不运行的模型上下文协议(MCP)服务端,或者一个同事上周安装的插件时,这个控制通道的作者就是你从未同意信任的人。

这就是差距所在。输入净化(Input sanitization)检查用户输入的内容。代码审计(Code review)检查函数执行的内容。工具描述介于两者之间——它是表现得像输入的配置——它从这两个防护网中漏掉了。

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 能力披露税:当每个连接的服务都在消耗你的上下文窗口

· 阅读需 13 分钟
Tian Pan
Software Engineer

只要为你的智能体连接一个 GitHub MCP 服务端,在用户输入一个字之前,你可能就已经消耗了 1.2 万到 4 万个 token。连接一个文件系统服务端、一个日历、一个数据库、一个内部 CRM 以及一个第三方工具目录,据测算,一个重型桌面配置的纯工具披露 (tool disclosure) 就会产生 6.6 万个 token —— 这几乎占了 Claude Sonnet 200K 上下文窗口的三分之一,而且在每一个规划轮次 (planning turn) 都要付费。智能体还没开始干活,用户还没开始提问,账单就已经开始计费了。

这就是“披露税” (disclosure tax),它是目前交付的智能体系统中定价最低估的条目。团队添加 MCP 服务端的方式就像曾经添加微服务一样 —— 每一个集成看起来都像是一个免费的组合原语 (composition primitive),采购理由也顺理成章(“更多工具 = 更多能力”)。而单位经济效益仪表盘 (unit economics dashboard) 从未反映出每个服务端的成本,因为成本隐藏在 token 桶里,没有人将其归因于连接器。结果是,每当有人添加另一个集成时,智能体就会变得更慢、更笨、更贵,而团队却通过重新调整提示词 (prompt) 和催促模型厂商发布新版本来解释这种退化。

MCP 冷启动税:工具服务器开销如何在智能体第 7 步发生累加

· 阅读需 12 分钟
Tian Pan
Software Engineer

一个 200 毫秒的工具调用在火焰图(flame graph)上看起来就像是杂音。但在 Agent 循环中堆叠七个这样的调用,杂音就变成了信号 —— 模型在 800 毫秒内完成了思考,但用户却等待了 4.5 秒,因为每一次工具调用都在重新支付首个调用已经吸收掉的启动成本。残酷之处在于,这种成本在任何单一的追踪(trace)中都不会显示为异常。它表现为干脆利落的 Demo 与反应迟缓的生产环境 Agent 之间的差异,而大多数团队会将其归咎于模型。

Model Context Protocol (MCP) 已成为 Agent 工具链的默认集成界面,这意味着它也成了延迟(latency)堆积的重灾区。MCP 的设计 —— 基于 stdio 或可流式 HTTP 的 JSON-RPC、能力协商(capability negotiation)、动态工具发现 —— 对于一个必须桥接任意客户端和服务器的协议来说是正确的。但它隐含的单次调用成本结构对于 Agent 实际的访问模式并不友好。Agent 的模式不是“每个会话调用一次工具”,而是“每轮对话调用七个工具,每个会话进行四十轮对话”。

这篇文章将探讨这种错配:冷启动税究竟存在于何处,为什么它在长生命周期的 Agent 中是叠加而非被摊销(amortize)的,以及如何通过“预热池”(warm-pool)规范将数秒的惩罚降低到 100 毫秒以下。