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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user