先说明一下,Gemini 1.5在本文写作时(2024年3月)还是预览版,API和功能还在快速迭代中,出现一些Bug和不稳定是正常的。本文分享的是一次真实的线上Bug排查经历,希望能给大家一些参考和启发。

那天晚上九点多,我正准备下班,手机突然响了,是运维群里的告警:AI对话接口错误率飙升,从平时的0.1%涨到了15%,而且还在持续上升。用户反馈也来了,说AI对话经常出错,有时候返回空内容,有时候返回乱码,有时候直接报错。

我心里一沉,知道今晚又要熬夜了。我们的AI对话服务,用的是Google Gemini 1.5 Pro的API,上线已经一个多月了,一直挺稳定的,怎么突然出问题了?

这篇文章就来分享这次排查的完整过程,从发现问题、定位原因、到最终解决,以及总结的经验教训。

问题初现

那天晚上九点十分,告警第一次触发。我打开监控面板,看到错误率确实在飙升,从下午六点开始慢慢上升,到九点已经到了15%。错误主要集中在Gemini API的调用上,错误类型主要有几种:500 Internal Server Error、503 Service Unavailable、超时、还有一些奇怪的错误,比如返回的内容不完整、格式错误。

我第一反应是:是不是Google那边出问题了?Gemini 1.5还是预览版,不稳定是正常的。我去Google的状态面板看了一下,显示所有服务正常,没有告警。又去开发者社区和Twitter上看了一下,也没有大规模的故障报告。

那就奇怪了,如果不是Google的问题,那就是我们这边的问题?但我们最近没有发版,代码没有变,配置没有变,怎么会突然出问题呢?

我先做了一个简单的测试,用curl直接调用Gemini API,用最简单的Prompt,比如"你好",看看能不能正常返回。测试结果是,大部分时候能正常返回,但偶尔会出错,大概十次里有一两次出错。错误率大概10%-20%,和线上的错误率差不多。

这说明,问题确实出在Gemini API的调用上,但不是完全不可用,而是间歇性的错误。这种间歇性的问题,最难排查了。

第一轮排查:网络问题

我首先怀疑的是网络问题。因为我们的服务器在国内,调用Google的API需要走代理,网络不稳定是常有的事。会不会是代理出问题了?

我检查了代理服务器的状态,CPU、内存、带宽都正常,没有异常。又测试了代理的连通性,ping了一下Google的API域名,延迟正常,丢包率也不高。

但网络问题不一定是连通性的问题,可能是TLS握手的问题,可能是HTTP/2的问题,可能是某些特定的请求触发了网络设备的问题。

我做了一个对比测试:同样的请求,走代理和不走代理(用海外的测试服务器),看看错误率有没有区别。测试结果是,走代理的错误率是15%左右,不走代理的错误率是2%左右。这说明,代理确实有问题,但不是唯一的问题,因为不走代理也有2%的错误率。

我又换了一个代理服务商测试,错误率降到了5%左右,比原来的代理好,但还是比不走代理高。这说明,原来的代理服务商确实有问题,可能是线路不稳定,可能是限流,可能是被Google针对了。

我先临时切换到了备用代理,错误率从15%降到了5%,虽然还没完全解决,但至少缓解了。用户的投诉也少了一些。

但5%的错误率还是太高了,平时只有0.1%。而且,不走代理也有2%的错误率,说明除了代理,还有其他问题。我继续排查。

第二轮排查:Prompt问题

切换代理之后,错误率降到了5%,但还是比平时高。我开始怀疑,是不是某些特定的Prompt触发了Gemini的Bug?

因为Gemini 1.5是预览版,可能对某些特定的输入处理不好,比如超长的上下文、特殊的字符、特定的格式要求,都可能触发Bug。

我开始分析错误日志,看看出错的请求有没有什么共同特征。我把最近一小时出错的1000多条请求的日志都导出来,逐条分析。

分析了半天,还真发现了规律:出错的请求,大部分都有一个共同特征,就是上下文比较长,超过了8000 token。而且,这些请求的输出,大部分都要求输出特定的格式,比如JSON、Markdown表格、代码块。

我做了一个测试:同样的Prompt,短上下文(1000 token)和长上下文(10000 token),看看错误率有没有区别。测试结果是,短上下文的错误率几乎为0,长上下文的错误率是8%左右。这说明,长上下文确实更容易出错。

我又做了一个测试:同样的长上下文,要求输出纯文本和要求输出JSON,看看错误率有没有区别。测试结果是,输出纯文本的错误率是3%,输出JSON的错误率是10%。这说明,要求输出特定格式,也更容易出错。

