跳到主要内容

720 篇博文 含有标签「llm」

查看所有标签

被反向代理剥离的 SSE Keep-Alive,以及你支付了两次费用的 Prompt

· 阅读需 11 分钟
Tian Pan
Software Engineer

你的 Agent 调用了一个耗时 35 秒的工具。在这 35 秒内,没有任何 Token 从模型流回浏览器。Provider 的 SSE 流仍然开启。你的工具仍在运行。用户的加载动画也在旋转。而在路径中间的某个你无法控制的反向代理认为连接静默时间过长,关闭了它,随后你的客户端重连逻辑尽职地从头重新启动了整个请求。

第一次响应产生了 4,200 个 Prompt Token 和 600 个 Completion Token。第二次响应也是 4,200 个 Prompt Token 和 600 个 Completion Token。用户得到了一个答案。而你的账单却收到了两份。

那个把用户的字面问题“改写”没了的摘要器

· 阅读需 9 分钟
Tian Pan
Software Engineer

一个用户问:“这是否符合第 28 条规定的‘转移’(transfer)?”四十轮对话后,模型给出了一个针对不同问题的答案。对话记录显示,模型回答了它收到的问题。用户正在阅读一份看起来像幻觉的投诉。两者都对。模型从未看到用户的提问——它看到的是你的摘要生成器对其进行的礼貌改写:“用户询问了第 28 条的适用性。”

“转移”一词就是问题所在。摘要生成器把它丢弃了,因为摘要生成器的损失函数被调优为保留事实而非措辞,而且评估准则从未学会区分改写主题和改写约束。主题被保留了。约束变成了迷雾。

这种失效模式是结构性的,而非偶发性的。任何通过模型生成的摘要来压缩长对话的应用,在关键路径上都有第二个模型——其质量契约通常被视为 Token 预算旋钮,而非一段产品逻辑。这种不对称性正是 Bug 所在。

客户端估算的 Token 数量与供应商结算账单的差异

· 阅读需 13 分钟
Tian Pan
Software Engineer

你的应用程序使用与你认为的提供商所使用的相匹配的分词器(tokenizer)库在本地计算 token 数量。SDK 在每次调用前报告“预计 4,200 个 token”。你的预算逻辑通过了该请求。然而,提供商的账单显示相同的负载为 6,800 个 token。将这 60% 的差距乘以每月数百万次的调用,财务团队无法根据你的日志核对的这一项开支,看起来就不再是四舍五入的误差,而是一个架构错误。

错误并不在于本地分词器是错的。错误在于将本地分词器视为一种契约,而不是一种猜测。Token 化(Tokenization)是提供商在其服务栈内部完成的事情——你的库只是该过程的一个模型,而非过程本身,而且两者会产生偏差:这种偏差在每次调用中虽然微小,但在你实际进行的总体调用量中却具有结构性。

你的延迟 SLO 取决于其他团队的 Prompt 大小

· 阅读需 11 分钟
Tian Pan
Software Engineer

你的聊天产品已经在 1.5 秒的 p99 延迟 SLO 下平静地运行了数月。请求率平稳,prompt 大小平稳,模型也未曾改变。接着,在某个周二下午,p99 突然飙升至 4.8 秒并保持在那里。值班排查发现聊天路径(chat path)没有任何异常:同样的每分钟请求数,同样的中位 prompt 长度(约 800 token),SDK 的重试行为也完全一致。聊天服务当天的部署日志为空。故障持续了六个小时。

原因出在另一个团队的代码库中。那天早上,一个长文本摘要功能上线了,使用的是同一个组织密钥(organization key),其平均 prompt 为 12,000 token。他们的请求率并不高 —— 每分钟仅几百次 —— 但每次调用消耗共享的每分钟 token(TPM)预算的速度比你的快 15 倍。供应商的限流在聊天路径上触发了,因为聊天路径与摘要团队共用同一个刚刚被掏空的“桶”。没人动过你的代码,没人超出计划的容量,而你的 SLO 现在却成了你的团队从未读过的工作负载的函数。

