跳到主要内容

130 篇博文 含有标签「agents」

查看所有标签

工具重入:你的函数调用层尚未察觉的 Bug 类别

· 阅读需 13 分钟
Tian Pan
Software Engineer

智能体用 400 毫秒回答了一个简单的问题,然后因递归限制错误(recursion-limit error)崩溃。Trace 显示了 25 次工具调用。从上到下阅读 Trace,工程师会得出结论:智能体糊涂了 —— 以略有不同的顺序反复调用那几个工具,始终无法收敛。这个结论是错误的。智能体并没有糊涂。它陷入了一个死循环:工具 A 调用了模型,模型选择了工具 B,工具 B 的实现再次调用模型来格式化其输出,而格式化程序又选择了工具 A。Trace UI 将四个嵌套调用渲染为扁平列表中的四个兄弟调用,导致唯一能发现问题的开发者也无法察觉这个循环。

这就是工具重入(tool reentrancy),这是一种你的函数调用层几乎肯定没有建模的 Bug 类别。并发安全的代码对此已有数十年的原语支持:记录同一线程嵌套获取次数的重入互斥锁(reentrant mutexes)、语言层面的递归限制、堆栈检查 API,以及一种文化共识:任何回调运行时的函数都需要一个明确的契约,规定允许何种重入。工具调用层默认采用“发后即忘”(fire-and-forget)模式。运行时没有可供检查的调用栈,调度前没有循环检测器,工具定义上没有重入属性,Trace UI 的形式像日志而非图。结果就是,任何超过十几个条目的工具目录都会悄悄变成框架无法察觉的递归。

你的工具结果缓存是一份你从未签署过的过期数据契约

· 阅读需 12 分钟
Tian Pan
Software Engineer

追踪记录看起来很干净。Agent 调用了 get_inventory_status,工具返回了 {"available": 142, "warehouse": "SEA-3"},模型将这些信息编织成了一个自信的回答。客户下单了。仓库却说该商品自上午 9 点以来一直缺货。缓存的行数据是四小时前的。团队中没人决定过四小时是可接受的 —— 这只是平台团队连接包装器(wrapper)时,缓存框架默认的设置。

这种失效模式经常被误归类为幻觉。模型并没有在胡编乱造;它是在忠实地根据一个过期的工具结果进行推理,而没人费心将该结果标记为过期。追踪记录显示的是一次干净的调用和干净的响应,评估集(eval set)从未见过过期缓存的情况,而这种退化在每一个撞上相同 TTL 窗口的客户身上悄无声息地累积。

工具 Schema 是提示词,而非 API 合约

· 阅读需 12 分钟
Tian Pan
Software Engineer

你智能体代码库中最昂贵的一行,就是从现有的 OpenAPI 规范中自动生成工具 Schema(tool schemas)的那一行。这看起来是一个利落的工程选择——单一事实来源、无重复、每次 API 变更时自动同步。但这也是为什么你的智能体在应该选择 searchUsersV3 时却选择了 searchUsersV2,因为你的规范示例中写了 limit=20 于是它就填了 20,并且悄无声息地丢掉了 tenant_id,因为它被埋在第七个参数槽位里。

单元测试中不会显现任何这类迹象。Schema 是有效的。端点(endpoint)是存在的。智能体的调用是格式正确的 JSON。然而,模型每次都会用错工具,且是以你的 QA 流水线永远察觉不到的方式,因为 QA 测试的是 API,而不是智能体对 API 的理解。

这个 Bug 是观念性的。OpenAPI 的设计初衷是向编写 SDK 代码的人类描述 API;而工具 Schema 则是作为 Prompt 的一部分,在每次调用时由 LLM 读取。将它们视为同一种产物,就好比根据数据库列名自动生成面向用户的文案一样,犯了同类型的错误。

智能体完成任务时房间已空:异步后台任务中的过时上下文交付

· 阅读需 10 分钟
Tian Pan
Software Engineer

一个需要 90 秒才能完成任务的后台智能体,其操作基于的是 90 秒前的世界快照。当它返回结果时,用户可能已经导航到了不同的视图,开始了一个新的对话,归档了原始请求,或者完全关闭了标签页。大多数智能体框架无论如何都会交付结果,修改状态以反映结果,并将这次往返视为成功。但这并不是成功。这是智能体在一间空屋子中结束。

这种失败模式比直接丢弃结果更糟糕。丢弃结果只是一次投递失败——虽然烦人但可以恢复。而应用陈旧的结果则是对一个用户不再提出的问题的回答,它是针对不再匹配的状态编写的,往往会覆盖用户已经开始的新工作。用户会注意到发生了他们没有要求的事情,却无法重构原因,从而对系统失去信任,这种信任损失是简单的超时永远不会造成的。

