跳到主要内容

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

查看所有标签

当用户取消对话后,下游 API 却仍在继续写入

· 阅读需 11 分钟
Tian Pan
Software Engineer

用户点击停止。浏览器关闭了 SSE 连接。你的 AI SDK 触发了 onAbort。Agent 运行时检测到信号,停止向模型请求更多 token,并终止其循环。从你的代码库内部来看,这次取消显得非常利索。你所能看到的每个子系统都在执行正确的操作。

与此同时,两秒钟前,模型发出了一个工具调用(tool call)。运行时分发了它。工具的 execute 函数打开了一个连接到第三方 API 的 TCP 连接并发送了 payload。该 HTTP 请求仍在传输中,第三方的服务器仍在处理它,而第三方完全无法得知它所服务的对话已不存在。写入操作成功提交。用户的心智模型认为他们通过点击停止避开了该操作。下游系统的数据库则记录了完全不同的结果。

由于注册表缓存存在一小时延迟,你的智能体仍在调用已被撤销的工具

· 阅读需 11 分钟
Tian Pan
Software Engineer

用户打开集成页面,找到上个月安装的 Stripe 连接器,点击“移除”,然后关闭了标签页。他们认为自己刚刚撤销了一项授权。实际上,他们只是修改了数据库中的某行数据,而当前正在与他们对话的 Agent 在接下来的 43 分钟内都不会再次读取该数据。在此期间,Agent 会尝试调用该 Stripe 工具,注册中心的授权层会正确地拒绝请求,Agent 的 harness 框架会将此次拒绝视为暂时的下游波动并重试三次,而用户自己的 Stripe 审计日志将记录来自他们认为刚刚已断开连接的供应商的三次未经授权访问尝试。

用户的投诉几乎原话是:在我移除之后,你们的平台仍在尝试访问我的 Stripe。这正是实际发生的情况,而根本原因比错误报告所涉及的层面更深。工具注册中心是 Agent 获准执行操作的唯一事实来源(source of truth)。但 Agent 并没有读取这个事实来源,它读取的是缓存。

增加更多工具后,你的智能体“不知道”率为何反而下降了

· 阅读需 10 分钟
Tian Pan
Software Engineer

你添加了搜索工具,接着是日历工具,然后是 CRM 工具,接着是四个数据库封装器(wrappers)和一个计算器。仪表盘的变化正如你所愿:任务完成率上升,延迟保持稳定,“我不知道”率从 14% 下降到 4%。这看起来像是能力的提升。事实并非如此。规划器并没有学到更多知识;它只是学会了减少拒答(abstention)。现在每个问题看起来都是可以回答的,因为总有 某个 工具的模式能与查询充分匹配并被调用。你消除的那 10 个百分点的“我不知道”并没有转化为正确的答案 —— 它们变成了自信的错误答案,分布在无人仔细评审的长尾场景中。

这就是工具表面扩展带来的“伪能力陷阱”(false-competence trap)。这是团队在庆祝改进时,发布了性能退化(regression)最常见的方式。评估准则衡量的是 Agent 是否尝试了任务并产生了一个形状合理的答案;它并不衡量 Agent 是否应该拒绝回答。拒答并非没有成本,但它是目前能获得的最廉价的正确行为,而一旦你的工具集变得足够大,以至于总有 某个 工具会被触发,你就再也看不见这种行为了。

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

· 阅读需 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 校验。但是,一个表达流畅的虚构 数值,会穿透你的整个流水线,看起来与真实数值毫无二致。没有异常,没有日志记录,没有红字。唯一的错误信号是某个恰好知道正确答案的人——而使用智能体的初衷本就是为了让人们不必亲自去查。