You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
11 KiB
11 KiB
Changelog: 为 AI Provider 添加专用剧本解析方法
日期: 2026-02-09
类型: Feature Enhancement
影响范围: AI Provider 架构、剧本解析功能
关联: 2026-02-09-screenplay-ai-parse-format-fix.md
问题描述
现状问题
-
通用方法不适合专用场景
process_text()是通用文本处理方法- 使用硬编码的
system_prompts字典 - 无法正确加载
ai_skills_registry中的剧本解析技能
-
提示词管理混乱
- AI Skill 文件(
screenplay_parsing.md)定义了标准格式 - 但
process_text()使用的是简化版硬编码提示词 - 导致 AI 返回格式与预期不一致
- AI Skill 文件(
-
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
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
}
"""
核心逻辑:
-
优先从文件系统加载 AI Skill
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) -
降级到硬编码提示词
if not system_prompt: system_prompt = """### 系统角色 你是一个专业的影视剧本分析专家... """ -
动态注入用户个性化要求
if custom_requirements: system_prompt += f"\n\n## 用户特殊要求\n{custom_requirements}" -
调用 AI 并解析 JSON
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'} ) -
返回结构化数据
return { 'result': parsed_json, 'usage': {...}, 'metadata': { 'model': response.model, 'skill_source': 'file_system' | 'fallback' } }
2. 修改 ai_tasks.py 调用逻辑
文件: server/app/tasks/ai_tasks.py
# ✅ 优先使用 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
# ✅ 保存大模型原始返回数据(用于存储到 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. 数据完整性
修复前:
{
"parsed_data": {
"locations": [...], // ❌ 已被 _transform_ai_tags_format 转换
"storyboards": [...] // ❌ 已被提取
}
}
修复后:
{
"parsed_data": {
"scenes": [ // ✅ 保留 AI 原始返回格式
{
"location": "海边",
"shots": [...]
}
]
}
}
使用示例
示例 1:正常调用(支持 parse_screenplay)
# 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)
# 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 方法
# 调用 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
# 使用不支持 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 加载失败
# 删除 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 加载逻辑分散在多处
建议:
# 创建统一的 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() 方法,解决了以下问题:
- 提示词管理规范化: 从 AI Skill Registry 加载,而不是硬编码
- 职责分离: 专用方法处理专用任务,提高代码可维护性
- 数据完整性: 保存大模型原始返回数据,便于调试和追溯
- 降级策略: 支持多级降级,确保系统健壮性
这种架构设计为未来添加更多专用 AI 方法(如 generate_storyboard_images()、generate_character_voices() 等)提供了良好的范例。