跳到主要内容

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

查看所有标签

像入职初级工程师一样入职 AI 智能体是一种范畴错误

· 阅读需 11 分钟
Tian Pan
Software Engineer

当一个智能体(agent)加入你的团队时,每个工程经理脑海中最接近的类比就是新员工。因此,应对策略显而易见:给它一个沙盒和只读日志,将最初的任务范围缩小,与其结对编程,预留一段磨合期,并随着信任的累积让它承担更重的工作。这听起来很负责任。这感觉就像是你把上一个初级工程师培养成高级工程师时那种耐心的管理方式。

这也是一个范畴错误(category error)——它不只是一个略显不完美的类比,而是一个错误的类比。初级工程师是一个还不太了解你系统的人。而智能体是一个无状态的函数,无论接触系统多少次,它永远都不会真正“了解”你的系统。这是两种完全不同的事物,适用于前者的管理直觉会在无形中让你错配注意力。

这之所以重要,是因为这个隐喻不仅会产生误导,还会让你把精力投在错误的地方。“培养智能体”并不是一种策略。智能体是固定的,你真正能改变的一切都存在于它之外。

权限提示是一个 UX Bug:当“人在回路”沦为“人工橡皮图章”

· 阅读需 10 分钟
Tian Pan
Software Engineer

观察一名开发者使用 Agentic 编程工具一小时,你会看到同一个动作重复四十次:弹窗出现,“允许 Agent 运行 git status 吗?”,手在眼睛读完之前就移到了批准按钮上。到第四十次提示时,提示内容根本没被阅读。它变成了一个用户学会全速通过的减速带。

这是“人机回环”(human-in-the-loop)的一种无声失败。架构图上仍然显示人类在把控每一个危险动作。审计日志仍然记录着对每个命令的明确批准。但人类已经停止了评估。他们变成了一个接入控制流的生物版 “yes” 函数 —— 虽然身在回路中,却不贡献任何判断。权限提示本应是一项安全控制,它却退化成了附带确认对话框的系统延迟。

Prompt Caching 的隐形代价:当缓存命中提供错误的用户上下文时

· 阅读需 13 分钟
Tian Pan
Software Engineer

Prompt 缓存被宣传为一种稳赚不赔的方案。缓存长期的共享前缀——你的系统提示词、工具定义、检索到的上下文——只需为变化的短尾部分支付全额费用,然后看着账单下降。数字是真实的:缓存读取的成本大约是新鲜输入 Token 的十分之一,因此具有大量稳定前缀的工作负载,其输入成本可以降低 80% 或更多。团队因此采用它,因此调整它,并用单一指标来汇报:缓存命中率,且趋势向好。

这种表述掩盖了一个事实:你刚刚划定的边界——缓存前缀与非缓存尾部之间的界线——并不是一个计费旋钮。它是一个正确性边界。缓存断点之上的所有内容都是系统认为可以在请求之间互换的内容。如果你为了最大化命中率而划定这条线,你就是在让财务指标来决定你的 Prompt 中哪些事实可以在用户之间、租户之间以及跨时间共享。这是一个隔离决策,理应有目的地做出。

这种失效模式是隐蔽的,因为它永远不会报错。如果缓存命中提供了一个由另一个用户概况塑造的上下文,它会返回一个格式完全正确的响应。如果缓存命中提供了一个在缓存预热时为真、但在重用时已失效的个性化信息,它会返回一个自信、连贯但错误的答案。你的延迟图表或错误率不会有任何波动。唯一的信号是看起来 非常棒 的命中率——因为 Key 太粗颗粒度了。

Prompt Injection 是混淆代理问题,而非内容过滤问题

· 阅读需 12 分钟
Tian Pan
Software Engineer

在 Prompt Injection(提示词注入)违规事件发生后,最常见的调查结果通常是某种形式的“模型被骗了”。检索到的文档包含了隐藏指令,Agent(智能体)执行了这些指令,导致客户数据泄露。随后的修复方案几乎无一例外地是内容过滤器:扫描输入、对恶意指令进行分类,并在其到达模型之前将其剥离。部署过滤器,结案。

这个结论是错误的,而过滤器就像是一台无休止的“跑步机”。“模型被骗了”描述的是症状,而不是漏洞。漏洞在于,一个拥有实际权限的 Agent——如数据库令牌、发送邮件能力、文件系统写入权——接受了来自一个永远不该被允许指挥这些权限的来源的指令。这并不是一种新型漏洞,而是一个“混淆代理”(Confused Deputy)问题,操作系统在近 40 年前就已经对其进行了命名并基本解决了它。

