feat: 错误提示中文化、GPT Image 余额查询、Nano Banana 自动重试

- gemini_client: 更新 429/504 中文错误文案,与产品文案对齐
- gpt_image_client: 新增 query_balance_sync / format_balance_info 余额查询方法
- gpt_image: 每次执行后打印余额日志(finally 块保证触发)
- nano_banana_pro: 单张生成支持自动重试,遇 429/503/504 最多重试 4 次,指数退避 2-32s,终端显示显眼重试状态
- nano_banana_pro: 关闭调试日志(DEBUG_LOG_ENABLED = False)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
This commit is contained in:
o1key
2026-04-22 18:10:35 +08:00
co-authored by Claude Sonnet 4.5
parent 949f7bb180
commit fe3cc65b71
4 changed files with 127 additions and 66 deletions
+60 -48
View File
@@ -141,20 +141,44 @@ class O1keyGPTImage:
raise ValueError("未授权!") from None
raise
# ── 4. 解析批量提示词 ─────────────────────────────────────────────────
batch_prompts = parse_batch_prompts(prompt)
try:
# ── 4. 解析批量提示词 ─────────────────────────────────────────────
batch_prompts = parse_batch_prompts(prompt)
# ── 5. 调用 API ───────────────────────────────────────────────────────
all_pil_images = []
# ── 5. 调用 API ───────────────────────────────────────────────────
all_pil_images = []
if batch_prompts:
# 批量模式:逐条提示词调用
total = len(batch_prompts)
print(f"[o1key GPT Image] 批量模式 | {total} 条提示词 | 每条生成 {生图数量}")
for idx, p in enumerate(batch_prompts, 1):
if batch_prompts:
# 批量模式:逐条提示词调用
total = len(batch_prompts)
print(f"[o1key GPT Image] 批量模式 | {total} 条提示词 | 每条生成 {生图数量}")
for idx, p in enumerate(batch_prompts, 1):
try:
pil_images = client.run_sync(
prompt=p,
model=模型,
quality=quality,
background="auto",
size=size,
n=生图数量,
seed=seed,
image_tensor=图片,
mask_tensor=遮罩,
)
all_pil_images.extend(pil_images)
snippet = p[:30] + ("..." if len(p) >= 30 else "")
print(f"[o1key GPT Image] [{idx}/{total}] ✓ {snippet}")
except Exception as e:
error_msg = str(e).split('\n')[0]
snippet = p[:30] + ("..." if len(p) >= 30 else "")
print(f"[o1key GPT Image] [{idx}/{total}] ❌ {snippet}{error_msg}")
else:
# 单提示词模式
if not prompt or not prompt.strip():
raise ValueError("提示词不能为空")
try:
pil_images = client.run_sync(
prompt=p,
prompt=prompt,
model=模型,
quality=quality,
background="auto",
@@ -165,47 +189,35 @@ class O1keyGPTImage:
mask_tensor=遮罩,
)
all_pil_images.extend(pil_images)
snippet = p[:30] + ("..." if len(p) >= 30 else "")
print(f"[o1key GPT Image] [{idx}/{total}] ✓ {snippet}")
except Exception as e:
error_msg = str(e).split('\n')[0]
snippet = p[:30] + ("..." if len(p) >= 30 else "")
print(f"[o1key GPT Image] [{idx}/{total}] ❌ {snippet}{error_msg}")
else:
# 单提示词模式
if not prompt or not prompt.strip():
raise ValueError("提示词不能为空")
try:
pil_images = client.run_sync(
prompt=prompt,
model=模型,
quality=quality,
background="auto",
size=size,
n=生图数量,
seed=seed,
image_tensor=图片,
mask_tensor=遮罩,
)
all_pil_images.extend(pil_images)
except Exception as e:
error_msg = str(e).split('\n')[0]
print(f"[o1key GPT Image] ❌ {error_msg}")
raise RuntimeError(error_msg) from None
print(f"[o1key GPT Image] ❌ {error_msg}")
raise RuntimeError(error_msg) from None
# ── 6. 检查是否有可用图像 ─────────────────────────────────────────────
if not all_pil_images:
raise RuntimeError("所有提示词均生成失败,无可用图像输出")
# ── 6. 检查是否有可用图像 ─────────────────────────────────────────
if not all_pil_images:
raise RuntimeError("所有提示词均生成失败,无可用图像输出")
# ── 7. PIL → tensor ───────────────────────────────────────────────────
output_tensor = GptImageClient._pil_list_to_tensor(all_pil_images)
# ── 7. PIL → tensor ───────────────────────────────────────────────
output_tensor = GptImageClient._pil_list_to_tensor(all_pil_images)
# ── 8. 完成日志 ───────────────────────────────────────────────────────
elapsed = time.time() - start_time
print(
f"[o1key GPT Image] 完成!耗时 {elapsed:.1f}s"
f"输出 {output_tensor.shape[0]}"
f"{output_tensor.shape[2]}×{output_tensor.shape[1]}"
)
# ── 8. 完成日志 ───────────────────────────────────────────────────
elapsed = time.time() - start_time
print(
f"[o1key GPT Image] 完成!耗时 {elapsed:.1f}s"
f"输出 {output_tensor.shape[0]}"
f"{output_tensor.shape[2]}×{output_tensor.shape[1]}"
)
return (output_tensor,)
return (output_tensor,)
finally:
self._print_balance(client)
def _print_balance(self, client):
try:
balance_data = client.query_balance_sync()
balance_info = client.format_balance_info(balance_data)
print(f"[o1key GPT Image] {balance_info}")
except Exception:
pass
+32 -15
View File
@@ -52,7 +52,7 @@ except ImportError:
# ============================================================================
# 是否启用调试日志(打印完整的 API 响应内容)
# 设置为 True 以启用调试日志,False 以禁用
DEBUG_LOG_ENABLED = True
DEBUG_LOG_ENABLED = False
# 是否启用请求体日志(打印发送给 API 的请求体,base64 图片数据将自动截断)
# 设置为 True 以启用请求体日志,False 以禁用
REQUEST_LOG_ENABLED = False
@@ -665,20 +665,37 @@ class NanoBananaPro:
else:
# 单提示词模式
if 生图数量 == 1:
# 单张:同步生成,输出 tensor
generated_images = self.client.generate_sync(
prompt=prompt,
model=模型,
resolution=分辨率,
aspect_ratio=宽高比,
batch_size=1,
images=input_images,
progress_callback=progress_callback,
debug=DEBUG_LOG_ENABLED,
debug_request=REQUEST_LOG_ENABLED,
enable_grounding=enable_grounding,
enable_image_search=enable_image_search,
)
# 单张:同步生成,自动重试(429/503/504
_RETRY_CODES = ("429", "503", "504")
_MAX_RETRIES = 5
for _attempt in range(1, _MAX_RETRIES + 1):
try:
generated_images = self.client.generate_sync(
prompt=prompt,
model=模型,
resolution=分辨率,
aspect_ratio=宽高比,
batch_size=1,
images=input_images,
progress_callback=progress_callback,
debug=DEBUG_LOG_ENABLED,
debug_request=REQUEST_LOG_ENABLED,
enable_grounding=enable_grounding,
enable_image_search=enable_image_search,
)
break
except RuntimeError as e:
error_msg = str(e)
if any(code in error_msg for code in _RETRY_CODES) and _attempt < _MAX_RETRIES:
_wait = 2 ** _attempt
print(f"{'=' * 60}")
print(f"⚠️ Nano Banana Pro 自动重试 [{_attempt}/{_MAX_RETRIES - 1}]")
print(f" 原因:{error_msg}")
print(f" 等待 {_wait}s 后重试...")
print(f"{'=' * 60}")
time.sleep(_wait)
else:
raise
else:
# 多张:异步并发,内存输出