""" prompt_composer.py — 统一 prompt 组装入口(≤100行) 扒自:banana prompts/service.py + worker/src/copy.js prompt 逻辑 Lead 指名接口:compose_variants / compose_preference_context 组装逻辑委托: _text_prompt.py → build_prompt (文案 prompt 主体) preference_aggregator.py → aggregate_preference_context (飞轮上下文) 原则:prompt 组装从这里进,不散落在 text_variants / generate_text_variants 里。 """ from __future__ import annotations import logging from typing import Any from ._text_prompt import build_prompt, COPY_SYSTEM from .preference_aggregator import aggregate_preference_context logger = logging.getLogger(__name__) # ── 主接口 ──────────────────────────────────────────────────────────────────── def compose_variants( product: dict, count: int, flywheel_context: str = "", extra_rules: str = "", ) -> tuple[str, str]: """ 一次出 count 角度文案的完整 prompt。 返回 (system_prompt, user_prompt)。 飞轮片段追加到 user_prompt 末尾(不改 system,避免覆盖质量红线)。 参数: product — 产品档案 dict(name/selling_points/text_angles/custom_prompt 等) count — 需要几条 flywheel_context— 由 compose_preference_context 返回的 prompt_fragment extra_rules — 额外规则(优化循环重生成时传 hint) """ combined_extra = "\n".join(filter(None, [flywheel_context, extra_rules])) user_prompt = build_prompt(product, count, extra_rules=combined_extra) logger.debug( "compose_variants: product=%s count=%d flywheel_len=%d", product.get("name", "?"), count, len(flywheel_context), ) return COPY_SYSTEM, user_prompt def compose_preference_context( events: list[dict], product: dict, workspace_id: int, product_id: int, ) -> dict: """ 聚合偏好事件 → 可注入 prompt 的飞轮上下文。 返回结构(对齐 API契约 GET /tasks/{id}/preference/context): { recent_preference: str, # 人类可读摘要(前端"本次已注入"显示) reject_reasons: list, # 最近打回原因 injected_count: int, # 有效信号数 prompt_fragment: str, # 注入 compose_variants flywheel_context 的字符串 } 信号不足 FLYWHEEL_COLD_START 条时用产品档案冷启动。 按 workspace_id + product_id 双维过滤(素颜霜偏好不串精华)。 """ return aggregate_preference_context(events, product, workspace_id, product_id) # ── 辅助:解析模型返回的 JSON(给 text_variants 调用,集中不散) ────────────── def parse_model_output(raw: str) -> list[dict]: """从 LLM 原始输出提取 JSON 数组(容错 markdown 包裹)""" from ._text_prompt import parse_json_array return parse_json_array(raw) # ── 辅助:图片 prompt 组装入口(预留,联调时填充)───────────────────────────── def compose_image_prompt( role_name: str, visual_system: dict, product: dict, extra: str = "", ) -> str: """ 单张分镜 prompt 组装(供 image_gen.generate_one_image 调用)。 TODO: 联调后从 storyboard.plan_image_set 取 base_prompt 注入。 role_name — 分镜角色(hook / pain_scene / closer 等) visual_system— build_visual_system 返回的视觉系统 dict extra — 追加约束(飞轮图片偏好片段,二期接入) """ name = product.get("name", "产品") style = visual_system.get("style", "") palette = visual_system.get("color_palette", "") base = visual_system.get("base_prompt", "") lines = [ f"[{role_name}] 为产品「{name}」生成种草图。", base and f"视觉基调:{base}", style and f"摄影风格:{style}", palette and f"色调:{palette}", extra, ] return "\n".join(l for l in lines if l)