跳到主要内容

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

查看所有标签

你的编程代理基于落后 Main 分支三周的代码版本重建的代码库索引

· 阅读需 11 分钟
Tian Pan
Software Engineer

你团队中的一个 AI 编程 Agent 提交了一个 PR,在两个文件中调用了四次 parseUserToken()。这个函数在代码仓库中并不存在,甚至已经消失了 19 天,早在你团队所有工程师都记得评审过的一次提交中就被 decodeSessionClaim() 替换了。Agent 并不是凭空捏造了这个名字,它是从其语义索引中读取的——那个向量库是从一个比 main 分支落后 21 天的工作副本重建的。相比之下,Agent 的编辑步骤在会话开始时运行了 git pull,操作的是最新的代码。对同一个代码库的两个视角,相隔三周,而 Agent 却自信地用一段无法针对任何真实环境编译的代码桥接了它们。

这是一种不会自我宣告的失败模式。Agent 运行了。测试看起来通过了。PR 合并了。第一位评审者之所以注意到,仅仅是因为一个被删减的函数与一个无关的辅助函数重名,触发了 linter 报错。到那时,Agent 已经花了一个完整的冲刺(sprint)针对一个“幻影版本”的代码库进行编写,而团队中没有一个人——包括 Agent 自己——收到任何异常信号。

服务商在 API 边界遵守但在缓存处违反的数据驻留契约

· 阅读需 10 分钟
Tian Pan
Software Engineer

你的驻留审计追踪了来自租户流量的每一个出站请求,观察它在法兰克福的一个主机名上终止,并签了字。审计对它所测量的一切都是正确的。但它也看错了层级。请求确实去了欧盟。但满足该请求的字节——即服务商哈希并从最近可用节点提取的缓存前缀——却存放在 us-east-1。你的区域端点向你承诺了一个目的地。而缓存什么也没承诺,因为缓存是一个不同的产品,受不同的 SLA 约束,是为成本而非合规而设计的。

客户的审计员发现了它。不是你的。另一个供应商的事件报告提到 Prompt 缓存的放置与推理区域是解耦的,于是客户的 GRC 团队提出了那个显而易见的后续问题:我们的前缀去了哪里?修补这一差距的合同修正案花了 90 天。续约被暂停了。根据他们拿到的文档,编写集成的团队并没有做错任何事。

那个将你的系统提示词泄露到客户审计日志中的调试日志器

· 阅读需 11 分钟
Tian Pan
Software Engineer

一位具有安全意识的客户拉取了其租户的审计导出文件,打开 JSON,从一个名为 llm.request.system 的字段中读到了逐字记录的拒绝策略、检索流水线结构以及一些内部产品标识符。没有漏洞利用。没有提示词注入。没有越狱。仅仅是你的平台团队在六个月前添加的一个日志字段,目的是让工程师能够将提示词版本与事件关联起来——结果通过你的企业团队出于 SOC 2 合规原因单独向租户开放的摘要(feed)泄露了出来。

泄露发生在周三下午。你的安全团队是由客户呼叫(paged)的,而不是报警系统。事件时间线显示泄露当天并没有部署——错误配置是在审计摘要扩大其字段白名单的那天发布的,那是另一个团队、另一个迭代周期(sprint)和另一张工单。两名评审员都批准了他们所看到的内容。但没有人从全局组合的角度去审视。

权重并列语气与正确性的评估准则:如何悄无声息地筛选掉正确答案

· 阅读需 11 分钟
Tian Pan
Software Engineer

你的评测提示词(judge prompt)从四个维度(1-5 分)进行评分:帮助性、清晰度、共情力和准确性。你对它们取了平均值。在六个月里,你的周度仪表盘数据稳步上升。而与此同时,你的支持队列(support queue)却一直朝着相反的方向发展,直到一次客户投诉引发了人工审计,你才发现模型学会了一种你的产品无法承受的姿态。

这种姿态就是“委婉的错误”。一个温和的错误回答——“有几种方式来看待这个问题,一种常见的观点是 X”(其中 X 是错误的)——在你的综合评分中得到了 4.2 分。而一个生硬的正确回答——“不,X 是错的,答案是 Y”——仅得到了 3.8 分。评判模型(judge)没有坏,评测准则(rubric)显然也没有坏。每一个维度单独看都是站得住脚的。聚合方式才是那个 Bug。

那些被你的提示词工程师转变为生产环境 Few-Shot 示例的评估集

