feat(product-suite): add prompt template settings

This commit is contained in:
chengma
2026-07-16 16:18:11 +08:00
parent 613950c9f3
commit eea3fdd0a9
14 changed files with 1017 additions and 35 deletions
+190 -15
View File
@@ -3,9 +3,15 @@
from __future__ import annotations
from collections import OrderedDict
import re
FIXED_CATEGORIES = ("白底图", "场景图", "卖点图")
FIXED_CATEGORY_HELPERS = {
"白底图": "白底主图,多角度呈现商品细节",
"场景图": "生活化场景展示商品使用方式",
"卖点图": "突出核心卖点和差异化优势",
}
DEFAULT_CATEGORY_COUNTS = OrderedDict(
(("白底图", 1), ("场景图", 2), ("卖点图", 2))
)
@@ -16,6 +22,60 @@ RATIOS = ("1:1", "3:4", "4:3", "16:9", "9:16")
LAST_SETTING_KEYS = ("platform", "country", "language", "ratio")
MAX_CATEGORY_NAME_LENGTH = 10
MAX_GENERATION_COUNT_WITHOUT_CONFIRM = 16
PRODUCT_SUITE_PLACEHOLDERS = (
"套图名称",
"补充描述",
"平台",
"国家地区",
"输出语言",
"图片比例",
"商品ID",
"主参考图序号",
"参考图规则",
"商品卖点与要求",
"尺寸与长图规则",
"禁用内容规则",
"价格信息规则",
"尺码信息规则",
)
PRODUCT_SUITE_REQUIRED_PLACEHOLDERS = (
"套图名称",
"补充描述",
"图片比例",
"参考图规则",
"商品卖点与要求",
"尺寸与长图规则",
"禁用内容规则",
"价格信息规则",
"尺码信息规则",
)
PRODUCT_SUITE_READ_ONLY_RULE_PLACEHOLDERS = (
"尺寸与长图规则",
"禁用内容规则",
"价格信息规则",
"尺码信息规则",
)
PRODUCT_SUITE_SIZE_RULE = (
"重要尺寸要求:最终输出必须严格符合所选比例的单张完整构图电商图,"
"禁止海报长图、详情页长图和多宫格拼接版面。"
)
PRODUCT_SUITE_FORBIDDEN_CONTENT_RULE = (
"重要禁用内容:禁止在画面中出现任何国旗、旗帜、国徽、地图轮廓、"
"政治符号或类似国家/地区标识。"
)
PRODUCT_SUITE_PRICE_RULE = (
"价格信息规则:除非用户明确提供价格、折扣或活动价,否则禁止自行添加"
"价格、币别符号、折扣数字或促销金额。"
)
PRODUCT_SUITE_SIZE_INFO_RULE = (
"尺码信息规则:除非用户或参考图明确提供尺码、尺寸或规格,否则禁止自行"
"编造尺码、尺寸、适用身高体重等内容。"
)
_PRODUCT_SUITE_PLACEHOLDER_RE = re.compile(r"\{([^{}\r\n]+)\}")
class ProductSuitePromptError(ValueError):
"""Raised when a product-suite prompt template is invalid."""
def default_suite_settings():
@@ -89,6 +149,15 @@ def category_order(settings):
return list(FIXED_CATEGORIES) + custom
def category_helper(category):
return FIXED_CATEGORY_HELPERS.get(str(category or ""), "")
def category_description(category):
helper = category_helper(category)
return "," + helper if helper else ""
def suite_total_count(settings, image_count):
normalized = normalize_suite_settings(settings)
categories = normalized["categories"]
@@ -100,24 +169,129 @@ def suite_total_count(settings, image_count):
return white_count + other_count * max(1, int(image_count or 0))
def build_suite_prompt(base_prompt, settings, category, item_id, source_index=1):
normalized = normalize_suite_settings(settings)
context = [
"生成一张电商商品套图。",
"平台:%s" % normalized["platform"],
"国家地区:%s" % normalized["country"],
"输出语言:%s" % normalized["language"],
"图片比例:%s" % normalized["ratio"],
"套图分类:%s" % str(category),
"商品ID:%s" % str(item_id or ""),
"当前主参考图序号:%d" % max(1, int(source_index or 1)),
"商品卖点与要求:%s" % str(base_prompt or "").strip(),
"保持商品主体、款式、颜色和关键细节准确,不添加无依据的功能或参数。",
def product_suite_prompt_errors(template_text):
text = str(template_text or "")
errors = []
if not text.strip():
return ["套图提示词模板不能为空"]
matches = list(_PRODUCT_SUITE_PLACEHOLDER_RE.finditer(text))
remainder = _PRODUCT_SUITE_PLACEHOLDER_RE.sub("", text)
if "{" in remainder or "}" in remainder:
errors.append("模板包含未闭合花括号或不支持的字面花括号")
names = [match.group(1) for match in matches]
unknown = sorted(set(names) - set(PRODUCT_SUITE_PLACEHOLDERS))
if unknown:
errors.append("模板包含未知变量:%s" % "、".join("{%s}" % name for name in unknown))
missing = [
name for name in PRODUCT_SUITE_REQUIRED_PLACEHOLDERS if name not in names
]
return "\n".join(context)
if missing:
errors.append("模板缺少必需变量:%s" % "、".join("{%s}" % name for name in missing))
invalid_rule_lines = []
for line in text.splitlines():
line_names = _PRODUCT_SUITE_PLACEHOLDER_RE.findall(line)
for name in line_names:
if (
name in PRODUCT_SUITE_READ_ONLY_RULE_PLACEHOLDERS
and line.strip() != "{%s}" % name
):
invalid_rule_lines.append(name)
if invalid_rule_lines:
errors.append(
"只读规则变量必须独占一行:%s"
% "、".join("{%s}" % name for name in sorted(set(invalid_rule_lines)))
)
return errors
def build_job_specs(source_assets, base_prompt, settings, item_id):
def validate_product_suite_prompt(template_text):
errors = product_suite_prompt_errors(template_text)
if errors:
raise ProductSuitePromptError(";".join(errors))
return str(template_text)
def product_suite_prompt_context(
base_prompt,
settings,
category,
item_id,
source_index=1,
):
normalized = normalize_suite_settings(settings)
item_text = str(item_id or "").strip()
if not item_text or item_text.startswith("draft_"):
item_text = "未绑定商品"
reference_index = max(1, int(source_index or 1))
return {
"套图名称": str(category or ""),
"补充描述": category_description(category),
"平台": normalized["platform"],
"国家地区": normalized["country"],
"输出语言": normalized["language"],
"图片比例": normalized["ratio"],
"商品ID": item_text,
"主参考图序号": str(reference_index),
"参考图规则": (
"参考图规则:当前上传图片是本任务唯一主参考图(序号%d);保持商品主体、"
"款式、颜色和关键细节准确;不编造用户与参考图均未提供的信息。"
% reference_index
),
"商品卖点与要求": str(base_prompt or "").strip(),
"尺寸与长图规则": PRODUCT_SUITE_SIZE_RULE,
"禁用内容规则": PRODUCT_SUITE_FORBIDDEN_CONTENT_RULE,
"价格信息规则": PRODUCT_SUITE_PRICE_RULE,
"尺码信息规则": PRODUCT_SUITE_SIZE_INFO_RULE,
}
def render_product_suite_prompt(template_text, context):
validate_product_suite_prompt(template_text)
values = {
name: str((context or {}).get(name, ""))
for name in PRODUCT_SUITE_PLACEHOLDERS
}
missing_context = [
name for name in PRODUCT_SUITE_REQUIRED_PLACEHOLDERS if name not in (context or {})
]
if missing_context:
raise ProductSuitePromptError(
"提示词上下文缺少变量:%s"
% "、".join("{%s}" % name for name in missing_context)
)
rendered = _PRODUCT_SUITE_PLACEHOLDER_RE.sub(
lambda match: values[match.group(1)],
str(template_text),
)
if "{" in rendered or "}" in rendered:
raise ProductSuitePromptError("提示词渲染后仍有未替换变量")
return rendered.strip()
def build_suite_prompt(
base_prompt,
settings,
category,
item_id,
source_index=1,
*,
template_text,
):
context = product_suite_prompt_context(
base_prompt,
settings,
category,
item_id,
source_index=source_index,
)
return render_product_suite_prompt(template_text, context)
def build_job_specs(source_assets, base_prompt, settings, item_id, *, template_text):
assets = list(source_assets or [])
if not assets:
return []
@@ -143,6 +317,7 @@ def build_job_specs(source_assets, base_prompt, settings, item_id):
category,
item_id,
source_index=source_index,
template_text=template_text,
),
}
)