跳到主要内容

202 篇博文 含有标签「agents」

查看所有标签

代理墙钟预算:一场与工具超时机制的赛跑

· 阅读需 12 分钟
Tian Pan
Software Engineer

有一种 Agent 漏洞,当你孤立地观察任何单个组件时,它都不会出现。模型没问题,工具没问题,重试策略也没问题。纸面上的超时值甚至可以说很慷慨。然而,一个通常在 8 秒内完成的工具,却总是在一个已经在 7.9 秒时将其宣告为失败的 Agent 面前折戟。Agent 围绕一个从未发生过的“错误”重新规划,并启动了第二次调用,而第一次调用的结果即将与其发生碰撞。

漏洞不在任何一个框框里。它存在于两个没人同意应该同步的时钟之间的缝隙中。

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

· 阅读需 11 分钟
Tian Pan
Software Engineer

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

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

那些你的团队遗忘在后台且正使用生产环境凭据运行的 MCP 服务器

· 阅读需 12 分钟
Tian Pan
Software Engineer

周一,一位新工程师加入了团队。到周三时,她已经搭建好了本地 Agent 环境:一个桥接到公司部署 API 的 MCP 服务,指向 Staging 环境,并与她的编辑器相连。入职文档引导她完成了 OAuth 流程。她粘贴到服务器环境文件中的令牌是同事发给她的——这与 CI 流水线用于发布到 Staging 环境的是同一个令牌。到周五,她已在共享办公空间与团队一起进行协作开发。

MCP 服务仍在运行。绑定在 127.0.0.1。无需身份验证。令牌已加载到进程中。她没多想,因为她并没在使用它。但那天访问任何网站的任何标签页,都可以通过她自己的浏览器与她的本地服务通信。共享办公空间 Wi-Fi 上的任何其他笔记本电脑也同样可以,因为她没注意到该服务实际上绑定到了 0.0.0.0。你的 CI 流水线用来推送到 Staging 的 OAuth 令牌,现在任何能诱导浏览器向本地 IP 发起请求的人都能触及——在 2026 年,这只需要一个弹窗。

本文讨论的就是这类故障:“我在笔记本上开发”与“我的笔记本是对手可以触及的服务器”之间的鸿沟。MCP 服务在设计上恰好处于这个鸿沟之中。大多数团队尚未察觉。

供应商将你的模型标识符重定向到特定租户的微调模型,而其他人使用的却是基础模型

· 阅读需 12 分钟
Tian Pan
Software Engineer

客户支持团队升级了一个问题:“你的助手以前能正确处理退款资格问题。但上周开始出错了。”值班工程师调取了对话记录,在开发账号中使用相同的模型标识符回放了完全相同的提示词(prompt),得到了正确的回答,于是以“无法复现”为由关闭了工单。两周后,另一名客户提出了同样的投诉。工程师再次在同一个开发账号中进行回放,结果依然正确。团队开始归咎于没人做过的提示词更改。

请求中的模型标识符从未改变。响应字段中的字符串与请求字段中的字符串匹配。评估套件在六周内一直保持绿色。生产流量使用的模型权重与评估套件使用的模型权重是两套不同的集合,而且在该账号的整个生命周期中一直如此——直到过去这六周,它们变成了同一套权重,而团队注意到这一点仅仅是因为客户先发现了。

当 On-Behalf-Of 悄然变成 Act-As:你的智能体继承的 OAuth 作用域陷阱

· 阅读需 10 分钟
Tian Pan
Software Engineer

安全审查称智能体(Agent)“代表”(on behalf of)用户操作。OAuth 令牌却有不同的说法,而审计日志也支持令牌的说法。

语言上的一点微小差别,在架构层面起到了意想不到的作用。“代表”(On behalf of)是安全审查在试图描述一种委派安排时使用的语言,即智能体是一个可识别的委派对象,并受此身份约束。“作为”(Act as)则是运行时的行为,此时智能体持有的令牌与用户本人的令牌完全一致,因此在任何下游系统看来,智能体就是用户。这两个短语描述了完全不同的威胁模型。典型的企业级 OAuth 集成交付的是后者,但宣传的却是前者。

那个保护了日志却让模型泄露输出结果的 PII 脱敏器

· 阅读需 13 分钟
Tian Pan
Software Engineer

一个仅针对入站流量运行的 PII 脱敏器就像是安装在管道错误一端的单向阀。它在用户提交的姓名、电子邮件和账号进入日志之前拦截它们。但它对模型的输出无能为力 —— 而现在,模型正是在输出端积极地组合可能包含这些相同标识符的文本,这些内容可能源自 RAG 检索、工具返回、对话历史或用户从另一个租户数据中粘贴的内容。我观察过每一个上线了输入端脱敏器的团队,他们的待办事项中都有一个标记为“输出端对齐”的后续任务。大多数这类任务永远不会关闭,因为在长达六个月的时间里,没有任何事故暴露出这个缺口。六个月后,该任务经过多次重新排序,看起来更像是一个功能需求,而不是缺失的一半安全控制手段。

