From 09453e71b9fd86942065e92ebc90b02dcb77ff47 Mon Sep 17 00:00:00 2001 From: Chenyme <118253778+chenyme@users.noreply.github.com> Date: Fri, 17 Oct 2025 13:42:08 +0800 Subject: [PATCH] feat: add automatic retry mechanism --- app/services/grok/client.py | 59 ++++++++++++++++++++++++++----------- app/services/grok/token.py | 9 ++++-- 2 files changed, 48 insertions(+), 20 deletions(-) diff --git a/app/services/grok/client.py b/app/services/grok/client.py index ef5b4b7..384638f 100644 --- a/app/services/grok/client.py +++ b/app/services/grok/client.py @@ -19,6 +19,7 @@ from app.core.exception import GrokApiException GROK_API_ENDPOINT = "https://grok.com/rest/app-chat/conversations/new" REQUEST_TIMEOUT = 120 IMPERSONATE_BROWSER = "chrome133a" +MAX_RETRY = 3 # 最大重试次数 class GrokClient: @@ -35,31 +36,55 @@ class GrokClient: # 提取消息内容和图片URL content, image_urls = GrokClient._extract_content(messages) - - # 获取认证令牌和模型信息 - auth_token = token_manager.get_token(model) model_name, model_mode = Models.to_grok(model) - - # 检查是否为视频模型 is_video_model = Models.get_model_info(model).get("is_video_model", False) - # 视频模型特殊处理:只允许一张图片 - if is_video_model and len(image_urls) > 1: - logger.warning(f"[Client] 视频模型只允许一张图片,当前有{len(image_urls)}张,只使用第一张") - image_urls = image_urls[:1] - - # 上传图片并获取附件ID列表 - image_attachments = await GrokClient._upload_imgs(image_urls, auth_token) - - # 视频模型:文本添加 --mode=custom + # 视频模型特殊处理 if is_video_model: + if len(image_urls) > 1: + logger.warning(f"[Client] 视频模型只允许一张图片,当前有{len(image_urls)}张,只使用第一张") + image_urls = image_urls[:1] content = f"{content} --mode=custom" logger.debug(f"[Client] 视频模型文本处理: {content}") - # 构建Grok请求载荷 - payload = GrokClient._build_payload(content, model_name, model_mode, image_attachments, is_video_model) + # 重试逻辑 + return await GrokClient._try(model, content, image_urls, model_name, model_mode, is_video_model, stream) - return await GrokClient._send_request(payload, auth_token, model, stream) + @staticmethod + async def _try(model: str, content: str, image_urls: List[str], model_name: str, model_mode: str, is_video: bool, stream: bool): + """带重试的请求执行""" + last_err = None + + for i in range(MAX_RETRY): + try: + # 获取token + auth_token = token_manager.get_token(model) + + # 上传图片 + imgs = await GrokClient._upload_imgs(image_urls, auth_token) + + # 构建并发送请求 + payload = GrokClient._build_payload(content, model_name, model_mode, imgs, is_video) + return await GrokClient._send_request(payload, auth_token, model, stream) + + except GrokApiException as e: + last_err = e + # 401/429 可重试,其他错误直接抛出 + if e.error_code not in ["HTTP_ERROR", "NO_AVAILABLE_TOKEN"]: + raise + + # 检查是否为可重试的状态码 + status = e.context.get("status") if e.context else None + if status not in [401, 429]: + raise + + if i < MAX_RETRY - 1: + logger.warning(f"[Client] 请求失败(状态码:{status}), 重试 {i+1}/{MAX_RETRY}") + await asyncio.sleep(0.5) # 短暂延迟 + else: + logger.error(f"[Client] 重试{MAX_RETRY}次后仍失败") + + raise last_err if last_err else GrokApiException("请求失败", "REQUEST_ERROR") @staticmethod def _extract_content(messages: List[Dict]) -> Tuple[str, List[str]]: diff --git a/app/services/grok/token.py b/app/services/grok/token.py index ac62222..77583d1 100644 --- a/app/services/grok/token.py +++ b/app/services/grok/token.py @@ -320,7 +320,7 @@ class GrokTokenManager: 错误码说明: - 401: SSO Token失效,会标记Token为expired - - 403: x-statsig-id失效,不影响Token状态 + - 403: 服务器IP被Block,不影响Token状态 Args: auth_token: 完整的认证Token (格式: sso-rw=xxx;sso=xxx) @@ -328,9 +328,12 @@ class GrokTokenManager: error_message: 错误信息 """ try: - # 403错误是x-statsig-id失效,不是Token问题 + # 403错误是服务器IP被Block,不是Token问题 if status_code == STATSIG_INVALID_CODE: - logger.warning(f"[Token] x-statsig-id失效 (403),需要更新配置文件中的x_statsig_id") + logger.warning( + f"[Token] 服务器IP被Block (403),请 1. 更换服务器IP 2. 使用代理IP " + f"3. 服务器登陆Grok.com,过盾后F12找到CF值填入后台设置" + ) return sso_value = self._extract_sso(auth_token)