模板导出增强 + 模特性别分组 + 三合一提示词精简

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)
This commit is contained in:
2026-08-26 18:05:11 +08:00
parent e317547b8b
commit b7f429db89
93 changed files with 2708 additions and 583 deletions
+6 -2
View File
@@ -156,16 +156,18 @@ class MockBackend:
random.shuffle(pool)
terms = pool[:count]
# 不足时用「种子词 + 风格词」组合补足(视觉导向,避免与已用重复)
style_tail = ["aesthetic", "style", "inspiration", "design", "vibe", "art"]
style_tail = ["t-shirt design", "graphic tee", "print art", "vintage tee", "flat design"]
i = 0
while len(terms) < count and pool:
combo = f"{pool[i % len(pool)]} {style_tail[(i // len(pool)) % len(style_tail)]}"
if combo.lower() not in used and combo not in terms:
terms.append(combo)
i += 1
# 自动追加 " t-shirt design":让 Pinterest 返回真正的 T 恤印花图(更适合作印花设计参考)
terms = [f"{t} t-shirt design" if "t-shirt design" not in t.lower() else t for t in terms]
return {"search_terms": terms}
def analyze_pinterest_images(self, image_paths, term="", country=""):
def analyze_pinterest_images(self, image_paths, term="", country="", on_400=None):
"""规则生成设计简报(零 API 成本):按搜索词启发式推导风格/配色/构图。"""
from ..classify import classify, prompt_suggestion
cat = classify(term)
@@ -184,6 +186,8 @@ class MockBackend:
"color_palette": palette,
"composition": composition,
"negative_prompt": negative,
"image_prompt": (f"{motif}, {art_style}, {palette}, {composition}, "
f"original {art_style} t-shirt print design"),
# 生图参考:每条简报对应其来源爬取图(mock 按图逐张产出简报,顺序一一对应)
"ref_images": [str(paths[i])] if i < len(paths) else [],
"source": "pinterest",
+107 -25
View File
@@ -15,9 +15,7 @@ from typing import Any, Dict, List
import requests
# 模型调用一律直连:用户常开 VPN(系统代理),LLM 网关多为国内/自建,走代理会被拦截或变慢。
# 环境变量级 NO_PROXY 双保险(requests/urllib3 均读取),Google 采集(pytrends)不受影响
os.environ.setdefault("NO_PROXY", "*")
os.environ.setdefault("no_proxy", "*")
# 仅请求级 proxies=NO_PROXY 直连,不设置进程级 NO_PROXY 环境变量(避免影响 Google Trends 等外部采集)
from .base import LLMBackend
from graph.paths import runtime_root
@@ -107,6 +105,7 @@ Rules:
# 模板字典按编号存放;TITLE_TEMPLATE_ROUTE 按国家路由到模板编号。
# 模板 1:英语市场(US/GB/AU/MX)→ en_title + cn_title
# 模板 2:日本市场(JP)→ en_title + cn_title + ja_title
# 模板 3:西班牙市场(ES)→ es_title + cn_title
TITLE_TEMPLATES: Dict[str, str] = {
"1": '''# Role
你是一位资深的跨境服装运营专家,精通英语电商的SEO标题逻辑。你的任务是通过分析服装图片,生成高权重的英语-中文商品标题。
@@ -151,15 +150,36 @@ TITLE_TEMPLATES: Dict[str, str] = {
- **Output**: 必须严格返回 JSON 格式,不要包含 Markdown 代码块标记,格式如下:
{"en_title": "Title in English", "cn_title": "中文标题", "ja_title": "日本語タイトル"}''',
"3": '''# Role
你是一位资深的跨境服装运营专家,精通西班牙语电商(Amazon ES, MercadoLibre)的SEO标题逻辑。你的任务是通过分析服装图片,生成高权重的西班牙语-中文商品标题。
# Task
请深度分析图片中的服装特征(品类、风格、材质、剪裁、细节、受众),生成符合西语电商搜索逻辑的中西文标题。
# 当前时间(标题须贴合当下,季节/年份词以此为准)
- **Current time**: {year}-{month}{season}),标题中的年份/季节等时效词必须使用以上时间。
# Analysis Focus (视觉分析重点)
- 品类识别:准确判断西班牙语核心词(如 Vestido, Blusa, Sudadera)和中文核心词(如 连衣裙, 卫衣)。
- 风格定位:判断风格流派(如 Boho, Vintage, Minimalista / 法式, 复古, 极简)。
- 设计细节:提取领型、袖型、裙长等(如 Escote en V, Manga abullonada / V领, 阔袖)。
- 适用场景:推断穿着场景(如 Playa, Oficina, Fiesta / 度假, 通勤, 约会)。
# Constraints (生成规则)
- Spanish Title: 遵循 Amazon ES/MercadoLibre 风格,核心词前置,包含材质、风格、场景等长尾词,符合西语搜索习惯。
- Chinese Title: 遵循淘宝/1688风格,关键词权重递减,包含年份/季节+风格+核心词+卖点+人群。
- Output: 必须严格返回 JSON 格式,不要包含 Markdown 代码块标记,格式如下:
{"es_title": "Título en español", "cn_title": "中文标题"}''',
}
# 国家 → 标题模板编号(JP 路由到模板 2,其余默认模板 1;后续可按国家新增模板 3...
# 国家 → 标题模板编号(JP 路由到模板 2,ES 路由到模板 3其余默认模板 1;后续可按国家新增模板)
TITLE_TEMPLATE_ROUTE: Dict[str, str] = {
"US": "1",
"GB": "1",
"JP": "2",
"AU": "1",
"MX": "1",
"ES": "3",
}
@@ -234,11 +254,19 @@ def build_user_prompt(country, topics, aesthetic_hint):
# —— Pinterest 参考模式:搜索词生成(json_schema 结构化 + 动态注入已用词防重复)——
PINTEREST_TERM_SYSTEM_PROMPT = """You are a Pinterest search-term generator for print-on-demand (POD) T-shirt design.
You turn seed words into diverse, visual, Pinterest-friendly search terms that will be used to scrape inspiration images.
PINTEREST_TERM_SYSTEM_PROMPT = """You are a Pinterest search-term generator for print-on-demand (POD) SHORT-SLEEVE T-SHIRT print design.
You turn seed words into diverse, visual, Pinterest-friendly search terms that will be used to scrape inspiration images
that are DIRECTLY usable as reference for a t-shirt print design.
RULES:
- Generate EXACTLY the requested number of search terms.
- Generate EXACTLY the requested number of search terms (usually 1 per call).
- Every term MUST be a "t-shirt design" style query: think of it as if the user typed "<concept> t-shirt design" on
Pinterest, so the scraped images are actual t-shirt graphics / flat print artworks, NOT lifestyle photos, scenery,
architecture, food plates, or anything that cannot become a clean chest print.
- Terms MUST be suitable for a SHORT-SLEEVE T-SHIRT PRINT: a flat, graphic, print-ready concept (illustration, mascot,
emblem, pattern, typography, slogan) that works as a chest print between about 15x18 cm and 26x32 cm.
- Prefer a clear central subject with a strong silhouette and balanced composition that reads well as a standalone print.
- AVOID terms that lead to full-scene photos, landscapes, architecture, food plates, or anything that cannot become a clean t-shirt print.
- Terms must be VISUAL / AESTHETIC concepts (style, motif, scene, color) suitable as T-shirt print inspiration.
- Terms must be DIVERSE and NON-OVERLAPPING: never repeat a concept, never give near-synonyms of each other.
- DO NOT repeat or closely paraphrase ANY of the "already used terms" provided in the user message.
@@ -257,7 +285,7 @@ PINTEREST_TERM_SCHEMA = {
"search_terms": {
"type": "array",
"items": {"type": "string"},
"description": "Diverse, non-overlapping Pinterest search terms for T-shirt design inspiration",
"description": "Diverse, non-overlapping Pinterest search terms for short-sleeve t-shirt print design inspiration",
}
},
"required": ["search_terms"],
@@ -267,10 +295,10 @@ PINTEREST_TERM_SCHEMA = {
def build_pinterest_term_user_prompt(context: Dict[str, Any]) -> str:
"""动态注入:种子词(灵感)+ 已用搜索词(禁止重复)+ 数量要求。"""
"""动态注入:种子词(灵感)+ 已用搜索词(禁止重复)+ 数量要求(按需每次 1 个)"""
seeds = context.get("seeds", []) or []
used = context.get("used_terms", []) or []
count = int(context.get("count", 10))
count = int(context.get("count", 1))
lines = [
f"Country: {context.get('country', '')}",
f"Seed words (inspiration, may combine or extend): {', '.join(seeds)}",
@@ -278,12 +306,18 @@ def build_pinterest_term_user_prompt(context: Dict[str, Any]) -> str:
f"Already used terms — DO NOT repeat or paraphrase ANY of these: "
f"{', '.join(used) if used else '(none yet)'}",
"",
f"Generate {count} new, diverse, non-overlapping Pinterest search terms.",
f"Generate {count} new, diverse, non-overlapping Pinterest search term(s) "
f"that are suitable for a SHORT-SLEEVE T-SHIRT PRINT design "
f"(flat, graphic, print-ready motif that works as a chest print). "
f"Each term should read like \"<concept> t-shirt design\" so Pinterest returns "
f"actual t-shirt graphics / flat print artwork as reference.",
]
return "\n".join(lines)
# —— Pinterest 参考模式:图片分析 → 原创设计简报(多模态)——
# image_prompt 由 LLM 直接输出完整的英文生图提示词(多模态对图片的描述拼接),
# 不再走「四要素 + 固定模板」装配;尺寸/白底等统一约束段由 prompt_node 自动追加。
PINTEREST_ANALYZE_SYSTEM_PROMPT = """You are a POD (print-on-demand) T-shirt design analyst.
You receive Pinterest reference images for one search term. For each image, extract the VISUAL CONCEPT
(style, mood, motif, color palette, composition) that makes it appealing, then produce an ORIGINAL
@@ -301,9 +335,14 @@ RULES:
- composition: English layout (e.g. "centered emblem with balanced negative space").
- concept: Chinese, one sentence describing the design idea.
- negative_prompt: what to avoid (real people, likeness, characters, logos, text).
- image_prompt: a COMPLETE, fluent English text-to-image prompt for generating the ORIGINAL flat print
design artwork (the print itself, NOT a garment photo). Describe the motif, art style, colors, layout
and mood in natural English, as a standalone print. Do NOT include garment / shirt / model / mannequin /
background-scene / watermark words. Do NOT mention any size or white-background suffix — a fixed
"small centered print on pure white" suffix will be appended automatically by the system.
Return JSON with the field "designs" (array of objects with keys:
motif, art_style, color_palette, composition, concept, negative_prompt)."""
motif, art_style, color_palette, composition, concept, negative_prompt, image_prompt)."""
PINTEREST_ANALYZE_SCHEMA = {
"name": "pinterest_design_briefs",
@@ -315,15 +354,17 @@ PINTEREST_ANALYZE_SCHEMA = {
"items": {
"type": "object",
"properties": {
"image_index": {"type": "integer"},
"motif": {"type": "string"},
"art_style": {"type": "string"},
"color_palette": {"type": "string"},
"composition": {"type": "string"},
"concept": {"type": "string"},
"negative_prompt": {"type": "string"},
"image_prompt": {"type": "string"},
},
"required": ["motif", "art_style", "color_palette", "composition",
"concept", "negative_prompt"],
"required": ["image_index", "motif", "art_style", "color_palette",
"composition", "concept", "negative_prompt", "image_prompt"],
"additionalProperties": False,
},
}
@@ -341,7 +382,11 @@ def build_pinterest_analyze_user_prompt(term: str, country: str, image_count: in
f"Reference images attached: {image_count} images.\n\n"
f"Analyze the attached images and produce {image_count} ORIGINAL design briefs "
f"(one per image), each capturing the visual vibe as an original T-shirt print design. "
f"Do NOT copy the images."
f"Do NOT copy the images.\n"
f"For EACH brief you MUST set image_index to the 0-based position of the input image "
f"it was derived from (first image = 0, second = 1, ...). Every image_index from 0 to "
f"{max(image_count - 1, 0)} must appear exactly once — this links each brief to its "
f"source image so the design is generated from the SAME image that was analyzed."
)
@@ -419,9 +464,31 @@ def _extract_json(text):
try:
return json.loads(text)
except json.JSONDecodeError:
m = re.search(r"\{.*\}", text, re.S)
if m:
return json.loads(m.group(0))
# 找第一个 { 到与之平衡的 },逐字符跳过字符串内的花括号,避免贪婪匹配截断 JSON
start = text.find("{")
if start == -1:
raise
depth = 0
in_str = False
esc = False
for i in range(start, len(text)):
ch = text[i]
if in_str:
if esc:
esc = False
elif ch == "\\":
esc = True
elif ch == '"':
in_str = False
else:
if ch == '"':
in_str = True
elif ch == "{":
depth += 1
elif ch == "}":
depth -= 1
if depth == 0:
return json.loads(text[start:i + 1])
raise
@@ -512,13 +579,17 @@ class OpenAICompatBackend(LLMBackend):
raw = _retry(lambda: call_openai_compatible_structured(cfg, messages, PINTEREST_TERM_SCHEMA, timeout=120))
parsed = _extract_json(raw)
terms = [str(x).strip() for x in (parsed.get("search_terms", []) or []) if str(x).strip()]
# 自动追加 " t-shirt design":让 Pinterest 返回真正的 T 恤印花图(更适合作印花设计参考)
terms = [f"{t} t-shirt design" if "t-shirt design" not in t.lower() else t for t in terms]
return {"search_terms": terms}
def analyze_pinterest_images(self, image_paths: List[str], term: str, country: str = "") -> List[Dict[str, Any]]:
def analyze_pinterest_images(self, image_paths: List[str], term: str, country: str = "",
on_400=None) -> List[Dict[str, Any]]:
"""多模态分析 Pinterest 图片 → 原创设计简报列表。
图片输入不被模型支持(纯文本模型 400)时自动降级为纯文本分析(仅用搜索词)。
失败返回 [],由节点兜底(回退 mock 规则简报)。
on_400: 每次 HTTP 400(且含「内容/图片」)时回调(供调用方累计放弃计数)。
"""
cfg = self._cfg
api_key = cfg.get("api_key", "")
@@ -542,6 +613,16 @@ class OpenAICompatBackend(LLMBackend):
except Exception as e: # noqa: BLE001
print(f"[pinterest_analyze] 图片读取失败 {p}: {e}")
def _notify_400(exc) -> None:
if on_400 is None:
return
try:
from graph.pinterest import is_400_content_image
if is_400_content_image(exc):
on_400()
except Exception: # noqa: BLE001
pass
def _call(use_images: bool) -> str:
user_content: List[Any] = [
{"type": "text", "text": build_pinterest_analyze_user_prompt(term, country, len(data_uris))},
@@ -568,7 +649,8 @@ class OpenAICompatBackend(LLMBackend):
resp = requests.post(url, json=payload, headers=headers, timeout=180, proxies=NO_PROXY)
resp.raise_for_status()
return str(resp.json()["choices"][0]["message"].get("content") or "")
except Exception: # noqa: BLE001 兼容厂商不支持 json_schema
except Exception as e: # noqa: BLE001 兼容厂商不支持 json_schema
_notify_400(e)
payload["response_format"] = {"type": "json_object"}
resp = requests.post(url, json=payload, headers=headers, timeout=180, proxies=NO_PROXY)
resp.raise_for_status()
@@ -611,21 +693,20 @@ class OpenAICompatBackend(LLMBackend):
"color_palette": str(d.get("color_palette", "")).strip(),
"composition": str(d.get("composition", "")).strip(),
"negative_prompt": str(d.get("negative_prompt", "")).strip(),
"image_prompt": str(d.get("image_prompt", "")).strip(),
# 生图参考:每条简报对应其来源爬取图(LLM 按图逐张产出简报,顺序一一对应)
"ref_images": [str(image_paths[i])] if i < len(image_paths) else [],
"source": "pinterest",
})
return designs
def generate_title(self, image_path: str, system_prompt: str = "", country: str = "",
fallback_text: str = "") -> Dict[str, Any]:
"""多模态标题生成;图片输入不被模型支持(如 qwen 纯文本模型 400)时,
自动降级为纯文本生成(fallback_text 为商品描述/热点主题)。"""
def generate_title(self, image_path: str, system_prompt: str = "", country: str = "") -> Dict[str, Any]:
"""多模态:分析服装图片,生成商品标题(按国家路由模板)。
系统提示词:显式传入优先;否则按 country 经 TITLE_TEMPLATE_ROUTE 路由到对应模板。
模板 1US/GB/AU/MX)返回 {"en_title","cn_title"}
模板 2(JP)额外返回 {"ja_title"}
模板 2(JP)额外返回 {"ja_title"}
模板 3ES)返回 {"es_title","cn_title"}。
无 key/调用失败返回 {}(调用方兜底不中断)。
"""
cfg = self._cfg
@@ -678,6 +759,7 @@ class OpenAICompatBackend(LLMBackend):
"en_title": str(parsed.get("en_title", "")).strip(),
"cn_title": str(parsed.get("cn_title", "")).strip(),
"ja_title": str(parsed.get("ja_title", "")).strip(),
"es_title": str(parsed.get("es_title", "")).strip(),
}
except Exception as e: # noqa: BLE001
print(f"[titles] 标题生成失败: {e}")