v89-v91 模板增强 + 图源映射 + 多模态提示词可配置化
- 图源映射统一:热点采集与 Pinterest 模式均走 config.product.mark_dirs 配置,按任务序号随机抽模特图/平铺图 - 商品产地固定:统一为「中国大陆」+「产地省份=广东省」(不再读站点/字典映射) - 模板 SKU 字段检测:按建议售价同一套路检测 SKU分类/SKU数量/SKU数量单位,必填时填入单品/1/件 - 多模态分析提示词可配置:prompts/pinterest_analyze_system.md + user.md,支持国家覆盖,不丢文件回退内置 - 自定义图片模式:新增 pinterest_custom_load_node,图片数量硬校验,选品清单 ≤ 有效图片数 - 模板导出优化:写入前按货号末 3 位升序排序,不再产生空白 xlsx - 修复 v90 project review 10 项(503 致命终止、线程安全、原子写入等)
This commit is contained in:
+89
-14
@@ -117,18 +117,57 @@ def _pinterest_route(state: Dict[str, Any]) -> str:
|
||||
return "search"
|
||||
|
||||
|
||||
def build_pinterest_graph():
|
||||
def _pinterest_custom_route(state: Dict[str, Any]) -> str:
|
||||
"""自定义模式图池路由:只消耗本地图片池,从不搜索/采集。
|
||||
|
||||
简报池还有在途 → wait(等待后台消化);简报达标 → done;
|
||||
简报池空闲 + 图池有未消费图片 → analyze;图池空 → done(自定义模式不采集)。
|
||||
致命 503(图像服务不可用)→ 立即终止,直接收尾合成模板。
|
||||
"""
|
||||
pipe = state.get("pinterest_pipeline")
|
||||
if pipe is not None and hasattr(pipe, "is_fatal_503_aborted") and pipe.is_fatal_503_aborted():
|
||||
print("[pinterest_custom_route] ⛔ 图像服务 503 已终止任务,停止分析,直接收尾合成模板")
|
||||
return "done"
|
||||
|
||||
target = int(state.get("pinterest_target") or 1) or 1
|
||||
briefs = state.get("briefs") or []
|
||||
if len(briefs) >= target:
|
||||
print(f"[pinterest_custom_route] 简报已达目标 {len(briefs)}/{target},结束")
|
||||
return "done"
|
||||
if pipe is not None and hasattr(pipe, "pending_count") and pipe.pending_count() > 0:
|
||||
return "wait"
|
||||
try:
|
||||
from graph.pinterest import load_image_pool, load_used_images, pool_unused_images
|
||||
pool = load_image_pool(str(state.get("output_dir") or ""), state.get("country") or "")
|
||||
used = load_used_images(str(state.get("output_dir") or ""), state.get("country") or "")
|
||||
unused = pool_unused_images(pool, used)
|
||||
except Exception: # noqa: BLE001
|
||||
unused = []
|
||||
if unused:
|
||||
print(f"[pinterest_custom_route] 图池还有 {len(unused)} 张未消费图片,继续分析(简报 {len(briefs)}/{target})")
|
||||
return "analyze"
|
||||
print(f"[pinterest_custom_route] 图池已空,简报 {len(briefs)}/{target},结束(自定义模式不采集)")
|
||||
return "done"
|
||||
|
||||
|
||||
def build_pinterest_graph(custom_mode: bool = False):
|
||||
"""Pinterest 参考模式图(按需搜索循环 + 简报池并发生成):
|
||||
pinterest_init(建简报池)→ pinterest_search → pinterest_scrape → pinterest_analyze
|
||||
→ [pinterest_route] 简报池还有在途 → wait(等待后台消化)→ 回到路由;
|
||||
简报池空闲 + 图池有图 → analyze;图池空 + 简报不足 → search;达标 → pinterest_finalize
|
||||
(排空简报池、后台并发生成 设计→三合一→OSS→种草图)→ template_export
|
||||
|
||||
custom_mode=True:自定义模式,不搜索不采集 ——
|
||||
pinterest_init → pinterest_custom_load(本地文件夹校验+入库)→ pinterest_analyze
|
||||
→ [pinterest_custom_route] wait / analyze / done(图池空或简报达标即结束,从不 search)。
|
||||
其余下游流程(analyze→设计→三合一→OSS→种草图→模板)与普通 Pinterest 模式完全一致。
|
||||
"""
|
||||
from graph.nodes import (
|
||||
pinterest_analyze_node,
|
||||
pinterest_scrape_node,
|
||||
pinterest_search_node,
|
||||
)
|
||||
from graph.nodes import pinterest_custom_load_node
|
||||
from graph.nodes.pinterest_finalize_node import pinterest_finalize_node
|
||||
from graph.nodes.pinterest_init_node import pinterest_init_node
|
||||
|
||||
@@ -149,23 +188,34 @@ def build_pinterest_graph():
|
||||
|
||||
builder = StateGraph(AgentState)
|
||||
builder.add_node("pinterest_init", pinterest_init_node)
|
||||
builder.add_node("pinterest_search", pinterest_search_node)
|
||||
builder.add_node("pinterest_scrape", pinterest_scrape_node)
|
||||
builder.add_node("pinterest_analyze", pinterest_analyze_node)
|
||||
builder.add_node("pinterest_wait", _pinterest_wait)
|
||||
builder.add_node("pinterest_finalize", pinterest_finalize_node)
|
||||
builder.add_node("template_export", template_export_node)
|
||||
|
||||
builder.add_edge("__start__", "pinterest_init")
|
||||
builder.add_edge("pinterest_init", "pinterest_search")
|
||||
builder.add_edge("pinterest_search", "pinterest_scrape")
|
||||
builder.add_edge("pinterest_scrape", "pinterest_analyze")
|
||||
builder.add_conditional_edges("pinterest_analyze", _pinterest_route, {
|
||||
"analyze": "pinterest_analyze", # 简报池空闲 + 图池有未消费图片 → 补分析(不搜索)
|
||||
"search": "pinterest_search", # 简报池空闲 + 图池空 + 简报不足 → 新一轮搜索
|
||||
"wait": "pinterest_wait", # 简报池还有在途 → 等待后台消化
|
||||
"done": "pinterest_finalize", # 简报达标/轮次耗尽 → 收尾(排空简报池)
|
||||
})
|
||||
if custom_mode:
|
||||
builder.add_node("pinterest_custom_load", pinterest_custom_load_node)
|
||||
builder.add_edge("__start__", "pinterest_init")
|
||||
builder.add_edge("pinterest_init", "pinterest_custom_load")
|
||||
builder.add_edge("pinterest_custom_load", "pinterest_analyze")
|
||||
builder.add_conditional_edges("pinterest_analyze", _pinterest_custom_route, {
|
||||
"analyze": "pinterest_analyze",
|
||||
"wait": "pinterest_wait",
|
||||
"done": "pinterest_finalize",
|
||||
})
|
||||
else:
|
||||
builder.add_node("pinterest_search", pinterest_search_node)
|
||||
builder.add_node("pinterest_scrape", pinterest_scrape_node)
|
||||
builder.add_edge("__start__", "pinterest_init")
|
||||
builder.add_edge("pinterest_init", "pinterest_search")
|
||||
builder.add_edge("pinterest_search", "pinterest_scrape")
|
||||
builder.add_edge("pinterest_scrape", "pinterest_analyze")
|
||||
builder.add_conditional_edges("pinterest_analyze", _pinterest_route, {
|
||||
"analyze": "pinterest_analyze", # 简报池空闲 + 图池有未消费图片 → 补分析(不搜索)
|
||||
"search": "pinterest_search", # 简报池空闲 + 图池空 + 简报不足 → 新一轮搜索
|
||||
"wait": "pinterest_wait", # 简报池还有在途 → 等待后台消化
|
||||
"done": "pinterest_finalize", # 简报达标/轮次耗尽 → 收尾(排空简报池)
|
||||
})
|
||||
builder.add_edge("pinterest_wait", "pinterest_analyze")
|
||||
builder.add_edge("pinterest_finalize", "template_export")
|
||||
builder.add_edge("template_export", END)
|
||||
@@ -231,14 +281,39 @@ def run_pinterest_ref(
|
||||
project_root: Path,
|
||||
output_root: Optional[Path] = None,
|
||||
task_timestamp: Optional[str] = None,
|
||||
custom_image_dir: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""Pinterest 参考模式入口:独立于 Google Trends 的完整流程。
|
||||
|
||||
种子词 → LLM 搜索词(json_schema + 动态注入防重复)→ 爬图 → LLM 分析图片
|
||||
→ 设计简报 → 设计稿 → 产品图 → 上传 → 种草图 → 模板导出。
|
||||
参数语义与 run_country 一致(project_root=数据根,output_root=产物根)。
|
||||
|
||||
custom_image_dir 非空 或 config.pinterest.mode=="custom" → 进入自定义模式:
|
||||
不搜索不采集,直接把指定文件夹的有效图片送多模态分析,沿用 Pinterest 后续所有步骤。
|
||||
数量硬校验(有效图片数 ≥ 选品清单总数)在启动前执行,不满足直接抛错。
|
||||
"""
|
||||
compiled = build_pinterest_graph()
|
||||
target = len((global_config.get("product") or {}).get("spu_tasks") or []) \
|
||||
or int((global_config.get("product") or {}).get("spu_count") or 0) or 1
|
||||
pinterest_cfg = global_config.get("pinterest") or {}
|
||||
mode = str(pinterest_cfg.get("mode") or "scrape").strip().lower()
|
||||
custom_dir = str(custom_image_dir or "").strip() \
|
||||
or str(pinterest_cfg.get("custom_image_dir") or "").strip()
|
||||
custom_mode = bool(custom_dir) or (mode == "custom")
|
||||
|
||||
if custom_mode:
|
||||
if not custom_dir:
|
||||
raise ValueError(
|
||||
"自定义模式需要填写 pinterest.custom_image_dir(选择上传的图片文件夹),"
|
||||
"当前为空,无法启动")
|
||||
from graph.pinterest import validate_custom_images
|
||||
ok, valid_n, msg = validate_custom_images(custom_dir, target)
|
||||
if not ok:
|
||||
raise ValueError(f"自定义模式数量校验未通过:{msg}(选品清单总数 {target})")
|
||||
global_config.setdefault("pinterest", {})["custom_image_dir"] = custom_dir
|
||||
print(f"[run_pinterest_ref] 自定义模式启用:{custom_dir} 有效图片 {valid_n} 张 ≥ 选品清单 {target}")
|
||||
|
||||
compiled = build_pinterest_graph(custom_mode=custom_mode)
|
||||
cc = build_country_config(global_config, country, project_root)
|
||||
prompts_dir = project_root / "prompts" / country
|
||||
cache_dir = (output_root or project_root) / "output" / country
|
||||
|
||||
Reference in New Issue
Block a user