Files
pod_trend_agent/graph/nodes/prompt_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

108 lines
5.8 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.
"""节点 5/6:提示词构造(prompt_build)。
读取 prompts/<country>/ 的 extra 风格规则,用固定模板装配四种最终提示词
image_prompt / wearable_prompt / composite_prompt / composite_negative)。
四要素缺失时用 derive_style_palette 动态兜底,保证每条提示词结构一致、有规则。
"""
from typing import Any, Dict, List
import random
from graph.style_rules import derive_style_palette, derive_composition
from graph.templates import assemble_prompts
from graph.validate import validate_brief, with_fallback
# —— Pinterest 图生图生最终设计稿时统一追加的「小印花 + 纯白底」约束段 ——
# (从热点搜集的文字生图模板里提炼:尺寸缩小、禁止自带背景/满幅)
PINTEREST_PRINT_SUFFIX = (
" standalone pure print design on a pure white background, "
"the print artwork is SMALL and CENTERED with clearly larger white margins around it, "
"print area between about 15x18 cm and 26x32 cm, "
"do NOT fill the entire canvas, do NOT force full-bleed, "
"do NOT add any gradient, texture or background color behind the artwork, "
"no garment, no shirt, no model, no mannequin, no watermark"
)
# 图像生成策略敏感词 → 安全等效描述(生成设计稿前清洗 motif,
# 避免 gpt-image 等内容策略频繁拦截导致"生图限制多")
_IMG_RISKY_SWAP = {
"skull": "smiley mascot", "skeleton": "cute mascot", "blood": "red accents",
"gore": "bold shapes", "gun": "star", "weapon": "tool", "bomb": "firework",
"drug": "confetti", "demon": "cute monster", "devil": "mischievous imp",
"occult": "mystic pattern", "satanic": "dark pattern", "nazi": "retro emblem",
"hitler": "retro emblem", "zombie": "friendly ghoul", "horror": "spooky-cute",
"vampire": "night owl", "politics": "abstract shapes", "political": "abstract",
"president": "captain", "army": "team", "police": "officer",
}
def _safe_motif(motif: str) -> str:
"""清洗 motif 中的图像策略敏感词(替换为安全等效描述),降低生图内容政策拦截率。"""
low = motif.lower()
for k, v in _IMG_RISKY_SWAP.items():
if k in low:
# 按词边界替换(避免误伤 "letterhead" 等)
import re
motif = re.sub(rf"\b{re.escape(k)}\b", v, motif, flags=re.IGNORECASE)
low = motif.lower()
return motif
@with_fallback("prompt_build")
def prompt_node(state: Dict[str, Any]) -> Dict[str, Any]:
screened: List[Dict[str, Any]] = state.get("screened") or []
config = state["config"]
country = state["country"]
cc = state["country_config"]
extra_rules = cc.get("extra_style_rules") or []
tpls = config.get("prompt_templates") or {}
briefs: List[Dict[str, Any]] = []
for r in screened:
r = validate_brief(r)
art, pal = derive_style_palette(
r["topic"], country, extra_rules=extra_rules, category=r.get("design_category")
)
motif = (r.get("motif") or "").strip() or r.get("topic", "")
cleaned = _safe_motif(motif)
if cleaned != motif:
print(f"[prompt] motif 敏感词清洗: 「{motif}」→「{cleaned}」(降低生图内容政策拦截)")
r["motif"] = cleaned
motif = cleaned
art_style = (r.get("art_style") or art).strip()
palette = (r.get("color_palette") or pal).strip()
composition = (r.get("composition") or derive_composition(r["topic"], r.get("design_category"))).strip()
prompts = assemble_prompts(motif, art_style, palette, composition, tpls, country)
# Pinterest 简报:直接用 LLM 多模态对图片的描述拼接的 image_prompt(跳过四要素模板),
# 仅追加统一的「小印花 + 纯白底」约束段;wearable/composite 仍用模板装配。
llm_ip = (r.get("image_prompt") or "").strip()
if r.get("source") == "pinterest" and llm_ip:
prompts["image_prompt"] = llm_ip + PINTEREST_PRINT_SUFFIX
print(f"[prompt] Pinterest 简报用 LLM 多模态描述作为 image_prompt(跳过四要素模板): 「{r['topic']}」")
# 文字印花(约 30% 概率):简报有 slogan 时,随机注入文字段到设计稿提示词
slogan = (r.get("slogan") or "").strip()
if slogan and random.random() < float(config.get("prompt_templates", {}).get("text_ratio", 0.3)):
text_seg = (f', with the text "{slogan}" rendered as bold retro typography, '
f'lettering clean and correctly spelled, high contrast, as the focal text of the print')
prompts["image_prompt"] = prompts["image_prompt"] + text_seg
r["used_slogan"] = slogan
# review(疑似商标/受保护主题)→ 动态注入「原创化魔改」引导:只做风格参考,禁止复刻品牌/商标/角色,
# 换名换细节,生成通用非侵权的致敬式设计
if str(r.get("risk_level", "")).strip().lower() == "review":
prompts["image_prompt"] = (prompts["image_prompt"]
+ " IMPORTANT: this theme is ONLY a loose stylistic reference. "
"Do NOT reproduce any brand logo, trademark, character, mascot, copyrighted artwork or real person. "
"Create a fully ORIGINAL design with a different name and distinct visual details and colors — "
"a generic, non-infringing homage in the same mood, clearly distinct from the original.")
print(f"[prompt] review 简报注入原创化魔改引导: 「{r['topic']}」")
r.update(prompts)
r["motif"] = motif
r["art_style"] = art_style
r["color_palette"] = palette
r["composition"] = composition
briefs.append(r)
stats = dict(state.get("stats") or {})
stats["prompt"] = {"briefs": len(briefs)}
return {"briefs": briefs, "stats": stats}