缓存击穿:这次冲击的是你的模型提供商,而不是数据库

· 阅读需 11 分钟
Tian Pan
Software Engineer

传呼机在 UTC 时间 14:02 响了。不是因为延迟,也不是因为错误——而是因为开销。费用仪表盘显示出一条垂直线:三分钟的输入 Token 计费大约是过去一小时平均水平的九倍,然后又恢复了正常。没有发布回归版本。没有新租户上线。流量精确到分钟来看都是平稳的。唯一改变的是,一个单一的 Prompt 前缀——集群中每个 Agent 共享的 14K Token 系统消息——在提供商端悄悄过期了,一千个 Worker 全都在同一个 200ms 的窗口内认定,自己就是那个需要将其写回的人。

这就是缓存雪崩(Cache Stampede)。这是自 2003 年 memcached 发布以来,运维人员一直在写事故复盘报告的那个老问题。2026 年的新变化在于,发生雪崩的缓存不再属于你。它存在于你的模型提供商内部,你无法检查其状态,而且每一次未命中(Miss)消耗的是真金白银,而不仅仅是几次额外的数据库查询。数据库工程师在二十年前就学会通过抖动(Jitter)来化解的同步 Bug,已经悄然出现在了一个没人想过要防御的账单细目中。

确定性种子:为什么供应商将其视为“提示”而非“契约”

· 阅读需 12 分钟
Tian Pan
Software Engineer

CI 测试只有一个断言:相同的模型、相同的温度、相同的提示词、相同的种子(seed)、相同的输出字符串。它在每个开发者的笔记本电脑上都通过了,在前一百次 CI 运行中也通过了,但在三周内每五十次运行就会出现一次随机失败(flake),直到最后有人承认这种模式是真实存在的。第一个假设是显而易见的——测试工具中某处存在非确定性依赖——三天的调查却一无所获。实际原因隐藏在供应商 API 参考文档的一个脚注中:“seed 提供尽力而为的确定性。”团队读到了参数名称,并将其视为一种契约。而供应商记录的只是一个提示。

这是托管推理的一种特定失败模式,它困扰着那些围绕单一心智模型设计测试基础设施的团队:模型是其输入的纯函数,而 seed 是使函数具有可重现性的关键。在生产环境中,这个模型的这两个部分都是错误的。API 表面与底层物理原理之间的差距如此之大,以至于团队在供应商明确否认的假设之上,构建了整个评估和回归测试栈。

你的 Token 预测从未考虑过的重尾效应

· 阅读需 10 分钟
Tian Pan
Software Engineer

你的 AI 功能成本预测是基于一个 50 人的试点项目建模的。那些用户输入了三句话的提示词,因为那是人们在被要求评估测试版时通常会输入的内容。产品上线了,你突破了一万名用户,财务团队指出你的模型账单是计划书中人均成本的三倍。你去寻找 Bug。但根本没有 Bug。你的试点项目是从一个分布中采样,而生产环境是从另一个分布中采样,两者的区别在于一个长尾用户群——他们是在 Twitter 上了解到你的产品,并粘贴了从推文中截取的 30 KB 非结构化上下文。

这是每家消费级互联网公司在 2010 年代都吸取过的同样财务教训,现在被移植到了 LLM 经济学中。试点项目的中位数用户并非生产环境中的 p99.5,而一个使用平均值作为预测输入的 Token 成本模型,在面对账单时注定会一败涂地。

你的编排器在规划步骤上消耗的延迟预算

· 阅读需 12 分钟
Tian Pan
Software Engineer

我上季度合作的一个团队对一个客户支持智能体(Agent)进行了为期一周的埋点分析。从纸面上看,该智能体的中值延迟非常合理:P50 在 SLO 范围内,P95 虽然偏高但尚可解释,工具调用的追踪(traces)看起来也很健康。然而,当有人按类型对 span 进行分桶统计时,全场陷入了沉默。该智能体每次运行的墙钟时间(wall-clock time)中,约有 58% 耗在了标记为“规划(plan)”、“反思(reflect)”、“决定下一步(decide-next-step)”和“自我检查(self-check)”的 span 中。而真正的工具执行——数据库查询、CRM 写入、权限检查——占比不足 30%。这个智能体在核心业务逻辑上花费的精力,竟然比那些没人关注的中间步骤还要少。