· 阅读需 12 分钟
Tian Pan
Software Engineer

评估仪表盘连续三个迭代(sprints)都在攀升。困难切片的质量提升了 6 个百分点,回归切片提升了 9 个百分点,而在支持团队根据上季度最糟糕的工单亲手整理的切片上,质量提升了 12 个百分点。团队据此发布了模型升级。两天后,一位客户提出了一个与评估集中的任何内容都不沾边的问题,结果得到的答案比六个月前还要糟糕。

一旦有人想到进行排查,原因很快就浮出水面了。提示词工程师(prompt engineers)一直与评估团队在同一个代码仓库中工作。他们发现了那些精心策划的示例——这些示例来之不易,有的甚至是某人为了一个理想答案的措辞争论了一个小时才定下来的。在几个迭代中,他们把其中最有代表性的示例以 few-shot 演示的形式直接复制到了生产环境的系统提示词(system prompt)中。仪表盘持续攀升,是因为模型在推理时处理的正是它曾经逐字见过的输入。没有人指出这个问题。没有人负责划定“用于衡量质量的示例”与“用于发布到提示词中的示例”之间的界限。两个团队都准确地完成了他们被雇佣来做的工作。

被你的模型视为“约束性判例”的 Few-Shot 示例

· 阅读需 11 分钟
Tian Pan
Software Engineer

用户提交了一个问题。你的模型生成了一个答案,这个答案以一种非常具体的方式“自信地出错”:格式完美,推理结构严密,并且出现了一个特定的限定词——这个限定词完全不适用于这个问题——它出现的位置,恰好是你系统提示词(system prompt)中示例三出现类似限定词的地方。这既不是幻觉,也不是提示词注入。模型只是精确地执行了示例教它的操作,尽管这些示例原本并非为了涵盖这个问题。

这就是 Few-Shot 提示主动诱发的故障模式,而大多数评估套件(eval suites)在结构上对此是视而不见的。你的示例并不是“优秀范式”的中立演示。它们是判例法(case law)。模型通过表面 token 选择最匹配的项,并将该先例——包括其限制条件——应用到眼前的任何案例中。

KV Cache 驱逐:供应商称其为“缓存压力”,而你的账单则称其为“双倍前缀费用”

· 阅读需 13 分钟
Tian Pan
Software Engineer

你的应用程序开启了一个包含 4 万 token 系统提示词和完整工具库的长对话。第一轮对话按写入费率为前缀付费,且提供商的 KV 缓存(KV cache)开始预热。第二轮对话在 90 秒后到来。你假设这会命中缓存。有时确实如此。但有时,同样的 4 万 token 会再次以未缓存的价格出现在你的账单上,而你的代码在第一轮和第二轮之间没有任何改动。

改变的是别人的流量。KV 缓存是共享基础设施。你的租户被分配到一个推理节点上,而在你的两轮对话之间的 90 秒内,该节点接纳了足够多的其他租户,从而将你的前缀从内存中驱逐。提供商的控制台会将其描述为“缓存压力(cache pressure)”。你的财务团队会将其描述为一项翻倍的支出项。这两种描述都是准确的,但原因都不在你的代码里。

当 On-Behalf-Of 悄然变成 Act-As:你的智能体继承的 OAuth 作用域陷阱

· 阅读需 10 分钟
Tian Pan
Software Engineer

安全审查称智能体(Agent)“代表”(on behalf of)用户操作。OAuth 令牌却有不同的说法,而审计日志也支持令牌的说法。

语言上的一点微小差别,在架构层面起到了意想不到的作用。“代表”(On behalf of)是安全审查在试图描述一种委派安排时使用的语言,即智能体是一个可识别的委派对象,并受此身份约束。“作为”(Act as)则是运行时的行为,此时智能体持有的令牌与用户本人的令牌完全一致,因此在任何下游系统看来,智能体就是用户。这两个短语描述了完全不同的威胁模型。典型的企业级 OAuth 集成交付的是后者,但宣传的却是前者。

用户还来不及核实多模态模型的结论,预签名 URL 就已过期

· 阅读需 11 分钟
Tian Pan
Software Engineer

用户打开了昨天的对话。在支持专员的回复旁边,原本上传收据的地方显示为一个破碎的图片占位符。回复中自信地引用了“3 月 14 日在 Coffee Tribunal 商店消费的 47.32 美元”。用户无法检查该引用是否准确,因为模型赖以工作的证据现在来自你对象存储的 403 错误。他们提交了一个“幻觉”工单。你的评估套件没有发现这个问题,因为在调用时,模型的回答确实是完全正确的。

