v110-v112 自定义模式完善 + 模板导出增强 + 多模态兼容优化
- 自定义模式:分析模型输出 delta 唯一改动指令,生图模板 custom_image_prompt.md({delta} 占位符),不再使用负向提示词;generate_design 按 custom_mode 分支,Pinterest 模式保留原创化指令,两模式互不影响
- 多模态分析 response_format 三级回退(json_schema → json_object → none),兼容 DeepSeek
- 模板导出:details 扩展列(细节1/2/3)、target_audience 扩展列(适用人群1)、固定值风格1=休闲/风格2=运动
- 童装特征库更新 + 标题模板外部化 + 图源映射增强
This commit is contained in:
+40
-16
@@ -20,6 +20,7 @@ from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from graph.paths import project_root, runtime_root
|
||||
from graph.validate import ThreadSafeErrors
|
||||
|
||||
|
||||
class PinterestPipeline:
|
||||
@@ -83,6 +84,12 @@ class PinterestPipeline:
|
||||
self._country_config = state.get("country_config") or {}
|
||||
# 补充重试次数:设计生成失败/侵权时,从图池取新图重新分析的最多尝试次数
|
||||
self._supply_attempts = int((self.config.get("pinterest") or {}).get("supply_attempts", 3))
|
||||
# 自定义模式标志:pinterest_init 先于 pinterest_custom_load 执行(state.custom_mode 尚未置位),
|
||||
# 故从 config 判断(mode=custom 或 custom_image_dir 非空),供 generate_design 决定是否追加原创化指令
|
||||
_pcfg = self.config.get("pinterest") or {}
|
||||
self.custom_mode = bool(state.get("custom_mode")) \
|
||||
or (str(_pcfg.get("mode") or "").strip().lower() == "custom") \
|
||||
or bool(str(_pcfg.get("custom_image_dir") or "").strip())
|
||||
|
||||
# 400 计数(per 种子词):多模态 + 生图模型合计,超限放弃当前种子词
|
||||
self._err400_lock = threading.Lock()
|
||||
@@ -470,6 +477,9 @@ class PinterestPipeline:
|
||||
# 单条简报完整链路:设计 → 三合一 → OSS → 种草图
|
||||
# ------------------------------------------------------------------ #
|
||||
def _process_one(self, brief: Dict[str, Any], idx: int) -> None:
|
||||
# 每个 worker 用独立线程安全错误收集器,后台调用链(generate_design/_process_spu)
|
||||
# 的 append 与 finish() 的 list(self._errors) 读取不再互相竞态,结束时统一合并。
|
||||
errs = ThreadSafeErrors()
|
||||
try:
|
||||
if self.is_fatal_503_aborted():
|
||||
return
|
||||
@@ -480,7 +490,7 @@ class PinterestPipeline:
|
||||
spu, skus = self._worklist[idx]
|
||||
img_code = f"{self._prefix}{idx:03d}"
|
||||
# 1) 生成设计(compose)——直接按货号命名 designs/{img_code}_design.png
|
||||
design_path = self._gen_design(brief, img_code)
|
||||
design_path = self._gen_design(brief, img_code, errs)
|
||||
if self.is_fatal_503_aborted():
|
||||
return
|
||||
if self.is_400_aborted():
|
||||
@@ -496,7 +506,7 @@ class PinterestPipeline:
|
||||
if new_brief is None:
|
||||
return
|
||||
brief = new_brief
|
||||
design_path = self._gen_design(brief, img_code)
|
||||
design_path = self._gen_design(brief, img_code, errs)
|
||||
if self.is_fatal_503_aborted():
|
||||
return
|
||||
if design_path:
|
||||
@@ -505,7 +515,7 @@ class PinterestPipeline:
|
||||
return
|
||||
brief["design_path"] = design_path
|
||||
# 2) 三合一(product)——同一货号;task_idx=本次简报序号,模特按任务独立随机
|
||||
prod = self._process_spu(brief, spu, skus, img_code, design_path, task_idx=idx)
|
||||
prod = self._process_spu(brief, spu, skus, img_code, design_path, task_idx=idx, errors=errs)
|
||||
if self.is_fatal_503_aborted():
|
||||
return
|
||||
if not prod:
|
||||
@@ -532,6 +542,9 @@ class PinterestPipeline:
|
||||
"message": f"简报 {idx} 处理失败: {e}", "trace": ""})
|
||||
print(f"[pinterest_pipeline] 简报 {idx} 处理失败: {e}")
|
||||
finally:
|
||||
# 合并本 worker 收集的错误到共享 _errors(持锁,供 finish() 安全读取)
|
||||
with self._errors_lock:
|
||||
self._errors.extend(list(errs))
|
||||
with self._cond:
|
||||
self._in_flight = max(0, self._in_flight - 1)
|
||||
self._cond.notify_all()
|
||||
@@ -571,7 +584,7 @@ class PinterestPipeline:
|
||||
print(f"[pinterest_pipeline] 读回落盘产品失败: {e}")
|
||||
return out
|
||||
|
||||
def _gen_design(self, brief: Dict[str, Any], img_code: str) -> Optional[str]:
|
||||
def _gen_design(self, brief: Dict[str, Any], img_code: str, errors: ThreadSafeErrors) -> Optional[str]:
|
||||
if self._ib is None:
|
||||
return None
|
||||
try:
|
||||
@@ -587,18 +600,18 @@ class PinterestPipeline:
|
||||
self.record_503()
|
||||
self.abort_unfinished()
|
||||
|
||||
return generate_design(self._ib, brief, design_dir, img_code, self._errors,
|
||||
return generate_design(self._ib, brief, design_dir, img_code, errors,
|
||||
on_400=_on_400, on_503=_on_503,
|
||||
size=str((self.config.get("compose") or {}).get("design_size") or "1024x1024"))
|
||||
size=str((self.config.get("compose") or {}).get("design_size") or "1024x1024"),
|
||||
custom_mode=self.custom_mode)
|
||||
except Exception as e: # noqa: BLE001
|
||||
if self.is_fatal_503(e):
|
||||
self.record_503()
|
||||
self.abort_unfinished()
|
||||
return None
|
||||
with self._errors_lock:
|
||||
self._errors.append({"node": "compose", "type": type(e).__name__,
|
||||
"message": f"设计稿生成失败 {brief.get('topic', '')}: {e}",
|
||||
"trace": ""})
|
||||
errors.append({"node": "compose", "type": type(e).__name__,
|
||||
"message": f"设计稿生成失败 {brief.get('topic', '')}: {e}",
|
||||
"trace": ""})
|
||||
return None
|
||||
|
||||
def _supply_from_pool(self, reason: str) -> Optional[Dict[str, Any]]:
|
||||
@@ -677,18 +690,29 @@ class PinterestPipeline:
|
||||
"model_kind": src.get("kind", "model")}
|
||||
|
||||
def _process_spu(self, brief: Dict[str, Any], spu, skus: str, img_code: str,
|
||||
design_path: str, task_idx: int = 0) -> Optional[Dict[str, Any]]:
|
||||
design_path: str, task_idx: int = 0, errors: Optional[ThreadSafeErrors] = None) -> Optional[Dict[str, Any]]:
|
||||
from graph.nodes.product_node import _process_spu as _ps
|
||||
prod_dir = self.output_dir / "product"
|
||||
prod_dir.mkdir(parents=True, exist_ok=True)
|
||||
# 设计稿已按货号命名(designs/{img_code}_design.png),直接复用,无需再拷贝
|
||||
brief = dict(brief)
|
||||
brief["design_path"] = design_path
|
||||
# 标题生成用:模版「类目」完整路径(注入标题提示词 {category_path}),懒加载缓存一次
|
||||
if not hasattr(self, "_category_path"):
|
||||
self._category_path = ""
|
||||
try:
|
||||
_tp = str((self.config.get("product") or {}).get("template_path") or "").strip()
|
||||
if _tp:
|
||||
from graph.seed_shot import read_template_category
|
||||
self._category_path = read_template_category(_tp)
|
||||
except Exception as _e: # noqa: BLE001
|
||||
print(f"[pinterest_pipeline] 读取模版类目路径失败(标题 category_path 留空): {_e}")
|
||||
r = _ps(self._db_path, self._basemap_root, self._material_root, self._category,
|
||||
prod_dir, brief, self._ib, spu, skus, self.config.get("product") or {},
|
||||
self._errors, design_path, self._title_backend, self.country,
|
||||
errors, design_path, self._title_backend, self.country,
|
||||
img_code=img_code,
|
||||
on_503=lambda: (self.record_503(), self.abort_unfinished()),
|
||||
on_503=lambda: (self.record_503(), self.abort_unfinished()),
|
||||
category_path=self._category_path,
|
||||
**self._model_source(task_idx, spu))
|
||||
if r:
|
||||
r["img_code"] = img_code
|
||||
@@ -710,7 +734,7 @@ class PinterestPipeline:
|
||||
continue
|
||||
try:
|
||||
compressed = compress_for_oss(src, str(Path(src).with_suffix(".oss.jpg")))
|
||||
key = build_oss_key(self.country, self.task_timestamp, base_code, _gen_rand4())
|
||||
key = build_oss_key(self.country, self.task_timestamp, base_code, _gen_rand4(), local=compressed)
|
||||
url = upload_to_oss(self._oss_cfg, compressed, key)
|
||||
if url:
|
||||
r[f"{kind}_url"] = url
|
||||
@@ -728,7 +752,7 @@ class PinterestPipeline:
|
||||
continue
|
||||
try:
|
||||
compressed = compress_for_oss(src, str(Path(src).with_suffix(".oss.jpg")))
|
||||
key = build_oss_key(self.country, self.task_timestamp, base_code, _gen_rand4())
|
||||
key = build_oss_key(self.country, self.task_timestamp, base_code, _gen_rand4(), local=compressed)
|
||||
url = upload_to_oss(self._oss_cfg, compressed, key)
|
||||
if url:
|
||||
cc["url"] = url
|
||||
@@ -796,7 +820,7 @@ class PinterestPipeline:
|
||||
compressed = compress_for_oss(pth, str(Path(pth).with_suffix(".oss.jpg")))
|
||||
url = upload_to_oss(self._oss_cfg, compressed,
|
||||
build_oss_key(self.country, self.task_timestamp,
|
||||
code, _gen_rand4()))
|
||||
code, _gen_rand4(), local=compressed))
|
||||
if url:
|
||||
urls.append(url)
|
||||
r["seed_shot_urls"] = urls
|
||||
|
||||
Reference in New Issue
Block a user