解决办法不是更快的智能体,而是一个交付时的相关性门控,它将返回的时刻视为一个全新的决定,而不是派发时刻预设的定论。

Agent 飞行记录仪:在第一次事故发生前必须捕获的字段

· 阅读需 14 分钟
Tian Pan
Software Engineer

当 agent 在生产环境中第一次失控时——它删错了行,给错误的客户发了邮件,在单个任务上烧掉了 400 美元的推理费用,或者对受监管的用户说了法律风险极高的话——团队打开日志,却发现他们实际上拥有的是:一串参数被截断的 CloudWatch 工具调用名,一个只捕获了最新一轮对话的“用户提示词”字段,而且没有记录实际运行的是哪个模型版本。供应商在两周前滚动更新了别名。系统提示词存在于一个没有快照的配置服务中。由于框架默认值是 0.7 且“人尽皆知”,因此没有记录温度。触发错误操作的工具结果超过了日志行大小限制,并被截断为“...”。

你无法重现决策过程。你只能猜测。六个月后,你堆积了一堆无解的“它为什么这么做”的报告,团队开始像对待天气一样对待 agent——把它当作一种发生在你身上的事情,而不是你可以调试的东西。

飞行记录仪准则(Flight recorder discipline)是你为了防止这种情况所能交付的最廉价的东西,但如果你等到第一次事故发生才开始,它也将是你交付的最昂贵的东西。以下字段是最低要求,存储形式不容商量,采样和隐私边界必须同步设计,而不是事后修补。

30 秒都去哪了:APM 无法察觉的 Agent 步骤内部延迟归因

· 阅读需 13 分钟
Tian Pan
Software Engineer

仪表盘显示 p95 的 agent.run = 28s。用户反馈该功能感觉已经挂了。值班工程师打开 Trace(追踪),看到一个没有任何值得调查的子节点的“肥大”长条,然后开始盲猜。当有人重建出足够的心理模型,搞清楚瓶颈到底是模型、检索器,还是某个没人添加 Span 的工具调用时,故障已经变成了积压的任务单,而用户早已放弃了。

这就是 2026 年 Agent 运营核心的失败模式:传统的 APM 将 Agent 步骤视为一个黑盒,而“Agent 延迟”并不是一个单一指标——它是七个指标的总和,这些指标根据 Agent 在该轮次中的决策,以不同的方式分解实际用时 (Wall-clock time)。如果一个团队不暴露这七个数字,他们交付的功能虽然大家都能感觉到慢,但谁也无法修复。

AI 网络保险:你的智能体会首先发现的保障缺口

· 阅读需 12 分钟
Tian Pan
Software Engineer

一个编程智能体在凌晨 2 点合并了一项变更,导致客户的生产数据库下线了 90 分钟。一个客户服务智能体在循环被终止前,向外发送了 1.4 万封措辞错误的退款拒绝邮件。一个自主对账工作流对 2800 张卡进行了重复扣款。损失是真实的,审计追踪指向了你的公司,你的财务团队针对六周前续签的网络保险保单提出了理赔。保险公司的回复是一封礼貌的信函,解释说该保单涵盖的是“恶意第三方的未经授权访问”和“对员工的社交工程攻击”——而该智能体是经过身份验证的,其行为是经过授权的,且没有员工被欺骗。理赔被拒。损失只能由你的资产负债表承担。

这并非假设性的极端案例。它是未来 18 个月内最典型的理赔画像,保险业深知这一点。网络保险(Cyber)、职业责任险(E&O)和董事高管责任险(D&O)的保单条款是根据一种威胁模型校准的,在该模型中,泄露的严重程度取决于记录外泄的数量,而事故响应则取决于计费的取证小时数。智能体 AI(Agentic AI)产生的事故并非这种形态。它产生的是一种精算师没有任何基准数据可参考的形态,而保险公司在缺乏精算基准时的第一反应,就是将这种风险敞口完全排除在保单之外。

校准弃答:你的 LLM 技术栈每一层都在惩罚的能力

· 阅读需 13 分钟
Tian Pan
Software Engineer

你的模型可以拥有一种能力,在关键时刻,这种能力比你发布的任何其他行为升级都更有价值:能够说“我没有可靠的答案”并且是认真的。不是那种基于关键词匹配的安全拒绝。也不是模型在处理争议性话题时,从 RLHF 中学到的那种模棱两可的坏习惯 (hedging tic)。而是真正的能力——一种经过校准的弃权 (calibrated abstention),仅当且仅当模型的内部证据不支持生成自信的回答时才会触发。

你永远不会偶然获得这种能力。LLM 技术栈中的每一个默认设置都在反向推动。

取消安全的智能体:你的“停止”按钮背后已经产生的副作用

· 阅读需 12 分钟
Tian Pan
Software Engineer

