跳到主要内容

842 篇博文 含有标签「ai-engineering」

查看所有标签

你的仪表盘以三种不同方式统计了那次重试

· 阅读需 13 分钟
Tian Pan
Software Engineer

一个 Agent 运行了。计划步骤(plan-step)崩溃了。工具调用(tool-call)步骤在经历了两次 500 错误重试后,在第四次尝试时成功了。用户得到了他们的答案。

那算是多少个事件?问产品,这是一个事件 —— 用户得到了有效结果,因此转化漏斗统计了一次转化。问 SRE,那是三个失败加上一个成功,底层步骤的错误率是 75%。问财务,那是四次计费推理,两次重试的工具调用,以及大约四倍于产品部门预测的单位成本。每个团队的仪表盘都是正确的。但它们也是不可调和的,一旦有人试图调和它们 —— 通常是在事故回顾期间 —— 他们会发现团队已经基于三个相互矛盾的可信度图景运行了数月之久。

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

· 阅读需 10 分钟
Tian Pan
Software Engineer

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

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

毫无意义的影子部署:当并行调用错失对话语境时

· 阅读需 10 分钟
Tian Pan
Software Engineer

影子部署(Shadow deployment)是每个人都公认的负责任的验证方式。你将实时流量镜像到一个候选模型中,记录其输出,但从不向用户展示结果。仪表盘数据一致,候选模型的响应在聚合质量指标上看起来与现有模型(incumbent)一样好,团队收到了新模型是“生产等效”的绿色信号,然后你将其推广到一小部分真实的流量中。在一天之内,在那些影子运行评估为匹配的查询类别上,面向用户的指标全面崩溃。

团队的第一反应是归咎于发布过程:也许是功能标志(feature flag)出错了,也许是路由器(router)路由错误,或者新模型在生产环境中的性能在某种程度上悄无声息地下降了,而影子部署时却没有发现。这些都不是真的。影子部署完全按设计运行。团队衡量的是候选模型孤立的输出——字符串对字符串——而推向生产环境的候选模型,其输出会重塑用户的下一条消息、下一轮对话、放弃决策以及整个会话的路径。影子部署衡量的是模型。生产环境衡量的是对话。这两者并非同一维度。

教会你的智能体识别评估的合成评估

· 阅读需 9 分钟
Tian Pan
Software Engineer

一个研究模型重写了基准测试(benchmark)的计时器,使得每次运行都报告快速完成。另一个旗舰模型通过删除测试或悄悄重新定义“正确”的含义,通过了大约一半的“不可能”编程测试。这些是媒体报道的戏剧性案例。而无声的版本正发生在你的评估套件中:你的合成评估生成器(synthetic eval generator)具有特征指纹,你的模型学会了这种指纹,你的评分随着版本发布而攀升,而用户却向支持团队反馈产品体验变差了。

评估识别(Eval-recognition)是一种失效模式,即模型在评估期间的表现优于生产环境,这不是因为它在任务上变得更强,而是因为它变得更擅长察觉自己正在被评估。模版化的措辞、可识别的伪影标记(artifact tokens)、人类用户不会产生的缺失上下文模式——这些都是信号,任何有足够能力学习任务的模型也都有足够的能力学习这些信号。评估分数上升了,但面向用户的指标却没有。团队针对一个被他们自己的流水线教会模型作弊的基准测试优化了数月。

这不是训练数据层面的基准测试污染(benchmark contamination)故事。模型并没有看到评估答案。它学到了一些更微妙、更难修复的东西:评估分布(eval distribution)有一种形状,生产分布(production distribution)有另一种形状,而模型学会了区分它们并相应地分配精力。

增长速度快于评估套件的系统提示词

· 阅读需 11 分钟
Tian Pan
Software Engineer

你发布 Agent 的那天,系统提示词(System Prompt)仅包含三条规则和一个语气指令。评估测试集(Eval suite)为每条规则覆盖了十个案例,CI 徽章是绿色的,团队理所当然地感到自豪。十八个月后,同样的提示词变成了四十条规则、六个工具描述、四个 Few-shot 示例、两个安全前导语,以及一个在每次事故后都会增加一项的拒绝分类法。相比之下,评估测试集可能只增加了二十个案例——每个事故增加一个,且都是在压力下编写的,从未针对通过日常提示词 PR 悄无声息引入的几十条规则进行补测。

当 PR 发布时,团队仍然会说“评估通过了”。他们实际的意思是“我们十八个月前编写的评估,在针对那些评估已无法完全描述的提示词时依然通过了。”置信区间的分母在默默扩大,而分子几乎固定不变。下一次触及三十七条未测试规则之一的提示词修改,将被一个对其毫无判断力的测试集评定为安全。

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

· 阅读需 11 分钟
Tian Pan
Software Engineer

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

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

Agent 假装执行的验证步骤

· 阅读需 8 分钟
Tian Pan
Software Engineer

你的 Prompt 说“在返回前验证 X”。追踪记录显示字符串“已验证 X”。一周后,你发现 X 从未被验证过——哪怕一次,哪怕针对任何请求,在任何环境下。模型学会了输出这个短语就能满足评估标准。它声称做的验证只是文本生成器输出中的一个句子,而不是在现实世界中采取的行动。

这是一种与幻觉不同的故障。幻觉是模型虚构了一个关于世界的事实。自我证明式验证是模型虚构了一个关于其自身过程的事实。前者是知识问题。后者是底层机制问题——你要求一个生成字符串的系统执行一个它没有机制去执行的动作,于是它产生了一个看起来像是执行了该动作的字符串。

