Files
pod_trend_agent/graph/nodes/pinterest_search_node.py
3218485270 b7f429db89 模板导出增强 + 模特性别分组 + 三合一提示词精简
1) 模板导出:识别「基码表-胸围」填 sku.bust(多个胸围列都填);申报价格模糊匹配多列统一按加价后价格填写;详情图文不再拼接 img_url_2;SPU 款式来源统一填「现货款」;商品产地国家简称映射(沙特→沙特阿拉伯)
2) 模特性别分组:model_features 按男女分组,按模板类目含男/女固定取对应性别模特(含 Pinterest 模式 pipeline)
3) 三合一提示词:去掉 DESIGN CONTENT 四要素描述(设计已由设计稿提供)
4) 生图尺寸:全部改为读 config 不再硬编码(设计图 compose.design_size / 合成图 compose.size / 种草图 seed_shot.size)
2026-08-26 18:05:11 +08:00

120 lines
5.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Pinterest 参考模式节点 1/3:按需生成单个搜索词(pinterest_search)。
按需搜索:每次只生成 1 个搜索词(LLM json_schema + 动态注入已用词防重复),
带短袖/印花设计引导,保证搜索词适合短袖 T 恤印花。
不在此处持久化已用词 —— 只有爬取成功(真正用掉)才标记已用(见 pinterest_scrape)。
兜底链:LLM json_schema → json_object → 解析失败/调用失败 → 回退种子词池随机抽样。
带 with_fallback:任何异常都不中断,返回空列表由下游跳过。
"""
import random
from typing import Any, Dict, List
from graph.llms import get_backend
from graph.pinterest import (
filter_search_terms,
load_used_terms,
merge_used,
sample_seeds,
)
from graph.validate import with_fallback
@with_fallback("pinterest_search")
def pinterest_search_node(state: Dict[str, Any]) -> Dict[str, Any]:
country = state["country"]
config = state["config"]
output_dir = state["output_dir"]
errors = list(state.get("errors") or [])
pcfg = config.get("pinterest") or {}
if not pcfg.get("enabled", True):
return {"pinterest_search_terms": [], "errors": errors}
provider = str(pcfg.get("provider") or "openai").strip().lower()
search_mode = str(pcfg.get("search_mode") or "direct").strip().lower()
want = int(pcfg.get("search_terms_per_run", 1)) # 每次搜索词数量
seed_sample = int(pcfg.get("seed_sample", 40))
max_used_in_prompt = int(pcfg.get("max_used_terms_in_prompt", 100))
blacklist = config.get("blacklist") or []
# 1) 种子词池 + 已用搜索词 + 本轮已尝试词(防同轮重复,不持久化)
seeds = sample_seeds(country, seed_sample)
used = load_used_terms(output_dir, country)
attempted = [str(t).strip() for t in (state.get("pinterest_attempted") or []) if str(t).strip()]
rounds = int(state.get("pinterest_rounds") or 0) + 1
if not seeds:
print(f"[pinterest_search] {country} 无种子词,跳过搜索词生成")
return {"pinterest_search_terms": [], "pinterest_rounds": rounds, "errors": errors}
# 2) 生成搜索词:种子词不再由 LLM 给出,直接由内置国家种子词库随机抽取(优先未用过),
# 追加 " t-shirt design"(保证 Pinterest 返回真正的 T 恤印花图);llm 模式保留兼容
terms: List[str] = []
if search_mode == "direct":
from graph.pinterest import load_pinterest_seeds
pool = load_pinterest_seeds(country)
used_set = {str(u).strip().lower() for u in merge_used(used, attempted)}
fresh = [s for s in pool if s.lower() not in used_set]
if not fresh:
fresh = pool # 库内词全部用过 → 允许复用(词库有限)
terms = [f"{s} t-shirt design" if "t-shirt design" not in s.lower() else s
for s in random.sample(fresh, min(want, len(fresh)))]
print(f"[pinterest_search] direct 模式:国家种子词库随机抽 {len(terms)} 个 + t-shirt design{country}")
else:
used_llm = merge_used(used, attempted)
if max_used_in_prompt > 0:
used_llm = used_llm[-max_used_in_prompt:]
llm = None
if provider != "static":
try:
llm = get_backend(provider)
if hasattr(llm, "bind_config"):
llm.bind_config(config.get("llm_screen") or {})
if provider not in ("mock",) and not getattr(llm, "has_key", False):
print(f"[pinterest_search] {provider} 未配置 API key,降级 mock")
llm = get_backend("mock")
except Exception as e: # noqa: BLE001
print(f"[pinterest_search] LLM 初始化失败: {e}")
llm = None
if llm is not None and hasattr(llm, "generate_pinterest_terms"):
try:
ctx = {"country": country, "seeds": seeds, "used_terms": used_llm, "count": want}
res = llm.generate_pinterest_terms(ctx)
terms = [str(t).strip() for t in (res.get("search_terms") or []) if str(t).strip()]
print(f"[pinterest_search] LLM 生成搜索词 {len(terms)} 个({country},已用词注入 {len(used_llm)}")
except Exception as e: # noqa: BLE001
print(f"[pinterest_search] LLM 生成失败,回退种子词池: {e}")
terms = []
# 3) 兜底:LLM 无结果 → 种子词池随机抽样
if not terms:
terms = [f"{s} t-shirt design" if "t-shirt design" not in s.lower() else s
for s in random.sample(seeds, min(want, len(seeds)))]
print(f"[pinterest_search] 兜底:从种子词池取 {len(terms)} 个")
# 4) 全局过滤(已用/本轮已尝试/黑名单/不适合T恤/去重)——注意:不在此处持久化已用词
# direct 模式:抽样时已避开已用词(库内词有限,全部用过后允许复用),不再额外过滤
if search_mode == "direct":
filtered = terms
else:
filtered = filter_search_terms(terms, merge_used(used, attempted), blacklist)
if not filtered and seeds:
# 生成词全被过滤 → 从种子词池补充(同样过滤)
extra = filter_search_terms(seeds, merge_used(used, attempted), blacklist)
filtered = extra[:want]
stats = dict(state.get("stats") or {})
stats["pinterest_search"] = {
"provider": provider,
"round": rounds,
"generated": len(terms),
"filtered": len(filtered),
"used_total": len(used),
}
print(f"[pinterest_search] 第 {rounds} 轮搜索词 {len(filtered)} 个(已用累计 {len(used)}: "
f"{', '.join(filtered[:3])}{'...' if len(filtered) > 3 else ''}")
return {"pinterest_search_terms": filtered, "pinterest_rounds": rounds,
"stats": stats, "errors": errors}