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.
6.7 KiB
6.7 KiB
Changelog: 剧本解析数据格式自动转换
日期: 2026-02-08
类型: Bug Fix / Enhancement
优先级: P1
关联文档:
📋 问题背景
问题描述
在 Phase 1 实施过程中,发现 AI 返回的数据格式与 ScreenplayTagService.store_tags 方法期望的格式存在差异:
AI 返回格式(嵌套标签)
{
"characters": [
{
"name": "孙悟空",
"tags": {
"青年": {
"description": "意气风发的年轻猴王",
"age_range": "青年",
"costume": "金甲战衣、紫金冠"
}
}
}
]
}
Service 期望格式(顶层标签)
{
"characters": [...],
"character_tags": {
"孙悟空": [
{
"tag_label": "青年",
"description": "意气风发的年轻猴王",
"meta_data": {"age_range": "青年", "costume": "金甲战衣、紫金冠"}
}
]
}
}
影响范围
- ❌ 标签数据无法正确存储
- ❌
store_tags日志显示:角色标签=0, 场景标签=0, 道具标签=0 - ❌ 集成测试需手动构造双重格式数据
✅ 解决方案
1. 自动标签格式转换
文件: server/app/services/screenplay_service.py
新增方法: _transform_ai_tags_format()
def _transform_ai_tags_format(self, parsed_data: Dict[str, Any]) -> Dict[str, Any]:
"""转换AI返回的嵌套标签格式为Service期望的顶层格式
转换逻辑:
1. 遍历 characters/locations/props 中的嵌套 tags
2. 提取 tag_label、description、meta_data
3. 构造顶层 character_tags/location_tags/prop_tags
"""
关键特性:
- ✅ 自动解析嵌套
tags字典 - ✅ 提取
description字段到顶层 - ✅ 其余字段归并到
meta_data - ✅ 保留原始
characters/locations/props结构
调用时机:
async def store_parsed_elements(...):
# 0. 数据格式预处理:转换AI嵌套标签格式为Service期望格式
parsed_data = self._transform_ai_tags_format(parsed_data)
logger.info("标签格式转换完成: screenplay_id=%s", screenplay_id)
2. dialogue 字段存储策略
问题: Storyboard 模型没有 dialogue 字段
解决方案: 存储到 meta_data JSONB 字段
修改位置: _create_storyboards_from_ai 方法
storyboard = Storyboard(
# ...
meta_data={
**sb_data.get('meta_data', {}),
# 存储对话信息(Storyboard模型没有dialogue字段)
'dialogue': sb_data.get('dialogue', ''),
# 冗余存储元素信息(用于快速查询)
'screenplay_id': str(screenplay_id),
# ...
}
)
优点:
- ✅ 无需修改数据库 Schema
- ✅ 保持向后兼容
- ✅ 易于查询和过滤
3. 测试用例简化
文件: server/tests/integration/test_data_integrity.py
变更:
- ❌ 移除手动构造的顶层标签字段 (
character_tags,location_tags,prop_tags) - ✅ 仅保留 AI 原始嵌套格式
- ✅ 新增
dialogue验证断言
测试覆盖:
# 验证 dialogue 存储
assert sb1.meta_data.get('dialogue') == ""
assert sb2.meta_data.get('dialogue') == "俺老孙今日要大闹天宫!"
assert sb3.meta_data.get('dialogue') == "悟空!不可造次!"
🧪 测试验证
测试命令
cd server
docker compose exec -T app pytest \
tests/integration/test_data_integrity.py::TestScreenplayDataIntegrity::test_store_parsed_elements_complete_integrity \
-v -s
测试结果
标签格式转换完成: screenplay_id=019c3c67-4650-7ca3-8664-94547a96b3e8
标签存储完成: 角色标签=3, 场景标签=3, 道具标签=3
✅ 角色记录验证通过
✅ 场景记录验证通过
✅ 道具记录验证通过
✅ 分镜记录验证通过
- 分镜1: 水帘洞外景 | WIDE_SHOT | DOLLY | 对话: ""
- 分镜2: 孙悟空登场 | MEDIUM_SHOT | STATIC | 对话: "俺老孙今日要大闹天宫!"
- 分镜3: 天宫对峙 | FULL_SHOT | PAN | 对话: "悟空!不可造次!"
✅ 分镜元素关联验证通过
✅ 标签数据结构验证通过
🎉 数据完整性测试通过!
验证指标:
- ✅ 标签转换正确:
角色标签=3, 场景标签=3, 道具标签=3 - ✅ 标签关联完整:每个元素的
tagsrelationship 包含正确标签 - ✅ dialogue 存储成功:所有分镜的
meta_data['dialogue']正确
📊 技术影响分析
代码变更统计
| 文件 | 变更类型 | 行数 |
|---|---|---|
screenplay_service.py |
新增方法 | +95 |
screenplay_service.py |
修改调用 | +2 |
screenplay_service.py |
dialogue 存储 | +2 |
test_data_integrity.py |
简化测试数据 | -41 |
test_data_integrity.py |
新增断言 | +3 |
性能影响
- 格式转换: O(n) 复杂度,n = 元素数量 + 标签数量
- 内存开销: 临时字典构造,完成后自动释放
- 数据库查询: 无额外查询,仅在存储前转换
向后兼容性
✅ 完全兼容:
- 保留原始 AI 数据结构
- 仅添加顶层字段,不修改原有字段
- 不影响现有 API 接口
🔗 关联问题
已解决
- ✅ P1-02: AI Prompt 文档与实际实现不一致(数据格式部分)
- ✅ 数据完整性测试 - 待办 1: dialogue 字段缺失
待解决(Phase 2)
- ⏳ P1-01:
project_resources表未关联 - ⏳ 实现
_sync_storyboard_resources()方法
📝 开发者注意事项
使用建议
- AI Prompt 设计: 可继续使用嵌套标签格式,无需调整
- 测试数据: 使用 AI 原始格式即可,无需手动转换
- 查询 dialogue: 使用 JSONB 查询语法
# 查询包含对话的分镜 stmt = select(Storyboard).where( Storyboard.meta_data['dialogue'].astext != '' )
迁移指南
现有测试代码:
- 可移除手动构造的
character_tags等字段 - Service 会自动处理格式转换
API 调用:
- 无需修改,内部自动转换
✨ 总结
本次修复实现了 AI 数据格式与 Service 期望格式的无缝桥接,消除了手动格式转换的需要,提升了代码的可维护性和测试的简洁性。
核心价值:
- ✅ 自动化数据格式转换
- ✅ 解决 dialogue 字段存储问题
- ✅ 简化测试用例编写
- ✅ 保持向后兼容
下一步:
- Phase 2: 实现
project_resources表关联逻辑 - 持续监控 AI 输出格式变化
审核: Pending
部署: Pending
文档更新: ✅ 已完成