소스 검색

错误的时候添加了更多的信息

ChenYu 7 달 전
부모
커밋
e1f5e7b5ce

+ 41 - 0
README.md

@@ -483,6 +483,47 @@ for message in pubsub.listen():
 | base64_encode | Base64 编码 |
 | base64_decode | Base64 解码 |
 
+## 任务提交重试机制
+
+所有调用外部 API Key 的任务在**提交阶段**内置了自动重试,采用指数退避策略。轮询(polling)阶段不受影响。
+
+### 涉及的任务
+
+| 任务 | API Key | 最大重试次数 | 退避基数 |
+|------|---------|:----------:|:-------:|
+| kontext_pro | `kontext_api_key` | 3 | 2s |
+| kontext_max | `kontext_api_key` | 3 | 2s |
+| seedream_image | `seed_api_key` | 3 | 2s |
+| seedance_video | `seed_api_key` | 3 | 2s |
+| nano_banana | `gemini_api_key` | 3 | 2s |
+| google_veo3 | `gemini_api_key` | 3 | 2s |
+| keling_image2video | `keling_app_key/secret` | 5 | 3s |
+| keling_video2effect | `keling_app_key/secret` | 5 | 3s |
+
+> 不涉及外部 API Key 的本地任务(smart_beauty、image_editor、video_editor、file_download、file_upload、base64_encode、base64_decode)不启用提交重试。
+
+### 重试触发条件
+
+- HTTP 请求异常(网络错误、超时等)
+- HTTP 状态码非 2xx
+- API 返回业务级错误(如空响应、缺少任务 ID、错误码等)
+
+### 退避策略
+
+等待时间 = `backoff_base ^ attempt`(秒),例如退避基数为 2s 时:
+
+| 重试次数 | 等待时间 |
+|:--------:|:-------:|
+| 第 1 次 | 2s |
+| 第 2 次 | 4s |
+| 第 3 次 | 8s |
+
+可灵系列任务因存在频率限制(错误码 1302/1303),配置了更高的重试次数(5 次)和更长的退避基数(3s)。
+
+### 实现
+
+重试逻辑由 `app/util/retry_util.py` 中的 `retry_on_failure` 函数提供,各任务将提交逻辑封装在 `_submit()` 方法中,提交不成功时抛出异常以触发重试。
+
 ## 配置说明
 
 所有配置通过环境变量注入,前缀为 `AI_FLOW_`。详见 `.env.example`。

BIN
app/service/task/impl/__pycache__/google_veo3_task.cpython-312.pyc


BIN
app/service/task/impl/__pycache__/keling_image2video_task.cpython-312.pyc


BIN
app/service/task/impl/__pycache__/kontext_pro_task.cpython-312.pyc


BIN
app/service/task/impl/__pycache__/nano_banana_task.cpython-312.pyc


BIN
app/service/task/impl/__pycache__/seedance_video_task.cpython-312.pyc


BIN
app/service/task/impl/__pycache__/seedream_image_task.cpython-312.pyc


+ 2 - 1
app/service/task/impl/google_veo3_task.py

@@ -55,7 +55,8 @@ class GoogleVeo3Task(BaseTask):
         with logged_client(timeout=120) as client:
             resp = client.post(endpoint, headers=self._headers(), json=inputs)
             logger.info("[google_veo3] Submit response: status=%d", resp.status_code)
-            resp.raise_for_status()
+            if not resp.is_success:
+                raise RuntimeError(f"HTTP {resp.status_code}: {resp.text}")
             data = resp.json()
         op_name = data.get("name")
         if not op_name:

+ 2 - 1
app/service/task/impl/keling_image2video_task.py

@@ -44,7 +44,8 @@ class KelingImage2VideoTask(BaseTask):
         with logged_client(timeout=60) as client:
             resp = client.post(self.execute_url, headers=self._headers(), json=inputs)
             logger.info("[%s] Submit response: status=%d", self.get_type(), resp.status_code)
-            resp.raise_for_status()
+            if not resp.is_success:
+                raise RuntimeError(f"HTTP {resp.status_code}: {resp.text}")
             bean = resp.json()
         code = bean.get("code", 0)
         if code in (1302, 1303):

+ 2 - 1
app/service/task/impl/kontext_pro_task.py

@@ -29,7 +29,8 @@ class KontextProTask(BaseTask):
         with logged_client(timeout=60) as client:
             resp = client.post(self.execute_url, headers=self._headers(), json=inputs)
             logger.info("[%s] Submit response: status=%d", self.get_type(), resp.status_code)
-            resp.raise_for_status()
+            if not resp.is_success:
+                raise RuntimeError(f"HTTP {resp.status_code}: {resp.text}")
             bean = resp.json()
         if not bean:
             raise RuntimeError("Empty response from API")

+ 2 - 1
app/service/task/impl/nano_banana_task.py

@@ -20,7 +20,8 @@ class NanoBananaTask(BaseTask):
             resp = client.post(endpoint, headers=headers, json=inputs)
             logger.info("[nano_banana] Submit response: status=%d, body_size=%d",
                         resp.status_code, len(resp.content))
-            resp.raise_for_status()
+            if not resp.is_success:
+                raise RuntimeError(f"HTTP {resp.status_code}: {resp.text}")
             data = resp.json()
         if not data.get("candidates"):
             raise RuntimeError("No candidates in response")

+ 2 - 1
app/service/task/impl/seedance_video_task.py

@@ -25,7 +25,8 @@ class SeedanceVideoTask(BaseTask):
         with logged_client(timeout=60) as client:
             resp = client.post(url, headers=self._headers(), json=inputs)
             logger.info("[seedance_video] Submit response: status=%d", resp.status_code)
-            resp.raise_for_status()
+            if not resp.is_success:
+                raise RuntimeError(f"HTTP {resp.status_code}: {resp.text}")
             data = resp.json()
         task_id = data.get("id")
         if not task_id:

+ 2 - 1
app/service/task/impl/seedream_image_task.py

@@ -25,7 +25,8 @@ class SeedreamImageTask(BaseTask):
         with logged_client(timeout=60) as client:
             resp = client.post(url, headers=self._headers(), json=inputs)
             logger.info("[seedream_image] Submit response: status=%d", resp.status_code)
-            resp.raise_for_status()
+            if not resp.is_success:
+                raise RuntimeError(f"HTTP {resp.status_code}: {resp.text}")
             data = resp.json()
         if "error" in data:
             err = data["error"]