这个比例并非偶然。它是任何你不主动监管的“规划-行动-观察(plan-act-observe)”循环的自然状态。编排器(Orchestrator)为了思考和行动支付延迟代价,而增加思考步骤几乎总是比增加行动步骤更容易,因此它会野蛮生长。当你意识到这一点时,“决定下一步做什么”已经变成了一个独立的预算大头——甚至比你最初构建智能体要服务的业务逻辑还要大。

本地化系统提示词:模型表现为何比英文原版更差

· 阅读需 12 分钟
Tian Pan
Software Engineer

你的英文系统提示词(system prompt)花了六周的时间进行调优。一位资深工程师先后四次重写了约束列表,评估套件终于在留存任务集(held-out task set)上跑出了 94% 的通过率,发布检查清单也为生产环境亮了绿灯。随后,国际化(i18n)团队接手,将其放入处理按钮标签和工具提示的相同翻译流水线中,并在下个迭代周期交付了日语、德语、印地语和阿拉伯语版本。针对非英语市场的发布仪表盘显示了相同的任务量、相同的用户转化漏斗,而且——直到六个月后收到东京客户的一张工单——始终保持着代表正常的绿色状态。

东京客户投诉称,智能体忽略了英文提示词中明确禁止的一项指令。你重新阅读了日语提示词,发现从语义上看,两者的意思完全相同。你针对英文变体重新运行了英文评估套件,通过了。但日语变体没有评估套件。从来都没有。

擦除模型仍在读取的上下文:数据保留策略带来的隐患

· 阅读需 13 分钟
Tian Pan
Software Engineer

一个每晚运行的数据留存 worker(retention worker)会删除任何超过 30 天的用户消息。一个从 3 月初开始的长周期企业支持会话,到 5 月底仍然处于活跃状态。在第 41 轮对话请求进来时,你的 Prompt 组装器(prompt assembler)从同一个消息表中读取数据,而那个留存 worker 一直在悄悄地清理这个表。第 1 到 28 轮已经消失了。模型接收到的对话是从第 29 轮开始的,没有任何信号表明之前的对话曾经存在过。用户问道:“我们之前商定的 SLA 是什么?”模型自信地编造了一个数字,因为真正的答案在第 4 轮——而留存 worker 在前一天晚上把它删除了。

这不是模型故障。模型完全按照其应有的方式运行:从交给它的上下文中生成一个看似合理的答案。故障发生在更上游,处于两个团队之间的鸿沟中——每个团队都认为自己拥有消息表。

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

· 阅读需 10 分钟
Tian Pan
Software Engineer

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

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

两个模型对同一结构化输出 Schema 的不同理解

· 阅读需 10 分钟
Tian Pan
Software Engineer

当你的备用路由(fallback route)第一次在生产环境中触发时,绝不是发现两个供应商对你的 schema 定义存在分歧的好时机。在两个客户端配置中,JSON Schema 看起来完全一样。验证器对两个输出都通过了。下游代码按名称读取字段并获取一个值。接着,账单总额以数字字符串而非整数的形式出现,或者长度为一的列表以纯对象而非单元素数组的形式到达,一段已经正常运行了六个月的代码路径会静默地返回错误答案。

结构化输出引人入胜之处在于它消除了一类错误——无法解析的 JSON、幻觉字段、缺失的键——因此让人感觉它彻底解决了解析问题。实际上它所做的是将解析问题向上移动了一层,从词法分析器(lexer)移到了类型系统,在那里问题变得更难被察觉。两个供应商可以都遵循 JSON Schema,但仍然产生不可互换的输出,因为在这个生态系统的角落里,“遵循”至少有四种不同的含义,而你的 schema 并没有指明你想要哪一种。