如果你将 Prompt Injection 视为检测问题,你就是在参与一场与每一个能组织语言的攻击者之间的军备竞赛。如果你将其视为权限问题,你就可以复用数十年来行之有效的安全工程经验。

每次事故后你的系统提示词都会增长 —— 而且没人会删掉任何一行

· 阅读需 10 分钟
Tian Pan
Software Engineer

打开任何一个已经在生产环境中运行了一年的智能体的系统提示词(system prompt)。滚动到底部。你会发现一层读起来像道歉一样的句子沉积层:“绝对不要伪造订单号。”“不要承诺无法确认的退款。”“如果用户在德国,不要提及旧版方案。”每一句都是一块化石。每一句都标志着生产环境中出现问题的确切时刻 —— 有人被传呼了,而当时能找到的最快修复方法就是增加一句话。

没人删除这些句子。不是因为它们还在发挥作用,而是因为删除一句话意味着需要证明一个否定命题 —— 证明模型不会在一个可能已经在三个模型版本前修复的 Bug 上发生回退。没人能证明这一点,所以那行字留了下来。系统提示词变成了一个关于过去事故的“只增不减”(append-only)日志,它让你在每一次调用中都永远支付着 token 费用。

这是 AI 系统中最隐蔽的一种技术债,因为它看起来不像债。它看起来像是在尽职尽责。

任务完成率指标变绿,而用户却在默默受苦

· 阅读需 9 分钟
Tian Pan
Software Engineer

你的智能体仪表盘显示任务完成率为 94%。领导层很满意。路线图获得了资金支持。然而,支持工单却在不断增加,核心用户变得沉默寡言,而那个负责观察追踪记录(traces)的工程师则一直在嘀咕情况不对劲。这两件事同时都是事实:智能体确实在完成任务;但它也为了完成一个两步就能搞定的工作,耗费了 12 分钟和 4000 个 token,反复回溯了三次,并要求用户确认一个它本可以从第一条消息中推断出来的实情。

任务完成率是一个隐藏了分布情况的二元指标。“智能体完成了任务”并不能告诉你它达成目标所走的路径,而路径才是用户实际体验的核心。完成率仪表盘在结构上无法察觉到一个缓慢、昂贵且令人恼火的智能体。它会一直保持绿色,直到用户流失。

这并不是一个可以通过更好的提示词来修补的测量差距,而是你选择测量什么而导致的“范畴错误”。完成率是最容易衡量的指标,但却是人们付费买单中最微不足道的部分。

那个记得你撤回了什么的智能体:将删除作为一等公民的记忆操作

· 阅读需 11 分钟
Tian Pan
Software Engineer

三月,一位用户告诉你的智能体停止推荐有室外座位的餐厅——他们搬到了一个带宝宝的公寓,深夜外出已经结束了。九月,智能体为他们的周年纪念建议了一家屋顶酒吧。用户感到恼火,而你感到困惑,因为你亲眼看着三月份的纠正生效了。它被写入了记忆。它仍然在那儿。问题在于它与最初的偏好并排存在,而最初的偏好也还在那里,检索算法浮现了旧的那个,因为它对“周年纪念晚餐”的向量嵌入(embedding)匹配度稍高一点。

这是没有人针对其设计的失败模式。团队在记忆写入上花费数周时间——提取、摘要、嵌入、命名空间——而将删除视为以后再解决的问题。长期记忆使得添加一个事实几乎是零成本的,因此事实不断堆积。但记忆库不是日记。日记允许包含过去真实的事情。智能体读取并据此做出决策的记忆库则不然,因为智能体无法区分事实与过时的残余。

Token 预算是调度问题,而非提示词问题

· 阅读需 11 分钟
Tian Pan
Software Engineer

当一个智能体(agent)给出的回答比上周差时,第一直觉往往是归咎于提示词(prompt)。有人会重新编写系统指令,删减几句话,增加一个示例,然后发布。有时这会有所帮助。但通常这毫无作用,因为提示词从来就不是问题所在。问题在于,一个冗长的工具调用结果静默地消耗了 18,000 个 token,将实际的任务指令推到了上下文窗口中关注度较低的中部位置,让模型在一个 70% 都是噪音的记录上进行推理。