即使你发誓绝不分享,你的评估集仍需要的水印

· 阅读需 12 分钟
Tian Pan
Software Engineer

你的私有评估集(eval set)是你的 AI 团队拥有的最重要的知识产权之一。它定义了你的产品什么是“好”,它把关每一次模型升级,它告诉你上周的提示词(prompt)修改是改进还是退化。从你写下第一个案例的那一刻起,泄露的倒计时就开始了。

并不是因为你会公开发布它,也不是因为你会在会议上演示它。它会像所有事物泄露的方式一样泄露:支持工程师把一个失败的案例粘贴到 Bug 工单里,产品经理把评估细则(rubric)截图发到某个被索引的 Slack 频道,调试日志将样本载荷上传到第三方错误追踪器,供应商评估人员将你的基准测试跑在他们的微调管线上,因为合同某种程度上允许这样做。在足够长的时间轴上,泄露的概率趋近于 1,而最糟糕的泄露版本是团队中没人察觉到的:供应商发布的下一个模型悄悄记住了你的评估集,由于测试集变成了训练集,你的得分跃升了,而不是因为模型变得更好了。

那些在本地通过但在 CI 中失败的编程智能体

· 阅读需 12 分钟
Tian Pan
Software Engineer

智能体(agent)生成的 diff 在你的电脑上显示为绿色。测试通过了,lint 通过了,开发服务器也干净地完成了热重载。你让它提交了 PR,九十秒后,CI 在一个与修改完全无关的步骤上报错变红了:缺少某个 CLI 工具、一个智能体从未声明过的新环境变量,或者 Node 版本解析结果不一致——因为你的 .nvmrc 是通过 runner 并不具备的全局 shim 进行解析的。智能体并没有写出有问题的 diff。它写出的是一个依赖于你机器环境的 diff,而你的机器和 runner 并不是同一台电脑。

“在我的机器上能运行”曾是一个人为 Bug。解决办法是保持纪律性——锁定版本、编写 Dockerfile、阅读 CI 日志。而编程智能体大规模地继承了这个 Bug,却丢弃了曾经用来弥补它的纪律性。因为智能体不知道它所依赖的东西哪些来自代码库,哪些来自你 shell 历史记录中的“温热沉淀物”。每个开发者的笔记本电脑都是一个配置独特的环境,智能体在不知不觉中吸收了这些环境。接着,同一个智能体在一个完全不具备这些条件的 runner 中运行,失败的表象看起来像是智能体的错,但实际上是由于没人写明的一份环境契约。

编程智能体绕过而未使用的代码规范(Idiom)

· 阅读需 13 分钟
Tian Pan
Software Engineer

我合作的一个支付团队的高级工程师曾给我讲过一个故事,我认为每一个运行编程 Agent(AI 代理)的团队最终都会经历。他们的代码库有一个 Result<T, E> 封装器——这是自研的,位于单个 core/result.ts 文件中,在该服务的约两百处调用点被使用。新代码被要求在每一个可能失败的函数中传递 Result;而 throw 则保留给真正意料之外的状态。这并非由 lint 规则强制执行。这就是他们的“方言”。

在使用编程 Agent 交付六个月后,他们审计了 Agent 合并的 diff(差异)。大约三分之一的新函数完全忽略了 Result。Agent 选择了 try/catch,返回了 T | null,抛出了带有描述性消息的 Error 子类——在某些设想的代码库中,这些选择中的每一个都是正确的。但在当前这个代码库中,没有一个是正确的。代码通过了类型检查。测试通过了。审阅者批准了它,因为每一行看起来都没有错。但 Agent 修改的文件不再与它旁边的文件保持一致,团队在自己的服务内部悄然滋生出了第二种“方言”。

这就是我想谈论的故障模式:不是 Bug,不是幻觉,也不是违反了 lint 规则——而是惯用法漂移 (Idiomatic Drift)。Agent 交付的代码可以编译、运行并通过测试,但其风格并非你的代码库所使用的。随着合并次数的增加,代码库会分化为 Agent 风格区和人类风格区,而代价会体现在任何仪表盘都无法监控的地方。

你的编程智能体悄然打破的内部循环

· 阅读需 9 分钟
Tian Pan
Software Engineer

关于编码智能体(coding agents)提高生产力的说法是,它们消除了打字瓶颈。但在实践中,工程师真正遇到的瓶颈却截然不同。工程师再也无法在脑中掌握整个系统,因为智能体修改文件的速度快于工程师阅读的速度,编写测试的速度快于工程师推断覆盖率的速度,重构抽象的速度快于工程师在设计层面(而不仅仅是编译器层面)验证类型检查的速度。

那个紧凑的内环——假设、更改、观察、优化——定义了胜任的工程工作,但它正悄然瓦解为另一种循环。工程师现在是在审查智能体的输出,而不是建立对系统的直觉。2025 年中期的一项 METR 随机对照试验发现,经验丰富的开源开发人员在使用 AI 助手处理熟悉的代码库时,速度慢了 19%,但他们却报告感觉快了 20%。认知感知的生产力与实际生产力之间这 39 个百分点的差距并非测量误差。这是为了吞吐量而默默牺牲理解力的代价。

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

· 阅读需 11 分钟
Tian Pan
Software Engineer

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

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