综合起来,长上下文 + 特定格式输出,错误率最高,能到15%左右。这正好和线上的错误率对上了。

那为什么之前没有这个问题,今天突然出现了呢?我查了一下Google的更新日志,发现Google在今天下午更新了Gemini 1.5 Pro的模型版本,从preview-0215更新到了preview-0305。新版本可能在长上下文和格式输出方面有Bug,导致错误率上升。

这就解释了为什么之前没问题,今天突然出问题了:模型更新了,新版本有Bug。

第三轮排查:流式输出问题

定位到是长上下文和格式输出的问题之后,我继续深入排查,想找到更具体的原因。

我发现,出错的请求,大部分都是用流式输出(streaming)的。我们的AI对话服务,为了提升用户体验,用的是流式输出,让用户能实时看到生成的内容。

我做了一个测试:同样的长上下文+JSON输出的请求,用流式输出和不用流式输出,看看错误率有没有区别。测试结果是,流式输出的错误率是15%,非流式输出的错误率是4%。这说明,流式输出更容易出错。

我又仔细分析了流式输出的错误日志,发现了一个规律:流式输出的错误,大部分不是一开始就报错,而是输出到一半的时候出错。比如,输出了一半的内容,然后突然断了,或者返回了一个错误码,或者输出的内容突然变成了乱码。

这说明,问题可能出在流式输出的传输过程中,而不是模型生成的过程中。可能是长上下文+特定格式的输出,导致模型生成的内容比较长,传输过程中出了问题。

我又分析了流式输出的SSE(Server-Sent Events)数据,发现了一个问题:当输出的内容包含某些特殊字符的时候(比如中文的标点符号、emoji、特殊符号),SSE数据的格式会出错,比如一条数据被拆成了两条,或者数据里多了一些奇怪的字符。

这可能是Google的API在处理流式输出的时候,对某些特殊字符的编码有问题,导致SSE格式错误,前端解析失败,就报错了。

我又做了一个测试:同样的请求,输出内容里有特殊字符和没有特殊字符,看看错误率有没有区别。测试结果是,有特殊字符的错误率是18%,没有特殊字符的错误率是5%。这说明,特殊字符确实会增加错误率。

综合起来,问题的根源是:Gemini 1.5 Pro新版本(preview-0305)在长上下文+特定格式输出+流式输出+特殊字符的情况下,会出现Bug,导致错误率上升。

临时解决方案

找到问题的原因之后,我开始想解决方案。因为是Google那边的Bug,我们没办法直接修,只能想办法绕过。

第一个方案是降级模型版本。Google的API支持指定模型版本,我们可以指定用旧版本preview-0215,而不是用最新的preview-0305。我测试了一下,旧版本的错误率确实很低,只有0.2%左右,和平时差不多。

但问题是,旧版本可能会被Google废弃,而且旧版本没有新版本的一些功能和优化。所以,降级只是临时方案,不能长期用。

第二个方案是关闭流式输出。既然流式输出更容易出错,那就关闭流式输出,用非流式输出。非流式输出的错误率只有4%,比流式输出低很多。但问题是,非流式输出用户体验差,用户要等很久才能看到结果,特别是长内容的生成,可能要等几十秒。

第三个方案是限制上下文长度。既然长上下文更容易出错,那就限制上下文长度,比如最多8000 token,超过的话就裁剪。这样能降低错误率,但问题是,上下文短了,对话的连贯性和质量会下降,用户体验也会受影响。

第四个方案是优化输出格式。既然特定格式输出更容易出错,那就不要强制要求输出JSON,而是让模型输出纯文本,然后我们自己解析。或者,用function calling来代替纯文本的JSON输出,function calling的格式更稳定,错误率更低。

综合考虑之后,我采取了一个组合方案:

  1. 临时降级到旧版本preview-0215,先把错误率降下来,保证服务稳定。
  2. 同时,在新版本上测试各种优化方案,找到最佳实践。
  3. 对于长上下文的请求,用滑动窗口+摘要的方式,控制上下文长度,同时保留重要信息。
  4. 对于需要结构化输出的请求,改用function calling,而不是纯文本的JSON输出。
  5. 对于流式输出,增加容错机制,比如断线重连、断点续传、输出格式校验,出错的时候自动降级为非流式输出。

实施了这个组合方案之后,线上的错误率降到了0.3%左右,基本恢复了正常。用户的投诉也没有了。

那时候已经是凌晨四点多了,我终于松了一口气。

后续跟进

问题解决之后,我没有就此罢休,而是做了后续的跟进。

