""" 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 为准,只返回配置中定义的大类。 Returns: [ { "code": "bk", "name": "装修避坑" }, ... ] """ 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) categories.append( { "code": cat_code, "name": cat_name, } ) return categories def list_prompt_files(category: str) -> list[dict]: """ 扫描指定分类目录下的所有提示词文件 文件名格式: 文案——描述.txt 解析为 label + desc 返回给前端展示。 Args: category: 大类代码,如 "bk" Returns: [ { "filename": "水电改造避坑——水电改造的4个坑.txt", "label": "水电改造避坑", "desc": "水电改造的4个坑" }, ... ] """ cat_dir = SYSTEM_PROMPTS_DIR / category if not cat_dir.exists(): return [] files = [] for f in sorted(cat_dir.iterdir()): if f.is_file() and f.suffix == ".txt": name = f.stem # 不含 .txt if "——" in name: label, desc = name.split("——", 1) else: label = name desc = "" files.append( { "filename": f.name, "label": label.strip(), "desc": desc.strip(), } ) return files def load_prompt_file(category: str, filename: str) -> str: """ 加载指定分类下的指定提示词文件 Args: category: 大类代码,如 "bk" filename: 文件名,如 "水电改造避坑——水电改造的4个坑.txt" Returns: 提示词内容,文件不存在返回空字符串 """ file_path = SYSTEM_PROMPTS_DIR / category / filename if file_path.exists(): return file_path.read_text(encoding="utf-8") return "" def load_system_prompt(category: str, subcategory: str) -> str: """ 【已废弃】根据大类+小类随机加载一个 System Prompt 保留兼容旧调用,实际行为改为从平铺文件中随机加载。 """ files = list_prompt_files(category) if not files: return "" chosen = random.choice(files) # nosec: B311 return load_prompt_file(category, chosen["filename"]) 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. 控制字数,不要过长 请直接返回优化后的文案,不要添加解释。"""