技术分享 | ChatGPT API 调用总超时?破题思路在这
问题重现
在调用 ChatGPT API 并使用流式输出时,我们经常会遇到网络问题导致的超时情况。有趣的是,笔者发现在本地调试遇到的超时,会在 10 分钟后自动恢复(为什么是 10 分钟?我们留到后面解释),但是在服务器上等待一会儿却会失败,报出超时异常(错误代码 502)。
笔者认为,本地能恢复的原因可能是自动重试,只是重试的时间有点久(ChatGPT API 没有重试功能,这是项目加入的)。服务器返回「502」是因为内容从后台返回到前端需要经过网关层,而网关层超时校验的时间比自动重试的时间(10 分钟)更短,所以撑不到重试就会报超时异常。
基于以上场景,本文着手解决 ChatGPT API 调用超时问题。
优化诉求
-
不向用户展示超时的报错信息。
-
缩短超时后重试的时间间隔。
解决思路
笔者考虑了两种方案。
一是彻底解决网络问题,但难度有点大。 这属于 OpenAI 服务器问题,即使是部署在国外的服务器也会出现超时的情况。
二是利用自动重试解决问题。 通过调整超时的时间,提升响应速度,方案可行。
实施解决方案
解决过程中,笔者分两步由浅至深地调整了超时时间;如果想直接了解最终方案,请移步「解决方案二」~
- 运行环境:
Python: 3.10.7
openai: 0.27.6
- 调用方法:
openai.api_resources.chat_completion.ChatCompletion.acreate
( 这是异步调用 ChatGPT 的方法。)
- 方法调用链路:
超时参数 ClientTimeout
,一共有 4 个属性 total
、connect
、sock_read
和 sock_connect
。
# 方法 -> 超时相关参数 openai.api_resources.chat_completion.ChatCompletion.acreate -> kwargs openai.api_resources.abstract.engine_api_resource.EngineAPIResource.acreate -> params openai.api_requestor.APIRequestor.arequest -> request_timeout # request_timeout 在这一步变成了 timeout,因此,只需要传参 request_timeout 即可 openai.api_requestor.APIRequestor.arequest_raw -> request_timeout aiohttp.client.ClientSession.request -> kwargs aiohttp.client.ClientSession._request -> timeout tm = TimeoutHandle(self._loop, real_timeout.total) -> ClientTimeout.total async with ceil_timeout(real_timeout.connect): -> ClientTimeout.connect # 子分支1 aiohttp.connector.BaseConnector.connect -> timeout aiohttp.connector.TCPConnector._create_connection -> timeout aiohttp.connector.TCPConnector._create_direct_connection -> timeout aiohttp.connector.TCPConnector._wrap_create_connection -> timeout async with ceil_timeout(timeout.sock_connect): -> ClientTimeout.sock_connect # 子分支2 aiohttp.client_reqrep.ClientRequest.send -> timeout aiohttp.client_proto.ResponseHandler.set_response_params -> read_timeout aiohttp.client_proto.ResponseHandler._reschedule_timeout -> self._read_timeout if timeout: self._read_timeout_handle = self._loop.call_later( timeout, self._on_read_timeout ) -> ClientTimeout.sock_read
解决方案一
openai.api_requestor.APIRequestor.arequest_raw
方法中的 request_timeout
参数可以传递 connect
和 total
参数.
因此可以在调用 openai.api_resources.chat_completion.ChatCompletion.acreate
时,设置 request_time(10, 300)
。
# async def arequest_raw( self, method, url, session, *, params=None, supplied_headers: Optional[Dict[str, str]] = None, files=None, request_id: Optional[str] = None, request_timeout: Optional[Union[float, Tuple[float, float]]] = None, ) -> aiohttp.ClientResponse: abs_url, headers, data = self._prepare_request_raw( url, supplied_headers, method, params, files, request_id ) if isinstance(request_timeout, tuple): timeout = aiohttp.ClientTimeout( connect=request_timeout[0], total=request_timeout[1], )else: timeout = aiohttp.ClientTimeout( total=request_timeout if request_timeout else TIMEOUT_SECS ) ...
该方案有效,但没有完全生效:它可以控制连接时间和请求的全部时间,但没有彻底解决超时异常,因为「请求连接时间」和「第一个字符读取时间」是两码事。「请求连接时间」基于 total
时间重试(300s),而网关时间并没有设置这么久。
于是,笔者继续提出「解决方案二」。
解决方案二
使用 monkey_patch
方式重写 openai.api_requestor.APIRequestor.arequest_raw
方法,重点在于重写 request_timeout 参数,让其支持原生的 aiohttp.client.ClientTimeout
参数。
1. 新建 api_requestor_mp.py
文件,并写入以下代码。
# 注意 request_timeout 参数已经换了,Optional[Union[float, Tuple[float, float]]] -> Optional[Union[float, tuple]] async def arequest_raw( self, method, url, session, *, params=None, supplied_headers: Optional[Dict[str, str]] = None, files=None, request_id: Optional[str] = None, request_timeout: Optional[Union[float, tuple]] = None, ) -> aiohttp.ClientResponse: abs_url, headers, data = self._prepare_request_raw( url, supplied_headers, method, params, files, request_id ) # 判断 request_timeout 的类型,按需设置 sock_read 和 sock_connect 属性 if isinstance(request_timeout, tuple): timeout = aiohttp.ClientTimeout( connect=request_timeout[0], total=request_timeout[1], sock_read=None if len(request_timeout) < 3 else request_timeout[2], sock_connect=None if len(request_timeout) < 4 else request_timeout[3], ) else: timeout = aiohttp.ClientTimeout( total=request_timeout if request_timeout else TIMEOUT_SECS ) if files: # TODO: Use aiohttp.MultipartWriter to create the multipart form data here. # For now we use the private requests method that is known to have worked so far. data, content_type = requests.models.RequestEncodingMixin._encode_files( # type: ignore files, data ) headers["Content-Type"] = content_type request_kwargs = { "method": method, "url": abs_url, "headers": headers, "data": data, "proxy": _aiohttp_proxies_arg(openai.proxy), "timeout": timeout, } try: result = await session.request(**request_kwargs) util.log_info( "OpenAI API response", path=abs_url, response_code=result.status, processing_ms=result.headers.get("OpenAI-Processing-Ms"), request_id=result.headers.get("X-Request-Id"), ) # Don't read the whole stream for debug logging unless necessary. if openai.log == "debug": util.log_debug( "API response body", body=result.content, headers=result.headers ) return result except (aiohttp.ServerTimeoutError, asyncio.TimeoutError) as e: raise error.Timeout("Request timed out") from e except aiohttp.ClientError as e: raise error.APIConnectionError("Error communicating with OpenAI") from e def monkey_patch(): APIRequestor.arequest_raw = arequest_raw
2. 在初始化 ChatGPT API 的文件头部补充:
from *.*.api_requestor_mp import monkey_patch do_api_requestor = monkey_patch
设置参数 request_timeout=(10, 300, 15, 10)
后,再调试就没什么问题了。
交付测试,通过。
经验总结
-
直接看代码、看方法调用链路会有点困难,可以通过异常堆栈来找调用链路,这样更方便。
-
ChatGPT API 暴露的
request_timeout
参数不够用,需要重写;搜索了一下重写方案,了解到monkey_patch
,非常实用。 -
项目过程中,笔者发现改代码本身不难,难的是知道「改哪里」「怎么改」以及「为什么」。
了解更多技术干货、研发管理实践等分享,请关注 LigaAI。LigaAI-智能研发协作平台,欢迎体验智能研发协作,期待与你一起变大变强!

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
一文搞懂得物前端监控
一、背景 得物的服务端监控是比较全面和有效的,除了上报原始日志数据,还通过数据分析制定线上告警机制,调用链路分析,而针对前端项目这一块,还是不够全面的。对前端线上问题感应不及时,靠人肉发现,没有告警机制等问题,所以就有个前端监控这个项目。前端监控也确实很有必要,我们需要对线上的页面有个全面的把控,而至于怎么做监控,做数据上报,以及数据分析,如何针对监控数据分析出有用的核心链路的告警等也能有个全面的认识。本文主要是介绍得物针对监控做了哪些事情以及对前端底层监控手段做个总结。 二、监控类型 前端监控的范围很广,如监控性能,监控异常,监控告警等一系列的维度来确保我们的页面和功能是正常的,在出现问题时研发可以及时做出响应,及时追踪,定位问题。 性能监控 目前前端平台在性能监控和异常监控是分开的,性能监控有专门 SDK,去做上报和分析,联合得物 App 端上一起做了性能优化和数据分析,这块是独立的。主要包括性能的上报,以及性能的优化手段。下图是前端性能监控方面的数据展示效果。 总体来说性能监控是做的比较大的,而且监控对提高页面的秒开是有实际优化指导意义的,离线包、预请求等都是优化手段。 异常告警...
- 下一篇
DBA的必备指南:解决数据库慢查询
对于一个 DBA 来说,从公司数据库系统的稳定程度可以看出他的能力几何,一个优秀的 DBA 不仅要保证数据库的稳定运行,还需要有能力有效处理数据库的各种突发性能问题,而最常见的性能问题,就是慢查询。 我们公司规模比较大,大大小小的项目加起来超过 100 个数据库实例,而保证这些数据库的稳定运行的重任,全部在我这里,每天都需要事无巨细地逐个排查所有问题,这是一项相当艰巨的任务,出点差错是常事,造成的影响不大还好说,如果涉及到企业利益,那就祈祷下一家公司没有那么多数据库吧。 太难了 由于每个数据库实例中都存在不少的慢查询问题,靠我人肉排查显然不太现实,在尝试了很多种自动化工具之后,终于艰难玉成,让我找到一款完美解决我问题的工具:NineData 的慢查询分析功能。它可以自动采集并记录数据库中的所有慢查询,比较亮眼的是它通过对每一条慢查询进行性能诊断,最终提供优化建议,包含添加或修改索引、调整表结构等,同时还可以根据业务类型配置 SQL 开发规范,配置完成后,系统还会基于这些规范诊断慢 SQL。 废话不多说,直接上图。 慢查询趋势图 这是单个数据源维度的慢查询趋势图,首先能看到一个时间范围内...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS8编译安装MySQL8.0.19
- CentOS6,CentOS7官方镜像安装Oracle11G
- CentOS7,8上快速安装Gitea,搭建Git服务器
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- MySQL8.0.19开启GTID主从同步CentOS8
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Red5直播服务器,属于Java语言的直播服务器
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7