Skip to main content
幕后花絮
幕后花絮EngineeringDecisions

当我们的 AI 服务商停用了模型

一次模型更换,没有任何错误的空白回复,以及一张突然高出好几倍的账单。哪里坏了,以及为什么原因如此难以发现。

发布于 作者 AlphaCloud4 分钟阅读

2026 年 7 月,多个功能同时停止工作:AI 聊天、工单中的 AI 回复、工单摘要、服务器构建器,还有创意和 bug 报告的审核与翻译。

原因不在我们的代码。我们的 AI 服务商停用了我们正在使用的模型。

第一部分:模型没了

这是显而易见的部分,而且很快就修好了。服务商停止提供一个模型,你填入后继模型,搞定。

真正的事故是在那之后开始的。

第二部分:空白回复,没有错误

更换之后,回复回来了。只是有时候是空的。不是被截断,也不是格式错误,而是一条完全空白的回复,没有任何错误。

原因在于一个模型名称不会透露的差异:后继模型是一个推理模型。在给出真正的答案之前,它会先把内部的思考过程写进一个单独的字段。两者共用同一个 token 预算。

如果预算被思考过程用完了,返回的回复在形式上没有问题:服务商报告“已达到长度上限”,而内容字段是空的。没有错误,没有提示,没有任何 try/catch 能捕捉到的东西。

我们用一次真实测试复现了这个问题:五个 token 的预算就足以让每一个请求都得到空白回复。

第三部分:账单

在代价最高的调用点,也就是 AI 服务器构建器,我们设置了一个宽裕的预算,因为那里会生成很长的回答。所以思考过程没有撞上限制,而是一直运行下去。

后果同时从两个方向袭来。请求耗时太长,以至于超时。而且费用高出好几倍,因为每次都要为一整段看不见的思考过程付费。最后出现了一条我们没有预料到的错误消息:余额不足。

修复只需要一行。可以在请求体中禁用思考,之后模型的表现就又和旧模型一样了。从那以后,这一行属于每一个调用点,而代码库里的调用点远不止寥寥几个。

第四部分:清理时我们发现了什么

既然反正要改动每一个 AI 调用点,你就会仔细看看它们。这让我们发现了第二个问题,它与这次事故无关,但同样昂贵。

对于开头保持不变、可以从缓存中提供的提示词,服务商收取的费用要低得多。命中与未命中之间的差价大约是三十倍。

这个折扣不是靠一个开关就能拿到的。我们专门测试过:请求体中专门为此设计的一个字段毫无效果,缓存反正是自动生效的。唯一的杠杆是提示词内部的顺序,从稳定到易变。

有四个地方我们恰恰反其道而行之:

固定指令块中的易变值。 bug 报告的重复检查在指令文本中包含了模块名称。这让一个原本完全相同的块在每个模块下都有自己的一条缓存线。这个值应该放在可变部分,反正它在那里本来就会变。

新内容放在稳定内容之前。 同一个检查把新的 bug 报告放在多达五十个比较候选项之前发送。这让 API 中最大的提示词永远完全未命中。

固定部分中的按用户数据。 公开 AI 聊天把关于单个用户的笔记放在指令块中,也就是放在共享的频道历史记录之前。这让同一频道中的每个用户都有自己的开头,共享的历史记录也就按用户分别付费。

历史记录上的滑动窗口。 每条消息都发送最近 N 条消息,会让开头在每一条消息时都发生偏移。完整的历史记录于是永远都是未命中。正确的做法是按块修剪,也就是修剪得少,但每次剪得多。

还有一个看起来像吹毛求疵、其实不然的细节:提示词中的一个列表如果只按一个条件排序,在出现并列时顺序可能会变。指令块中间一行顺序的调换,就会让它后面所有内容都失去命中。提示词中的排序需要一个明确的决胜条件。

我们从中学到了什么

更换模型不是配置变更。 名称看起来像变量里的一个值。它背后却是不同的行为,而这种行为破坏的是我们的代码,而不是接口。

危险的故障是静默的那一个。 旧模型的消失立刻就能看到,几分钟内就修好了。空白回复以形式上有效的响应返回,本可能无限期地持续下去。

可测量性应该放在调用点。 现在我们在每个调用点都记录缓存命中率。一个构建正确的调用点在第一次请求后会稳定在较高的命中率,一个构建糟糕的则始终为零。你只能在一行日志中看到这一点,别处都看不到,因为两者都能正常工作。

还有一个令人不安的结论: 使用别人的模型,意味着你产品的一部分取决于别处做出的决定。这就是为什么从八月起,任何服务器都可以把 AI 功能指向它自己的端点,而不必依赖我们的。

更多文章