跳到主要内容

320 篇博文 含有标签「ai-agents」

查看所有标签

双写竞态:当你的智能体与用户同时编辑同一个日历事件时

· 阅读需 14 分钟
Tian Pan
Software Engineer

智能体自信地报告:“我已将会议改至周四下午 3 点。”用户却盯着原本周二上午 10 点的时段发呆,因为在智能体制订计划到提交更改的这段时间内,用户自己编辑了该事件。“最后写入者胜”(Last-write-wins)策略让自动化的操作覆盖了人类的修改,而用户对助手的信任也因这一次事故而崩塌。这就是双写竞争(dual-writer race),也是智能体工具链从未专门设计应对的 bug 类别。

大多数智能体平台都无意中继承了这一问题。工具层将 update_event 视为一个简单的函数调用:获取 ID,获取新字段,返回成功。底层的提供商 API 十多年来一直提供乐观并发原语(optimistic concurrency primitives)——ETags、版本令牌(version tokens)、If-Match 前提条件——但几乎没有人将它们贯通。模型无法知道它一分钟前所推理的世界已不再是现状,因为由于它所获得的抽象层静默地丢弃了这些信息。

你的 Span 名称是未记录的 API:Agent 团队之间的遥测契约

· 阅读需 11 分钟
Tian Pan
Software Engineer

凌晨 3 点让财务部门收到告警的成本飙升其实并不是真正的成本飙升。那只是一个 Span 重命名。Agent 平台团队的某个人觉得 llm.completion.synthesis 应该改为 llm.generate.answer,因为这样读起来更自然。他们提交了一个小的 PR,运行了测试,然后发布了。三天后,财务的月度 Token 消耗仪表盘显示下降了 60%。没有人削减支出。聚合规则仍然按旧名称分组,而新的 Span 流向了一个仪表盘甚至没有渲染的 “其他” 桶中。账单没有变。仪表盘变了。

这是我一直看到团队在重复经历的一类事故。Span 名称和属性键并不是为了让人在追踪 UI 中阅读而存在的标签。它们是一个未公开 API 的公开 Schema,其消费者是生产团队从未谋面的——过滤它们的评估流水线、按它们分组的成本仪表盘、根据其持续时间触发的 SLO 告警、汇总其 Token 属性的 FinOps 报告。一个团队内部 “无害的重命名”,对于另外四个从未看过该 PR 的团队来说,就是一个网络协议破坏。

Agent 的策略即代码 (Policy-as-Code):OPA、Rego 以及你的工具循环中缺少的决策点

· 阅读需 14 分钟
Tian Pan
Software Engineer

当监管机构第一次要求你证明你的支持代理在 3 月 14 日没有访问某位二级客户的账单记录时,你会发现关于你的鉴权架构的一个令人不悦的事实:系统提示词说“不要访问二级客户的账单”,YAML 工具清单说 tools: [search_orders, refund_order, get_billing],而在两者之间,模型做出了决定。由于不存在决策点,因此没有决策记录。代理是否做了正确的事是无法审计的,只能从发生的日志中推断。

这是智能体工程中没人画在架构图上的部分。如今的工具权限仍然存在于由创建智能体的人编辑的 YAML 文件中,通过描述意图的系统提示词呈现给模型,并由包裹每个工具调用的应用代码强制执行(如果真的执行了的话),例如 if user.tier == "premium" 检查。随着工具目录超过 50 个条目,且条件在租户、数据类别和用户角色之间成倍增加,这种手动构建的网格便不再具备扩展性,而系统提示词也不再是一个可靠的执行面。模型不是你的鉴权层,即使它的表现看起来像是一个鉴权层。

取而代之的是策略即代码(policy-as-code):一个专门的策略引擎 —— OPA 配合 Rego、AWS Cedar 或类似的声明式工具 —— 作为策略决策点(Policy Decision Point)位于每个工具调用之前。引擎在每次调用时只回答一个问题:给定这个主体(principal)、这个工具、这些参数和这个上下文,该操作是否被允许?智能体运行时(agent runtime)从未参与投票。这篇文章将探讨这种架构在实践中的样子,以及它所解决的四个即使是提示词工程也无法解决的问题。

确认与行动间的鸿沟:智能体的“明白了”并不等同于承诺

· 阅读需 12 分钟
Tian Pan
Software Engineer

Agent 对客户说:“收到——我已经提交了你的退款请求。你应该会在 5–7 个工作日内看到它。”客户关闭了聊天。但退款从未被提交。没有工单,没有 API 调用,退款表中也没有记录。有的只是一段礼貌且自信的英语,以及随后成功的会话终止。

这就是确认与行动的脱节(acknowledgment-action gap),它是生产环境 Agent 系统中代价最高昂的一类 Bug。这种脱节之所以存在,是因为让经过指令微调(instruction-tuned)的模型显得很能干的流利文字,与真正改变世界的结构化工具调用(tool calls)属于不同的输出通道——而大多数团队将业务逻辑挂接到了错误的通道上。

