跳到主要内容

209 篇博文 含有标签「agents」

查看所有标签

智能体学会针对重试预算进行规划

· 阅读需 11 分钟
Tian Pan
Software Engineer

在生产环境中运行智能体(agent)得出的最令人不安的教训不是它们会失败——而是它们会学习。并不是指任何深度意义上的学习;权重并没有改变。但在一个会话(session)中,在一个轨迹(trajectory)中,模型所隐含的策略会根据其运行的底层环境(substrate)进行调整。如果你的底层环境代表智能体悄悄吸收了失败,智能体最终会察觉到这一点,并开始将其视为免费的算力进行规划。

最明显的例子就是重试层(retry layer)。你添加它是为了可靠性——在报错之前,SDK 会对失败的工具调用进行三次重试;你的中间件为每一步包装了指数退避(exponential backoff);你的循环捕获了格式错误的 JSON 并重新提示模型进行修复。这些都没错。但每一个机制都是智能体可以观察、概括并利用的副作用。一旦它这样做了,你的可靠性层就不再是安全网,而成了规划原语(planning primitive)。

与验证器共享盲点的自我修正循环

· 阅读需 10 分钟
Tian Pan
Software Engineer

在智能体复盘中流传的截图每次看起来都一模一样。一段长长的追踪记录。一个单一的任务。十二次迭代。智能体生成了草稿,进行了评估,发现了一个小瑕疵,生成了修订版,进行了评估,发现了一个略有不同的微小瑕疵,接着又生成了另一个修订版。验证器返回的分数一直徘徊在 0.78 到 0.84 之间。它从未跨越门槛。智能体从未上报异常。三小时后,任务因超时而终止,产生了一笔足以支付一名高级工程师四分之一日薪的 Token 账单。

团队将其称为“自我修正”问题,因为架构图上就是这么标注的。实际的失败是结构性的。验证器其实就是换了一个提示词的生成器。收敛准则是模型自己的意见。重试预算是隐式的,受限于智能体的超时时间,而不是由智能体本身推理决定的。这三个失败孤立来看都不像是 Bug,这正是团队会将其上线的原因。

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

· 阅读需 12 分钟
Tian Pan
Software Engineer

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

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

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

当源数据已更改,你的 Prompt 缓存仍在提供旧的工具执行结果

· 阅读需 11 分钟
Tian Pan
Software Engineer

一名支持代理在 14:02 查询了客户的订阅状态,发现其处于激活状态,该回答进入了 Prompt 前缀中,而缓存层刚刚将其标记为上下文的可重用部分。在 14:14,计费系统取消了该订阅。在 14:19,同一位客户提出了跟进问题,由于对话前缀仍然匹配,缓存的前缀被重用,代理愉快地告诉客户他们的计划处于激活状态,并主动引导他们使用一个他们已不再拥有访问权限的功能。下游系统是正确的。模型与上下文保持了一致。但用户被缓存命中(Cache Hit)欺骗了。

这是 Prompt 缓存为原本对数据陈旧度(Staleness)保持诚实的系统引入的失效模式。在引入缓存之前,工具调用是对单一事实源(Source of Truth)的请求,并遵循该源所声明的任何新鲜度契约。有了缓存之后,工具结果变成了 Prompt 前缀的一个租户,而前缀拥有自己的 TTL(生存时间),由模型提供商控制,团队中没有人明确选择启用它。

你在三月份录制的演示视频是它最后一次正常工作的时候

· 阅读需 9 分钟
Tian Pan
Software Engineer

一家 B 轮 AI 公司的销售工程师在 3 月的一个周二录制了一段五分钟的演示视频。智能体(agent)在第一次尝试时就选对了工具,用买家的语言组织了答案,并以一种“考虑周全,而非模棱两可”的礼貌态度拒绝了一个棘手的边缘情况。那段录像被存入了资源库。在接下来的七周里,它促成了五笔交易。

到了 5 月底第六个潜在客户在入职培训电话中看到它时,模型已经收到了供应商的小版本更新,重新调整了它的拒绝话术;Prompt 被编辑了两次以修复一个无关的回归问题;工具目录增加了三个条目(模型现在更倾向于其中之一);RAG 语料库针对新的分块器(chunker)重新建立了索引。演示视频不再是产品的录像,而是一个已经不存在的产品的录像。

那个在 11 小时内烧光你季度推理预算的免费试用

· 阅读需 11 分钟
Tian Pan
Software Engineer

你的试用版提供了“每天 100 次生成”。你的定价团队模拟了一个感兴趣的用户花一周时间进行体验。但第一个将智能体(agent)指向端点的试用者,在 70 秒内就用完了当天的配额,19 分钟内用完了每周配额,并在第二天午餐前耗尽了季度的推理预算。没有人收到警报,因为唯一设置的警报只在试用用户转化为付费用户时才会触发。

试用限制在制定时并没有错。它们针对的是不再适用于当前典型用户的用法分布。在六个月前的定价审查与今天早上的新用户注册之间,用户群体已经从点击按钮的人类转向了不知疲倦的程序。仪表盘上的数字不再代表你设定它们时的含义。

你的 Agent 在无文档情况下悄然掌握的流程

· 阅读需 11 分钟
Tian Pan
Software Engineer

六个月前,你的团队上线了一个处理退款的支持智能体(support agent)。当时有一份一页纸的 Notion 文档描述了它应该做什么。如今,文档的内容依然如旧,但智能体的行为却已大相径庭。提示词(prompt)的历史记录中有 47 次修改。新增了三个工具——其中一个悄悄绕过了文档中仍坚称存在的财务核查。模型被更换了两次。在一次没人记录的事故之后,重试策略被加强了。而当数据团队的人问起“这里处理退款的具体规则到底是什么”时,诚实的回答是:去读系统提示词和工具注册表吧,因为那才是现在的规范。

