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
+103
View File
@@ -0,0 +1,103 @@
"""种草图(Seed Shot)生成。
- 模板:configs/seed_shot_templates.yaml(可自定义,占位符 [商品名称]/[材质]/[模特特征])
- 模特特征:configs/model_features.yaml(可自定义,随机取一条)
- 生成:以 product 合成图(图1)为参考,img2img 生成 N 张种草图(保留衣服外观、换场景/模特)
- 占位替换:[商品名称]→cn_title(缺省回退 topic);[材质]→SPU.material[模特特征]→随机
"""
import random
from pathlib import Path
from typing import Any, Dict, List, Optional
import yaml
from graph.paths import project_root
def _load_yaml(rel: str) -> Dict[str, Any]:
for root in (project_root(),):
p = root / rel
if p.exists():
try:
return yaml.safe_load(p.read_text(encoding="utf-8")) or {}
except Exception as e: # noqa: BLE001
print(f"[seed_shot] 读取 {rel} 失败: {e}")
return {}
def load_templates() -> List[Dict[str, str]]:
"""种草图提示词模板列表(无配置时给内置兜底)。"""
data = _load_yaml("configs/seed_shot_templates.yaml")
tpls = data.get("seed_shot_templates") or []
if not tpls:
tpls = [{
"name": "default",
"prompt": (
"【最高优先级约束】请严格保留参考图1中模特所穿衣服的完整外观,包括其原有的颜色、"
"印花图案、版型款式、材质纹理与缝线细节,绝对禁止对衣服本身进行任何形式的修改、"
"重新设计、变色或改变图案。仅提取这件[商品名称],将其穿在一位[模特特征]的身上。"
"全身动态抓拍构图,行走在阳光斑驳的城市林荫道上,微微低头微笑,凸显[材质]的透气与百搭。"
"徕卡Q2摄影质感,高对比度色彩,35mm镜头,f/1.7大光圈,8k分辨率。"
),
}]
return [{"name": str(t.get("name", "default")), "prompt": str(t.get("prompt", ""))}
for t in tpls if t.get("prompt")]
def load_model_features() -> List[str]:
"""模特特征列表(无配置时给内置兜底)。"""
data = _load_yaml("configs/model_features.yaml")
feats = [str(f) for f in (data.get("model_features") or []) if str(f).strip()]
if not feats:
feats = ["20岁清新少女,素颜通透感", "25岁都市职场女性,干练气质"]
return feats
def load_style_features() -> List[str]:
"""服装风格列表(style_features.yaml,随机取一条替换 [服装风格];无配置时内置兜底)。"""
data = _load_yaml("configs/style_features.yaml")
feats = [str(f) for f in (data.get("style_features") or []) if str(f).strip()]
if not feats:
feats = ["极简基础款风格,干净纯粹,无过多繁复装饰",
"日系City Boy/Girl风,微宽松版型,注重舒适度与层次感"]
return feats
def render_prompt(template_prompt: str, cn_title: str, material: str, model_feature: str,
style_feature: str = "") -> str:
"""占位替换:[商品名称]/[材质]/[模特特征]/[服装风格]"""
out = template_prompt.replace("[商品名称]", (cn_title or "").strip() or "这件衣服")
out = out.replace("[材质]", (material or "").strip() or "面料")
out = out.replace("[模特特征]", (model_feature or "").strip() or "模特")
out = out.replace("[服装风格]", (style_feature or "").strip() or "日常休闲风")
return out
def generate_seed_shots(image_backend, base_image: str, cn_title: str, material: str,
count: int, out_dir: str, negative: str = "",
size: str = "1504x2000", prefix: str = "") -> List[str]:
"""生成 count 张种草图(img2img,图1=合成图)。返回产物路径列表。
size: 种草图统一 1504x2000。
prefix: 货号前缀(对应产品货号,命名 {prefix}_seedshot_{n}.png,不覆盖旧文件)。
占位符 [商品名称]/[材质]/[模特特征]/[服装风格] 均随机组合(模板/模特/服装风格各随机取一条)。"""
templates = load_templates()
features = load_model_features()
style_features = load_style_features()
out = Path(out_dir)
out.mkdir(parents=True, exist_ok=True)
paths: List[str] = []
for i in range(count):
tpl = random.choice(templates)
feat = random.choice(features)
style_feat = random.choice(style_features)
prompt = render_prompt(tpl["prompt"], cn_title, material, feat, style_feat)
out_path = str(out / f"{prefix}_seedshot_{i + 1:02d}.png" if prefix
else out / f"seed_shot_{i + 1:02d}.png")
try:
image_backend.print(prompt, base_image, out_path, negative, size=size)
paths.append(out_path)
print(f"[seed_shot] 已生成种草图 {i + 1}/{count}: {out_path}"
f"(模板={tpl['name']},模特={feat[:14]}…,风格={style_feat[:14]}…)")
except Exception as e: # noqa: BLE001
print(f"[seed_shot] 种草图 {i + 1} 生成失败: {e}")
return paths