v92-v105 多国适配 + 模板导出增强 + 生图可靠性优化

- 新增韩国(KR)适配:countries/pinterest 种子词、K-pop 提示词、UI 国家列表
- 爬虫跨运行持久化已采集 URL(.collected_urls.json),同关键词重复搜索采新图
- 模板导出增强:SPU 商品属性列名前缀剥离匹配、尺码下拉框 INDIRECT 动态引用、
  SPU 字段映射(袖长/门襟/胸垫等)、季节/印花图案女装映射、建议售价统一必填、
  SKU 分类/数量/单位直接填默认值
- 自定义模式:多模态提示词独立(custom_analyze_*)、生图提示词模板可配置
- Pinterest:空选品守卫、连续空分析保护(max_empty_analyze)、flat_prompt 配置覆盖
- 生图可靠性:开始/完成进度日志、异步任务轮询超时 60s→300s
This commit is contained in:
2026-08-31 18:35:51 +08:00
parent 71a48e4ed5
commit 3dc594cf57
29 changed files with 1369 additions and 321 deletions
+26 -16
View File
@@ -78,29 +78,32 @@ def _shrink_blob_to_2mb(img_path: str, blob: bytes, max_bytes: int = 2 * 1024 *
return blob
def _save_from_response(j: dict, out_path: str) -> str:
def _save_from_response(j: dict, out_path: str, started: float = None) -> str:
"""从同步/异步最终响应提取图片(data[].b64_json 或 url)并保存。"""
data_item = (j.get("data") or [{}])[0]
b64 = data_item.get("b64_json")
if b64:
Path(out_path).parent.mkdir(parents=True, exist_ok=True)
Path(out_path).write_bytes(base64.b64decode(b64))
return out_path
url = data_item.get("url")
if not url:
raise RuntimeError(f"图像 API 返回无 b64_json/url: {str(j)[:200]}")
img_resp = requests.get(url, proxies=NO_PROXY, timeout=120)
img_resp.raise_for_status()
Path(out_path).parent.mkdir(parents=True, exist_ok=True)
Path(out_path).write_bytes(img_resp.content)
else:
url = data_item.get("url")
if not url:
raise RuntimeError(f"图像 API 返回无 b64_json/url: {str(j)[:200]}")
img_resp = requests.get(url, proxies=NO_PROXY, timeout=120)
img_resp.raise_for_status()
Path(out_path).parent.mkdir(parents=True, exist_ok=True)
Path(out_path).write_bytes(img_resp.content)
if started is not None:
print(f"[img] 已生成(耗时 {time.time() - started:.0f}s: {out_path}")
return out_path
def _wait_task(base_url: str, headers: dict, task_id: str, poll_after_ms: int,
timeout: int = 60) -> dict:
timeout: int = 300) -> dict:
"""轮询异步图像任务直到完成;uncertain(结果暂时不确定)继续等,不重复提交。
timeout 默认 60s:异步路径不稳定(ai-media 网关常 uncertain/task not found),
超时即抛异常由调用方重试同步提交"""
timeout 默认 300s(与提交请求 timeout 一致):网关异步任务实际需 30s~6 分钟
(设计稿/三图合成),60s 轮询超时会把仍在处理中的任务误判为失败而重复提交
白白消耗配额和时间。超时即抛异常由调用方重试同步提交。"""
deadline = time.time() + timeout
last = ""
while time.time() < deadline:
@@ -126,14 +129,15 @@ def _wait_task(base_url: str, headers: dict, task_id: str, poll_after_ms: int,
raise RuntimeError(f"图像异步任务超时({timeout}s")
def _resolve_task_or_sync(j: dict, base_url: str, headers: dict, out_path: str) -> str:
def _resolve_task_or_sync(j: dict, base_url: str, headers: dict, out_path: str,
started: float = None) -> str:
"""提交后的统一处理:异步任务则轮询,然后提取图片保存。"""
if j.get("object") == "image.task" or j.get("task_id") or j.get("id", "").startswith("imgtask"):
tid = j.get("task_id") or j.get("id") or ""
if not tid:
raise RuntimeError(f"异步任务无 task_id: {str(j)[:200]}")
j = _wait_task(base_url, headers, tid, int(j.get("poll_after_ms") or 2000))
return _save_from_response(j, out_path)
return _save_from_response(j, out_path, started=started)
class OpenAIImageBackend(ImageBackend):
@@ -191,6 +195,8 @@ class OpenAIImageBackend(ImageBackend):
data["execution_mode"] = em
if seed is not None:
data["seed"] = seed
started = time.time()
print(f"[img] 开始图生图({model}{data['size']},参考图 {len(file_payloads)} 张)→ {Path(out_path).name}")
# 提交重试:异步路径不稳定 → 失败重试同步提交(最多 3 次);
# 内容政策拦截(content_policy_violation)多为网关误判 → 等待后重试
last_err: Optional[str] = None
@@ -222,7 +228,8 @@ class OpenAIImageBackend(ImageBackend):
continue
raise RuntimeError(f"图像 API {resp.status_code}: {body[:300]}")
try:
return _resolve_task_or_sync(resp.json(), base_url, headers, out_path)
return _resolve_task_or_sync(resp.json(), base_url, headers, out_path,
started=started)
except json.JSONDecodeError as e:
# 空/非 JSON 响应:多为网关过载返回空 body → 退避后重试,
# 避免即时重压触发网关 429(用户实测空响应风暴 → 429)
@@ -264,6 +271,8 @@ class OpenAIImageBackend(ImageBackend):
data["execution_mode"] = em
if seed is not None:
data["seed"] = seed
started = time.time()
print(f"[img] 开始文生图({model}{data['size']})→ {Path(out_path).name}")
last_err: Optional[str] = None
for attempt in range(3):
resp = requests.post(f"{base_url}/images/generations", headers=headers, json=data,
@@ -293,7 +302,8 @@ class OpenAIImageBackend(ImageBackend):
continue
raise RuntimeError(f"图像 API {resp.status_code}: {body[:300]}")
try:
return _resolve_task_or_sync(resp.json(), base_url, headers, out_path)
return _resolve_task_or_sync(resp.json(), base_url, headers, out_path,
started=started)
except json.JSONDecodeError as e:
# 空/非 JSON 响应:多为网关过载返回空 body → 退避后重试,
# 避免即时重压触发网关 429(用户实测空响应风暴 → 429)