跳到主要内容

41 篇博文 含有标签「tool-use」

查看所有标签

流式中止后遗留的计费副作用

· 阅读需 12 分钟
Tian Pan
Software Engineer

用户正在查看你的智能体(Agent)流式传输回复。在 200 毫秒时,他们点击了停止。UI 清除了气泡,加载图标消失,产品的表现就像请求从未发生过一样。

但它确实发生了。Agent 已经调用了 send_invoice_email。供应商的邮件转发服务器返回了 250 OK。客户收到了一份用户从未批准的草案发票。你的计费系统向用户收取了中止前流式传输的 token 费用。但它无法撤回发送邮件产生的费用。

这是每个使用流式工具(streaming tool use)的团队至少都会遇到一次的失败模式,而且大多数团队甚至从未察觉。流层(stream layer)报告 cancelled。工具层(tool layer)报告 succeeded。你的面向客户的日志会根据最后刷新的子系统从中挑选一个,于是同一个请求的两个部分现在对于该请求是否发生产生了分歧。

你的 LLM 抄不准的那个账号

· 阅读需 12 分钟
Tian Pan
Software Engineer

一个客服智能体读完工单、拉出账户、总结了最近的活动、发起了退款。退款落到了错误的账户上。不是被凭空捏造出来的账户——是一个真实存在的、只差一位数的账户。模型写下了 acct_7H9j2,可这位客户的真实记录是 acct_7H9j3。trace 干净得无可指摘:搜索调用拿到了正确的记录,总结调用产出了正确的摘要,退款调用毫无报错地完成。每一步都成功了。钱落进了错的人手里。

这并不是事故复盘里通常说的那种"幻觉"。模型没有凭空发明一个客户。它把一个真实存在的客户的两个字符换错了位置——这是另一类失败,一类你的评测集大概率从没抓到过的失败,因为你测试样本里的合成标识符在构造上本就是唯一的。两个账号同时出现在上下文里、前三个字符相同,而语言模型——一个从未被训练成"忠实复制随机字符串"的 token 预测器——挑错了那个。

教训是结构性的,不是行为性的。模型没有任何专门为标识符设计的注意力机制。在模型眼里,acct_7H9j2 只是一串子词 token,它们的延续概率会随窗口中其他每一个 token 漂移。一旦上下文里出现了一个"近亲"标识符,模型就只差一次坏采样,就会做出一次悄无声息的替换——而 harness 会毫不犹豫地把它执行下去。

那个用一小时反复重试同一个 400 错误的 Agent

· 阅读需 12 分钟
Tian Pan
Software Engineer

一个 agent 调用了某个工具。工具返回 400 Bad Request,错误体结构清晰得无可挑剔:{"error": "missing required field", "field": "email"}。Agent 的推理链一字不差地复述了错误,说"我需要在请求里加上 email 字段",然后发出了下一个工具调用——负载和上一次一模一样。框架的重试策略是多年前为不稳定 HTTPS 连接写的,把这个 400 当成瞬时错误,又发了一次。三次。八次。十五次。一小时后,这个 agent 烧掉了上下文、烧掉了预算、烧掉了限流额度,却从未发出过一个工具能接受的请求。

这种失败看起来像是模型的问题。其实不是。模型把错误读对了。是包在它外面的框架,没有给"修正"留任何落脚点。

无法说出"等一下"的智能体

· 阅读需 10 分钟
Tian Pan
Software Engineer

随便挑一个过去两年里搭建的生产级智能体,清点它在某一轮里实际能做的事。清单很短:发起一次工具调用、给出最终答案,或向用户提一个澄清问题。这就是整个动作词汇表。注意一下缺了什么。没有动词表达"我想在决定之前多花点时间";没有动词表达"我足够不确定,所以希望暂停并重新考虑,而不立刻承诺";没有动词表达"我想在采取任何行动之前先在这件事上多停留一会儿"。智能体在字面意义上无法说出"等一下"。语法里压根没有这个词。

这并不是表面打磨的问题,而是结构性的问题。一旦智能体的全部输出都是动作,任何内部状态都必须经由一个动作来表达。犹豫变成多余的工具调用,怀疑变成自信的承诺。只设计了动作动词的团队,实际上发布了一个唯一的语言就是"做事"的智能体——然后又奇怪它怎么从来不像在思考。

把每个工具都当作 O(1) 的规划器

· 阅读需 9 分钟
Tian Pan
Software Engineer

你的规划器输出了五次工具调用。从纸面上看,这是一个干净的解决方案:lookup_usersearch_documentscall_external_apispawn_sub_agentrequest_human_approval。轨迹优雅、逻辑自洽,智能体最终也会给出正确答案。可在生产环境中,这五个步骤分别耗时 12 毫秒、800 毫秒、4 秒、2 分钟和 6 小时。规划器从未察觉,它这五步计划在成本上跨越了九个数量级。