第一,我给Google的支持团队提交了Bug报告,详细描述了问题的现象、复现步骤、测试结果,希望他们能尽快修复。Google的响应还挺快的,第二天就回复了,确认是新版本的Bug,说会在接下来的更新中修复。

第二,我完善了监控和告警。之前的监控太简单了,只监控了整体的错误率,没有细分到模型版本、上下文长度、输出格式、是否流式等维度。这次之后,我增加了多维度的监控,能更精细地发现问题,更快地定位原因。

第三,我完善了降级和容错机制。之前的服务,一旦API出问题,就直接报错,没有降级方案。这次之后,我增加了多模型备份,主模型出问题的时候,自动切换到备用模型(比如GPT-4、Claude);增加了输出格式校验,格式不对的时候自动重试或者降级;增加了熔断机制,错误率太高的时候自动熔断,保护服务。

第四,我总结了这次排查的经验,写了一份文档,分享给团队的其他同学。包括AI接口排查的方法论、常见问题和解决方案、监控和告警的最佳实践等。希望大家以后遇到类似问题,能更快地定位和解决。

大概一周之后,Google更新了模型版本,修复了这个Bug。我们测试了一下,新版本的错误率降到了0.1%以下,和旧版本差不多了。我们就切回了新版本,同时保留了旧版本作为备份。

这次排查,虽然熬了一夜,很辛苦,但收获也很大。不仅解决了问题,还完善了服务的稳定性,积累了AI接口排查的经验。

经验教训

这次排查,总结了几条经验教训,分享给大家:

第一,AI接口的稳定性,不能只靠供应商。大模型API还在快速迭代中,版本更新频繁,Bug和不稳定是常态。不能完全依赖供应商的稳定性,自己要有备份和降级方案。多模型备份、自动降级、熔断机制,这些都是必须的。

第二,监控要多维度、精细化。只监控整体的错误率是不够的,要细分到模型版本、接口类型、上下文长度、输出格式、是否流式、用户群体等多个维度。这样才能更快地发现问题,更准地定位原因。这次排查,如果一开始就有多维度的监控,就能更快地定位到是长上下文+格式输出+流式的问题,不用花那么多时间。

第三,排查问题要有方法论。遇到问题不要慌,不要瞎猜,要有系统的排查方法。先收集信息(日志、监控、用户反馈),然后提出假设,然后逐一验证,排除不可能的,找到真正的原因。这次排查,就是按照这个方法,从网络问题到Prompt问题,再到流式输出问题,一步步缩小范围,最终定位到原因。

第四,要做好版本管理。大模型API的版本更新很快,新版本可能有新功能,也可能有新Bug。不要盲目追新,不要自动更新到最新版本。要固定版本,测试没问题之后再更新。而且,要保留旧版本作为备份,新版本出问题的时候能快速回滚。

第五,要和供应商保持沟通。遇到问题,不要自己扛着,要及时和供应商的支持团队沟通,提交Bug报告,反馈问题。供应商通常会重视用户的反馈,会尽快修复问题。而且,和供应商保持良好的沟通,能提前了解版本更新的信息,提前做好准备。

第六,要做好文档和知识沉淀。每次排查问题之后,都要总结经验,写文档,分享给团队。这样,团队的整体能力才能提升,以后遇到类似问题,才能更快地解决。不要让经验只停留在个人脑子里,要沉淀下来,成为团队的财富。

写在最后

这次线上Bug排查,从晚上九点到凌晨四点,整整七个小时,确实很辛苦。但现在回头看,觉得很值得。

AI大模型是一个快速发展的领域,新技术、新模型、新功能层出不穷。作为开发者,我们在享受AI带来的便利的同时,也要面对AI带来的新挑战:接口不稳定、版本更新快、Bug难以排查、成本控制难等。这些挑战,是以前传统开发没有遇到过的,需要我们不断学习,不断积累,不断适应。

这次排查,让我深刻地认识到,AI应用的开发,不仅仅是调个API那么简单。要做好一个稳定、可靠、高性能的AI应用,需要在监控、降级、容错、版本管理、成本控制等很多方面下功夫。这些基础工作,虽然不那么光鲜亮丽,但却是AI应用稳定运行的基石。

希望这篇文章能给正在做AI应用开发的朋友一些参考和启发。如果你们也遇到过类似的问题,或者有更好的解决方案,欢迎在评论区交流,一起学习,一起进步。

最后,愿我们的AI应用都能稳定运行,少出Bug,少熬夜。

凌晨四点的城市,很安静。我关了电脑,准备回家睡觉。明天,又是新的一天,还有新的问题等着我去解决。但我不怕,因为每一次解决问题,都是一次成长。

做开发,就是这样,痛并快乐着。