feat(prompts): 引入新的提示词管理系统以优化解说文案生成

更新generate_narration_script.py、short_drama_explanation.py和step1_subtitle_analyzer_openai.py文件,集成新的提示词管理系统,提升解说文案和短剧分析的生成效率与准确性。通过使用PromptManager简化提示词构建过程,增强系统的灵活性和可维护性。
This commit is contained in:
linyq 2025-07-07 17:13:54 +08:00
parent 7309208282
commit dbbb06eda8
20 changed files with 2084 additions and 128 deletions

View File

@ -15,7 +15,8 @@ from typing import Dict, Any, Optional
from loguru import logger
from app.config import config
from app.utils.utils import get_uuid, storage_dir
from app.services.SDE.prompt import subtitle_plot_analysis_v1, plot_writing
# 导入新的提示词管理系统
from app.services.prompts import PromptManager
class SubtitleAnalyzer:
@ -49,7 +50,15 @@ class SubtitleAnalyzer:
self.provider = provider or self._detect_provider()
# 设置提示词模板
self.prompt_template = custom_prompt or subtitle_plot_analysis_v1
if custom_prompt:
self.prompt_template = custom_prompt
else:
# 使用新的提示词管理系统
self.prompt_template = PromptManager.get_prompt(
category="short_drama_narration",
name="plot_analysis",
parameters={}
)
# 根据提供商类型确定是否为原生Gemini
self.is_native_gemini = self.provider.lower() == 'gemini'
@ -360,8 +369,15 @@ class SubtitleAnalyzer:
Dict[str, Any]: 包含生成结果的字典
"""
try:
# 构建完整提示词
prompt = plot_writing % (short_name, plot_analysis)
# 使用新的提示词管理系统构建提示词
prompt = PromptManager.get_prompt(
category="short_drama_narration",
name="script_generation",
parameters={
"drama_name": short_name,
"plot_analysis": plot_analysis
}
)
if self.is_native_gemini:
# 使用原生Gemini API格式

View File

@ -7,6 +7,8 @@ import os
import json
from .utils import load_srt
# 导入新的提示词管理系统
from app.services.prompts import PromptManager
def analyze_subtitle(
@ -45,28 +47,24 @@ def analyze_subtitle(
base_url=base_url
)
# 使用新的提示词管理系统
subtitle_analysis_prompt = PromptManager.get_prompt(
category="short_drama_editing",
name="subtitle_analysis",
parameters={
"subtitle_content": subtitle_content,
"custom_clips": custom_clips
}
)
messages = [
{
"role": "system",
"content": """你是一名经验丰富的短剧编剧,擅长根据字幕内容按照先后顺序分析关键剧情,并找出 %s 个关键片段。
请返回一个JSON对象包含以下字段
{
"summary": "整体剧情梗概",
"plot_titles": [
"关键剧情1",
"关键剧情2",
"关键剧情3",
"关键剧情4",
"关键剧情5",
"..."
]
}
请确保返回的是合法的JSON格式, 请确保返回的是 %s 个片段
""" % (custom_clips, custom_clips)
"content": "你是一名短剧编剧和内容分析师,擅长从字幕中提取剧情要点和关键情节。"
},
{
"role": "user",
"content": f"srt字幕如下{subtitle_content}"
"content": subtitle_analysis_prompt
}
]
# DeepSeek R1 和 V3 不支持 response_format=json_object
@ -90,40 +88,31 @@ def analyze_subtitle(
print(json.dumps(summary_data, indent=4, ensure_ascii=False))
# 获取爆点时间段分析
prompt = f"""剧情梗概:
{summary_data['summary']}
需要定位的爆点内容
"""
# 构建爆点标题列表
plot_titles_text = ""
print(f"找到 {len(summary_data['plot_titles'])} 个片段")
for i, point in enumerate(summary_data['plot_titles'], 1):
prompt += f"{i}. {point}\n"
plot_titles_text += f"{i}. {point}\n"
# 使用新的提示词管理系统
plot_extraction_prompt = PromptManager.get_prompt(
category="short_drama_editing",
name="plot_extraction",
parameters={
"subtitle_content": subtitle_content,
"plot_summary": summary_data['summary'],
"plot_titles": plot_titles_text
}
)
messages = [
{
"role": "system",
"content": """你是一名短剧编剧,非常擅长根据字幕中分析视频中关键剧情出现的具体时间段。
请仔细阅读剧情梗概和爆点内容然后在字幕中找出每个爆点发生的具体时间段和爆点前后的详细剧情
请返回一个JSON对象包含一个名为"plot_points"的数组数组中包含多个对象每个对象都要包含以下字段
{
"plot_points": [
{
"timestamp": "时间段格式为xx:xx:xx,xxx-xx:xx:xx,xxx",
"title": "关键剧情的主题",
"picture": "关键剧情前后的详细剧情描述"
}
]
}
请确保返回的是合法的JSON格式"""
"content": "你是一名短剧编剧,非常擅长根据字幕中分析视频中关键剧情出现的具体时间段。"
},
{
"role": "user",
"content": f"""字幕内容:
{subtitle_content}
{prompt}"""
"content": plot_extraction_prompt
}
]
# DeepSeek R1 和 V3 不支持 response_format=json_object

View File

@ -18,6 +18,8 @@ from loguru import logger
# 导入新的LLM服务模块 - 确保提供商被注册
import app.services.llm # 这会触发提供商注册
from app.services.llm.migration_adapter import generate_narration as generate_narration_new
# 导入新的提示词管理系统
from app.services.prompts import PromptManager
def parse_frame_analysis_to_markdown(json_file_path):
@ -116,95 +118,20 @@ def _generate_narration_legacy(markdown_content, api_key, base_url, model):
:return: 生成的解说文案
"""
try:
# 构建提示词
prompt = """
我是一名荒野建造解说的博主以下是一些同行的对标文案请你深度学习并总结这些文案的风格特点跟内容特点
# 使用新的提示词管理系统构建提示词
prompt = PromptManager.get_prompt(
category="documentary",
name="narration_generation",
parameters={
"video_frame_description": markdown_content
}
)
<example_text_1>
解压助眠的天花板就是荒野建造沉浸丝滑的搭建过程可以说每一帧都是极致享受我保证强迫症来了都找不出一丁点毛病更别说全屋严丝合缝的拼接工艺还能轻松抵御零下二十度气温让你居住的每一天都温暖如春
在家闲不住的西姆今天也打算来一次野外建造行走没多久他就发现许多倒塌的树任由它们自生自灭不如将其利用起来想到这他就开始挥舞铲子要把地基挖掘出来虽然每次只能挖一点点但架不住他体能惊人没多长时间一个 2x3 的深坑就赫然出现这深度住他一人绰绰有余
随后他去附近收集来原木这些都是搭建墙壁的最好材料而在投入使用前自然要把表皮刮掉防止森林中的白蚁蛀虫处理好一大堆后西姆还在两端打孔使用木钉固定在一起这可不是用来做墙壁的而是做庇护所的承重柱只要木头间的缝隙足够紧密那搭建出的木屋就能足够坚固
每向上搭建一层他都会在中间塞入苔藓防寒保证不会泄露一丝热量其他几面也是用相同方法很快西姆就做好了三面墙壁每一根木头都极其工整保证强迫症来了都要点个赞再走
在继续搭建墙壁前西姆决定将壁炉制作出来毕竟森林夜晚的气温会很低保暖措施可是重中之重完成后他找来一块大树皮用来充当庇护所的大门而上面刮掉的木屑还能作为壁炉的引火物可以说再完美不过
测试了排烟没问题后他才开始搭建最后一面墙壁这一面要预留门和窗所以在搭建到一半后还需要在原木中间开出卡口让自己劈砍时能轻松许多此时只需将另外一根如法炮制两端拼接在一起后就是一扇大小适中的窗户而随着随后一层苔藓铺好最后一根原木落位这个庇护所的雏形就算完成
大门的安装他没选择用合页而是在底端雕刻出榫头门框上则雕刻出榫眼只能说西姆的眼就是一把尺这完全就是严丝合缝此时他才开始搭建屋顶这里西姆用的方法不同他先把最外围的原木固定好随后将原木平铺在上面就能得到完美的斜面屋顶等他将四周的围栏也装好后工整的屋顶看起来十分舒服西姆躺上去都不想动
稍作休息后他利用剩余的苔藓对屋顶的缝隙处密封可这样西姆觉得不够保险于是他找来一些黏土再次对原本的缝隙二次加工保管这庇护所冬天也暖和最后只需要平铺上枯叶以及挖掘出的泥土整个屋顶就算完成
考虑到庇护所的美观性自然少不了覆盖上苔藓翠绿的颜色看起来十分舒服就连门口的庭院旁他都移植了许多小树做点缀让这木屋与周边环境融为一体西姆才刚完成好这件事一场大雨就骤然降临好在此时的他已经不用淋雨更别说这屋顶防水十分不错室内没一点雨水渗透进来
等待温度回升的过程西姆利用墙壁本身的凹槽把床框镶嵌在上面只需要铺上苔藓以及自带的床单枕头一张完美的单人床就做好辛苦劳作一整天西姆可不会亏待自己他将自带的牛肉腌制好后直接放到壁炉中烤只需要等待三十分钟就能享受这美味的一顿
在辛苦建造一星期后他终于可以在自己搭建的庇护所中享受最纯正的野外露营后面西姆回家补给了一堆物资再次回来时森林已经大雪纷飞让他原本翠绿的小屋更换上了冬季限定皮肤好在内部设施没受什么影响和他离开时一样整洁
就是房间中已经没多少柴火让西姆今天又得劈柴寒冷干燥的天气让木头劈起来十分轻松没多久他就收集到一大堆这些足够燃烧好几天虽然此时外面大雪纷飞但小屋中却开始逐渐温暖这次他除了带来一些食物外还有几瓶调味料以及一整套被褥让自己的居住舒适度提高一大截
而秋天他有收集干草的缘故只需要塞入枕套中密封起来就能作为靠垫用就这居住条件比一般人在家过的还要奢侈趁着壁炉木头变木炭的过程西姆则开始不紧不慢的处理食物他取出一块牛排改好花刀以后撒上一堆调料腌制起来接着用锡纸包裹好放到壁炉中直接炭烤搭配上自带的红酒是一个非常好的选择
随着时间来到第二天外面的积雪融化了不少西姆简单做顿煎蛋补充体力后决定制作一个室外篝火堆用来晚上驱散周边野兽搭建这玩意没什么技巧只需要找到一大堆木棍利用大树的夹缝将其掰弯然后将其堆积在一起就是一个简易版的篝火堆看这外形有点像帐篷好在西姆没想那么多
等待天色暗淡下来后他才来到室外将其点燃顺便处理下多余的废料只可惜这场景没朋友陪在身边对西姆来说可能是个遗憾而哪怕森林只有他一个人都依旧做了好几个小时等到里面的篝火彻底燃尽后西姆还找来雪球覆盖到上面将火熄灭这防火意识可谓十分好最后在室内二十五度的高温下裹着被子睡觉
</example_text_1>
<example_text_2>
解压助眠的天花板就是荒野建造沉浸丝滑的搭建过程每一帧都是极致享受全屋严丝合缝的拼接工艺能轻松抵御零下二十度气温居住体验温暖如春
在家闲不住的西姆开启野外建造他发现倒塌的树决定加以利用先挖掘出 2x3 的深坑作为地基接着收集原木刮掉表皮防白蚁蛀虫打孔用木钉固定制作承重柱搭建墙壁时每一层都塞入苔藓防寒很快做好三面墙
为应对森林夜晚低温西姆制作壁炉用大树皮当大门刮下的木屑做引火物搭建最后一面墙时预留门窗通过在原木中间开口拼接做出窗户大门采用榫卯结构安装严丝合缝
搭建屋顶时先固定外围原木再平铺原木形成斜面屋顶之后用苔藓黏土密封缝隙铺上枯叶和泥土为美观在木屋覆盖苔藓移植小树点缀完工时遇大雨木屋防水良好
西姆利用墙壁凹槽镶嵌床框铺上苔藓床单枕头做成床劳作一天后他用壁炉烤牛肉享用建造一星期后他开始野外露营
后来西姆回家补给物资回来时森林大雪纷飞他劈柴储备带回食物调味料和被褥提高居住舒适度还用干草做靠垫他用壁炉烤牛排搭配红酒
第二天积雪融化西姆制作室外篝火堆防野兽用大树夹缝掰弯木棍堆积而成晚上点燃处理废料结束后用雪球灭火最后在室内二十五度的环境中裹被入睡
</example_text_2>
<example_text_3>
如果战争到来这个深埋地下十几米的庇护所绝对是 bug 般的存在即使被敌人发现还能通过快速通道一秒逃出里面不仅有竹子地暖地下水井还自制抽水机在解决用水问题的同时甚至自研无土栽培技术过上完全自给自足的生活
阿伟的老婆美如花但阿伟从来不回家来到野外他乐哈哈一言不合就开挖众所周知当战争来临时地下堡垒的安全性是最高的阿伟苦苦研习两载半只为练就一身挖洞本领在这双逆天麒麟臂的加持下如此坚硬的泥土都只能当做炮灰
得到了充足的空间后他便开始对这些边缘进行打磨随后阿伟将细线捆在木棍上以此描绘出圆柱的轮廓接着再一点点铲掉多余的部分虽然是由泥土一体式打造但这样的桌子保准用上千年都不成问题
考虑到十几米的深度进出非常不方便于是阿伟找来两根长达 66.6 米的木头打算为庇护所打造一条快速通道只见他将木桩牢牢地插入地下并顺着洞口的方向延伸出去直到贯穿整个山洞接着在每个木桩的连接处钉入铁钉确保轨道不能有一毫米的偏差完成后再制作一个木质框架从而达到前后滑动的效果
不得不说阿伟这手艺简直就是大钢管子杵青蛙在上面放上一个木制的车斗还能加快搬运泥土的速度没多久庇护所的内部就已经初见雏形为了住起来更加舒适还需要为自己打造一张床虽然深处的泥土同样很坚固但好处就是不用担心垮塌的风险
阿伟不仅设计了更加符合人体工学的拱形并且还在一旁雕刻处壁龛就是这氛围怎么看着有点不太吉利别看阿伟一身腱子肉但这身体里的艺术细菌可不少每个边缘的地方他都做了精雕细琢瞬间让整个卧室的颜值提升一大截
住在地下的好处就是房子面积全靠挖每平方消耗两个半馒头不仅没有了房贷的压力就连买墓地的钱也省了阿伟将中间的墙壁挖空从而得到取暖的壁炉当然最重要的还有排烟问题要想从上往下打通十几米的山体是件极其困难的事好在阿伟年轻时报过忆坤年的古墓派补习班这打洞技术堪比隔壁学校的土拨鼠专业虽然深度长达十几米但排烟效果却一点不受影响一个字专业
随后阿伟继续对壁炉底部雕刻打通了底部放柴火的空间并制作出放锅的灶头完成后阿伟从侧面将壁炉打通并制作出一条导热的通道以此连接到床铺的位置毕竟住在这么一个风湿宝地不注意保暖除湿很容易得老寒腿
阿伟在床面上挖出一条条管道以便于温度能传输到床的每个角落接下来就可以根据这些通道的长度裁切出同样长短的竹子根据竹筒的大小凿出相互连接的孔洞最后再将竹筒内部打通以达到温度传送的效果
而后阿伟将这些管道安装到凹槽内在他严谨的制作工艺下每根竹子刚好都能镶嵌进去在铺设床面之前还需要用木塞把圆孔堵住防止泥土掉落进管道泥土虽然不能隔绝湿气但却是十分优良的导热材料等他把床面都压平后就可以小心的将这些木塞拔出来最后再用黏土把剩余的管道也遮盖起来直到整个墙面恢复原样
接下来还需要测试一下加热效果当他把火点起来后温度很快就传送到了管道内把火力一点点加大直到热气流淌到更远的床面随着小孔里的青烟冒出也预示着阿伟的地暖可以投入使用而后阿伟制作了一些竹条并用细绳将它们喜结连理
千里之行始于足下美好的家园要靠自己双手打造明明可以靠才艺吃饭的阿伟偏偏要用八块腹肌征服大家就问这样的男人哪个野生婆娘不喜欢完成后阿伟还用自己 35 码的大腚感受了一下真烫
随后阿伟来到野区找到一根上好的雷击木他当即就把木头咔嚓成两段并取下两节较为完整的带了回去刚好能和圆桌配套另外一个在里面凿出凹槽并插入木棍连接得到一个夯土的木锤住过农村的小伙伴都知道这样夯出来的地面堪比水泥地不仅坚硬耐磨还不用担心脚底打滑忙碌了一天的阿伟已经饥渴难耐拿出野生小烤肠安安心心住新房光脚爬上大热炕一觉能睡到天亮
第二天阿伟打算将房间扩宽毕竟吃住的地方有了还要解决个人卫生的问题阿伟在另一侧增加了一个房间他打算将这里打造成洗澡的地方为了防止泥土垮塌他将顶部做成圆弧形等挖出足够的空间后旁边的泥土已经堆成了小山
为了方便清理这些泥土阿伟在之前的轨道增加了转弯交接处依然是用铁钉固定一直延伸到房间的最里面有了运输车的帮助这些成吨的泥土也能轻松的运送出去并且还能体验过山车的感觉很快他就完成了清理工作
为了更方便的在里面洗澡他将底部一点点挖空这么大的浴缸看来阿伟并不打算一个人住完成后他将墙面雕刻的凹凸有致让这里看起来更加豪华接着用洛阳铲挖出排水口并用一根相同大小的竹筒作为开关
由于四周都是泥土还不能防水阿伟特意找了一些白蚁巢用来制作可以防水的野生水泥现在就可以将里里外外能接触到水的地方都涂抹一遍细心的阿伟还找来这种 500 克一斤的鹅卵石对池子表面进行装饰
没错水源问题阿伟早已经考虑在内他打算直接在旁边挖个水井毕竟已经挖了这么深再向下挖一挖应该就能到达地下水的深度经过几日的奋战能看得出阿伟已经消瘦了不少但一想到马上就能拥有的豪宅他直接化身为无情的挖土机器很快就挖到了好几米的深度
考虑到自己的弹跳力有限阿伟在一旁定入木桩然后通过绳子爬上爬下随着深度越来越深井底已经开始渗出水来这也预示着打井成功没多久这里面将渗满泉水仅凭一次就能挖到水源看来这里还真是块风湿宝地
随后阿伟在井口四周挖出凹槽以便于井盖的安置这一量才知道井的深度已经达到了足足的 5 阿伟把木板组合在一起再沿着标记切掉多余部分他甚至还给井盖做了把手可是如何从这么深的井里打水还是个问题但从阿伟坚定的眼神来看他应该想到了解决办法
只见他将树桩锯成两半然后用凿子把里面一点点掏空另外一半也是如法炮制接着还要在底部挖出圆孔要想成功将水从 5 米深的地方抽上来那就不得不提到大家熟知的勾股定理没错这跟勾股定理没什么关系
阿伟给竹筒做了一个木塞并在里面打上安装连接轴的孔为了增加密闭性阿伟不得不牺牲了自己的 AJ剪出与木塞相同的大小后再用木钉固定住随后他收集了一些树胶并放到火上加热融化接下来就可以涂在木塞上增加使用寿命
现在将竹筒组装完成就可以利用虹吸原理将水抽上来完成后就可以把井盖盖上去再用泥土在上面覆盖现在就不用担心失足掉下去了
接下来阿伟去采集了一些大漆将它涂抹在木桶接缝处就能将其二合为一完了再接入旁边浴缸的入水口每个连接的地方都要做好密封不然后面很容易漏水随后就可以安装上活塞并用一根木桩作为省力杠杆根据空气压强的原理将井水抽上来
经过半小时的来回拉扯硕大的浴缸终于被灌满阿伟也是忍不住洗了把脸接下来还需要解决排水的问题阿伟在地上挖出沟渠一直贯穿到屋外然后再用竹筒从出水口连接每个接口处都要抹上胶水就连门外的出水口他都做了隐藏
在野外最重要的就是庇护所水源还有食物既然已经完成了前二者那么阿伟还需要拥有可持续发展的食物来源他先是在地上挖了两排地洞然后在每根竹筒的表面都打上无数孔洞这就是他打算用来种植的载体在此之前还需要用大火对竹筒进行杀菌消毒
趁着这时候他去搬了一麻袋的木屑先用芭蕉叶覆盖在上面再铺上厚厚的黏土隔绝温度在火焰的温度下能让里面的木屑达到生长条件
等到第二天所有材料都晾凉后阿伟才将竹筒内部掏空并将木屑一点点地塞入竹筒一切准备就绪就可以将竹筒插入提前挖好的地洞最后再往竹筒里塞入种子依靠房间内的湿度和温度就能达到大棚种植的效果稍加时日这些种子就会慢慢发芽
虽然暂时还吃不上自己培养的食物但好在阿伟从表哥贺强那里学到不少钓鱼本领哪怕只有一根小小的竹竿也能让他钓上两斤半的大鲶鱼新鲜的食材那肯定是少不了高温消毒的过程趁着鱼没熟阿伟直接爬进浴缸冰凉的井水瞬间洗去了身上的疲惫这一刻的阿伟是无比的享受
不久后鱼也烤得差不多了阿伟的生活现在可以说是有滋有味住在十几米的地下不仅能安全感满满哪怕遇到危险还能通过轨道快速逃生
<example_text_3>
<video_frame_description>
%s
</video_frame_description>
我正在尝试做这个内容的解说纪录片视频我需要你以 <video_frame_description> </video_frame_description> 中的内容为解说目标根据我刚才提供给你的对标文案 <example_text> 特点以及你总结的特点帮我生成一段关于荒野建造的解说文案文案需要符合平台受欢迎的解说风格请使用 json 格式进行输出使用 <output> 中的输出格式
<output>
{
"items": [
{
"_id": 1, # 唯一递增id
"timestamp": "00:00:05,390-00:00:10,430",
"picture": "画面描述",
"narration": "解说文案",
}
}
</output>
<restriction>
1. 只输出 json 内容不要输出其他任何说明性的文字
2. 解说文案的语言使用 简体中文
3. 严禁虚构画面所有画面只能从 <video_frame_description> 中摘取
</restriction>
""" % (markdown_content)
# 使用OpenAI SDK初始化客户端
client = OpenAI(

View File

@ -0,0 +1,68 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@Project: NarratoAI
@File : __init__.py
@Author : AI Assistant
@Date : 2025/1/7
@Description: 统一提示词管理模块
"""
from .manager import PromptManager
from .base import BasePrompt, VisionPrompt, TextPrompt, ParameterizedPrompt
from .registry import PromptRegistry
from .template import TemplateRenderer
from .validators import PromptOutputValidator
from .exceptions import (
PromptError,
PromptNotFoundError,
PromptValidationError,
TemplateRenderError
)
# 版本信息
__version__ = "1.0.0"
__author__ = "AI Assistant"
# 导出的公共接口
__all__ = [
# 核心管理器
"PromptManager",
# 基础类
"BasePrompt",
"VisionPrompt",
"TextPrompt",
"ParameterizedPrompt",
# 工具类
"PromptRegistry",
"TemplateRenderer",
"PromptOutputValidator",
# 异常类
"PromptError",
"PromptNotFoundError",
"PromptValidationError",
"TemplateRenderError",
# 版本信息
"__version__",
"__author__"
]
# 模块初始化
def initialize_prompts():
"""初始化提示词模块,注册所有提示词"""
from . import documentary
from . import short_drama_editing
from . import short_drama_narration
# 注册各模块的提示词
documentary.register_prompts()
short_drama_editing.register_prompts()
short_drama_narration.register_prompts()
# 自动初始化
initialize_prompts()

View File

@ -0,0 +1,180 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@Project: NarratoAI
@File : base.py
@Author : AI Assistant
@Date : 2025/1/7
@Description: 提示词基础类定义
"""
from abc import ABC, abstractmethod
from typing import Dict, Any, Optional, List
from enum import Enum
from dataclasses import dataclass, field
from datetime import datetime
class ModelType(Enum):
"""模型类型枚举"""
TEXT = "text" # 文本模型
VISION = "vision" # 视觉模型
MULTIMODAL = "multimodal" # 多模态模型
class OutputFormat(Enum):
"""输出格式枚举"""
TEXT = "text" # 纯文本
JSON = "json" # JSON格式
MARKDOWN = "markdown" # Markdown格式
STRUCTURED = "structured" # 结构化数据
@dataclass
class PromptMetadata:
"""提示词元数据"""
name: str # 提示词名称
category: str # 分类
version: str # 版本
description: str # 描述
model_type: ModelType # 适用的模型类型
output_format: OutputFormat # 输出格式
author: str = "AI Assistant" # 作者
created_at: datetime = field(default_factory=datetime.now) # 创建时间
updated_at: datetime = field(default_factory=datetime.now) # 更新时间
tags: List[str] = field(default_factory=list) # 标签
parameters: List[str] = field(default_factory=list) # 支持的参数列表
class BasePrompt(ABC):
"""提示词基础类"""
def __init__(self, metadata: PromptMetadata):
self.metadata = metadata
self._template = None
self._system_prompt = None
self._examples = []
@property
def name(self) -> str:
"""获取提示词名称"""
return self.metadata.name
@property
def category(self) -> str:
"""获取提示词分类"""
return self.metadata.category
@property
def version(self) -> str:
"""获取提示词版本"""
return self.metadata.version
@property
def model_type(self) -> ModelType:
"""获取适用的模型类型"""
return self.metadata.model_type
@property
def output_format(self) -> OutputFormat:
"""获取输出格式"""
return self.metadata.output_format
@abstractmethod
def get_template(self) -> str:
"""获取提示词模板"""
pass
def get_system_prompt(self) -> Optional[str]:
"""获取系统提示词"""
return self._system_prompt
def get_examples(self) -> List[str]:
"""获取示例"""
return self._examples.copy()
def validate_parameters(self, parameters: Dict[str, Any]) -> bool:
"""验证参数"""
required_params = set(self.metadata.parameters)
provided_params = set(parameters.keys())
missing_params = required_params - provided_params
if missing_params:
from .exceptions import TemplateRenderError
raise TemplateRenderError(
template_name=self.name,
error_message="缺少必需参数",
missing_params=list(missing_params)
)
return True
def render(self, parameters: Dict[str, Any] = None) -> str:
"""渲染提示词"""
parameters = parameters or {}
# 验证参数
if self.metadata.parameters:
self.validate_parameters(parameters)
# 渲染模板
template = self.get_template()
try:
return template.format(**parameters)
except KeyError as e:
from .exceptions import TemplateRenderError
raise TemplateRenderError(
template_name=self.name,
error_message=f"模板参数错误: {str(e)}",
missing_params=[str(e)]
)
def to_dict(self) -> Dict[str, Any]:
"""转换为字典"""
return {
"metadata": {
"name": self.metadata.name,
"category": self.metadata.category,
"version": self.metadata.version,
"description": self.metadata.description,
"model_type": self.metadata.model_type.value,
"output_format": self.metadata.output_format.value,
"author": self.metadata.author,
"created_at": self.metadata.created_at.isoformat(),
"updated_at": self.metadata.updated_at.isoformat(),
"tags": self.metadata.tags,
"parameters": self.metadata.parameters
},
"template": self.get_template(),
"system_prompt": self.get_system_prompt(),
"examples": self.get_examples()
}
class TextPrompt(BasePrompt):
"""文本模型专用提示词"""
def __init__(self, metadata: PromptMetadata):
if metadata.model_type not in [ModelType.TEXT, ModelType.MULTIMODAL]:
raise ValueError(f"TextPrompt只支持TEXT或MULTIMODAL模型类型当前: {metadata.model_type}")
super().__init__(metadata)
class VisionPrompt(BasePrompt):
"""视觉模型专用提示词"""
def __init__(self, metadata: PromptMetadata):
if metadata.model_type not in [ModelType.VISION, ModelType.MULTIMODAL]:
raise ValueError(f"VisionPrompt只支持VISION或MULTIMODAL模型类型当前: {metadata.model_type}")
super().__init__(metadata)
class ParameterizedPrompt(BasePrompt):
"""支持参数化的提示词"""
def __init__(self, metadata: PromptMetadata, required_parameters: List[str] = None):
super().__init__(metadata)
if required_parameters:
self.metadata.parameters.extend(required_parameters)
# 去重
self.metadata.parameters = list(set(self.metadata.parameters))

View File

@ -0,0 +1,33 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@Project: NarratoAI
@File : __init__.py
@Author : AI Assistant
@Date : 2025/1/7
@Description: 纪录片解说提示词模块
"""
from .frame_analysis import FrameAnalysisPrompt
from .narration_generation import NarrationGenerationPrompt
from ..manager import PromptManager
def register_prompts():
"""注册纪录片解说相关的提示词"""
# 注册视频帧分析提示词
frame_analysis_prompt = FrameAnalysisPrompt()
PromptManager.register_prompt(frame_analysis_prompt, is_default=True)
# 注册解说文案生成提示词
narration_prompt = NarrationGenerationPrompt()
PromptManager.register_prompt(narration_prompt, is_default=True)
__all__ = [
"FrameAnalysisPrompt",
"NarrationGenerationPrompt",
"register_prompts"
]

View File

@ -0,0 +1,67 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@Project: NarratoAI
@File : frame_analysis.py
@Author : AI Assistant
@Date : 2025/1/7
@Description: 纪录片视频帧分析提示词
"""
from ..base import VisionPrompt, PromptMetadata, ModelType, OutputFormat
class FrameAnalysisPrompt(VisionPrompt):
"""纪录片视频帧分析提示词"""
def __init__(self):
metadata = PromptMetadata(
name="frame_analysis",
category="documentary",
version="v1.0",
description="分析纪录片视频关键帧,提取画面内容和场景描述",
model_type=ModelType.VISION,
output_format=OutputFormat.JSON,
tags=["纪录片", "视频分析", "关键帧", "画面描述"],
parameters=["video_theme", "custom_instructions"]
)
super().__init__(metadata)
self._system_prompt = "你是一名专业的视频内容分析师,擅长分析纪录片视频帧内容,提取关键信息和场景描述。"
def get_template(self) -> str:
return """请仔细分析这些视频关键帧图片,我需要你提供详细的画面分析。
视频主题${video_theme}
分析要求
1. 按时间顺序分析每一帧画面
2. 详细描述画面中的主要内容人物物体环境
3. 注意画面的构图色彩光线等视觉元素
4. 识别画面中的关键动作或变化
5. 提供准确的时间戳信息
${custom_instructions}
请按照以下JSON格式输出分析结果
{{
"analysis": [
{{
"timestamp": "00:00:05,390",
"picture": "详细的画面描述,包括场景、人物、物体、动作等",
"scene_type": "场景类型(如:建造、准备、完成等)",
"key_elements": ["关键元素1", "关键元素2"],
"visual_quality": "画面质量描述(构图、光线、色彩等)"
}}
],
"summary": "整体视频内容概述",
"total_frames": "分析的帧数"
}}
重要要求
1. 只输出JSON格式不要添加任何其他文字或代码块标记
2. 画面描述要详细准确为后续解说文案生成提供充分信息
3. 时间戳必须准确对应视频帧
4. 严禁虚构不存在的内容"""

View File

@ -0,0 +1,82 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@Project: NarratoAI
@File : narration_generation.py
@Author : AI Assistant
@Date : 2025/1/7
@Description: 纪录片解说文案生成提示词
"""
from ..base import TextPrompt, PromptMetadata, ModelType, OutputFormat
class NarrationGenerationPrompt(TextPrompt):
"""纪录片解说文案生成提示词"""
def __init__(self):
metadata = PromptMetadata(
name="narration_generation",
category="documentary",
version="v1.0",
description="根据视频帧分析结果生成纪录片解说文案,特别适用于荒野建造类内容",
model_type=ModelType.TEXT,
output_format=OutputFormat.JSON,
tags=["纪录片", "解说文案", "荒野建造", "文案生成"],
parameters=["video_frame_description"]
)
super().__init__(metadata)
self._system_prompt = "你是一名专业的短视频解说文案撰写专家,擅长创作引人入胜的纪录片解说内容。"
def get_template(self) -> str:
return """我是一名荒野建造解说的博主,以下是一些同行的对标文案,请你深度学习并总结这些文案的风格特点跟内容特点:
<example_text_1>
解压助眠的天花板就是荒野建造沉浸丝滑的搭建过程可以说每一帧都是极致享受我保证强迫症来了都找不出一丁点毛病更别说全屋严丝合缝的拼接工艺还能轻松抵御零下二十度气温让你居住的每一天都温暖如春
在家闲不住的西姆今天也打算来一次野外建造行走没多久他就发现许多倒塌的树任由它们自生自灭不如将其利用起来想到这他就开始挥舞铲子要把地基挖掘出来虽然每次只能挖一点点但架不住他体能惊人没多长时间一个 2x3 的深坑就赫然出现这深度住他一人绰绰有余
随后他去附近收集来原木这些都是搭建墙壁的最好材料而在投入使用前自然要把表皮刮掉防止森林中的白蚁蛀虫处理好一大堆后西姆还在两端打孔使用木钉固定在一起这可不是用来做墙壁的而是做庇护所的承重柱只要木头间的缝隙足够紧密那搭建出的木屋就能足够坚固
每向上搭建一层他都会在中间塞入苔藓防寒保证不会泄露一丝热量其他几面也是用相同方法很快西姆就做好了三面墙壁每一根木头都极其工整保证强迫症来了都要点个赞再走
在继续搭建墙壁前西姆决定将壁炉制作出来毕竟森林夜晚的气温会很低保暖措施可是重中之重完成后他找来一块大树皮用来充当庇护所的大门而上面刮掉的木屑还能作为壁炉的引火物可以说再完美不过
测试了排烟没问题后他才开始搭建最后一面墙壁这一面要预留门和窗所以在搭建到一半后还需要在原木中间开出卡口让自己劈砍时能轻松许多此时只需将另外一根如法炮制两端拼接在一起后就是一扇大小适中的窗户而随着随后一层苔藓铺好最后一根原木落位这个庇护所的雏形就算完成
</example_text_1>
<example_text_2>
解压助眠的天花板就是荒野建造沉浸丝滑的搭建过程每一帧都是极致享受全屋严丝合缝的拼接工艺能轻松抵御零下二十度气温居住体验温暖如春
在家闲不住的西姆开启野外建造他发现倒塌的树决定加以利用先挖掘出 2x3 的深坑作为地基接着收集原木刮掉表皮防白蚁蛀虫打孔用木钉固定制作承重柱搭建墙壁时每一层都塞入苔藓防寒很快做好三面墙
为应对森林夜晚低温西姆制作壁炉用大树皮当大门刮下的木屑做引火物搭建最后一面墙时预留门窗通过在原木中间开口拼接做出窗户大门采用榫卯结构安装严丝合缝
搭建屋顶时先固定外围原木再平铺原木形成斜面屋顶之后用苔藓黏土密封缝隙铺上枯叶和泥土为美观在木屋覆盖苔藓移植小树点缀完工时遇大雨木屋防水良好
西姆利用墙壁凹槽镶嵌床框铺上苔藓床单枕头做成床劳作一天后他用壁炉烤牛肉享用建造一星期后他开始野外露营
后来西姆回家补给物资回来时森林大雪纷飞他劈柴储备带回食物调味料和被褥提高居住舒适度还用干草做靠垫他用壁炉烤牛排搭配红酒
第二天积雪融化西姆制作室外篝火堆防野兽用大树夹缝掰弯木棍堆积而成晚上点燃处理废料结束后用雪球灭火最后在室内二十五度的环境中裹被入睡
</example_text_2>
<video_frame_description>
${video_frame_description}
</video_frame_description>
我正在尝试做这个内容的解说纪录片视频我需要你以 <video_frame_description> </video_frame_description> 中的内容为解说目标根据我刚才提供给你的对标文案特点以及你总结的特点帮我生成一段关于荒野建造的解说文案文案需要符合平台受欢迎的解说风格请使用 json 格式进行输出使用 <output> 中的输出格式
<output>
{{
"items": [
{{
"_id": 1,
"timestamp": "00:00:05,390-00:00:10,430",
"picture": "画面描述",
"narration": "解说文案",
}}
]
}}
</output>
<restriction>
1. 只输出 json 内容不要输出其他任何说明性的文字
2. 解说文案的语言使用 简体中文
3. 严禁虚构画面所有画面只能从 <video_frame_description> 中摘取
4. 严禁虚构时间戳所有时间戳只能从 <video_frame_description> 中摘取
5. 解说文案要生动有趣符合荒野建造解说的风格特点
6. 每个片段的解说文案要与画面内容高度匹配
7. 保持解说的连贯性和故事性
</restriction>"""

View File

@ -0,0 +1,79 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@Project: NarratoAI
@File : exceptions.py
@Author : AI Assistant
@Date : 2025/1/7
@Description: 提示词管理模块异常定义
"""
class PromptError(Exception):
"""提示词模块基础异常类"""
pass
class PromptNotFoundError(PromptError):
"""提示词未找到异常"""
def __init__(self, category: str, name: str, version: str = None):
self.category = category
self.name = name
self.version = version
if version:
message = f"提示词未找到: {category}.{name} (版本: {version})"
else:
message = f"提示词未找到: {category}.{name}"
super().__init__(message)
class PromptValidationError(PromptError):
"""提示词验证异常"""
def __init__(self, message: str, validation_errors: list = None):
self.validation_errors = validation_errors or []
super().__init__(message)
class TemplateRenderError(PromptError):
"""模板渲染异常"""
def __init__(self, template_name: str, error_message: str, missing_params: list = None):
self.template_name = template_name
self.error_message = error_message
self.missing_params = missing_params or []
message = f"模板渲染失败 '{template_name}': {error_message}"
if missing_params:
message += f" (缺少参数: {', '.join(missing_params)})"
super().__init__(message)
class PromptRegistrationError(PromptError):
"""提示词注册异常"""
def __init__(self, category: str, name: str, reason: str):
self.category = category
self.name = name
self.reason = reason
message = f"提示词注册失败 {category}.{name}: {reason}"
super().__init__(message)
class PromptVersionError(PromptError):
"""提示词版本异常"""
def __init__(self, category: str, name: str, version: str, reason: str):
self.category = category
self.name = name
self.version = version
self.reason = reason
message = f"提示词版本错误 {category}.{name} v{version}: {reason}"
super().__init__(message)

View File

@ -0,0 +1,287 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@Project: NarratoAI
@File : manager.py
@Author : AI Assistant
@Date : 2025/1/7
@Description: 提示词管理器
"""
from typing import Dict, Any, List, Optional, Union
from loguru import logger
from .base import BasePrompt, ModelType, OutputFormat
from .registry import get_registry
from .template import get_renderer
from .validators import PromptOutputValidator
from .exceptions import (
PromptNotFoundError,
PromptValidationError,
TemplateRenderError
)
class PromptManager:
"""提示词管理器 - 统一的提示词管理接口"""
def __init__(self):
self._registry = get_registry()
self._renderer = get_renderer()
@classmethod
def get_prompt(cls,
category: str,
name: str,
version: Optional[str] = None,
parameters: Optional[Dict[str, Any]] = None) -> str:
"""
获取渲染后的提示词
Args:
category: 分类
name: 名称
version: 版本可选默认使用最新版本
parameters: 模板参数可选
Returns:
渲染后的提示词字符串
"""
instance = cls()
prompt_obj = instance._registry.get(category, name, version)
try:
rendered = prompt_obj.render(parameters)
logger.debug(f"提示词渲染成功: {category}.{name} v{prompt_obj.version}")
return rendered
except Exception as e:
logger.error(f"提示词渲染失败: {category}.{name} - {str(e)}")
raise
@classmethod
def get_prompt_object(cls,
category: str,
name: str,
version: Optional[str] = None) -> BasePrompt:
"""
获取提示词对象
Args:
category: 分类
name: 名称
version: 版本可选
Returns:
提示词对象
"""
instance = cls()
return instance._registry.get(category, name, version)
@classmethod
def register_prompt(cls, prompt: BasePrompt, is_default: bool = True) -> None:
"""
注册提示词
Args:
prompt: 提示词对象
is_default: 是否设为默认版本
"""
instance = cls()
instance._registry.register(prompt, is_default)
@classmethod
def list_categories(cls) -> List[str]:
"""列出所有分类"""
instance = cls()
return instance._registry.list_categories()
@classmethod
def list_prompts(cls, category: str) -> List[str]:
"""列出指定分类下的所有提示词"""
instance = cls()
return instance._registry.list_prompts(category)
@classmethod
def list_versions(cls, category: str, name: str) -> List[str]:
"""列出指定提示词的所有版本"""
instance = cls()
return instance._registry.list_versions(category, name)
@classmethod
def exists(cls, category: str, name: str, version: Optional[str] = None) -> bool:
"""检查提示词是否存在"""
instance = cls()
return instance._registry.exists(category, name, version)
@classmethod
def search_prompts(cls,
keyword: str = None,
category: str = None,
model_type: ModelType = None,
output_format: OutputFormat = None) -> List[Dict[str, str]]:
"""
搜索提示词
Args:
keyword: 关键词
category: 分类过滤
model_type: 模型类型过滤
output_format: 输出格式过滤
Returns:
匹配的提示词列表
"""
instance = cls()
results = instance._registry.search(keyword, category, model_type, output_format)
return [
{
"category": cat,
"name": name,
"version": ver,
"full_name": f"{cat}.{name}",
"identifier": f"{cat}.{name}@{ver}"
}
for cat, name, ver in results
]
@classmethod
def get_stats(cls) -> Dict[str, Any]:
"""获取统计信息"""
instance = cls()
registry_stats = instance._registry.get_stats()
return {
"registry": registry_stats,
"categories": cls.list_categories(),
"total_categories": registry_stats["categories"],
"total_prompts": registry_stats["prompts"],
"total_versions": registry_stats["versions"]
}
@classmethod
def validate_output(cls,
output: Union[str, Dict],
category: str,
name: str,
version: Optional[str] = None) -> Any:
"""
验证提示词输出
Args:
output: 输出内容
category: 提示词分类
name: 提示词名称
version: 提示词版本
Returns:
验证后的数据
"""
instance = cls()
prompt_obj = instance._registry.get(category, name, version)
# 根据输出格式进行验证
output_format = prompt_obj.metadata.output_format
try:
if output_format == OutputFormat.JSON:
# 特殊处理解说文案和剧情分析
if "narration" in name.lower() or "script" in name.lower():
return PromptOutputValidator.validate_narration_script(output)
elif "plot" in name.lower() or "analysis" in name.lower():
return PromptOutputValidator.validate_plot_analysis(output)
else:
return PromptOutputValidator.validate_json(output)
else:
return PromptOutputValidator.validate_by_format(output, output_format)
except Exception as e:
logger.error(f"输出验证失败 {category}.{name}: {str(e)}")
raise PromptValidationError(f"输出验证失败: {str(e)}")
@classmethod
def get_prompt_info(cls, category: str, name: str, version: Optional[str] = None) -> Dict[str, Any]:
"""
获取提示词详细信息
Args:
category: 分类
name: 名称
version: 版本
Returns:
提示词详细信息
"""
instance = cls()
prompt_obj = instance._registry.get(category, name, version)
return {
"metadata": {
"name": prompt_obj.metadata.name,
"category": prompt_obj.metadata.category,
"version": prompt_obj.metadata.version,
"description": prompt_obj.metadata.description,
"model_type": prompt_obj.metadata.model_type.value,
"output_format": prompt_obj.metadata.output_format.value,
"author": prompt_obj.metadata.author,
"created_at": prompt_obj.metadata.created_at.isoformat(),
"updated_at": prompt_obj.metadata.updated_at.isoformat(),
"tags": prompt_obj.metadata.tags,
"parameters": prompt_obj.metadata.parameters
},
"template_preview": prompt_obj.get_template()[:500] + "..." if len(prompt_obj.get_template()) > 500 else prompt_obj.get_template(),
"system_prompt": prompt_obj.get_system_prompt(),
"examples_count": len(prompt_obj.get_examples()),
"has_parameters": bool(prompt_obj.metadata.parameters)
}
@classmethod
def export_prompts(cls, category: Optional[str] = None) -> Dict[str, Any]:
"""
导出提示词配置
Args:
category: 分类过滤可选
Returns:
提示词配置数据
"""
instance = cls()
categories = [category] if category else instance._registry.list_categories()
export_data = {
"version": "1.0.0",
"exported_at": instance._get_current_time(),
"categories": {}
}
for cat in categories:
export_data["categories"][cat] = {}
prompts = instance._registry.list_prompts(cat)
for prompt_name in prompts:
versions = instance._registry.list_versions(cat, prompt_name)
export_data["categories"][cat][prompt_name] = {}
for ver in versions:
prompt_obj = instance._registry.get(cat, prompt_name, ver)
export_data["categories"][cat][prompt_name][ver] = prompt_obj.to_dict()
return export_data
def _get_current_time(self) -> str:
"""获取当前时间字符串"""
from datetime import datetime
return datetime.now().isoformat()
# 便捷函数
def get_prompt(category: str, name: str, version: str = None, **parameters) -> str:
"""获取提示词的便捷函数"""
return PromptManager.get_prompt(category, name, version, parameters)
def validate_prompt_output(output: Union[str, Dict], category: str, name: str, version: str = None) -> Any:
"""验证提示词输出的便捷函数"""
return PromptManager.validate_output(output, category, name, version)

View File

@ -0,0 +1,222 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@Project: NarratoAI
@File : registry.py
@Author : AI Assistant
@Date : 2025/1/7
@Description: 提示词注册机制
"""
from typing import Dict, List, Optional, Tuple
from collections import defaultdict
from loguru import logger
from .base import BasePrompt, ModelType, OutputFormat
from .exceptions import (
PromptNotFoundError,
PromptRegistrationError,
PromptVersionError
)
class PromptRegistry:
"""提示词注册表"""
def __init__(self):
# 存储结构: {category: {name: {version: prompt}}}
self._prompts: Dict[str, Dict[str, Dict[str, BasePrompt]]] = defaultdict(
lambda: defaultdict(dict)
)
# 默认版本映射: {category: {name: default_version}}
self._default_versions: Dict[str, Dict[str, str]] = defaultdict(dict)
def register(self, prompt: BasePrompt, is_default: bool = True) -> None:
"""
注册提示词
Args:
prompt: 提示词实例
is_default: 是否设为默认版本
"""
category = prompt.category
name = prompt.name
version = prompt.version
# 检查是否已存在相同版本
if version in self._prompts[category][name]:
raise PromptRegistrationError(
category=category,
name=name,
reason=f"版本 {version} 已存在"
)
# 注册提示词
self._prompts[category][name][version] = prompt
# 设置默认版本
if is_default or name not in self._default_versions[category]:
self._default_versions[category][name] = version
logger.info(f"已注册提示词: {category}.{name} v{version}")
def get(self, category: str, name: str, version: Optional[str] = None) -> BasePrompt:
"""
获取提示词
Args:
category: 分类
name: 名称
version: 版本为None时使用默认版本
Returns:
提示词实例
"""
if category not in self._prompts:
raise PromptNotFoundError(category, name, version)
if name not in self._prompts[category]:
raise PromptNotFoundError(category, name, version)
# 确定版本
if version is None:
if name not in self._default_versions[category]:
raise PromptNotFoundError(category, name, version)
version = self._default_versions[category][name]
if version not in self._prompts[category][name]:
raise PromptNotFoundError(category, name, version)
return self._prompts[category][name][version]
def list_categories(self) -> List[str]:
"""列出所有分类"""
return list(self._prompts.keys())
def list_prompts(self, category: str) -> List[str]:
"""列出指定分类下的所有提示词名称"""
if category not in self._prompts:
return []
return list(self._prompts[category].keys())
def list_versions(self, category: str, name: str) -> List[str]:
"""列出指定提示词的所有版本"""
if category not in self._prompts or name not in self._prompts[category]:
return []
return list(self._prompts[category][name].keys())
def get_default_version(self, category: str, name: str) -> Optional[str]:
"""获取默认版本"""
return self._default_versions.get(category, {}).get(name)
def set_default_version(self, category: str, name: str, version: str) -> None:
"""设置默认版本"""
if (category not in self._prompts or
name not in self._prompts[category] or
version not in self._prompts[category][name]):
raise PromptVersionError(category, name, version, "版本不存在")
self._default_versions[category][name] = version
logger.info(f"已设置默认版本: {category}.{name} -> v{version}")
def exists(self, category: str, name: str, version: Optional[str] = None) -> bool:
"""检查提示词是否存在"""
try:
self.get(category, name, version)
return True
except PromptNotFoundError:
return False
def remove(self, category: str, name: str, version: Optional[str] = None) -> None:
"""移除提示词"""
if version is None:
# 移除所有版本
if category in self._prompts and name in self._prompts[category]:
del self._prompts[category][name]
if name in self._default_versions.get(category, {}):
del self._default_versions[category][name]
logger.info(f"已移除提示词所有版本: {category}.{name}")
else:
# 移除指定版本
if (category in self._prompts and
name in self._prompts[category] and
version in self._prompts[category][name]):
del self._prompts[category][name][version]
# 如果移除的是默认版本,需要重新设置默认版本
if (self._default_versions.get(category, {}).get(name) == version and
self._prompts[category][name]):
# 选择最新版本作为默认版本
new_default = max(self._prompts[category][name].keys())
self._default_versions[category][name] = new_default
logger.info(f"默认版本已更新: {category}.{name} -> v{new_default}")
logger.info(f"已移除提示词版本: {category}.{name} v{version}")
def search(self,
keyword: str = None,
category: str = None,
model_type: ModelType = None,
output_format: OutputFormat = None) -> List[Tuple[str, str, str]]:
"""
搜索提示词
Args:
keyword: 关键词在名称和描述中搜索
category: 分类过滤
model_type: 模型类型过滤
output_format: 输出格式过滤
Returns:
匹配的提示词列表 [(category, name, version), ...]
"""
results = []
categories = [category] if category else self._prompts.keys()
for cat in categories:
for name in self._prompts[cat]:
for version, prompt in self._prompts[cat][name].items():
# 关键词过滤
if keyword:
if (keyword.lower() not in name.lower() and
keyword.lower() not in prompt.metadata.description.lower()):
continue
# 模型类型过滤
if model_type and prompt.metadata.model_type != model_type:
continue
# 输出格式过滤
if output_format and prompt.metadata.output_format != output_format:
continue
results.append((cat, name, version))
return results
def get_stats(self) -> Dict[str, int]:
"""获取注册表统计信息"""
total_prompts = 0
total_versions = 0
for category in self._prompts:
for name in self._prompts[category]:
total_prompts += 1
total_versions += len(self._prompts[category][name])
return {
"categories": len(self._prompts),
"prompts": total_prompts,
"versions": total_versions
}
# 全局注册表实例
_global_registry = PromptRegistry()
def get_registry() -> PromptRegistry:
"""获取全局注册表实例"""
return _global_registry

View File

@ -0,0 +1,33 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@Project: NarratoAI
@File : __init__.py
@Author : AI Assistant
@Date : 2025/1/7
@Description: 短剧混剪提示词模块
"""
from .subtitle_analysis import SubtitleAnalysisPrompt
from .plot_extraction import PlotExtractionPrompt
from ..manager import PromptManager
def register_prompts():
"""注册短剧混剪相关的提示词"""
# 注册字幕分析提示词
subtitle_analysis_prompt = SubtitleAnalysisPrompt()
PromptManager.register_prompt(subtitle_analysis_prompt, is_default=True)
# 注册爆点提取提示词
plot_extraction_prompt = PlotExtractionPrompt()
PromptManager.register_prompt(plot_extraction_prompt, is_default=True)
__all__ = [
"SubtitleAnalysisPrompt",
"PlotExtractionPrompt",
"register_prompts"
]

View File

@ -0,0 +1,70 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@Project: NarratoAI
@File : plot_extraction.py
@Author : AI Assistant
@Date : 2025/1/7
@Description: 短剧爆点提取提示词
"""
from ..base import TextPrompt, PromptMetadata, ModelType, OutputFormat
class PlotExtractionPrompt(TextPrompt):
"""短剧爆点提取提示词"""
def __init__(self):
metadata = PromptMetadata(
name="plot_extraction",
category="short_drama_editing",
version="v1.0",
description="根据剧情梗概和字幕内容,精确定位关键剧情的时间段",
model_type=ModelType.TEXT,
output_format=OutputFormat.JSON,
tags=["短剧", "爆点定位", "时间戳", "剧情提取"],
parameters=["subtitle_content", "plot_summary", "plot_titles"]
)
super().__init__(metadata)
self._system_prompt = "你是一名短剧编剧,非常擅长根据字幕中分析视频中关键剧情出现的具体时间段。"
def get_template(self) -> str:
return """请仔细阅读剧情梗概和爆点内容,然后在字幕中找出每个爆点发生的具体时间段和爆点前后的详细剧情。
剧情梗概
${plot_summary}
需要定位的爆点内容
${plot_titles}
字幕内容
${subtitle_content}
分析要求
1. 为每个爆点找到对应的具体时间段
2. 时间段要准确反映该爆点的完整发展过程
3. 提供爆点前后的详细剧情描述
4. 确保时间戳格式正确且存在于字幕中
5. 选择最具戏剧张力的时间段
请返回一个JSON对象包含一个名为"plot_points"的数组数组中包含多个对象每个对象都要包含以下字段
{{
"plot_points": [
{{
"timestamp": "时间段格式为xx:xx:xx,xxx-xx:xx:xx,xxx",
"title": "关键剧情的主题",
"picture": "关键剧情前后的详细剧情描述,包括人物对话、动作、情感变化等"
}}
]
}}
重要要求
1. 请确保返回的是合法的JSON格式
2. 时间戳必须严格按照字幕中的格式
3. 剧情描述要详细具体包含关键对话和动作
4. 每个爆点的时间段要合理不能过短或过长
5. 严禁虚构不存在的时间戳或剧情内容
6. 只输出JSON内容不要添加任何说明文字"""

View File

@ -0,0 +1,68 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@Project: NarratoAI
@File : subtitle_analysis.py
@Author : AI Assistant
@Date : 2025/1/7
@Description: 短剧字幕分析提示词
"""
from ..base import TextPrompt, PromptMetadata, ModelType, OutputFormat
class SubtitleAnalysisPrompt(TextPrompt):
"""短剧字幕分析提示词"""
def __init__(self):
metadata = PromptMetadata(
name="subtitle_analysis",
category="short_drama_editing",
version="v1.0",
description="分析短剧字幕内容,提取剧情梗概和关键情节点",
model_type=ModelType.TEXT,
output_format=OutputFormat.JSON,
tags=["短剧", "字幕分析", "剧情梗概", "情节提取"],
parameters=["subtitle_content", "custom_clips"]
)
super().__init__(metadata)
self._system_prompt = "你是一名短剧编剧和内容分析师,擅长从字幕中提取剧情要点和关键情节。"
def get_template(self) -> str:
return """请仔细分析以下短剧字幕内容,提取剧情梗概和关键情节点。
字幕内容
${subtitle_content}
分析要求
1. 提取整体剧情梗概概括主要故事线和核心冲突
2. 识别 ${custom_clips} 个最具吸引力的关键情节点爆点
3. 每个情节点要包含具体的时间段和详细描述
4. 关注剧情的转折点冲突高潮情感爆发等关键时刻
5. 确保选择的情节点具有强烈的戏剧张力和观看价值
请按照以下JSON格式输出分析结果
{{
"summary": "整体剧情梗概,简要概括主要故事线、角色关系和核心冲突",
"plot_titles": [
"情节点1标题",
"情节点2标题",
"情节点3标题"
],
"analysis_details": {{
"main_characters": ["主要角色1", "主要角色2"],
"story_theme": "故事主题",
"conflict_type": "冲突类型(如:爱情、复仇、家庭等)",
"emotional_peaks": ["情感高潮点1", "情感高潮点2"]
}}
}}
重要要求
1. 必须输出有效的JSON格式不能包含注释或其他文字
2. 剧情梗概要简洁明了突出核心看点
3. 情节点标题要吸引人体现戏剧冲突
4. 严禁虚构不存在的剧情内容
5. 分析要客观准确基于字幕实际内容"""

View File

@ -0,0 +1,33 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@Project: NarratoAI
@File : __init__.py
@Author : AI Assistant
@Date : 2025/1/7
@Description: 短剧解说提示词模块
"""
from .plot_analysis import PlotAnalysisPrompt
from .script_generation import ScriptGenerationPrompt
from ..manager import PromptManager
def register_prompts():
"""注册短剧解说相关的提示词"""
# 注册剧情分析提示词
plot_analysis_prompt = PlotAnalysisPrompt()
PromptManager.register_prompt(plot_analysis_prompt, is_default=True)
# 注册解说脚本生成提示词
script_generation_prompt = ScriptGenerationPrompt()
PromptManager.register_prompt(script_generation_prompt, is_default=True)
__all__ = [
"PlotAnalysisPrompt",
"ScriptGenerationPrompt",
"register_prompts"
]

View File

@ -0,0 +1,90 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@Project: NarratoAI
@File : plot_analysis.py
@Author : AI Assistant
@Date : 2025/1/7
@Description: 短剧剧情分析提示词
"""
from ..base import TextPrompt, PromptMetadata, ModelType, OutputFormat
class PlotAnalysisPrompt(TextPrompt):
"""短剧剧情分析提示词"""
def __init__(self):
metadata = PromptMetadata(
name="plot_analysis",
category="short_drama_narration",
version="v1.0",
description="分析短剧字幕内容,提供详细的剧情分析和分段解析",
model_type=ModelType.TEXT,
output_format=OutputFormat.TEXT,
tags=["短剧", "剧情分析", "字幕解析", "分段分析"],
parameters=["subtitle_content"]
)
super().__init__(metadata)
self._system_prompt = "你是一位专业的剧本分析师和剧情概括助手。"
def get_template(self) -> str:
return """# 角色
你是一位专业的剧本分析师和剧情概括助手
# 任务
我将为你提供一部短剧的完整字幕文本请你基于这些字幕完成以下任务
1. **整体剧情分析**简要概括整个短剧的核心剧情脉络主要冲突和结局如果有的话
2. **分段剧情解析与时间戳定位**
* 将整个短剧划分为若干个关键的剧情段落例如开端发展转折高潮结局或根据具体情节自然划分
* 段落数应该与字幕长度成正比
* 对于每一个剧情段落
* **概括该段落的主要内容**用简洁的语言描述这段剧情发生了什么
* **标注对应的时间戳范围**明确指出该剧情段落对应的开始字幕时间戳和结束字幕时间戳请直接从字幕中提取时间信息
# 输入格式
字幕内容通常包含时间戳和对话例如
```
00:00:05,000 --> 00:00:10,000
[角色A]: 你好吗
00:00:10,500 --> 00:00:15,000
[角色B]: 我很好谢谢发生了一些有趣的事情
... (更多字幕内容) ...
```
我将把实际字幕粘贴在下方
# 输出格式要求
请按照以下格式清晰地呈现分析结果
**整体剧情概括**
[此处填写对整个短剧剧情的概括]
**分段剧情解析**
**剧情段落 1[段落主题/概括例如主角登场与背景介绍]**
* **时间戳** [开始时间戳] --> [结束时间戳]
* **内容概要** [对这段剧情的详细描述]
**剧情段落 2[段落主题/概括例如第一个冲突出现]**
* **时间戳** [开始时间戳] --> [结束时间戳]
* **内容概要** [对这段剧情的详细描述]
... (根据实际剧情段落数量继续) ...
**剧情段落 N[段落主题/概括例如结局与反思]**
* **时间戳** [开始时间戳] --> [结束时间戳]
* **内容概要** [对这段剧情的详细描述]
# 注意事项
* 请确保时间戳的准确性直接引用字幕中的时间
* 剧情段落的划分应合乎逻辑能够反映剧情的起承转合
* 语言表达应简洁准确客观
# 限制
1. 严禁输出与分析结果无关的内容
2. 时间戳必须严格按照字幕中的实际时间
# 请处理以下字幕:
${subtitle_content}"""

View File

@ -0,0 +1,64 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@Project: NarratoAI
@File : script_generation.py
@Author : AI Assistant
@Date : 2025/1/7
@Description: 短剧解说脚本生成提示词
"""
from ..base import ParameterizedPrompt, PromptMetadata, ModelType, OutputFormat
class ScriptGenerationPrompt(ParameterizedPrompt):
"""短剧解说脚本生成提示词"""
def __init__(self):
metadata = PromptMetadata(
name="script_generation",
category="short_drama_narration",
version="v1.0",
description="根据剧情分析生成短剧解说脚本,包含解说文案和原声片段",
model_type=ModelType.TEXT,
output_format=OutputFormat.JSON,
tags=["短剧", "解说脚本", "文案生成", "原声片段"],
parameters=["drama_name", "plot_analysis"]
)
super().__init__(metadata, required_parameters=["drama_name", "plot_analysis"])
self._system_prompt = "你是一位专业的短视频解说脚本撰写专家。你必须严格按照JSON格式输出不能包含任何其他文字、说明或代码块标记。"
def get_template(self) -> str:
return """我是一个影视解说up主需要为我的粉丝讲解短剧《${drama_name}》的剧情,目前正在解说剧情,希望能让粉丝通过我的解说了解剧情,并且产生继续观看的兴趣,请生成一篇解说脚本,包含解说文案,以及穿插原声的片段,下面<plot>中的内容是短剧的剧情概述:
<plot>
${plot_analysis}
</plot>
请严格按照以下JSON格式输出不要添加任何其他文字说明或代码块标记
{{
"items": [
{{
"_id": 1,
"timestamp": "00:00:05,390-00:00:10,430",
"picture": "剧情描述或者备注",
"narration": "解说文案,如果片段为穿插的原片片段,可以直接使用 '播放原片+_id' 进行占位",
"OST": 0
}}
]
}}
重要要求
1. 必须输出有效的JSON格式不能包含注释
2. OST字段必须是数字0表示解说片段1表示原片片段
3. _id必须是递增的数字
4. 只输出JSON内容不要输出任何说明文字
5. 不要使用代码块标记```json
6. 解说文案使用简体中文
7. 严禁虚构剧情所有内容只能从<plot>中摘取
8. 严禁虚构时间戳所有时间戳只能从<plot>中摘取
9. 解说文案要生动有趣能够吸引观众继续观看
10. 合理安排解说片段和原片片段的比例保持节奏感"""

View File

@ -0,0 +1,177 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@Project: NarratoAI
@File : template.py
@Author : AI Assistant
@Date : 2025/1/7
@Description: 模板渲染引擎
"""
import re
from typing import Dict, Any, List, Optional
from string import Template
from loguru import logger
from .exceptions import TemplateRenderError
class TemplateRenderer:
"""模板渲染器"""
def __init__(self):
self._custom_filters = {}
def register_filter(self, name: str, func: callable) -> None:
"""注册自定义过滤器"""
self._custom_filters[name] = func
logger.debug(f"已注册模板过滤器: {name}")
def render(self, template: str, parameters: Dict[str, Any] = None) -> str:
"""
渲染模板
Args:
template: 模板字符串
parameters: 参数字典
Returns:
渲染后的字符串
"""
parameters = parameters or {}
try:
# 使用Python内置的Template类进行基础渲染
tmpl = Template(template)
# 先进行基础参数替换
rendered = tmpl.safe_substitute(**parameters)
# 处理自定义过滤器
rendered = self._apply_filters(rendered, parameters)
return rendered
except Exception as e:
raise TemplateRenderError(
template_name="unknown",
error_message=f"模板渲染失败: {str(e)}"
)
def _apply_filters(self, text: str, parameters: Dict[str, Any]) -> str:
"""应用自定义过滤器"""
# 查找过滤器模式: ${variable|filter_name}
filter_pattern = r'\$\{([^}]+)\|([^}]+)\}'
def replace_filter(match):
var_name = match.group(1).strip()
filter_name = match.group(2).strip()
if filter_name not in self._custom_filters:
logger.warning(f"未知的过滤器: {filter_name}")
return match.group(0) # 返回原始文本
if var_name not in parameters:
logger.warning(f"参数不存在: {var_name}")
return match.group(0) # 返回原始文本
try:
filter_func = self._custom_filters[filter_name]
filtered_value = filter_func(parameters[var_name])
return str(filtered_value)
except Exception as e:
logger.error(f"过滤器执行失败 {filter_name}: {str(e)}")
return match.group(0) # 返回原始文本
return re.sub(filter_pattern, replace_filter, text)
def extract_variables(self, template: str) -> List[str]:
"""提取模板中的变量名"""
# 匹配 ${variable} 和 ${variable|filter} 模式
pattern = r'\$\{([^}|]+)(?:\|[^}]+)?\}'
matches = re.findall(pattern, template)
return list(set(match.strip() for match in matches))
def validate_template(self, template: str, required_params: List[str] = None) -> bool:
"""验证模板"""
try:
# 提取模板变量
template_vars = self.extract_variables(template)
# 检查必需参数
if required_params:
missing_params = set(required_params) - set(template_vars)
if missing_params:
raise TemplateRenderError(
template_name="validation",
error_message="模板缺少必需参数",
missing_params=list(missing_params)
)
# 尝试渲染测试
test_params = {var: f"test_{var}" for var in template_vars}
self.render(template, test_params)
return True
except Exception as e:
logger.error(f"模板验证失败: {str(e)}")
return False
# 内置过滤器
def _upper_filter(value: Any) -> str:
"""转换为大写"""
return str(value).upper()
def _lower_filter(value: Any) -> str:
"""转换为小写"""
return str(value).lower()
def _title_filter(value: Any) -> str:
"""转换为标题格式"""
return str(value).title()
def _strip_filter(value: Any) -> str:
"""去除首尾空白"""
return str(value).strip()
def _truncate_filter(value: Any, length: int = 100) -> str:
"""截断文本"""
text = str(value)
if len(text) <= length:
return text
return text[:length] + "..."
def _json_filter(value: Any) -> str:
"""转换为JSON字符串"""
import json
return json.dumps(value, ensure_ascii=False, indent=2)
# 全局渲染器实例
_global_renderer = TemplateRenderer()
# 注册内置过滤器
_global_renderer.register_filter("upper", _upper_filter)
_global_renderer.register_filter("lower", _lower_filter)
_global_renderer.register_filter("title", _title_filter)
_global_renderer.register_filter("strip", _strip_filter)
_global_renderer.register_filter("truncate", _truncate_filter)
_global_renderer.register_filter("json", _json_filter)
def get_renderer() -> TemplateRenderer:
"""获取全局渲染器实例"""
return _global_renderer
def render_template(template: str, parameters: Dict[str, Any] = None) -> str:
"""便捷的模板渲染函数"""
return _global_renderer.render(template, parameters)

View File

@ -0,0 +1,250 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@Project: NarratoAI
@File : validators.py
@Author : AI Assistant
@Date : 2025/1/7
@Description: 提示词输出验证器
"""
import json
import re
from typing import Dict, Any, List, Optional, Union
from loguru import logger
from .base import OutputFormat
from .exceptions import PromptValidationError
class PromptOutputValidator:
"""提示词输出验证器"""
@staticmethod
def validate_json(output: str, schema: Dict[str, Any] = None) -> Dict[str, Any]:
"""
验证JSON输出
Args:
output: 输出字符串
schema: JSON schema可选
Returns:
解析后的JSON对象
"""
try:
# 清理输出(移除可能的代码块标记)
cleaned_output = PromptOutputValidator._clean_json_output(output)
# 解析JSON
parsed = json.loads(cleaned_output)
# Schema验证如果提供
if schema:
PromptOutputValidator._validate_json_schema(parsed, schema)
return parsed
except json.JSONDecodeError as e:
raise PromptValidationError(f"JSON格式错误: {str(e)}")
except Exception as e:
raise PromptValidationError(f"JSON验证失败: {str(e)}")
@staticmethod
def validate_narration_script(output: Union[str, Dict]) -> Dict[str, Any]:
"""
验证解说文案输出格式
Args:
output: 输出内容字符串或字典
Returns:
验证后的解说文案数据
"""
# 如果是字符串先解析为JSON
if isinstance(output, str):
data = PromptOutputValidator.validate_json(output)
else:
data = output
# 验证必需字段
if "items" not in data:
raise PromptValidationError("解说文案缺少 'items' 字段")
items = data["items"]
if not isinstance(items, list):
raise PromptValidationError("'items' 字段必须是数组")
if not items:
raise PromptValidationError("解说文案不能为空")
# 验证每个item
for i, item in enumerate(items):
PromptOutputValidator._validate_narration_item(item, i)
logger.debug(f"解说文案验证通过,包含 {len(items)} 个片段")
return data
@staticmethod
def validate_plot_analysis(output: Union[str, Dict]) -> Dict[str, Any]:
"""
验证剧情分析输出格式
Args:
output: 输出内容
Returns:
验证后的剧情分析数据
"""
if isinstance(output, str):
data = PromptOutputValidator.validate_json(output)
else:
data = output
# 验证剧情分析必需字段
required_fields = ["summary", "plot_points"]
for field in required_fields:
if field not in data:
raise PromptValidationError(f"剧情分析缺少 '{field}' 字段")
# 验证plot_points
plot_points = data["plot_points"]
if not isinstance(plot_points, list):
raise PromptValidationError("'plot_points' 字段必须是数组")
for i, point in enumerate(plot_points):
PromptOutputValidator._validate_plot_point(point, i)
logger.debug(f"剧情分析验证通过,包含 {len(plot_points)} 个情节点")
return data
@staticmethod
def _clean_json_output(output: str) -> str:
"""清理JSON输出"""
# 移除可能的代码块标记
output = re.sub(r'^```json\s*', '', output, flags=re.MULTILINE)
output = re.sub(r'^```\s*$', '', output, flags=re.MULTILINE)
# 移除前后空白
output = output.strip()
# 尝试提取JSON部分如果有其他文本
json_match = re.search(r'\{.*\}', output, re.DOTALL)
if json_match:
output = json_match.group(0)
return output
@staticmethod
def _validate_json_schema(data: Dict[str, Any], schema: Dict[str, Any]) -> None:
"""验证JSON Schema"""
# 简单的schema验证实现
for field, field_type in schema.items():
if field not in data:
raise PromptValidationError(f"缺少必需字段: {field}")
if not isinstance(data[field], field_type):
raise PromptValidationError(
f"字段 '{field}' 类型错误,期望: {field_type.__name__},实际: {type(data[field]).__name__}"
)
@staticmethod
def _validate_narration_item(item: Dict[str, Any], index: int) -> None:
"""验证解说文案项目"""
required_fields = ["_id", "timestamp", "picture", "narration"]
for field in required_fields:
if field not in item:
raise PromptValidationError(f"{index + 1} 个片段缺少 '{field}' 字段")
# 验证_id
if not isinstance(item["_id"], int) or item["_id"] <= 0:
raise PromptValidationError(f"{index + 1} 个片段的 '_id' 必须是正整数")
# 验证timestamp格式
timestamp = item["timestamp"]
if not isinstance(timestamp, str):
raise PromptValidationError(f"{index + 1} 个片段的 'timestamp' 必须是字符串")
# 验证时间戳格式 (HH:MM:SS,mmm-HH:MM:SS,mmm)
timestamp_pattern = r'^\d{2}:\d{2}:\d{2},\d{3}-\d{2}:\d{2}:\d{2},\d{3}$'
if not re.match(timestamp_pattern, timestamp):
raise PromptValidationError(
f"{index + 1} 个片段的时间戳格式错误,应为 'HH:MM:SS,mmm-HH:MM:SS,mmm'"
)
# 验证文本字段不为空
for field in ["picture", "narration"]:
if not isinstance(item[field], str) or not item[field].strip():
raise PromptValidationError(f"{index + 1} 个片段的 '{field}' 不能为空")
# 验证OST字段如果存在
if "OST" in item:
if not isinstance(item["OST"], int) or item["OST"] not in [0, 1, 2]:
raise PromptValidationError(
f"{index + 1} 个片段的 'OST' 必须是 0、1 或 2"
)
@staticmethod
def _validate_plot_point(point: Dict[str, Any], index: int) -> None:
"""验证剧情点"""
required_fields = ["timestamp", "title", "picture"]
for field in required_fields:
if field not in point:
raise PromptValidationError(f"{index + 1} 个剧情点缺少 '{field}' 字段")
# 验证字段类型和内容
for field in required_fields:
if not isinstance(point[field], str) or not point[field].strip():
raise PromptValidationError(f"{index + 1} 个剧情点的 '{field}' 不能为空")
# 验证时间戳格式
timestamp = point["timestamp"]
# 支持多种时间戳格式
patterns = [
r'^\d{2}:\d{2}:\d{2},\d{3}-\d{2}:\d{2}:\d{2},\d{3}$', # HH:MM:SS,mmm-HH:MM:SS,mmm
r'^\d{2}:\d{2}:\d{2}-\d{2}:\d{2}:\d{2}$', # HH:MM:SS-HH:MM:SS
]
if not any(re.match(pattern, timestamp) for pattern in patterns):
raise PromptValidationError(
f"{index + 1} 个剧情点的时间戳格式错误"
)
@staticmethod
def validate_by_format(output: str, format_type: OutputFormat, schema: Dict[str, Any] = None) -> Any:
"""
根据格式类型验证输出
Args:
output: 输出内容
format_type: 输出格式类型
schema: 验证schema可选
Returns:
验证后的数据
"""
if format_type == OutputFormat.JSON:
return PromptOutputValidator.validate_json(output, schema)
elif format_type == OutputFormat.TEXT:
return output.strip()
elif format_type == OutputFormat.MARKDOWN:
return output.strip()
elif format_type == OutputFormat.STRUCTURED:
# 结构化数据需要根据具体类型处理
return PromptOutputValidator.validate_json(output, schema)
else:
raise PromptValidationError(f"不支持的输出格式: {format_type}")
# 便捷函数
def validate_json_output(output: str, schema: Dict[str, Any] = None) -> Dict[str, Any]:
"""验证JSON输出的便捷函数"""
return PromptOutputValidator.validate_json(output, schema)
def validate_narration_output(output: Union[str, Dict]) -> Dict[str, Any]:
"""验证解说文案输出的便捷函数"""
return PromptOutputValidator.validate_narration_script(output)

View File

@ -0,0 +1,221 @@
# 提示词管理系统文档
## 概述
本项目实现了统一的提示词管理系统,用于集中管理三个核心功能的提示词:
- **纪录片解说** - 视频帧分析和解说文案生成
- **短剧混剪** - 字幕分析和爆点提取
- **短剧解说** - 剧情分析和解说脚本生成
## 系统架构
```
app/services/prompts/
├── __init__.py # 模块初始化
├── base.py # 基础提示词类
├── manager.py # 提示词管理器
├── registry.py # 提示词注册机制
├── template.py # 模板渲染引擎
├── validators.py # 输出验证器
├── exceptions.py # 异常定义
├── documentary/ # 纪录片解说提示词
│ ├── __init__.py
│ ├── frame_analysis.py # 视频帧分析
│ └── narration_generation.py # 解说文案生成
├── short_drama_editing/ # 短剧混剪提示词
│ ├── __init__.py
│ ├── subtitle_analysis.py # 字幕分析
│ └── plot_extraction.py # 爆点提取
└── short_drama_narration/ # 短剧解说提示词
├── __init__.py
├── plot_analysis.py # 剧情分析
└── script_generation.py # 解说脚本生成
```
## 核心特性
### 1. 统一管理
- 所有提示词集中在 `app/services/prompts/` 模块中
- 按功能模块分类组织
- 支持版本控制和回滚
### 2. 模型类型适配
- **TextPrompt**: 文本模型专用
- **VisionPrompt**: 视觉模型专用
- **ParameterizedPrompt**: 支持参数化
### 3. 参数化支持
- 动态参数替换
- 参数验证
- 模板渲染
### 4. 输出验证
- 严格的JSON格式验证
- 特定业务场景验证(解说文案、剧情分析等)
- 自定义验证规则
## 使用方法
### 基本用法
```python
from app.services.prompts import PromptManager
# 获取纪录片解说的视频帧分析提示词
prompt = PromptManager.get_prompt(
category="documentary",
name="frame_analysis",
parameters={
"video_theme": "荒野建造",
"custom_instructions": "请特别关注建造过程的细节"
}
)
# 获取短剧解说的剧情分析提示词
prompt = PromptManager.get_prompt(
category="short_drama_narration",
name="plot_analysis",
parameters={"subtitle_content": "字幕内容..."}
)
```
### 高级功能
```python
# 搜索提示词
results = PromptManager.search_prompts(
keyword="分析",
model_type=ModelType.TEXT
)
# 获取提示词详细信息
info = PromptManager.get_prompt_info(
category="documentary",
name="narration_generation"
)
# 验证输出
validated_data = PromptManager.validate_output(
output=llm_response,
category="documentary",
name="narration_generation"
)
```
## 已注册的提示词
### 纪录片解说 (documentary)
- `frame_analysis` - 视频帧分析提示词
- `narration_generation` - 解说文案生成提示词
### 短剧混剪 (short_drama_editing)
- `subtitle_analysis` - 字幕分析提示词
- `plot_extraction` - 爆点提取提示词
### 短剧解说 (short_drama_narration)
- `plot_analysis` - 剧情分析提示词
- `script_generation` - 解说脚本生成提示词
## 迁移指南
### 旧代码迁移
**之前的用法:**
```python
from app.services.SDE.prompt import subtitle_plot_analysis_v1
prompt = subtitle_plot_analysis_v1
```
**新的用法:**
```python
from app.services.prompts import PromptManager
prompt = PromptManager.get_prompt(
category="short_drama_narration",
name="plot_analysis",
parameters={"subtitle_content": content}
)
```
### 已更新的文件
- `app/services/SDE/short_drama_explanation.py`
- `app/services/SDP/utils/step1_subtitle_analyzer_openai.py`
- `app/services/generate_narration_script.py`
## 扩展指南
### 添加新提示词
1. 在相应分类目录下创建新的提示词类:
```python
from ..base import TextPrompt, PromptMetadata, ModelType, OutputFormat
class NewPrompt(TextPrompt):
def __init__(self):
metadata = PromptMetadata(
name="new_prompt",
category="your_category",
version="v1.0",
description="提示词描述",
model_type=ModelType.TEXT,
output_format=OutputFormat.JSON,
parameters=["param1", "param2"]
)
super().__init__(metadata)
def get_template(self) -> str:
return "您的提示词模板内容..."
```
2. 在 `__init__.py` 中注册:
```python
def register_prompts():
new_prompt = NewPrompt()
PromptManager.register_prompt(new_prompt, is_default=True)
```
### 添加新分类
1. 创建新的分类目录
2. 实现提示词类
3. 在主模块的 `__init__.py` 中导入并注册
## 测试
运行测试脚本验证系统功能:
```bash
python test_prompt_system.py
```
## 注意事项
1. **模板参数**: 使用 `${parameter_name}` 格式
2. **JSON转义**: 模板中的JSON需要使用双大括号 `{{``}}`
3. **参数验证**: 必需参数会自动验证
4. **版本管理**: 支持多版本共存,默认使用最新版本
5. **输出验证**: 建议对LLM输出进行验证以确保格式正确
## 性能优化
- 提示词模板会被缓存
- 支持批量操作
- 异步渲染支持(未来版本)
## 故障排除
### 常见问题
1. **模板渲染错误**: 检查参数名称和格式
2. **提示词未找到**: 确认分类、名称和版本正确
3. **输出验证失败**: 检查LLM输出格式是否符合要求
### 日志调试
系统使用 loguru 记录详细日志,可通过日志排查问题:
```python
from loguru import logger
logger.debug("调试信息")
```