v88 功能增强:产品落盘持久化 + 生图网关适配 + 模板导出优化
- 产品持久化:每完成一个产品立即追加写入 products_pending.jsonl,崩溃不丢已完成产品,finish 读盘合并后统一写模板 - 503 致命错误提前终止:compose/product/seed_shot 端到端识别,提前终止搜索分析,丢弃未完成简报,保留已完成落盘产品直接合成模板 - 模特分配:material_library 合格模特图按任务序号独立随机,同 SPU 多款不再共用同一模特 - 图像网关适配:execution_mode/background 默认不再传入 yunfei 等标准网关,base_url 需带 /v1;429/5xx/空响应退避重试 - Pinterest 分析:删除 term 注入与纯文本降级,失败直接放弃;图片上传前 PIL 完整性校验;suitable_for_print=False 过滤丢弃 - 模板导出:不再产生空白 xlsx,文件名=模板原文件名_已填写;写入前按货号末 3 位升序排序 - 删除对接文档.md,更新 README,gitignore 排除测试产物
This commit is contained in:
@@ -12,7 +12,7 @@ import time
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from graph.validate import with_fallback
|
||||
from graph.validate import ThreadSafeErrors, with_fallback
|
||||
|
||||
RISK_LABEL = {"safe": "✅ 安全", "review": "⚠️ 待复核", "blocked": "⛔ 拦截"}
|
||||
|
||||
@@ -29,6 +29,18 @@ def _notify_400(on_400, exc) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def _notify_503(on_503, exc) -> None:
|
||||
"""致命图像服务错误(503 / 账户不可用)时触发 on_503 回调(供调用方提前终止任务)。"""
|
||||
if on_503 is None:
|
||||
return
|
||||
try:
|
||||
from graph.pinterest_pipeline import PinterestPipeline
|
||||
if PinterestPipeline.is_fatal_503(exc):
|
||||
on_503()
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
|
||||
|
||||
def _build_briefs_md(briefs: List[Dict[str, Any]], generated_at: str) -> str:
|
||||
lines = [
|
||||
"# POD 印花设计简报(LLM 合规筛选 + 生图提示词)",
|
||||
@@ -112,6 +124,7 @@ def generate_design(ib, brief: Dict[str, Any], design_dir: Path, out_stem: str,
|
||||
errors: List[Dict[str, Any]] = None,
|
||||
seed: Optional[int] = None,
|
||||
on_400=None,
|
||||
on_503=None,
|
||||
size: str = "1024x1024") -> Optional[str]:
|
||||
"""生成单张纯印花设计稿(图2)。返回设计稿路径;失败 / 全局 MD5 重复返回 None。
|
||||
|
||||
@@ -123,6 +136,7 @@ def generate_design(ib, brief: Dict[str, Any], design_dir: Path, out_stem: str,
|
||||
无参考图或图生图失败 → 回退 ib.generate() 纯文生图。
|
||||
seed: 随机种子(None=不传,网关随机;固定值=可复现,网关支持才生效)。
|
||||
on_400: 每次 HTTP 400(且含「内容/图片」)时回调(供调用方累计放弃计数)。
|
||||
on_503: 致命图像服务错误(503/账户不可用)时回调(供调用方提前终止任务)。
|
||||
"""
|
||||
try:
|
||||
from graph.style_rules import sanitize_image_prompt, ensure_rebrand_hint
|
||||
@@ -145,6 +159,7 @@ def generate_design(ib, brief: Dict[str, Any], design_dir: Path, out_stem: str,
|
||||
except Exception as e: # noqa: BLE001
|
||||
print(f"[compose] 图生图(参考图)失败,回退文生图 {brief.get('topic','')}: {e}")
|
||||
_notify_400(on_400, e)
|
||||
_notify_503(on_503, e)
|
||||
out_path = ib.generate(
|
||||
img_prompt, str(design_dir / f"{out_stem}_design.png"),
|
||||
brief.get("composite_negative", ""), size=size, seed=seed)
|
||||
@@ -161,6 +176,7 @@ def generate_design(ib, brief: Dict[str, Any], design_dir: Path, out_stem: str,
|
||||
return out_path
|
||||
except Exception as e: # noqa: BLE001
|
||||
_notify_400(on_400, e)
|
||||
_notify_503(on_503, e)
|
||||
if errors is not None:
|
||||
errors.append({"node": "compose", "type": type(e).__name__,
|
||||
"message": f"设计稿生成失败 {brief.get('topic','')}: {e}", "trace": ""})
|
||||
@@ -238,12 +254,13 @@ def compose_node(state: Dict[str, Any]) -> Dict[str, Any]:
|
||||
def _gen_one(i: int, b: Dict[str, Any]):
|
||||
"""单张设计稿生成(并发线程内调用,每设计一线程)。"""
|
||||
out_path = generate_design(ib, b, design_dir, f"{country}_{i:02d}",
|
||||
state.get("errors"), seed=_seed,
|
||||
_safe_errors, seed=_seed,
|
||||
size=compose_cfg.get("design_size", "1024x1024"))
|
||||
if out_path is None:
|
||||
return i, b, None, None
|
||||
return i, b, out_path, None
|
||||
|
||||
_safe_errors = ThreadSafeErrors()
|
||||
targets = [(i, b) for i, b in enumerate(safe_briefs[:design_count], 1)]
|
||||
# 并发生成:每张设计一个线程(并行调图像网关),数量多时不串行等待
|
||||
workers = max(1, min(len(targets), int((config.get("compose") or {}).get("design_workers", 5))))
|
||||
@@ -263,6 +280,9 @@ def compose_node(state: Dict[str, Any]) -> Dict[str, Any]:
|
||||
b["design_path"] = out_path
|
||||
designs.append({"topic": b.get("topic", ""), "path": out_path, "design_path": out_path})
|
||||
print(f"[compose] 印花设计稿已生成: {out_path}")
|
||||
# 合并并发线程收集的错误(线程安全收集器 → 主线程统一追加)
|
||||
if len(_safe_errors):
|
||||
state.setdefault("errors", []).extend(list(_safe_errors))
|
||||
|
||||
stats = dict(state.get("stats") or {})
|
||||
stats["compose"] = {"written": len(briefs), "designs": len(designs), "output_dir": str(output_dir)}
|
||||
|
||||
Reference in New Issue
Block a user