v106-v109 童装支持 + 标题模板外部化 + 模板导出增强

- 新增男童/女童检测(gender_from_category 优先判童装)与童装场景图生成
  (configs/kids_features.yaml:模特/场景/服装风格,同一商品固定同一组)
- 童装 SPU 字段映射:kids_type→SPU商品属性-类型、kids_age→适用年龄段、
  target_audience 按性别映射、kids_type_map 女童「上衣」→「针织上衣」
- 标题生成提示词外部化:prompts/title_prompt_{1,2,3}.md + config.yaml 路由表
- 模板多站点匹配:经营站点可配多个,命中任意一个即匹配
- 种草图生成失败自动重试(seed_shot.retries,换场景/模特/风格)
- 模板导出新增 Preview 文件夹(成功产品 _composite.oss.jpg + result.xlsx)
- 修复 SKU 尺码未按从小到大排序(_size_rank 支持单一年龄码 6Y/10Y)
This commit is contained in:
2026-09-01 17:24:23 +08:00
parent 3dc594cf57
commit d68cc3b9e3
20 changed files with 826 additions and 136 deletions
+41 -8
View File
@@ -13,6 +13,7 @@ from pathlib import Path
from typing import Any, Dict, List
import requests
import yaml
# 模型调用一律直连:用户常开 VPN(系统代理),LLM 网关多为国内/自建,走代理会被拦截或变慢。
# 仅请求级 proxies=NO_PROXY 直连,不设置进程级 NO_PROXY 环境变量(避免影响 Google Trends 等外部采集)。
@@ -101,12 +102,14 @@ Rules:
- Return ONLY JSON of shape: {"style_seeds": [...], "related_seeds": [...]}'''
# —— 商品标题生成(多模态:分析服装图片 → 中英双语 SEO 标题)——
# 模板字典按编号存放;TITLE_TEMPLATE_ROUTE 按国家路由到模板编号。
# —— 商品标题生成(多模态:分析服装图片 → SEO 标题)——
# 提示词模板存 prompts/title_prompt_<编号>.md(可自由编辑,优先运行根 exe 旁,回退数据根);
# 国家 → 模板编号路由存 config.yaml 顶层 title_templates.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] = {
# 以下为「内置默认」兜底:md/yaml 缺失或留空时回退,避免标题生成中断。
_TITLE_TEMPLATES_FALLBACK: Dict[str, str] = {
"1": '''# Role
你是一位资深的跨境服装运营专家,精通英语电商的SEO标题逻辑。你的任务是通过分析服装图片,生成高权重的英语-中文商品标题。
@@ -172,8 +175,8 @@ TITLE_TEMPLATES: Dict[str, str] = {
{"es_title": "Título en español", "cn_title": "中文标题"}''',
}
# 国家 → 标题模板编号(JP 路由到模板 2ES 路由到模板 3,其余默认模板 1;后续可按国家新增模板
TITLE_TEMPLATE_ROUTE: Dict[str, str] = {
# 内置默认路由(config.yaml title_templates.route 缺失/留空时回退;JP→模板2ES模板3,其余默认模板1
_TITLE_ROUTE_FALLBACK: Dict[str, str] = {
"US": "1",
"GB": "1",
"JP": "2",
@@ -183,6 +186,33 @@ TITLE_TEMPLATE_ROUTE: Dict[str, str] = {
}
def _read_title_prompt(tpl_no: str) -> str:
"""读取 prompts/title_prompt_<编号>.md:优先运行根(exe 旁,可编辑),回退数据根;找不到/空返回空串。"""
for base in (runtime_root(), project_root()):
p = base / "prompts" / f"title_prompt_{tpl_no}.md"
if p.exists():
text = p.read_text(encoding="utf-8").strip()
if text:
return text
return ""
def _load_title_route() -> Dict[str, str]:
"""从 config.yaml 顶层 title_templates.route 读取国家→模板编号路由;缺失/留空回退内置默认。"""
for base in (runtime_root(), project_root()):
p = base / "config.yaml"
if not p.exists():
continue
try:
data = yaml.safe_load(p.read_text(encoding="utf-8")) or {}
route = (data.get("title_templates") or {}).get("route") or {}
if isinstance(route, dict) and route:
return {str(k): str(v) for k, v in route.items()}
except Exception as e: # noqa: BLE001
print(f"[titles] 读取 config.yaml title_templates.route 失败: {e}")
return dict(_TITLE_ROUTE_FALLBACK)
def _inject_now(prompt: str) -> str:
"""把模板中的 {year}/{month}/{season} 替换为当前时间(用 replace 避免 JSON 花括号冲突)。"""
import datetime
@@ -197,8 +227,10 @@ def _inject_now(prompt: str) -> str:
def resolve_title_prompt(country: str = "") -> str:
"""按国家解析标题生成提示词(自动注入当前时间变量);未知国家/留空回退模板 1。"""
tpl_no = TITLE_TEMPLATE_ROUTE.get(country or "", "1")
return _inject_now(TITLE_TEMPLATES.get(tpl_no, TITLE_TEMPLATES["1"]))
route = _load_title_route()
tpl_no = route.get(country or "", "1")
prompt = _read_title_prompt(tpl_no) or _TITLE_TEMPLATES_FALLBACK.get(tpl_no, _TITLE_TEMPLATES_FALLBACK["1"])
return _inject_now(prompt)
def build_seed_user_prompt(context: Dict[str, Any]) -> str:
@@ -810,7 +842,8 @@ class OpenAICompatBackend(LLMBackend):
def generate_title(self, image_path: str, system_prompt: str = "", country: str = "") -> Dict[str, Any]:
"""多模态:分析服装图片,生成商品标题(按国家路由模板)。
系统提示词:显式传入优先;否则按 country 经 TITLE_TEMPLATE_ROUTE 路由到对应模板。
系统提示词:显式传入优先;否则按 country 经 config.yaml title_templates.route 路由到
prompts/title_prompt_<编号>.md 对应模板(缺失回退内置默认)。
模板 1US/GB/AU/MX)返回 {"en_title","cn_title"}
模板 2(JP)额外返回 {"ja_title"}
模板 3ES)返回 {"es_title","cn_title"}。