失败模式是恒定的:输入端脱敏被视为标准的控制手段,因为它的工程问题更简单,审计故事也更容易讲。你编写了一套正则表达式,运行了一个标注好的基准测试,证明了在固定语料库上的精确率和召回率,在特性开关后上线了它,安全评审也将其接受为 PII 边界。输出端则完全没有这些优势。模型的响应是生成性的,表面积是无限的,而且测试方法论 —— “在无限多的上下文中它不应该说什么” —— 在结构上比“我们应该从已知输入中剥离什么”要困难得多。因此,上线入口端的团队将出口端视为未来的工作,而这个未来永远不会到来,直到有客户举报另一个客户的电子邮件出现在他们的对话记录中。

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

· 阅读需 11 分钟
Tian Pan
Software Engineer

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

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

被反向代理剥离的 SSE Keep-Alive,以及你支付了两次费用的 Prompt

· 阅读需 11 分钟
Tian Pan
Software Engineer

你的 Agent 调用了一个耗时 35 秒的工具。在这 35 秒内,没有任何 Token 从模型流回浏览器。Provider 的 SSE 流仍然开启。你的工具仍在运行。用户的加载动画也在旋转。而在路径中间的某个你无法控制的反向代理认为连接静默时间过长,关闭了它,随后你的客户端重连逻辑尽职地从头重新启动了整个请求。

第一次响应产生了 4,200 个 Prompt Token 和 600 个 Completion Token。第二次响应也是 4,200 个 Prompt Token 和 600 个 Completion Token。用户得到了一个答案。而你的账单却收到了两份。

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

· 阅读需 10 分钟
Tian Pan
Software Engineer

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

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

那个‘总结’掉用户原始提问的压缩策略

· 阅读需 12 分钟
Tian Pan
Software Engineer

一位用户询问我们的支持智能体:“为什么发票 INV-2025-08-44719 在 4 月 3 日被扣了两次款?”45 分钟和 18 次工具调用之后,智能体自信地回复道:该账户在该季度没有任何重复计费的证据。用户理所当然地投诉了。当我们回放追踪记录(trace)时,答案变得显而易见。智能体在第九轮对话时对内容进行了压缩(compacted)。摘要显示用户“在询问 4 月初的一笔重复收费”。其中并不包含字符串 “INV-2025-08-44719”。随后的每一次工具调用——账本查询、退款 API 查询、审计日志扫描——都是针对转述后的意图发出的,而不是用户输入的字面意义上的发票编号。

Bug 不在工具里,也不在模型的推理中。问题在于我们的上下文管理器(context manager)与每个下游组件之间都有一个契约,但没有人把它写下来。契约写着:“我会保留语义。”而组件需要的是:“我会保留字符串。”

抹除后续问题所需上下文的对话记忆修剪启发法

· 阅读需 10 分钟
Tian Pan
Software Engineer

一个用户打开你的长会话智能体(agent),在第 3 轮对话时说:“我是素食主义者,而且预算有限。”对话继续进行。11 轮之后,裁剪器(pruner)开始运行。它计算 Token 数量,发现第 3 轮内容既陈旧又短小,于是为了将窗口维持在预算范围内,将其丢弃了。第 14 轮用户问:“我今晚该做点什么菜?”模型查看一个约束条件已不存在的窗口,推荐了一份 40 美元的肋眼牛排。用户觉得智能体变得越来越难用,打开满意度调查,给这次会话打了一个 2 分。

!["https://opengraph-image.blockeden.xyz/api/og-tianpan-co?title=%E6%8A%B9%E6%8E%89%E4%B8%8B%E4%B8%80%E8%BD%AE%E9%97%AE%E9%A2%98%E6%89%80%E9%9C%80%E4%B8%8A%E4%B8%8B%E6%96%87%E7%9A%84%E5%AF%B9%E8%AF%9D%E8%AE%B0%E5%BF%86%E8%A3%81%E5%89%AA%E5%90%AF%E5%8F%91%E6%B3%95"]辨

你的技术栈中没有任何环节会报告记忆失败。Token 预算仪表盘会显示窗口健康地维持在上限之下。延迟仪表盘会显示绿色。评估套件——将单轮回答与预留集进行对比评分——会报告没有退化。唯一能体现智能体能力下降的信号是一个差评,而你的产品团队会将其归咎于“模型方差”。但这并不是模型方差,而是裁剪启发法在错误的目标上,精准地执行了它被调优后该做的任务。

会话摘要抹掉了用户授予你的同意标志

· 阅读需 12 分钟
Tian Pan
Software Engineer

在第 3 轮,你的用户点击了“不要保留我的代码”。在第 7 轮,他们关闭了“使用我的对话来改进模型”。在第 12 轮,他们选择了退出跨会话记忆。到第 40 轮时,你的上下文预算耗尽了。压缩过程将第 1-30 轮折叠成一个简洁的 200 token 摘要,读起来非常顺畅:它捕捉了用户的提问、你的智能体做了什么以及结果如何。在第 41 轮,你的智能体——带着那份摘要和最近的 10 轮对话——自信地将用户的代码写入了一个用户在第 7 轮就已经选择退出的存储库中。

你的审计日志现在包含一个在 t=3 时的授权事件,一个在 t=41 时的违规操作,而两者之间是一段没有任何字段说明为什么该操作被允许的文本。摘要生成器经过训练是为了压缩对话,而不是为了转发控制状态。没有人告诉它那个授权开关是承重的(load-bearing)。也没人能告诉它,因为授权信息并不在对话中——它存在于对话旁的一个结构化字段里,而这个结构化字段没能熬过摘要化的过程。