![](https://opengraph-image.blockeden.xyz/api/og-tianpan-co?title=%E6%8A%8A%E6%AF%8F%E4%B8%AA%E5%B7%A5%E5%85%B7%E9%83%BD%E5%BD%93%E4%BD%9C%20O(1%29%20%E7%9A%84%E8%A7%84%E5%88%92%E5%99%A8)

这并不是幻觉。模型选对了工具,顺序也合理。它做不到的——工具模式根本没给它做这件事的途径——是去推理:计划的最后一步在性质上和第一步完全不同。在规划器眼里,工具就是工具,计划图中每个节点的权重都是 1。

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

· 阅读需 12 分钟
Tian Pan
Software Engineer

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

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

流式 Token 是无法收回的承诺

· 阅读需 10 分钟
Tian Pan
Software Engineer

模型已经向用户屏幕推送了 70% 听起来很自信的回答。接着,它即将进行的工具调用返回了错误、无结果或 429 错误。现在你必须在两种损失之间做出选择:让模型通过编造剩余部分来优雅地结束,或者在句子中间戛然而止,且没有体面的方式撤回。这两种都不是修复 —— 它们都是损害。

这是流式传输 UX 中没人考虑过成本的部分。流式传输被描绘成一种感知延迟的胜利:首个 Token 时间 (TTFT) 是核心指标,用户更早开始阅读,应用显得充满活力。但这种描绘忽略了你推送的每一个 Token 都是一种承诺。你发布了一个你还不知道是否正确的答案草稿,而你系统的后半部分还没有运行完毕。当它运行结束并产生分歧时,你的 UI 没有原生方法来撤回已经显示的内容。

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

· 阅读需 11 分钟
Tian Pan
Software Engineer

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

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

那个本该计算却随口编造数字的智能体

· 阅读需 11 分钟
Tian Pan
Software Engineer

询问你的智能体上季度的流失率,它会用一个简洁的句子回答 4.2%。这个数字看起来很合理。周围的文字描述也显得很有信心。然而,当有人最终检查仪表盘时,显示的却是 6.8%。智能体根本没有进行任何查询——它只是产生了一个符合“流失率”特征的 Token 序列,因为对于语言模型来说,口述一个数字和计算一个数字在输出过程中看起来是一模一样的。

这是一种能躲过所有 Demo 的隐形失败模式。一个虚构的工具名称会抛出你可以捕获的错误。一个格式错误的参数会通不过 Schema 校验。但是,一个表达流畅的虚构 数值,会穿透你的整个流水线,看起来与真实数值毫无二致。没有异常,没有日志记录,没有红字。唯一的错误信号是某个恰好知道正确答案的人——而使用智能体的初衷本就是为了让人们不必亲自去查。

组合性税收:为什么增加工具会让你的规划器性能下降

· 阅读需 11 分钟
Tian Pan
Software Engineer

团队最开始有 5 个工具和一个在生产流量中命中率达 95% 的规划器(planner)。18 个月后,他们有了 51 个工具,而规划器的命中率降到了 26%,原本那 5 个工具能干净利落处理的简单案例——预订会议、查询客户、提交工单——现在有时会路由到错误的工具,因为目录中有三个听起来很像的“替代品”。没有人故意让规划器变差。每一次工具的增加在当时看来都是合理的。这种累积的代价就是“可组合性税”(composability tax),每一个在工具目录增长过程中缺乏淘汰机制的产品都在支付这笔费用。

这笔“税”是一条曲线,而不是悬崖。Berkeley Function Calling Leaderboard 直接测量了这一点:在日历调度任务中,当跨多个领域的工具从 4 个增加到 51 个时,准确率从 43% 下降到了 2%。在客户支持类任务中,GPT-4o 从 58%(单一领域,9 个工具)下降到 26%(7 个领域,51 个工具)。Llama-3.3-70B 在同样的扩张下从 21% 降到了 0%。这种趋势在不同模型和任务类型中不断重复:每增加一个工具,规划器就会在曲线上进一步下滑,而且随着目录变大,边际损害会变得更严重,因为新加入的条目与现有的条目越来越难以区分。

延迟感知工具选择:当“当下的足够好”优于“未来的最出色”

· 阅读需 11 分钟
Tian Pan
Software Engineer

你智能体系统提示词中的工具描述是一个六个月前的评估产物(eval artifact)。它说 search_pricing 返回“带有结构化定价的最新库存数据”,规划器(planner)对此深信不疑,因为自描述调优的那天起,提示词中的任何内容都没有更新过。而实际上,在过去的 40 分钟里,search_pricing 端点的 p95 延迟一直保持在 11 秒,因为上游供应商正在对你的账户进行限流。而那个被提示词描述为“可能略微陈旧”的更便宜的 search_cache 工具,只需 200 毫秒就能返回同样的答案。但规划器还是选择了 search_pricing,因为描述读起来仍和评估时一样,且规划器没有任何关于目前调用这两个工具成本的信号。

这就是静态工具描述的结构性失效。规划器是在根据一个已经发生变化的世界快照做出路由决策。工具选择实际上并不是一个能力问题——大多数生产环境中的智能体都有两三个在回答内容上高度重合的工具——它本质上是一个“等待成本”问题,而等待成本正是你的提示词模板所看不见的东西。

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) 和催促模型厂商发布新版本来解释这种退化。