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:
+55
-14
@@ -26,6 +26,7 @@ from graph.nodes import (
|
||||
template_export_node,
|
||||
)
|
||||
from graph.state import AgentState
|
||||
from graph.validate import with_fallback
|
||||
|
||||
|
||||
def build_graph():
|
||||
@@ -59,8 +60,16 @@ def build_graph():
|
||||
|
||||
|
||||
def _pinterest_route(state: Dict[str, Any]) -> str:
|
||||
"""图池路由:简报达标 → done;图池还有未消费图片 → analyze(继续分析,不搜索);
|
||||
图池不足 → search(新一轮搜索);轮次耗尽 → done。"""
|
||||
"""图池路由:按简报池实时需求补——简报池还有待处理/在途简报 → 不分析不采集;
|
||||
简报池空闲 + 图池有未消费图片 → 补分析;简报池空闲 + 图池空 + 简报不足 → 搜索采集;
|
||||
简报达标 → done。不按任务数量提前结束,先消耗图池存量,不够用了才主动采集。
|
||||
致命 503(图像服务不可用)→ 立即终止,不再搜索/分析,直接收尾合成模板。"""
|
||||
# 致命 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_route] ⛔ 图像服务 503 已终止任务,停止搜索/分析,直接收尾合成模板")
|
||||
return "done"
|
||||
|
||||
target = int(state.get("pinterest_target") or 0)
|
||||
if target <= 0:
|
||||
target = 1
|
||||
@@ -72,11 +81,14 @@ def _pinterest_route(state: Dict[str, Any]) -> str:
|
||||
if max_rounds <= 0:
|
||||
max_rounds = max(target * 2, 5)
|
||||
|
||||
if len(briefs) >= target:
|
||||
print(f"[pinterest_route] 简报已达目标 {len(briefs)}/{target},结束")
|
||||
return "done"
|
||||
# 简报池还有待处理/在途简报 → 先让后台消化,不分析新图也不采集
|
||||
if pipe is not None and hasattr(pipe, "pending_count"):
|
||||
pending = pipe.pending_count()
|
||||
if pending > 0:
|
||||
# 日志由 pinterest_wait 节点进入时统一打印(阻塞等待消化,避免此处高频刷屏)
|
||||
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 "")
|
||||
@@ -85,24 +97,31 @@ def _pinterest_route(state: Dict[str, Any]) -> str:
|
||||
except Exception: # noqa: BLE001
|
||||
unused = []
|
||||
if unused:
|
||||
print(f"[pinterest_route] 图池还有 {len(unused)} 张未消费图片,继续分析(简报 {len(briefs)}/{target})")
|
||||
print(f"[pinterest_route] 简报池空闲,图池还有 {len(unused)} 张未消费图片,补分析"
|
||||
f"(简报 {len(briefs)}/{target})")
|
||||
return "analyze"
|
||||
|
||||
# 图池不足 → 搜索
|
||||
# 简报池空闲 + 图池空 → 检查简报是否已达标
|
||||
if len(briefs) >= target:
|
||||
print(f"[pinterest_route] 图池已空,简报已达目标 {len(briefs)}/{target},结束")
|
||||
return "done"
|
||||
|
||||
# 简报池空闲 + 图池空 + 简报不达标 → 新一轮搜索采集
|
||||
if rounds >= max_rounds:
|
||||
print(f"[pinterest_route] 已达最大轮次 {max_rounds},简报 {len(briefs)}/{target},按现有结果继续")
|
||||
print(f"[pinterest_route] 已达最大轮次 {max_rounds},图池已空,简报 {len(briefs)}/{target},按现有结果继续")
|
||||
return "done"
|
||||
if not terms and rounds > 0:
|
||||
print(f"[pinterest_route] 无可用搜索词,停止搜索(简报 {len(briefs)}/{target})")
|
||||
print(f"[pinterest_route] 无可用搜索词,图池已空,停止搜索(简报 {len(briefs)}/{target})")
|
||||
return "done"
|
||||
print(f"[pinterest_route] 图池不足,新一轮搜索(第 {rounds} 轮,简报 {len(briefs)}/{target})")
|
||||
print(f"[pinterest_route] 简报池空闲,图池不足,简报未达标,新一轮搜索(第 {rounds} 轮,简报 {len(briefs)}/{target})")
|
||||
return "search"
|
||||
|
||||
|
||||
def build_pinterest_graph():
|
||||
"""Pinterest 参考模式图(按需搜索循环 + 简报池并发生成):
|
||||
pinterest_init(建简报池)→ pinterest_search → pinterest_scrape → pinterest_analyze
|
||||
→ [pinterest_route] 简报不足 → 回到 pinterest_search;达标 → pinterest_finalize
|
||||
→ [pinterest_route] 简报池还有在途 → wait(等待后台消化)→ 回到路由;
|
||||
简报池空闲 + 图池有图 → analyze;图池空 + 简报不足 → search;达标 → pinterest_finalize
|
||||
(排空简报池、后台并发生成 设计→三合一→OSS→种草图)→ template_export
|
||||
"""
|
||||
from graph.nodes import (
|
||||
@@ -113,11 +132,27 @@ def build_pinterest_graph():
|
||||
from graph.nodes.pinterest_finalize_node import pinterest_finalize_node
|
||||
from graph.nodes.pinterest_init_node import pinterest_init_node
|
||||
|
||||
@with_fallback("pinterest_wait")
|
||||
def _pinterest_wait(state: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""简报池还有在途简报时阻塞等待后台消化(最多 60s),避免轮询刷屏。
|
||||
|
||||
用 pipeline.wait_idle 的 Condition 阻塞等待(_process_one 完成即唤醒),
|
||||
消化完才返回,路由重新判断;超时兜底返回,避免死等。
|
||||
"""
|
||||
pipe = state.get("pinterest_pipeline")
|
||||
if pipe is not None and hasattr(pipe, "wait_idle"):
|
||||
pipe.wait_idle(60)
|
||||
else:
|
||||
import time as _t
|
||||
_t.sleep(5)
|
||||
return {}
|
||||
|
||||
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)
|
||||
|
||||
@@ -126,10 +161,12 @@ def build_pinterest_graph():
|
||||
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", # 图池不足 → 新一轮搜索
|
||||
"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)
|
||||
return builder.compile()
|
||||
@@ -232,5 +269,9 @@ def run_pinterest_ref(
|
||||
or int((global_config.get("product") or {}).get("spu_count") or 0) or 1,
|
||||
"pinterest_rounds": 0,
|
||||
"pinterest_attempted": [],
|
||||
"pinterest_images": {},
|
||||
"pinterest_briefs": [],
|
||||
"pinterest_login": {},
|
||||
"pinterest_pipeline": None,
|
||||
}
|
||||
return compiled.invoke(state)
|
||||
|
||||
Reference in New Issue
Block a user