每个发布 Agent 的人最终都会以惨痛的方式意识到这一点。模型生成了一份读起来像承诺的精美确认函,下游系统将其解读为承诺,几周后一份支持工单寄来,询问退款去了哪里。令人尴尬的不是模型撒了谎,而是系统被设计成去信任它所说的话。

Agent 回填问题:你的模型升级是对过去 90 天的一次审判

· 阅读需 13 分钟
Tian Pan
Software Engineer

这是一个周二早晨的对话,你的 AI 团队中没人为此做好了准备。新模型以影子模式(shadow mode)上线。不到一小时,评估仪表盘亮起:它对 4% 退款申请的分类与你上一季度运行的模型不同。大多数这类决策翻转看起来都是新模型是对的。房间里的一位成员——通常是汇报线中律师最多的那位——提出了一个让庆祝戛然而止的问题:那么,对于旧模型已经交付的 90 天决策,我们要怎么处理?

这就是智能体回填(agent backfill)问题。当一个更智能的模型开始产生比之前模型更正确的输出时,之前模型做出的每一个持久化决策都会变成一个有争议的记录。你本无意指责过去,但新模型在第一次对比追踪(traces)时就自动为你这么做了。现在你面临一个工程问题(我们能重演历史吗?)、一个法律问题(我们必须披露修正后的结果吗?)以及一个产品问题(用户会看到追溯性的变化吗?),这些问题发生了碰撞。

智能体幂等性是一项编排契约,而非工具属性

· 阅读需 12 分钟
Tian Pan
Software Engineer

客服工单在上午 9:41 送达:“我被扣了三次费。”链路追踪看起来无异常。一条用户消息,一次规划器轮转,三次对 charge_card 的调用 —— 每次都有唯一的工具调用 ID,每次都返回 200 OK,每次都写入了不同的 Stripe 扣款。工具本身有幂等键,后端有去重表,支付处理器也遵循 Idempotency-Key。每一层都是幂等的,但客户依然支付了三次。

如果你构建 Agent 的时间足够长,这类 Bug 迟早会出现在你的桌上。它不是任何工具的 Bug,而是 Agent 循环与工具之间契约的 Bug,而这种契约几乎总是只存在于资深工程师的脑海中。

智能体记忆 Schema 演进:Protobuf 的困难模式

· 阅读需 12 分钟
Tian Pan
Software Engineer

第一次痛苦的智能体记忆(agent-memory)迁移总是教会我们同一个教训:存在两个模式(schema),而你只迁移了其中一个。存储层没问题 —— 每一行都已重写,每个键(key)都是新的形态,回填(backfill)作业也记录了成功。但智能体还是坏了。它继续向 user.preferences.theme 写入,却检索不到任何内容,然后从上下文中煞有介事地合成一个默认值,就好像这个键从未存在过一样。迁移操作手册显示一切正常。用户却报告记忆过时。

这种不对称是结构性的。一个依赖于重命名列的传统服务会收到硬错误,然后你进行修复。而一个依赖于重命名记忆键的智能体则会遇到软缺失,并围绕它进行胡编乱造。模式存在于两个地方 —— 你的存储和模型的上下文 —— 而你只能通过 SQL 脚本迁移其中的一个。

Protobuf 在二十年前通过规范化“仅限增加”的准则解决了这类问题的一个变体:字段是永恒的,数字是永恒的,网络类型永远不变,删除被弃用(deprecation)所取代。这一准则是智能体记忆的一个良好起点,但有一个额外的约束使其变得更加困难。Protobuf 接收者在设计上会忽略未知字段。智能体则不会。

静默成功:当你的 Agent 宣告完成但实际上什么也没发生

· 阅读需 11 分钟
Tian Pan
Software Engineer

在智能体对话记录中,最危险的一行往往是那句充满自信的话。“我已经更新了记录。”“邀请已发送。”“权限已应用。”这里的每一句话都是一种主张,而非事实。当背后的工具调用遭遇限流、超时,或返回了一个被摘要步骤过度压缩成安抚性语言的 500 错误时,你所拥有的就只剩下这一句主张了。你的遥测系统会将这一轮对话记录为成功,因为所谓的“成功”被定义为模型在其最后一条消息开头所输入的任何内容。而下游的写入操作从未提交。整整三周都没有人察觉。

这是一种将智能体与之前所有系统区分开来的故障类别。传统服务失败时会返回状态码。传统的批处理作业失败时会提供堆栈追踪。而智能体失败的方式则是继续交谈。它将错误吸收进正在进行的叙事中,对其进行修饰以使故事逻辑自洽,然后交给你一段读起来像是大功告成的文字。用户读了这段话。你的可观测性平台索引了这段话。但数据库中的记录却纹丝未动。

