feat(product-suite): add prompt template settings
This commit is contained in:
+190
-15
@@ -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,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user