""" Prompt 简单加载器 ================= 从文件加载 Prompt,支持热更新。 目录结构约定: system/ ├── / # 大类目录 │ ├── / # 小类目录 │ │ ├── _meta.json # 元数据 {"name": "显示名称"} │ │ ├── 1.txt # 提示词文件(随机取其一) │ │ └── 2.txt │ └── ... └── ... """ import json import random from pathlib import Path from string import Template _PROMPTS_DIR = Path(__file__).parent def load_prompt(path: str) -> str: """ 加载 Prompt 文件 Args: path: 相对路径,如 "script/system", "polish/scene" Returns: Prompt 内容,文件不存在返回空字符串 """ file_path = _PROMPTS_DIR / f"{path}.txt" if file_path.exists(): return file_path.read_text(encoding="utf-8") return "" def render_template(template: str, **kwargs) -> str: """ 安全渲染模板变量 Args: template: 模板字符串 **kwargs: 变量值 Returns: 渲染后的字符串 """ try: # 转义 $ 符号防止用户输入干扰 safe_kwargs = {k: str(v).replace("$", "$$") for k, v in kwargs.items()} return Template(template).substitute(**safe_kwargs) except KeyError as e: raise ValueError(f"模板缺少变量: {e}") # ====================== 新分类体系:动态扫描 ====================== SYSTEM_PROMPTS_DIR = _PROMPTS_DIR / "system" _SYSTEM_META_PATH = SYSTEM_PROMPTS_DIR / "_meta.json" def _load_system_meta() -> dict: """读取 system/_meta.json""" if _SYSTEM_META_PATH.exists(): try: return json.loads(_SYSTEM_META_PATH.read_text(encoding="utf-8")) except (json.JSONDecodeError, OSError): pass return {} def list_categories() -> list[dict]: """ 返回所有分类结构 以 system/_meta.json 为准,只返回配置中定义的分类。 count 动态扫描对应目录统计。 Returns: [ { "code": "bk", "name": "装修避坑", "subcategories": [ {"code": "ht", "name": "装修合同避坑", "count": 2}, ... ] }, ... ] """ meta = _load_system_meta() categories = [] for cat_meta in meta.get("categories", []): cat_code = cat_meta["code"] cat_name = cat_meta.get("name", cat_code) cat_dir = SYSTEM_PROMPTS_DIR / cat_code subcategories = [] for sub_meta in cat_meta.get("subcategories", []): sub_code = sub_meta["code"] sub_name = sub_meta.get("name", sub_code) sub_dir = cat_dir / sub_code # 统计提示词文件数量 count = 0 if sub_dir.exists(): count = len([ f for f in sub_dir.iterdir() if f.is_file() and f.suffix == ".txt" ]) subcategories.append({ "code": sub_code, "name": sub_name, "count": count, }) categories.append({ "code": cat_code, "name": cat_name, "subcategories": subcategories, }) return categories def load_system_prompt(category: str, subcategory: str) -> str: """ 根据大类+小类随机加载一个 System Prompt Args: category: 大类代码,如 "bk" subcategory: 小类代码,如 "ht" Returns: 随机选中的提示词内容,未找到返回空字符串 """ sub_dir = SYSTEM_PROMPTS_DIR / category / subcategory if not sub_dir.exists(): return "" # 收集所有提示词文件 prompt_files = [ f for f in sub_dir.iterdir() if f.is_file() and f.suffix == ".txt" ] if not prompt_files: return "" # 随机取一个 chosen = random.choice(prompt_files) return chosen.read_text(encoding="utf-8") def load_script_user_prompt( topic: str, extra_params: str | None = None, ) -> str: """ 加载并渲染脚本生成 User Prompt Args: topic: 创作主题名称 extra_params: 额外参数(如风格、人设等),以换行分隔的字符串 Returns: 渲染后的用户提示词 """ template = load_prompt("user/script") return render_template( template, topic=topic, extra_params=extra_params or "", ) class ScriptPromptBuilder: """ 脚本 Prompt 构建器 用于构建家装行业短视频脚本的 System Prompt。 """ def build( self, duration: int = 30, script_type: str = "干货型", video_style: str = "口播", industry: str = "家装", tone: str | None = None, custom_requirements: str | None = None, ) -> str: """ 构建系统 Prompt Args: duration: 视频时长(秒) script_type: 脚本类型(干货型、故事型等) video_style: 视频风格(口播、剧情等) industry: 行业(家装) tone: 语气风格 custom_requirements: 自定义要求 Returns: 完整的 System Prompt """ # 基础 System Prompt(已废弃的脚本 system.txt,这里留空) base_prompt = "" # 构建上下文信息 context_parts = [ f"行业:{industry}", f"时长:{duration}秒", f"类型:{script_type}", f"风格:{video_style}", ] if tone: context_parts.append(f"语气:{tone}") context = "\n".join(context_parts) # 构建完整 Prompt full_prompt = f"""{base_prompt} 【创作要求】 {context} """ if custom_requirements: full_prompt += f""" 【特殊要求】 {custom_requirements} """ # 添加输出格式要求 full_prompt += """ 【输出格式】 请严格按照以下 JSON 数组格式返回,每个元素代表一个镜头: [ { "id": 1, "type": "segment", "scene": "画面描述", "voiceover": "配音文本", "duration": "5s" } ] type 可以是: - "segment": 分镜(有画面+配音) - "empty_shot": 空镜(纯画面,voiceover 可为空) 注意: 1. 只返回 JSON 数组,不要有其他文字 2. 确保 JSON 格式正确 3. 总时长必须严格控制在要求范围内 """ return full_prompt class PolishPromptBuilder: """ 润色 Prompt 构建器 用于构建润色文案或画面描述的 Prompt。 """ POLISH_TYPES = { "scene": "画面描述", "voiceover": "配音文本", "text": "文案内容", } def build(self, polish_type: str = "voiceover") -> str: """ 构建润色 Prompt Args: polish_type: 润色类型(scene/voiceover/text) Returns: System Prompt """ if polish_type == "scene": return self._build_scene_prompt() else: return self._build_voiceover_prompt() def _build_scene_prompt(self) -> str: """构建画面描述润色 Prompt""" return """你是一位专业的视频画面描述优化师。你的任务是优化画面描述,使其更加生动、具体、有画面感。 优化要求: 1. 增加细节描写(光线、色彩、构图) 2. 使用专业的影视语言 3. 描述要具体可执行 4. 保持简洁,不要过度渲染 5. 适合 AI 视频生成模型理解 请直接返回优化后的画面描述,不要添加解释。""" def _build_voiceover_prompt(self) -> str: """构建配音文本润色 Prompt""" return """你是一位专业的短视频文案编辑。你的任务是优化口播文案,使其更加流畅、有吸引力。 优化要求: 1. 语言口语化,适合朗读 2. 增加节奏感和停顿 3. 保留核心信息点 4. 适当使用修辞手法 5. 控制字数,不要过长 请直接返回优化后的文案,不要添加解释。"""