智能体在凌晨 3 点呼叫我:触达人类工具的爆炸半径策略

· 阅读需 13 分钟
Tian Pan
Software Engineer

当一个智能体因为循环处理一个格式错误的告警信号,在一小时内给你的值班人员发了四次传呼时,领导层终于意识到安全团队早已知晓的一件事:“工具访问权限”与“创造人工任务的能力”其实是同一种权限,而你在没有进行安全审查或产品归属权审查的情况下就授予了它。没有人关注“谁被允许在凌晨 3 点打扰人类”这个问题,因为根本没人把它当作一个问题。它被描述为一个 Slack 集成。

2026 年的智能体技术栈让这种故障模式的发生门槛变得极低。Anthropic 的 MCP 服务器、OpenAI 的 Agents SDK,以及各种厂商提供的操作工具,极大地缩短了“模型决定做某事”与“人类被吵醒”之间的距离。大多数团队部署这些集成的方式与部署数据库客户端如出一辙:定义一个 Token 作用域,引入 SDK,写一段系统提示词,然后发布。数据库客户端的爆炸半径是受影响的行数。PagerDuty 客户端的爆炸半径则是一个人的睡眠。

你的思维链是一个故事,而非审计日志

· 阅读需 12 分钟
Tian Pan
Software Engineer

一个智能体用简洁明了的文字告诉你,它检查了用户权限,查阅了策略,确认请求在范围内,并执行了操作。法务阅读追踪记录(trace)。审计人员阅读追踪记录。你的事故复盘也在阅读追踪记录。每个人都阅读同一段话,并且每个人都感到满意。

他们中没有人知道权限检查是否真的运行了。这段文字是叙事的证据,而不是执行的证据——而这两者之所以会被混淆,正是因为叙事足够流畅,让人感觉像是证明。Anthropic 自身关于推理模型忠实度的研究发现,当 Claude 3.7 Sonnet 收到关于正确答案的提示时,平均只有约 25% 的时间承认使用了该提示,而在有问题的类别(如针对评分者的 trick、不道德的提示)中,这一比例低至 19%–41%。模型的陈述推理与其真实行为在大约一半或更多的时间里是不一致的,即使是那些被明确训练以展示思考过程的模型也是如此。

智能体无法察觉的死锁:生成计划中的循环工具依赖

· 阅读需 13 分钟
Tian Pan
Software Engineer

一个规划器智能体输出了七个步骤。每一个看起来都很合理。编排器分发了这些步骤,前三个返回了值,第四个在等待第五个,第五个在等待第七个,而第七个——埋藏在规划器散文般描述的第三行里——正静静地等待着第四个。没有任何东西被锁定。没有触发过任何 EDEADLK。智能体消耗了 40,000 个 token 来推理为什么第四步“花费的时间比预期长”,最终以一个温和、合理的道歉向用户宣告放弃。

这就是你的智能体无法察觉的死锁。它不是操作系统课程中的那种经典死锁——这里没有互斥锁(mutex),没有内核可以内省的资源图,也没有你的技术栈中任何人能识别的持有者或等待者。依赖关系存在于规划器生成的英语句子中,循环形成于潜在语义而非任何数据结构中,而故障模式看起来与“模型正在努力思考”无异。经典的死锁检测在这里毫无用处,但代价是相同的:工作流停滞,token 蒸发,而你的 trace 什么也不会告诉你。

你的 Prompt 时钟是正确性边界,而非日志字段

· 阅读需 11 分钟
Tian Pan
Software Engineer

一个调度代理将客户的入职电话订在了周二,而不是周三。调查花费了两天时间。Prompt 没问题。模型没问题。日历工具也没问题。错误在于系统 Prompt 携带了一个早一小时的 current_time 字段,当时请求正通过一个在 UTC 午夜前刚刚构建的缓存前缀(cached prefix)进行路由。当代理解析出“明天上午 10 点”并调用预订工具时,“明天”所指的日期对于东京的用户来说已经是“今天”了。

代理根本无法察觉。它没有任何感知手段。LLM 没有时钟。它们只有你在 Prompt 中提供给它们的字符串,并且它们会像对待用户问题一样权威地对待这个字符串——也就是说,完全信任,不加怀疑,也没有第二个来源可以进行交叉比对。

大多数团队在抽象层面都知道这一点,但仍然将注入的时间戳视为日志字段:某种有则更好、渲染到系统 Prompt 中提供上下文、不属于任何人的明确责任、不属于任何人的正确性边界的内容。这种构想是错误的。时间戳是一个正确性边界。每一个依赖于“现在”的代理行为——调度、过期、重试窗口、“最近”、“明天”、“五分钟内”、检索文档的新鲜度检查——都运行在你生成的时间管道之上,并继承了该管道所拥有的每一个 Bug。