POD 趋势感知 Agent:缓存热点模式 + 三图合成 + 热点去重/风格去重 + review 兜底

- 缓存热点批量流程(有采集缓存不触发 Google)
- 简报不足直接从采集缓存生成(轻量补齐)
- 三图合成(模特/印花/底图)+ 底图压缩 <2MB
- 热点去重→风格去重自动切换 + 不适合类目 review 兜底
- 透明背景(background=transparent)+ 提示词清洗(敏感词/背景描述)
- 任务前 basemap 校验 + 模板国家校验 + 模特任务级分配
This commit is contained in:
2026-08-22 14:14:01 +08:00
commit f493bde8a9
98 changed files with 10280 additions and 0 deletions
+120
View File
@@ -0,0 +1,120 @@
#!/usr/bin/env python
"""POD 热点抓取 Agent —— LangGraph 工程化版命令行入口。
用法示例:
# 跑全部国家(默认 US GB JP AU)
python cli.py
# 只跑英国,使用 Mock 兜底 LLM
python cli.py -c GB --provider mock
# 跑美国并用真实 LLMprovider 在 config.llm_screen 已配好时也可不传)
python cli.py -c US
# 提供平铺衣服底图,把设计印上去(需 config.compose.backend + api_key
python cli.py -c US --base path/to/flatlay.png
产物按国家落在 output/<country>/design_briefs.json/md、composite_prompts.json/md、report.md)。
每个国家的提示词规则在 prompts/<country>/system_prompt.md + aesthetics.yaml)。
"""
import argparse
import sys
from pathlib import Path
# 把项目根加入 sys.path,确保 `import graph` 可用
ROOT = Path(__file__).resolve().parent
sys.path.insert(0, str(ROOT))
import yaml # noqa: E402
from graph.agent import run_country # noqa: E402
def main():
parser = argparse.ArgumentParser(description="POD 热点抓取 Agent (LangGraph)")
parser.add_argument("-c", "--countries", nargs="*", default=None,
help="国家代码(US GB JP AU),可多个;默认取 config.countries")
parser.add_argument("--provider", default=None,
help="覆盖 LLM 后端:mock / openai_compat / openai / deepseek / qwen / moonshot")
parser.add_argument("--base", default=None,
help="平铺衣服底图路径;提供则尝试印图(需 config.compose 配置图像后端)")
parser.add_argument("--config", default="config.yaml", help="全局配置文件路径")
# —— 产品图生成(product 节点)——
parser.add_argument("--spu", default=None, help="选品款号(如 DG004;留空自动选第一个有底图的)")
parser.add_argument("--sku", default=None, help="颜色编码(如 DG004-BL01;多个用逗号分隔,如 DG015-VT01,DG015-DARK HEATHER;留空自动选)")
parser.add_argument("--single-spu", action="store_true", help="模板导出:单 SPU 下挂多颜色变体(默认每颜色一个 SPU 块)")
parser.add_argument("--product-backend", default=None, help="产品图像后端:openai / mock")
parser.add_argument("--list-spus", action="store_true", help="列出 db 可选 SPU 并退出")
parser.add_argument("--list-colors", default=None, metavar="SPU", help="列出某款号颜色并退出")
args = parser.parse_args()
cfg_path = ROOT / args.config
if not cfg_path.exists():
print(f"配置文件不存在: {cfg_path}")
sys.exit(1)
cfg = yaml.safe_load(cfg_path.read_text(encoding="utf-8")) or {}
# 产品选品查询(--list-spus / --list-colors
pcfg0 = cfg.get("product") or {}
dbp = Path(pcfg0.get("db_path", "db/spu_sku.db"))
if not dbp.is_absolute():
dbp = ROOT / dbp
if args.list_spus:
from graph.product import list_spus
print("=== db 可选 SPU(款号 / 品类 / 印花类型 / 国家)===")
for s in list_spus(dbp):
print(f" {s['code']:10s} {s.get('style') or '':8s} {s.get('printing_type') or '':12s} {s.get('country') or ''}")
sys.exit(0)
if args.list_colors:
from graph.product import list_colors
print(f"=== {args.list_colors} 可选颜色(SKU.code / 色名)===")
for c in list_colors(dbp, args.list_colors):
print(f" {c['sku_code']:16s} {c['color']}")
sys.exit(0)
if args.provider:
cfg.setdefault("llm_screen", {})["provider"] = args.provider
if args.spu or args.sku or args.product_backend or args.single_spu:
p = cfg.setdefault("product", {})
if args.spu:
p["spu_code"] = args.spu
if args.sku:
p["sku_code"] = args.sku
if args.product_backend:
p["backend"] = args.product_backend
if args.single_spu:
p["spu_per_color"] = False
countries = args.countries or cfg.get("countries", ["US", "GB", "JP", "AU"])
base_image = args.base
overall_errors = 0
for c in countries:
print(f"\n===== 开始处理 {c} =====")
try:
res = run_country(c, cfg, ROOT, base_image=base_image)
except Exception as e: # noqa: BLE001
print(f"[{c}] 运行失败: {e}")
overall_errors += 1
continue
stats = res.get("stats", {})
f = stats.get("fetch", {})
fl = stats.get("filter", {})
sc = stats.get("screen", {})
pr = stats.get("prompt", {})
print(f"[{c}] 抓取原始 {f.get('raw_rows')} 行 | 过滤后保留 {fl.get('kept')} | "
f"筛选保留 {sc.get('kept')} | 设计简报 {pr.get('briefs')}")
errs = res.get("errors") or []
if errs:
overall_errors += len(errs)
print(f"[{c}] 节点兜底捕获 {len(errs)} 条错误:")
for e in errs:
print(f" - [{e.get('node')}] {e.get('type')}: {e.get('message')}")
print(f"[{c}] 产物目录: output/{c}/")
print(f"\n全部完成。累计兜底错误 {overall_errors} 条(不影响产出)。")
if __name__ == "__main__":
main()