用户点击“停止”,因为智能体(agent)误解了请求。UI 界面闪烁着“已停止”。在加载图标消失时,智能体已经发送了两封邮件,在你的日历上安排了周二的会议,针对错误的分支开启了一个草稿拉取请求(pull request),并排队发送了一条正在通过工具层追赶取消信号的 Slack 消息。模型已经听话地停止了生成 token。但外部世界并未停止对它三十秒前生成的 token 做出反应。

这是智能体演示中没人提及的失败模式。同步代码中的取消操作本身就是一个难题,背后有一整代协作式取消理论的支持:Go contexts、Python 的 asyncio.cancel、带有任务组的结构化并发,以及“礼貌请求、谨慎升级、不留资源”的整套语法。智能体在这个本就困难的问题上又增加了一层复杂性:规划器不知道用户在第 4 步和第 5 步之间撤回了授权,而它在第 4 步启动的工具在第 5 步被取消时也不会收到通知。“停止”只是一个 UI 交互功能。其背后的系统必须经过专门设计。

聊天历史是数据库。别再把它当成滚动回溯了。

· 阅读需 12 分钟
Tian Pan
Software Engineer

针对 Agent 类产品,生产环境下最常见的投诉通常是某种形式的“它忘记了我们刚才说的话”。这种投诉往往出现在第 8 轮、第 15 轮或第 30 轮——绝不会在第 2 轮出现。团队的第一反应往往如出一辙:扩大上下文窗口。但这其实是错误的直觉,因为 Bug 不在模型本身,而在于团队将对话历史视为了终端的滚动回放(scrollback)——追加一行、渲染尾部、满了就截断。实际上,他们不知不觉中构建的是一个读多写少的数据库,具有仅追加写入、热工作集、隐藏在截断规则中的淘汰策略,以及取决于所提问题类型的查询模式。一旦你接受了这一点,整个问题的本质就改变了。

滚动回放模式之所以如此诱人,是因为聊天界面看起来就像一份对话记录。消息向下流动,用户自上而下阅读,而喂给模型的自然方式就是将最新的 N 轮对话拼接到提示词中。这种数据结构感觉是“免费”的:没有 Schema,没有索引,没有查询——只需追加、渲染、重复。在最初的几轮对话中,任何架构都表现良好。模型拥有完整的上下文,费用低廉,演示效果极佳。

确定性预算:将随机性视为按层面的分配,而非全局开关

· 阅读需 12 分钟
Tian Pan
Software Engineer

Temperature 之争是 AI 工程中最具宗教色彩的争论,也是最没效率的争论之一。每个团队都会形成两个阵营:决定论者希望将所有地方的 Temperature 都固定为 0,因为他们无法调试不稳定的系统;而创意论者则希望调高它,因为这样输出结果感觉更“有灵性”。两者都错了,因为他们都在错误的层面上回答这个问题。Temperature 不是一个全局设置。它是一项预算——就像任何预算一样,它应该被分配,而不是被宣告。

高效的框架很简单:系统中每个模型调用都有其目的,随机性要么在那个层面(surface)发挥作用,要么就不该存在。决定下一个调用哪个工具的规划器(planner)无法从变化中获益;选错一个工具就是调试噩梦,而且没有任何创意上的好处。如果一万个用户看到的摘要措辞都一模一样,那么为他们总结搜索结果的响应合成层很快就会显得呆板——SEO 团队最终会标记这些样板内容。一个让模型提出备选方案供人类选择的头脑风暴层,在 Temperature 为 0 时表现反而 更糟;多样性本身就是其核心功能。

如果你无法清晰地说明随机性在特定调用位置的作用,你就不应该为此付费。

Wiki 迎来了第二位租客:为什么面向 AI Agent 的文档与面向人类的文档截然不同

· 阅读需 12 分钟
Tian Pan
Software Engineer

一家中型 SaaS 公司的资深工程师在上个季度花了整整两天时间去排查一个部署 bug,结果发现竟然是智能体的错。该智能体读取了一份最后更新于 2023 年的运行手册(Runbook),忠实地执行了第三步,并运行了一个在当前部署工具中已不再存在的命令。这份运行手册在 Wiki 中依然渲染良好——甚至截图也依然清晰可见——但它已经悄然变得对那些无法察觉环境已过时的读者充满敌意。人类作者完全没意识到,这份文档现在已经成了每个新员工的 AI 助手的关键输入。

这就是过去 18 个月里大多数工程团队中发生的悄然转变:内部 Wiki 累积了第二批受众。同样的 Confluence 页面、同样的架构图、同样的“我们如何部署”的 Gist,现在正由两个截然不同的消费者阅读——工程师本人和工程师使用的 AI 助手。这两类读者在完全不同的约束条件下消费同样的文字,并且当文档在编写时仅考虑了第一类读者时,会产生系统性的不同故障模式。