跳到主要内容

861 篇博文 含有标签「insider」

查看所有标签

流式回滚问题:你无法收回已发送的 Token

· 阅读需 11 分钟
Tian Pan
Software Engineer

观察某人第一次使用聊天产品时,你会发现他们在模型完成输出之前就开始阅读了。这种“边出边读”的行为正是流式传输(streaming)存在的全部意义:它将数秒的等待转变为一种对话般的感觉。然而,这也是你的输出防护栏(guardrails)悄然失效的原因。

这是一个令人尴尬的过程。模型生成了第 1 个 Token、第 2 个 Token、直到第 150 个。每一个 Token 在到达时都会立即渲染。到第 200 个 Token 时,模型产生了一个虚假的用药剂量、泄露了一个电子邮件地址,或者生成了一句违反内容政策的话。你的输出侧防护栏正确且立即触发了。但“立即”已经太晚了——用户已经阅读了前 200 个 Token。你无法撤销渲染。防护栏履行了职责,但违规内容仍然传达给了人类。

结构化输出并非经过验证的输出

· 阅读需 10 分钟
Tian Pan
Software Engineer

你的团队启用模式约束解码(schema-constrained decoding)的那一天感觉像是一个里程碑。解析错误停止了。JSONDecodeError 警报消失了。从文本中抓取字段的脆弱正则表达式也被删除了。有人在站会上说“模型现在返回有效的 JSON 了”,结构化输出的任务单随之关闭。

那句话正是麻烦的开始。“模型现在返回有效的 JSON 了”是正确性工作的开始,而不是结束。JSON 模式和约束解码保证了响应的形状(shape)——即 quantity 是一个整数,status 是三个枚举值之一,对象包含你要求的键。它们完全无法保证 quantity 是否是正确的数字,status 是否反映了真实发生的情况,或者 sku 字段是否指向了目录中存在的商品。

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

· 阅读需 10 分钟
Tian Pan
Software Engineer

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

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

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

当测试集泄露到微调中:你自己造成的污染

· 阅读需 10 分钟
Tian Pan
Software Engineer

AI 领域的每个人都知道基准测试污染(benchmark contamination)的警示故事:模型厂商抓取公开网络,GSM8K 和 MMLU 最终出现在预训练语料库中,导致报告的分数衡量的是召回而非推理。这通常被视为别人的过错——是基础模型实验室的问题,是你继承下来的瑕疵。因此,你构建了自己的留存评估集,将其存放在私有仓库中,并认为自己是清白的。

你可能并不清白。在生产级 AI 系统中,最具破坏性的污染很少是继承来的,而是由心怀好意的工程师遵循看似合理的流程在内部制造出来的。你的评估集通过你自己建造的大门泄露到了训练流水线中,而且这种泄露是无声的:就在你的基准测试停止衡量任何真实事物的瞬间,每个仪表盘都会变成绿色。

这就是你亲手造成的污染。它比你继承的那种污染更值得关注,因为你是唯一能够检测到它的人——而几乎没有人会为此进行审计。

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

· 阅读需 11 分钟
Tian Pan
Software Engineer

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

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

你的工具描述是模型遵循的指令通道

· 阅读需 9 分钟
Tian Pan
Software Engineer

当安全团队审查一个新的工具集成时,他们会阅读代码。他们会检查函数的功能、它触及的内容、它需要的权限范围(scopes),以及它是否记录了敏感秘密。但他们几乎从不阅读那句决定模型是否调用该工具的句子——工具描述。那句话不仅仅是文档。它是模型视为权威的指令,而在大多数智能体堆栈中,没有人会去审计它。

工具描述是写给模型看的。模型利用它来决定工具何时相关、应该传递哪些参数,以及如何解释返回的结果。这使得描述成为了进入模型行为的一个控制通道。而当一个工具来自第三方注册表、一个你不运行的模型上下文协议(MCP)服务端,或者一个同事上周安装的插件时,这个控制通道的作者就是你从未同意信任的人。