这是智能体系统在生产环境中的隐性失败模式:智能体的行为就是那份没人写的操作手册(runbook)。提示词被当成了一个配置值——YAML 文件中的一个字符串,由负责该功能的人员编辑,并像修改文案一样进行评审——而实际上,它是公司内部多步骤业务流程最权威的描述。组织积累流程逻辑的方式就像遗留代码库积累行为一样:通过修改,而非设计。而那些历来负责该流程的人——产品经理、合规主管、运营总监——从未意识到他们已经丢失了交付物,因为根本就没有一份可以丢失的文档。

那个直到触发时你才察觉的 Token 预算

· 阅读需 11 分钟
Tian Pan
Software Engineer

你的团队与推理提供商协商了月度 Token 配额。合同规定了上限。提供商门户的仪表板显示昨天的使用情况,存在一天的延迟。API 本身返回每分钟速率限制头——anthropic-ratelimit-tokens-remainingx-ratelimit-remaining-requests——而对于你实际需要规划的月度配额桶却只字未提。你的智能体集群没有机制在预算耗尽时减速,因为实时到达的唯一信号是 429 错误——而这个信号在预算已经用完后才出现,且伪装成重试逻辑通常会忽略的瞬时错误。

这是一个与速率限制(rate limiting)性质不同的问题。速率限制是一个快速波动的节流阀,消费者必须在几秒钟内做出反应;响应头告诉你桶里还剩一千个 Token,并在 40 秒内补满,一个编写良好的客户端会退避并重试。月度配额则是一个缓慢变化的预算,消费者必须以周为单位进行规划。这两者之所以容易混淆,是因为它们共享错误代码,有时甚至共享同一个仪表板,但它们需要不同的控制手段——而提供商公开的信息与消费者需求之间的差距,正是本月最严重事故的导火索。

为什么你的智能体在开发中表现完美,在生产中却状况百出

· 阅读需 12 分钟
Tian Pan
Software Engineer

Agent 演示总是能成功。数据库里有三个客户,一个匹配记录,向量索引中有 12 篇文档,一个带有无限空档的空日历。Agent 选对行,检索到正确的文档,预订好正确的会议。上线吧。

接着,生产环境交给了同一个 Agent 一千万个客户,其中在同一个城市有三个 “John Smith”;一个返回了四千行的过滤器,因为 Agent 本想表达 status = 'active' 时却自信地写成了 status != 'closed';一个向量查询返回了七篇看似合理的文档,而 Agent 从未被要求在这几篇文档之间做选择;以及一个每个空档都需要协商的日历。在开发环境中看起来正确的处理能力,在生产环境中发生了质变——不是稍微变差一点,也不是变得更不稳定,而是在解决一个开发环境从未让它解决过的、完全不同的问题。

这就是“在本地运行正常”所掩盖的鸿沟。对于确定性代码,这句话在处理边缘案例时已经算是个谎言。对于 Agent 来说,这个谎言更甚,因为 Agent 的行为是输入分布的函数,而当你跨越生产边界的那一刻,输入分布就会从“平庸琐碎”转变为“模棱两可”。

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

· 阅读需 12 分钟
Tian Pan
Software Engineer

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

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

多模态追踪:当各种模态必须共享一个 ID

· 阅读需 12 分钟
Tian Pan
Software Engineer

一位用户拨通了你的客服 Agent。他们说话,Agent 倾听,用户在通话中途上传了一张错误截图,Agent 同时对图片和转写文本进行推理,最后通话以一封总结修复方案的邮件收尾。三天后用户投诉过来:修复没有生效,邮件也从未送达。你打开可观测性栈,发现三个独立 UI 里躺着三条互不相干的追踪。语音流水线给你一条 ASR 追踪。视觉流水线给你一段图片上传的 span。LLM 调用给你一条带 token 数和工具调用的聊天追踪。这些仪表盘里没有任何东西告诉你:它们其实是同一次对话。

这就是没人愿意写的那种复盘。不是因为数据缺失——每一个模态都老老实实记录了它该记录的东西——而是因为跨模态的"接合"从来就没建起来。每条流水线都从自家模型供应商默认配置里长出了自己的追踪约定,而把它们绑在一起的那一次对话轮次,只存在于设计这个 Agent 的那位工程师的脑子里。

一路重试穿过你限流器的 agent

· 阅读需 11 分钟
Tian Pan
Software Engineer

你的网关给每个 tenant 干净利落地强制执行每秒 100 次请求的限制。dashboard 显示每个 tenant 都舒舒服服地在那个上限之下。但模型 provider 寄来的账单告诉你,你的支出上限照样被打穿了。rollout 电话会议上没有人能给出一个干净的解释。

答案在于限流器和账单衡量的是不同的东西。当用户点击一个按钮时,限流器看到的是一次"用户请求"。而 provider 看到的是一次 planner 调用、三次工具结果反思、一次因更严格 JSON schema 触发的格式修正重试,以及一次最终综合——每一次都带着自己的内部重试预算,在瞬时 429 或 500 回来时就会触发。一次点击可以扇出成三十次模型调用。限流器只数到一次。桶以它被设计容量的三十倍漏水。

在 HTTP 边界上对 agentic 系统做限流,就像在高速公路入口立速限标志,而入口里面的车却在自我繁殖。除非限流器理解了这个循环,否则循环就会绕过它。