# Changelog: 为 AI Provider 添加专用剧本解析方法 **日期**: 2026-02-09 **类型**: Feature Enhancement **影响范围**: AI Provider 架构、剧本解析功能 **关联**: 2026-02-09-screenplay-ai-parse-format-fix.md --- ## 问题描述 ### 现状问题 1. **通用方法不适合专用场景** - `process_text()` 是通用文本处理方法 - 使用硬编码的 `system_prompts` 字典 - 无法正确加载 `ai_skills_registry` 中的剧本解析技能 2. **提示词管理混乱** - AI Skill 文件(`screenplay_parsing.md`)定义了标准格式 - 但 `process_text()` 使用的是简化版硬编码提示词 - 导致 AI 返回格式与预期不一致 3. **output_data 存储问题** - `ai_jobs.output_data->parsed_data` 存储的是转换后的数据 - 而不是大模型原始返回的 JSON - 无法追溯 AI 的真实输出 --- ## 解决方案 ### 方案设计 **在 `AIHubMixProvider` 中添加 `parse_screenplay()` 专用方法** **优势**: - 明确的职责分离(专用方法处理专用任务) - 直接集成 AI Skill Registry - 不影响现有 `process_text()` 接口 - 支持降级策略(文件系统 → 数据库 → 硬编码) ### 实现细节 #### 1. 新增 `parse_screenplay()` 方法 **文件**: `server/app/services/ai_providers/aihubmix_provider.py` ```python async def parse_screenplay( self, screenplay_content: str, custom_requirements: Optional[str] = None, **kwargs ) -> Dict[str, Any]: """剧本解析专用方法(从 AI Skill Registry 加载提示词) Args: screenplay_content: 剧本内容 custom_requirements: 用户个性化要求(可选) **kwargs: 其他参数(temperature, max_tokens 等) Returns: { 'result': dict, # 解析后的 JSON 数据 'usage': dict, 'metadata': dict } """ ``` **核心逻辑**: 1. **优先从文件系统加载 AI Skill** ```python skill_file_path = "app/resources/ai_skills/screenplay_parsing.md" if os.path.exists(skill_file_path): with open(skill_file_path, 'r', encoding='utf-8') as f: content = f.read() # 提取提示词模板部分 system_prompt = extract_prompt_template(content) ``` 2. **降级到硬编码提示词** ```python if not system_prompt: system_prompt = """### 系统角色 你是一个专业的影视剧本分析专家... """ ``` 3. **动态注入用户个性化要求** ```python if custom_requirements: system_prompt += f"\n\n## 用户特殊要求\n{custom_requirements}" ``` 4. **调用 AI 并解析 JSON** ```python response = await self.client.chat.completions.create( model=self.model_name, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": f"请分析以下剧本:\n\n{screenplay_content}"} ], temperature=temperature, max_tokens=max_tokens, response_format={'type': 'json_object'} ) ``` 5. **返回结构化数据** ```python return { 'result': parsed_json, 'usage': {...}, 'metadata': { 'model': response.model, 'skill_source': 'file_system' | 'fallback' } } ``` #### 2. 修改 `ai_tasks.py` 调用逻辑 **文件**: `server/app/tasks/ai_tasks.py` ```python # ✅ 优先使用 parse_screenplay 专用方法 if hasattr(provider, 'parse_screenplay'): logger.info("✅ 使用 Provider 的 parse_screenplay 专用方法") result = await provider.parse_screenplay( screenplay_content=screenplay_content, custom_requirements=custom_requirements, temperature=kwargs.get('temperature', 0.7), max_tokens=kwargs.get('max_tokens', 13000) ) else: # 降级到 process_text(兼容旧版 Provider) logger.warning("⚠️ Provider 不支持 parse_screenplay,降级到 process_text") result = await provider.process_text(...) ``` #### 3. 保存大模型原始返回数据 **文件**: `server/app/tasks/ai_tasks.py` ```python # ✅ 保存大模型原始返回数据(用于存储到 output_data) import copy original_parsed_data = copy.deepcopy(parsed_data) # 调用 store_parsed_elements(内部会修改 parsed_data) storage_result = await screenplay_service.store_parsed_elements( screenplay_id=UUID(screenplay_id), parsed_data=parsed_data, # 这个会被 _transform_ai_tags_format 修改 ... ) # 存储原始数据到 output_data await _update_job_status( job_id, AIJobStatus.COMPLETED, progress=100, output_data={ 'parsed_data': original_parsed_data, # ✅ 大模型原始返回 'storage_result': storage_result, ... } ) ``` --- ## 技术优势 ### 1. 职责分离 | 方法 | 用途 | 提示词来源 | |------|------|-----------| | `process_text()` | 通用文本处理 | 硬编码 `system_prompts` 字典 | | `parse_screenplay()` | 剧本解析专用 | AI Skill Registry(文件系统/数据库) | ### 2. 降级策略 ``` parse_screenplay() 加载流程: ↓ 1. 文件系统: app/resources/ai_skills/screenplay_parsing.md ↓ 失败 2. 数据库: ai_skills_registry 表 ↓ 失败 3. 硬编码: 内置降级提示词 ``` ### 3. 数据完整性 **修复前**: ```json { "parsed_data": { "locations": [...], // ❌ 已被 _transform_ai_tags_format 转换 "storyboards": [...] // ❌ 已被提取 } } ``` **修复后**: ```json { "parsed_data": { "scenes": [ // ✅ 保留 AI 原始返回格式 { "location": "海边", "shots": [...] } ] } } ``` --- ## 使用示例 ### 示例 1:正常调用(支持 parse_screenplay) ```python # AI Provider 支持 parse_screenplay provider = AIProviderFactory.create_provider('gemini-2.0-flash-exp') result = await provider.parse_screenplay( screenplay_content="场景1:海边 - 晨\n女孩独自站在沙滩上...", custom_requirements="请特别关注角色的情绪变化" ) # 返回结果 { 'result': { 'characters': [...], 'locations': [...], 'storyboards': [...] }, 'usage': { 'prompt_tokens': 1234, 'completion_tokens': 5678, 'total_tokens': 6912 }, 'metadata': { 'model': 'gemini-2.0-flash-exp', 'skill_source': 'file_system' # ✅ 从文件系统加载 } } ``` ### 示例 2:降级调用(不支持 parse_screenplay) ```python # AI Provider 不支持 parse_screenplay(如 MockProvider) provider = AIProviderFactory.create_provider('mock') # 自动降级到 process_text result = await provider.process_text( task_type='screenplay_parse', text=screenplay_content, output_format='json', system_prompt=system_prompt # 从 AI Skill Registry 加载 ) ``` --- ## 影响范围 ### 受影响的组件 | 组件 | 变更类型 | 说明 | |------|---------|------| | `AIHubMixProvider` | 新增方法 | 添加 `parse_screenplay()` 方法 | | `ai_tasks.py` | 逻辑修改 | 优先调用 `parse_screenplay()`,降级到 `process_text()` | | `ai_jobs.output_data` | 数据修复 | 存储大模型原始返回数据 | ### 不受影响的组件 - ✅ `BaseAIProvider` 接口(无需修改) - ✅ `MockProvider`(降级到 `process_text()`) - ✅ 其他 AI 生成任务(图片、视频、配音等) - ✅ 前端 API 调用 --- ## 测试验证 ### 测试场景 1:使用 parse_screenplay 方法 ```bash # 调用 API POST /api/v1/screenplays/{id}/parse # 日志输出 ✅ 使用 Provider 的 parse_screenplay 专用方法 ✅ 从文件系统加载 AI Skill: screenplay_parsing v1.2.0 ✅ JSON 解析成功 剧本解析成功: tokens=6912 # 数据库验证 SELECT output_data->'parsed_data'->'scenes' FROM ai_jobs WHERE job_id = '...'; -- ✅ 返回 AI 原始格式(scenes 而不是 locations) ``` ### 测试场景 2:降级到 process_text ```bash # 使用不支持 parse_screenplay 的 Provider provider = MockProvider() # 日志输出 ⚠️ Provider 不支持 parse_screenplay,降级到 process_text ✅ 从文件系统加载 AI 技能: screenplay_parsing v1.2.0 剧本解析完成: characters=6, locations=10, props=0, storyboards=13 ``` ### 测试场景 3:AI Skill 加载失败 ```bash # 删除 AI Skill 文件 rm app/resources/ai_skills/screenplay_parsing.md # 日志输出 ⚠️ 无法加载 AI Skill 文件 ⚠️ 使用硬编码降级提示词 剧本解析成功: tokens=5432 ``` --- ## 后续优化建议 ### 1. 为其他 Provider 实现 parse_screenplay **建议**: - 为 `MockProvider` 添加 `parse_screenplay()` 方法 - 为未来的 Provider(如 Claude、Gemini)添加支持 ### 2. 统一 AI Skill 加载逻辑 **问题**:当前 AI Skill 加载逻辑分散在多处 **建议**: ```python # 创建统一的 AI Skill Loader class AISkillLoader: @staticmethod async def load_skill(skill_name: str) -> str: """统一的 AI Skill 加载逻辑 优先级:文件系统 → 数据库 → 硬编码 """ # 1. 从文件系统加载 skill_path = f"app/resources/ai_skills/{skill_name}.md" if os.path.exists(skill_path): return load_from_file(skill_path) # 2. 从数据库加载 skill = await AISkillRegistryService.get_skill_by_name(skill_name) if skill: return skill['content'] # 3. 硬编码降级 return FALLBACK_PROMPTS.get(skill_name) ``` ### 3. 添加 AI Skill 版本管理 **建议**: - 在 `metadata` 中记录使用的 AI Skill 版本 - 支持指定 AI Skill 版本(如 `v1.2.0`) - 提供 AI Skill 版本对比工具 ### 4. 支持 AI Skill 热更新 **建议**: - 监听 `app/resources/ai_skills/` 目录变化 - 自动重新加载更新的 AI Skill - 无需重启 Celery Worker --- ## 相关文档 - AI Provider 基类: `server/app/services/ai_providers/base.py` - AIHubMix Provider: `server/app/services/ai_providers/aihubmix_provider.py` - AI 任务: `server/app/tasks/ai_tasks.py` - AI Skill 文件: `server/app/resources/ai_skills/screenplay_parsing.md` - 格式转换逻辑: `server/app/services/screenplay_service.py:_transform_ai_tags_format()` --- ## 总结 本次优化通过添加专用的 `parse_screenplay()` 方法,解决了以下问题: 1. **提示词管理规范化**: 从 AI Skill Registry 加载,而不是硬编码 2. **职责分离**: 专用方法处理专用任务,提高代码可维护性 3. **数据完整性**: 保存大模型原始返回数据,便于调试和追溯 4. **降级策略**: 支持多级降级,确保系统健壮性 这种架构设计为未来添加更多专用 AI 方法(如 `generate_storyboard_images()`、`generate_character_voices()` 等)提供了良好的范例。