豆包 Doubao-Seed-2.0-Lite 常不按扁平契约输出,而是回
{"ScriptDraft":{"basicInfo":...,"shots":[{scene,dialogue,subtitle}]}}。
normalize_draft 在顶层找不到 segments → 走补占位镜兜底 → 旁白/画面全空(只剩 role)。
normalize_draft 增加容错:
- 解开 {"ScriptDraft":{...}} 外壳;basicInfo 的 camelCase 时长/比例拍平
- shots → segments;每镜 scene→画面、dialogue/subtitle→旁白
- 防 dialogue 为整句字符串时被逐字符遍历
原扁平契约路径不受影响。新增 apps/ai/tests.py 3 个回归用例,全套 21 测试通过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
57 lines
3.2 KiB
Python
57 lines
3.2 KiB
Python
import json
|
|
|
|
from django.test import SimpleTestCase
|
|
|
|
from apps.ai.script_agent import normalize_draft
|
|
|
|
|
|
class NormalizeDraftTests(SimpleTestCase):
|
|
"""normalize_draft 对模型不按契约输出的容错(防「旁白/画面全空」回归)。"""
|
|
|
|
def test_scriptdraft_shots_variant_fills_narration_and_visual(self):
|
|
"""模型常见变体:{"ScriptDraft":{"basicInfo":..,"shots":[{scene,dialogue,subtitle}]}}。
|
|
必须解开外壳 + 把 shots→segments、scene→画面、dialogue/subtitle→旁白,而非全填空占位镜。"""
|
|
raw = json.dumps({
|
|
"ScriptDraft": {
|
|
"basicInfo": {"totalDuration": 60, "aspectRatio": "9:16", "totalShots": 4},
|
|
"shots": [
|
|
{"shotNo": 1, "duration": 15, "scene": "更衣室扯卡裆旧裤", "dialogue": "卡裆太社死!", "subtitle": "还在卡裆?"},
|
|
{"shotNo": 2, "duration": 15, "scene": "特写拉扯面料回弹", "dialogue": "高弹不变形!", "subtitle": "裸感面料"},
|
|
{"shotNo": 3, "duration": 15, "scene": "健身切通勤", "dialogue": "都能穿!", "subtitle": "一裤多穿"},
|
|
{"shotNo": 4, "duration": 15, "scene": "对镜弹小黄车", "dialogue": "点小黄车抢!", "subtitle": "点击入手"},
|
|
],
|
|
}
|
|
}, ensure_ascii=False)
|
|
draft = normalize_draft(raw, aspect_ratio="9:16", total_duration=60)
|
|
self.assertEqual(len(draft["segments"]), 4)
|
|
self.assertEqual([s["role"] for s in draft["segments"]], ["钩子", "痛点", "卖点", "CTA"])
|
|
for seg in draft["segments"]:
|
|
self.assertTrue(seg["narration"], "旁白不应为空")
|
|
self.assertTrue(seg["visual"], "画面不应为空")
|
|
self.assertEqual(draft["segments"][0]["narration"], "卡裆太社死!")
|
|
self.assertEqual(draft["segments"][0]["visual"], "更衣室扯卡裆旧裤")
|
|
|
|
def test_string_dialogue_not_iterated_as_chars(self):
|
|
"""dialogue 为整句字符串时,要当作旁白而不是逐字符遍历。"""
|
|
raw = json.dumps({
|
|
"segments": [{"role": "钩子", "dialogue": "一句完整口播", "visual": "画面"}],
|
|
"total_duration": 15,
|
|
}, ensure_ascii=False)
|
|
draft = normalize_draft(raw, aspect_ratio="9:16", total_duration=15)
|
|
self.assertEqual(draft["segments"][0]["narration"], "一句完整口播")
|
|
self.assertEqual(draft["segments"][0]["dialogue"], []) # 字符串不进结构化对白
|
|
|
|
def test_canonical_flat_schema_still_works(self):
|
|
"""契约内的扁平 schema(segments/narration/visual)不受兼容改动影响。"""
|
|
raw = json.dumps({
|
|
"total_duration": 30,
|
|
"segments": [
|
|
{"role": "钩子", "narration": "口播1", "visual": "画面1"},
|
|
{"role": "CTA", "narration": "口播2", "visual": "画面2"},
|
|
],
|
|
}, ensure_ascii=False)
|
|
draft = normalize_draft(raw, aspect_ratio="9:16", total_duration=30)
|
|
self.assertEqual(len(draft["segments"]), 2)
|
|
self.assertEqual(draft["segments"][0]["narration"], "口播1")
|
|
self.assertEqual(draft["segments"][1]["visual"], "画面2")
|