这不仅是措辞问题,更是资源分配问题。资源分配在系统工程中有一个专属名称:调度(scheduling)。上下文窗口是一种固定大小的资源,多个消费者在其中竞争,而目前大多数智能体技术栈“调度”它的方式就像 1960 年代的批处理系统调度内存一样——先到先得,直到用完为止。

工具的默认参数其实是伪装的策略决策

· 阅读需 11 分钟
Tian Pan
Software Engineer

打开任何智能体运行的 trace(追踪),观察一次工具调用。你会看到工具名称以及模型选择传递的参数。你没看到的是它 没有 传递的所有内容。一个仅设置了 querysearch 调用,在运行时仍然具有页面大小、超时时间、结果排序和可见性范围。这些都不是智能体决定的。而是你在几个月前编写工具 schema 时决定的,当时你将这些参数设为可选并留下了默认值。

默认值并非为了方便。它是伪装成合理留白的策略决策。默认的页面大小限制了智能体在一次调用中能看到的范围。默认的超时时间决定了智能体何时放弃并开始即兴发挥。默认的可见性范围决定了“搜索文档”是指公共手册,还是包含未发布路线图在内的整个内部维基。默认的 dry_run 标志决定了智能体的行为是一场演习,还是生产环境中真实的、不可逆转的事件。

你在没告诉智能体的情况下修改了工具 Schema

· 阅读需 12 分钟
Tian Pan
Software Engineer

一位后端工程师重命名了一个字段。user_id 变成了 customer_id,因为团队终于在所有服务中统一了 “customer” 这个术语。他们还增加了一个参数 region,因为计费系统现在需要它。这次变更通过一个包含两个批准的普通拉取请求(pull request)发布。每一个调用该端点的下游服务都在同一个发布版本中进行了更新。集成测试全部通过。按照后端团队衡量的一切标准,这是一次常规且执行良好的 API 变更。

一周后,支持工单开始增加。负责下单的智能体偶尔会在没有关联客户的情况下下单,或者将其关联到错误的区域。没有人改动过智能体。没有人改动过提示词(prompt)。模型的版本与上个月完全相同。然而,智能体现在却出现了一种以前从未有过的错误。

原因既不是模型中的 Bug,也不是后端中的 Bug。而是工具 Schema(tool schema)有两个消费者,但在审查变更时,只有其中一个在场。

原本运行良好的工具,直到两个智能体同时调用它

· 阅读需 11 分钟
Tian Pan
Software Engineer

一个工具通过了测试。你从一个智能体(agent)调用它,看着它读取记录、转换、写回并返回一个清晰的结果。几周以来,它每次都表现完美。然后你将智能体集群从一个 worker 扩展到十二个,结果一个客户报告说他们的订阅在同一分钟内被升级了两次。工具没有变,只是调用它的并发量变了。

这是单智能体测试无法捕获的失败模式,因为单智能体测试永远不会产生触发该模式的条件。从结构上看,单个调用者是一个串行工作负载。你的工具默默依赖的所有并发假设——读取时没有其他人在写入、自增的计数器是属于它自己的、保存时正在编辑的草稿依然存在——在只有一个调用者时都是理所当然成立的。工具并非正确,只是未经过测试。这两者是不同的,而在第二个智能体出现之前,这种差异是不可见的。

在智能体交接处中断的分布式链路追踪

· 阅读需 12 分钟
Tian Pan
Software Engineer

你打开一个失败运行的追踪(trace)。Span 树非常漂亮:用户请求、规划者 Agent 的推理、三次工具调用、Token 计数、延迟,所有这些都整齐地嵌套在一起。然后规划者交接给一个专家 Agent —— 追踪到此结束。并不是出现了错误 Span。它只是停止了。接下来的内容是来自专家 Agent 的另一个、无根的追踪,它从思考的中途开始,没有父级,没有可见的输入,也与导致它的请求没有任何联系。

Bug 就存在于那个间隙中。一直以来都是如此。交接是一个 Agent 的假设与另一个 Agent 的理解相遇的地方,也是你的追踪无法跟随的唯一地方。

这不是日志记录的问题。你的 Agent 可能在两端都正确地发出了 Span。问题在于追踪上下文(trace context)—— 将 Span 缝合成一个故事的线程 ID —— 没能在从调用者到被调用者的跳转中幸存下来。你技术栈中的每个 HTTP 客户端和 gRPC 存根都会免费传播该上下文。但你的 Agent 交接没有这样做,因为没有人告诉它去这样做。