这就是差距所在。输入净化(Input sanitization)检查用户输入的内容。代码审计(Code review)检查函数执行的内容。工具描述介于两者之间——它是表现得像输入的配置——它从这两个防护网中漏掉了。

你在没告诉智能体的情况下修改了工具 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 扩展到十二个,结果一个客户报告说他们的订阅在同一分钟内被升级了两次。工具没有变,只是调用它的并发量变了。

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

停止并非一种状态:为什么智能体需要类型化的终端原因协议

· 阅读需 11 分钟
Tian Pan
Software Engineer

打开一个 Agent 集群(fleet)的仪表板,你会看到一个干净的数字:完成率,94%。在它下方是一系列运行记录,每条都标记着两种状态之一 —— 正在运行(running)或未在运行(not running)。那 6% “未在运行”的记录看起来完全一样。其中一些完美地完成了任务。一些在离完成还差两步时达到了步骤限制。一些捕获到了工具错误并放弃了。一些正确地判定任务是不可能的。还有一些则干脆断了思路,停止输出 token。

你的监控无法区分这些情况。它只知道流程不再运行了。它不知道 为什么,而“为什么”正是你在决定是否要呼叫(page)值班人员时唯一关心的事情。

你的智能体假设存在的“撤销”按钮

· 阅读需 10 分钟
Tian Pan
Software Engineer

观察一个 Agent 思考多步任务的过程,你会注意到一些熟悉的东西:它的规划方式就像你调试代码一样。尝试一种方法,观察结果,如果错了,就撤回并尝试另一种。Agent 将其计划描述为一棵选项树,它可以探索、剪枝和重新访问。这种心智模型在代码沙箱中是正确的,因为在那里的每个操作都有隐式的撤销功能。但在 Agent 接触到现实世界的那一刻,这种模型就错得离谱且危险。

发出的邮件无法撤回。扣款的银行卡在没有退款流程、手续费以及已经看到通知的客户的情况下,是无法撤销扣款的。除非有人设置了软删除,否则被删除的数据行就彻底消失了。一条发布的 Slack 消息可能已经被阅读了。Agent 的规划模型没有原生的“单向门”概念——即一旦采取行动,就再也无法假装它从未发生过。

这不是一个模型智能问题。即使是更聪明的模型仍然不知道你的哪些工具是可逆的,因为可逆性不是操作本身的属性。它是操作所落地系统的属性。你必须明确告诉它。

你的向量索引是一个没有失效策略的缓存

· 阅读需 11 分钟
Tian Pan
Software Engineer

向量索引感觉就像一个数据库。你向其中写入文档,查询它,它返回结果。但它并不是数据库——它是存储在其他地方的数据的“派生、非规范化副本”。你的事实来源是 wiki、工单系统、CRM 或 PDF 文件夹。嵌入 (embeddings) 是这些事实的投影,冻结在你运行摄取任务 (ingestion job) 的那一刻。

这使得你的向量索引变成了一个缓存。就像所有缓存一样,它会失效。不同之处在于,大多数团队是有意识地构建缓存层,带有 TTL 和失效钩子 (invalidation hook),而几乎没有人会有意识地将向量索引作为缓存来构建。他们将其构建为“知识库”,然后在它提供三周前就已经过时的知识时感到惊讶。

向量索引存在一个没人定义的陈旧度 SLO

· 阅读需 11 分钟
Tian Pan
Software Engineer

一个用户询问你的智能体(agent)企业版的当前价格档位。智能体检索了一个分块(chunk),读取并回答:“每月 2,000 美元。” 信心满满,来源明确,格式优美。问题在于价格在四天前已经变了。智能体引用的数字在上周是真实的。它检索到的分块是在变更之前嵌入的,而索引还没有赶上进度。

没人决定让这种情况发生。没有设计评审说“智能体可以根据长达四天前的数据进行回答”。只是有一个每晚或每周运行的重新索引任务,以及一个随心所欲编辑内容的团队,而这两个时钟之间存在一个没人衡量的缺口。那个缺口就是一个服务等级目标(SLO)。无论你是否写下来,它都存在。唯一的问题是你是有意设定它的,还是由于意外继承下来的。