mirror of
https://github.com/linyqh/NarratoAI.git
synced 2026-08-01 10:55:54 +00:00
在WebUI脚本设置页面新增字数输入控件,支持100-5000区间的目标字数设置 更新LLM提示词模板,使用用户指定字数作为目标(±10%浮动)替代原固定范围 补充中英文国际化翻译文案 升级项目版本至0.8.7 更新相关服务函数与单元测试适配新参数
248 lines
10 KiB
Python
248 lines
10 KiB
Python
import json
|
|
import unittest
|
|
from unittest import mock
|
|
|
|
from app.services.llm.migration_adapter import SubtitleAnalyzerAdapter
|
|
from app.services.llm.unified_service import UnifiedLLMService
|
|
from app.services.prompts import PromptManager
|
|
|
|
|
|
class SubtitleAnalyzerAdapterPipelineTests(unittest.TestCase):
|
|
def test_generate_narration_copy_uses_plain_text_prompt_with_selected_type(self):
|
|
adapter = SubtitleAnalyzerAdapter(
|
|
api_key="sk-test",
|
|
model="test-model",
|
|
base_url="https://example.test/v1",
|
|
provider="openai",
|
|
)
|
|
|
|
with mock.patch.object(adapter, "_run_async_safely", return_value="她被家人逼到绝路,反击从这一刻开始。") as call:
|
|
result = adapter.generate_narration_copy(
|
|
short_name="测试短剧",
|
|
plot_analysis="女主被家人误会后反击。",
|
|
subtitle_content="# 视频 1: 1.mp4\n00:00:01,000 --> 00:00:04,000\n女主被误会。",
|
|
temperature=0.7,
|
|
narration_language="简体中文(中国)",
|
|
drama_genre="家庭伦理",
|
|
narration_word_count=800,
|
|
)
|
|
|
|
self.assertEqual("success", result["status"])
|
|
self.assertIn("反击", result["narration_copy"])
|
|
self.assertIn("家庭伦理", call.call_args.kwargs["prompt"])
|
|
self.assertIn("800", call.call_args.kwargs["prompt"])
|
|
self.assertNotIn("300-650", call.call_args.kwargs["prompt"])
|
|
self.assertNotIn("response_format", call.call_args.kwargs)
|
|
|
|
def test_generate_narration_copy_can_use_film_tv_prompt_category(self):
|
|
self.assertTrue(PromptManager.exists("film_tv_narration", "narration_copy"))
|
|
adapter = SubtitleAnalyzerAdapter(
|
|
api_key="sk-test",
|
|
model="test-model",
|
|
base_url="https://example.test/v1",
|
|
provider="openai",
|
|
prompt_category="film_tv_narration",
|
|
)
|
|
|
|
with mock.patch.object(adapter, "_run_async_safely", return_value="他发现证据不对,真正的凶手另有其人。") as call:
|
|
result = adapter.generate_narration_copy(
|
|
short_name="测试电影",
|
|
plot_analysis="主角发现证据疑点。",
|
|
subtitle_content="# 视频 1: 1.mp4\n00:00:01,000 --> 00:00:04,000\n证据不对。",
|
|
temperature=0.7,
|
|
narration_language="简体中文(中国)",
|
|
drama_genre="悬疑/犯罪",
|
|
narration_word_count=1200,
|
|
)
|
|
|
|
self.assertEqual("success", result["status"])
|
|
self.assertIn("影视解说正文创作任务", call.call_args.kwargs["prompt"])
|
|
self.assertIn("用户选择的影视类型", call.call_args.kwargs["prompt"])
|
|
self.assertIn("1200", call.call_args.kwargs["prompt"])
|
|
self.assertNotIn("350-750", call.call_args.kwargs["prompt"])
|
|
self.assertNotIn("短剧解说正文创作任务", call.call_args.kwargs["prompt"])
|
|
|
|
def test_film_tv_script_prompts_exclude_intro_outro_and_ads(self):
|
|
base_parameters = {
|
|
"drama_name": "测试电影",
|
|
"drama_genre": "悬疑/犯罪",
|
|
"plot_analysis": "主角发现证据疑点。",
|
|
"subtitle_content": "# 视频 1: 1.mp4\n00:00:01,000 --> 00:00:04,000\n证据不对。",
|
|
"narration_language": "简体中文(中国)",
|
|
}
|
|
prompt_parameters = {
|
|
"segment_planning": base_parameters,
|
|
"script_matching": {
|
|
**base_parameters,
|
|
"narration_copy": "他发现证据不对,真正的凶手另有其人。",
|
|
"original_sound_ratio": 30,
|
|
},
|
|
"script_generation": {
|
|
**base_parameters,
|
|
"segment_plan": '{"segments": []}',
|
|
},
|
|
"script_repair": {
|
|
**base_parameters,
|
|
"invalid_script": '{"items": []}',
|
|
"validation_errors": "片段包含广告",
|
|
},
|
|
}
|
|
|
|
for prompt_name, parameters in prompt_parameters.items():
|
|
with self.subTest(prompt_name=prompt_name):
|
|
prompt = PromptManager.get_prompt(
|
|
category="film_tv_narration",
|
|
name=prompt_name,
|
|
parameters=parameters,
|
|
)
|
|
self.assertIn("片头", prompt)
|
|
self.assertIn("片尾", prompt)
|
|
self.assertIn("广告", prompt)
|
|
self.assertIn("绝对不能", prompt)
|
|
|
|
def test_match_narration_copy_to_script_uses_json_prompt_with_selected_type(self):
|
|
adapter = SubtitleAnalyzerAdapter(
|
|
api_key="sk-test",
|
|
model="test-model",
|
|
base_url="https://example.test/v1",
|
|
provider="openai",
|
|
)
|
|
matched = json.dumps(
|
|
{
|
|
"items": [
|
|
{
|
|
"_id": 1,
|
|
"video_id": 1,
|
|
"video_name": "1.mp4",
|
|
"timestamp": "00:00:01,000-00:00:04,000",
|
|
"picture": "女主被家人误会",
|
|
"narration": "她被家人逼到绝路,反击从这一刻开始。",
|
|
"OST": 0,
|
|
}
|
|
]
|
|
},
|
|
ensure_ascii=False,
|
|
)
|
|
|
|
with mock.patch.object(adapter, "_run_async_safely", return_value=matched) as call:
|
|
result = adapter.match_narration_copy_to_script(
|
|
short_name="测试短剧",
|
|
plot_analysis="女主被家人误会后反击。",
|
|
subtitle_content="# 视频 1: 1.mp4\n00:00:01,000 --> 00:00:04,000\n女主被误会。",
|
|
narration_copy="她被家人逼到绝路,反击从这一刻开始。",
|
|
temperature=0.7,
|
|
narration_language="简体中文(中国)",
|
|
drama_genre="家庭伦理",
|
|
original_sound_ratio=60,
|
|
)
|
|
|
|
self.assertEqual("success", result["status"])
|
|
self.assertEqual(1, json.loads(result["narration_script"])["items"][0]["_id"])
|
|
self.assertIn("家庭伦理", call.call_args.kwargs["prompt"])
|
|
self.assertIn("60%", call.call_args.kwargs["prompt"])
|
|
self.assertEqual("json", call.call_args.kwargs["response_format"])
|
|
|
|
def test_match_narration_copy_to_script_uses_streaming_when_callback_exists(self):
|
|
adapter = SubtitleAnalyzerAdapter(
|
|
api_key="sk-test",
|
|
model="test-model",
|
|
base_url="https://example.test/v1",
|
|
provider="openai",
|
|
)
|
|
matched = json.dumps({"items": []}, ensure_ascii=False)
|
|
|
|
with mock.patch.object(adapter, "_run_async_safely", return_value=matched) as call:
|
|
result = adapter.match_narration_copy_to_script(
|
|
short_name="测试短剧",
|
|
plot_analysis="女主被家人误会后反击。",
|
|
subtitle_content="# 视频 1: 1.mp4",
|
|
narration_copy="她被家人逼到绝路,反击从这一刻开始。",
|
|
stream_callback=lambda _event: None,
|
|
)
|
|
|
|
self.assertEqual("success", result["status"])
|
|
self.assertIs(UnifiedLLMService.generate_text_stream, call.call_args.args[0])
|
|
self.assertIn("on_chunk", call.call_args.kwargs)
|
|
|
|
def test_generate_narration_script_plans_segments_before_copywriting(self):
|
|
adapter = SubtitleAnalyzerAdapter(
|
|
api_key="sk-test",
|
|
model="test-model",
|
|
base_url="https://example.test/v1",
|
|
provider="openai",
|
|
)
|
|
responses = iter(
|
|
[
|
|
json.dumps(
|
|
{
|
|
"segments": [
|
|
{
|
|
"_id": 1,
|
|
"video_id": 1,
|
|
"video_name": "1.mp4",
|
|
"timestamp": "00:00:01,000-00:00:04,000",
|
|
"OST": 0,
|
|
"intent": "开场钩子",
|
|
}
|
|
]
|
|
},
|
|
ensure_ascii=False,
|
|
),
|
|
json.dumps(
|
|
{
|
|
"items": [
|
|
{
|
|
"_id": 1,
|
|
"video_id": 1,
|
|
"video_name": "1.mp4",
|
|
"timestamp": "00:00:01,000-00:00:04,000",
|
|
"picture": "女主被误会",
|
|
"narration": "她被所有人误会,真正的反击却刚刚开始。",
|
|
"OST": 0,
|
|
}
|
|
]
|
|
},
|
|
ensure_ascii=False,
|
|
),
|
|
]
|
|
)
|
|
|
|
with mock.patch.object(adapter, "_run_async_safely", side_effect=lambda *_args, **_kwargs: next(responses)) as call:
|
|
result = adapter.generate_narration_script(
|
|
short_name="测试短剧",
|
|
plot_analysis="女主被误会后反击。",
|
|
subtitle_content="# 视频 1: 1.mp4\n00:00:01,000 --> 00:00:04,000\n女主被误会。",
|
|
temperature=0.7,
|
|
narration_language="简体中文(中国)",
|
|
)
|
|
|
|
self.assertEqual("success", result["status"])
|
|
self.assertEqual(2, call.call_count)
|
|
self.assertEqual(1, json.loads(result["narration_script"])["items"][0]["_id"])
|
|
|
|
def test_repair_narration_script_returns_repaired_json(self):
|
|
adapter = SubtitleAnalyzerAdapter(
|
|
api_key="sk-test",
|
|
model="test-model",
|
|
base_url="https://example.test/v1",
|
|
provider="openai",
|
|
)
|
|
repaired = json.dumps({"items": []}, ensure_ascii=False)
|
|
|
|
with mock.patch.object(adapter, "_run_async_safely", return_value=repaired):
|
|
result = adapter.repair_narration_script(
|
|
short_name="测试短剧",
|
|
plot_analysis="",
|
|
subtitle_content="# 视频 1: 1.mp4",
|
|
invalid_script="{bad}",
|
|
validation_errors="时间戳错误",
|
|
narration_language="简体中文(中国)",
|
|
)
|
|
|
|
self.assertEqual("success", result["status"])
|
|
self.assertEqual(repaired, result["narration_script"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|