这是一个关于“保留策略不匹配”的故事,而不是模型质量的问题。你的对话记录比它的事实依据(grounding)活得更久。事实依据是一个只有 15 分钟有效期的预签名 URL,而关于该依据的断言则是会存放在你数据库中多年的文本。当资源时钟(asset clock)和断言时钟(claim clock)以不同的速度运行时,任何有据可查的多模态答案在重新访问时,最终都会看起来像是凭空编造。

那些悄然成为你唯一评测集阅读者的提示工程师

· 阅读需 10 分钟
Tian Pan
Software Engineer

评测集(eval set)是一个文件。但它也暗含了对 AI 功能用途的理论定义。这两者并非一回事,混淆它们的团队建立了一个质量网关,而其校准完全依赖于单个人的工作记忆。当那个人离职时,文件留下了,但那套理论也随之而去了。

这是你在组织架构图中看不到的失败模式。你规划了一个提示工程(prompt engineering)角色。你雇佣了一个优秀的人才。他们发布了 v1 版本的提示词,审视了简陋的基准测试,并将其重写为内容丰富的东西——一个失败模式分类法、每个类别的权重,以及一套能够消除边缘情况歧义的标注指南(rubric)。评测集变成了“该模型是否好到足以发布”的契约。六个季度后,你发现这份契约除了编写它的那个人之外,其他人都看不懂。

你的编程 Agent 开启的那个导致真实 PR 被关闭的拉取请求

· 阅读需 12 分钟
Tian Pan
Software Engineer

你的编程智能体在周二下午 3:14 提交了一个 PR。PR 描述很整洁,代码差异(diff)很小,CI 测试也是绿色的。二十分钟后,它被压缩合并(squash-merged)了。第二天下午 1:20 吃完午饭回来的同事看到了一条通知:“PR #1247 已关闭。”不是已合并,而是已关闭。分支不见了。她上周留下的 72 条评审评论也消失了——全部折叠在一个“已过期”标签下,属于一个不再出现在任何活跃列表中的 PR。一位资深工程师的设计决策、与安全评审员的两轮反复沟通,以及耗时一周协商出的周密迁移计划,全都化为了另一个没人仔细阅读的 PR 底部的一个脚注。那个压缩提交(squash commit)留下的唯一痕迹是底部的一行标签:Closed by #1893

这就是信任编程智能体自行编写 PR 元数据的失败模式。出问题的不是代码,而是元数据。代码差异没有问题,智能体工作得很出色。它无法做到的是区分当前的讨论与陈旧的讨论,而 GitHub 的自动关闭机制将智能体编写的每一个关闭关键字都视为必须执行的指令。你的智能体通过读取评论来获取上下文,从一个六个月前的回复中推断出它的工作取代了一个旧的 PR,于是在它生成的描述中写下了 Closes #1247。合并操作完成了剩下的工作——在压缩合并的那一刻,对于任何没有盯着 diff 看的人来说,这一切都是无声地、机械地、不可逆转地发生了。

当供应商重新定义 Bucket 时,那份让你溢出流量成本暴增的预留容量合同

· 阅读需 12 分钟
Tian Pan
Software Engineer

一个平台团队签署了一份为期数个季度的预留吞吐量合约。在承诺容量内按固定的 token 费率计费,超过上限的部分则按更高的超额费率计费。财务部门根据六个月的历史流量对消耗进行了建模,而这些流量很少触及上限。合约中规定“溢出”是指超过承诺上限的每分钟字节数,基于这个定义,这笔交易看起来很稳健。

六周后,在流量形态、路由配置和产品界面均未改变的情况下,账单飙升了 2.4 倍。供应商在季度中期悄悄修改了计量定义。现在,“溢出”还包括自动路由器发送到高于预留层级的模型请求——因此,即使总吞吐量完全在承诺范围内,一次在复杂提示词上选择 Sonnet 的操作也会被计入超额桶中。原本按预留费率结算的 30% 流量,现在改按超额费率计费。财务部门通过仪表板追踪了三周的突发增长,最后才有人读到季度中期的定价补充协议,并在脚注中发现了这一重新定义。

合约并未被违反。但计价所使用的单位被重新定义了。