diff --git a/server/services/planner.py b/server/services/planner.py index edbf1c9..1566954 100644 --- a/server/services/planner.py +++ b/server/services/planner.py @@ -22,23 +22,27 @@ ALLOWED_KINDS = [ "size_chart", "sku_collection", "custom", ] +# 方案总张数硬上限(提示词预算 + 代码兜底裁减都用它) +MAX_PLAN_TOTAL = 8 + SYSTEM_PROMPT = """你是一名资深电商视觉策划。根据商品信息规划一套电商套图的出图方案。 ## 输出硬性约束(违反即失败) 1. 输出必须是**单行紧凑 JSON**:无换行、无缩进、无空格填充、无注释、无 markdown 围栏。 2. 顶层只有 summary 和 items 两个字段;每个 item 严格只有 kind/title/detail/prompt_hint/count/variant_name 六个字段,不得增删。 3. 文本长度上限(中文字符/英文单词数):summary ≤ 25 字;title ≤ 8 字;detail ≤ 20 字;prompt_hint ≤ 15 个英文词。超限必须删减,不得省略号截断。 -4. count 默认 1,仅当该类图确需多个变体时才 >1,最大 3。总张数 8-15。 +4. count 默认 1,仅当该类图确需多个变体时才 >1,最大 3。总张数(所有 count 之和)不得超过 8,这是硬性上限。 5. variant_name 只能从「SKU规格」列表原样照抄;没有绑定就输出 null。 -## 规划规则 -1. SKU 主图:每个带图 SKU 出 1 张独立主图(kind=white_bg),variant_name 填对应规格名;单 SKU 出 1 张(variant_name=null)。 -2. 场景图(kind=lifestyle):按核心使用场景出 2-4 张,每张聚焦一个场景。 -3. 细节图(kind=material 或 custom):按关键细节/材质/结构出 2-3 张,每张聚焦一个卖点。 -4. 尺寸标注图(kind=size_chart):参数含长宽高/尺寸时出 1 张。 -5. SKU 合集图(kind=sku_collection):SKU >1 时出 1 张。 -6. kind 枚举:white_bg / key_features / selling_pt / material / lifestyle / multi_scene / ecommerce_detail / size_chart / sku_collection / custom。 -7. title 用中文短语(如「主图·粉色」「浴室壁挂」);detail 中文说明这张图展示什么;prompt_hint 用英文描述构图要点。 +## 规划规则(总预算 8 张,按优先级分配) +1. 总预算:所有项 count 之和 ≤ 8。空间不足时砍低优先级项,优先级从低到高:尺寸标注图 / SKU 合集图 / 细节图 → 场景图;SKU 主图不砍。 +2. SKU 主图:每个带图 SKU 出 1 张独立主图(kind=white_bg),variant_name 填对应规格名;单 SKU 出 1 张(variant_name=null)。 +3. 场景图(kind=lifestyle):预算内出 1-3 张,每张聚焦一个场景。 +4. 细节图(kind=material 或 custom):预算内出 1-2 张,每张聚焦一个卖点。 +5. 尺寸标注图(kind=size_chart):参数含长宽高/尺寸且预算富余时出 1 张。 +6. SKU 合集图(kind=sku_collection):SKU >1 且预算富余时出 1 张。 +7. kind 枚举:white_bg / key_features / selling_pt / material / lifestyle / multi_scene / ecommerce_detail / size_chart / sku_collection / custom。 +8. title 用中文短语(如「主图·粉色」「浴室壁挂」);detail 中文说明这张图展示什么;prompt_hint 用英文描述构图要点。 ## 输出示例(紧凑单行) {"summary":"三色收纳盒全套图","items":[{"kind":"white_bg","title":"主图·粉色","detail":"粉色SKU白底主视觉","prompt_hint":"front view on white background","count":1,"variant_name":"粉色"}]}""" @@ -214,14 +218,17 @@ async def generate_plan( items = _normalize_items(data.get("items") or [], sku_variants) if not items: raise RuntimeError("规划器未返回有效方案项") - # 总量保护:超过 18 张时按比例截断 - total = sum(i["count"] for i in items) - while total > 18 and items: + # 总量保护:超过上限时从尾部(低优先级项)裁减,SKU 主图排在前面不受影响 + before_trim = sum(i["count"] for i in items) + total = before_trim + while total > MAX_PLAN_TOTAL and items: last = items[-1] if last["count"] > 1: last["count"] -= 1 else: items.pop() total = sum(i["count"] for i in items) + if total < before_trim: + log.info("规划方案 %d 张超出上限 %d,已裁减至 %d 张(从尾部低优先级项开始)", before_trim, MAX_PLAN_TOTAL, total) return {"summary": str(data.get("summary") or "").strip()[:100], "items": items}