update:1.开放系统内置提示词,支持用户自定义模板

This commit is contained in:
xiamuceer
2025-11-29 22:01:02 +08:00
parent e772676621
commit d102328b75
23 changed files with 2325 additions and 746 deletions
+1 -1
View File
@@ -8,7 +8,7 @@
# 应用配置
# ==========================================
APP_NAME=MuMuAINovel
APP_VERSION=1.0.0
APP_VERSION=1.0.8
APP_HOST=0.0.0.0
APP_PORT=8000
DEBUG=false
+58 -21
View File
@@ -37,7 +37,7 @@ from app.schemas.regeneration import (
RegenerationTaskStatus
)
from app.services.ai_service import AIService
from app.services.prompt_service import prompt_service
from app.services.prompt_service import prompt_service, PromptService, WritingStyleManager
from app.services.plot_analyzer import PlotAnalyzer
from app.services.memory_service import memory_service
from app.services.chapter_regenerator import ChapterRegenerator
@@ -1236,9 +1236,11 @@ async def generate_chapter_content_stream(
chapter_outline_content = outline.content if outline else current_chapter.summary or '暂无大纲'
logger.warning(f"⚠️ 一对多模式但无expansion_plan,使用大纲内容")
# 根据是否有前置内容选择不同的提示词,并应用写作风格、记忆增强和MCP参考资料
# 根据是否有前置内容选择不同的提示词,并应用写作风格、记忆增强和MCP参考资料(支持自定义)
if previous_content:
prompt = prompt_service.get_chapter_generation_with_context_prompt(
template = await PromptService.get_template("CHAPTER_GENERATION_WITH_CONTEXT", current_user_id, db_session)
base_prompt = PromptService.format_prompt(
template,
title=project.title,
theme=project.theme or '',
genre=project.genre or '',
@@ -1253,14 +1255,25 @@ async def generate_chapter_content_stream(
chapter_number=current_chapter.chapter_number,
chapter_title=current_chapter.title,
chapter_outline=chapter_outline_content,
style_content=style_content,
target_word_count=target_word_count,
memory_context=memory_context,
mcp_references=mcp_reference_materials,
outline_mode=outline_mode
max_word_count=target_word_count + 1000,
memory_context=memory_context.get('recent_context', '') + "\n" + memory_context.get('relevant_memories', '') + "\n" + memory_context.get('foreshadows', '') + "\n" + memory_context.get('character_states', '') + "\n" + memory_context.get('plot_points', '') if memory_context else "暂无相关记忆"
)
# 插入模式说明和MCP参考
mode_instruction = "\n\n【创作模式说明】\n本章采用细纲模式:本章是大纲节点的细化展开之一。请严格遵循上述详细规划(expansion_plan)中的剧情点、角色焦点、情感基调和叙事目标,确保与整体规划保持一致,同时自然衔接前文内容。\n" if outline_mode == 'one-to-many' else "\n\n【创作模式说明】\n本章采用一对一模式:一个大纲节点对应一个章节。请在承接前文的基础上,充分展开大纲中的情节,保持叙事的完整性。\n"
mcp_text = ""
if mcp_reference_materials:
mcp_text = "\n【📚 MCP工具搜索 - 参考资料】\n以下是通过MCP工具搜索到的相关参考资料,可用于丰富情节和细节:\n\n" + mcp_reference_materials + "\n"
base_prompt = base_prompt.replace("本章信息:", mcp_text + mode_instruction + "\n本章信息:")
# 应用写作风格
if style_content:
prompt = WritingStyleManager.apply_style_to_prompt(base_prompt, style_content)
else:
prompt = base_prompt
else:
prompt = prompt_service.get_chapter_generation_prompt(
template = await PromptService.get_template("CHAPTER_GENERATION", current_user_id, db_session)
base_prompt = PromptService.format_prompt(
template,
title=project.title,
theme=project.theme or '',
genre=project.genre or '',
@@ -1274,12 +1287,23 @@ async def generate_chapter_content_stream(
chapter_number=current_chapter.chapter_number,
chapter_title=current_chapter.title,
chapter_outline=chapter_outline_content,
style_content=style_content,
target_word_count=target_word_count,
memory_context=memory_context,
mcp_references=mcp_reference_materials,
outline_mode=outline_mode
max_word_count=target_word_count + 1000
)
# 插入模式说明和记忆、MCP参考
mode_instruction = "\n\n【创作模式说明】\n本章采用细纲模式:本章是大纲节点的细化展开之一。请严格遵循上述详细规划中的剧情点、角色焦点和情感基调,确保与整体规划保持一致。\n" if outline_mode == 'one-to-many' else "\n\n【创作模式说明】\n本章采用一对一模式:一个大纲节点对应一个章节。请充分展开大纲中的情节,注重叙事的完整性和丰满度。\n"
memory_text = ""
if memory_context:
memory_text = "\n【🧠 智能记忆系统 - 重要参考】\n" + memory_context.get('recent_context', '') + "\n" + memory_context.get('relevant_memories', '') + "\n" + memory_context.get('foreshadows', '') + "\n" + memory_context.get('character_states', '') + "\n" + memory_context.get('plot_points', '')
mcp_text = ""
if mcp_reference_materials:
mcp_text = "\n【📚 MCP工具搜索 - 参考资料】\n以下是通过MCP工具搜索到的相关参考资料,可用于丰富情节和细节:\n\n" + mcp_reference_materials + "\n"
base_prompt = base_prompt.replace("本章信息:", memory_text + mcp_text + mode_instruction + "\n\n本章信息:")
# 应用写作风格
if style_content:
prompt = WritingStyleManager.apply_style_to_prompt(base_prompt, style_content)
else:
prompt = base_prompt
if mcp_reference_materials:
logger.info(f"📖 已整合MCP参考资料({len(mcp_reference_materials)}字符)到章节生成提示词")
@@ -2412,9 +2436,12 @@ async def generate_single_chapter_for_batch(
chapter_outline_content = outline.content if outline else chapter.summary or '暂无大纲'
logger.warning(f"⚠️ 批量生成 - 一对多模式但无expansion_plan,使用大纲内容")
# 生成提示词
# 生成提示词(支持自定义)
if previous_content:
prompt = prompt_service.get_chapter_generation_with_context_prompt(
# 获取自定义提示词模板
template = await PromptService.get_template("CHAPTER_GENERATION_WITH_CONTEXT", user_id, db_session)
base_prompt = PromptService.format_prompt(
template,
title=project.title,
theme=project.theme or '',
genre=project.genre or '',
@@ -2429,13 +2456,20 @@ async def generate_single_chapter_for_batch(
chapter_number=chapter.chapter_number,
chapter_title=chapter.title,
chapter_outline=chapter_outline_content,
style_content=style_content,
target_word_count=target_word_count,
memory_context=memory_context,
outline_mode=outline_mode
max_word_count=target_word_count + 1000,
memory_context=memory_context.get('recent_context', '') + "\n" + memory_context.get('relevant_memories', '') + "\n" + memory_context.get('foreshadows', '') + "\n" + memory_context.get('character_states', '') + "\n" + memory_context.get('plot_points', '') if memory_context else "暂无相关记忆"
)
# 应用写作风格
if style_content:
prompt = WritingStyleManager.apply_style_to_prompt(base_prompt, style_content)
else:
prompt = base_prompt
else:
prompt = prompt_service.get_chapter_generation_prompt(
# 获取自定义提示词模板
template = await PromptService.get_template("CHAPTER_GENERATION", user_id, db_session)
base_prompt = PromptService.format_prompt(
template,
title=project.title,
theme=project.theme or '',
genre=project.genre or '',
@@ -2449,11 +2483,14 @@ async def generate_single_chapter_for_batch(
chapter_number=chapter.chapter_number,
chapter_title=chapter.title,
chapter_outline=chapter_outline_content,
style_content=style_content,
target_word_count=target_word_count,
memory_context=memory_context,
outline_mode=outline_mode
max_word_count=target_word_count + 1000
)
# 应用写作风格
if style_content:
prompt = WritingStyleManager.apply_style_to_prompt(base_prompt, style_content)
else:
prompt = base_prompt
# 非流式生成内容
full_content = ""
+11 -4
View File
@@ -19,7 +19,7 @@ from app.schemas.character import (
CharacterGenerateRequest
)
from app.services.ai_service import AIService
from app.services.prompt_service import prompt_service
from app.services.prompt_service import prompt_service, PromptService
from app.logger import get_logger
from app.api.settings import get_user_ai_service
@@ -419,8 +419,11 @@ async def generate_character(
- 其他要求:{request.requirements or ''}
"""
# 使用统一的提示词服务
prompt = prompt_service.get_single_character_prompt(
# 获取自定义提示词模板
template = await PromptService.get_template("SINGLE_CHARACTER", user_id, db)
# 格式化提示词
prompt = PromptService.format_prompt(
template,
project_context=project_context,
user_input=user_input
)
@@ -825,7 +828,11 @@ async def generate_character_stream(
yield await SSEResponse.send_progress("构建AI提示词...", 20)
prompt = prompt_service.get_single_character_prompt(
# 获取自定义提示词模板
template = await PromptService.get_template("SINGLE_CHARACTER", user_id, db)
# 格式化提示词
prompt = PromptService.format_prompt(
template,
project_context=project_context,
user_input=user_input
)
+52 -120
View File
@@ -1,5 +1,5 @@
"""灵感模式API - 通过对话引导创建项目"""
from fastapi import APIRouter, Depends
from fastapi import APIRouter, Depends, Request
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Dict, Any
import json
@@ -7,97 +7,13 @@ import json
from app.database import get_db
from app.services.ai_service import AIService
from app.api.settings import get_user_ai_service
from app.services.prompt_service import prompt_service, PromptService
from app.logger import get_logger
router = APIRouter(prefix="/inspiration", tags=["灵感模式"])
logger = get_logger(__name__)
# 灵感模式提示词模板
INSPIRATION_PROMPTS = {
"title": {
"system": """你是一位专业的小说创作顾问。
用户的原始想法:{initial_idea}
请根据用户的想法,生成6个吸引人的书名建议,要求:
1. 紧扣用户的原始想法和核心故事构思
2. 富有创意和吸引力
3. 涵盖不同的风格倾向
返回JSON格式:
{{
"prompt": "根据你的想法,我为你准备了几个书名建议:",
"options": ["书名1", "书名2", "书名3", "书名4", "书名5", "书名6"]
}}
只返回纯JSON,不要有其他文字。""",
"user": "用户的想法:{initial_idea}\n请生成6个书名建议"
},
"description": {
"system": """你是一位专业的小说创作顾问。
用户的原始想法:{initial_idea}
已确定的书名:{title}
请生成6个精彩的小说简介,要求:
1. 必须紧扣用户的原始想法,确保简介是原始想法的具体展开
2. 符合已确定的书名风格
3. 简洁有力,每个50-100字
4. 包含核心冲突
5. 涵盖不同的故事走向,但都基于用户的原始构思
返回JSON格式:
{{"prompt":"选择一个简介:","options":["简介1","简介2","简介3","简介4","简介5","简介6"]}}
只返回纯JSON,不要有其他文字,不要换行。""",
"user": "原始想法:{initial_idea}\n书名:{title}\n请生成6个简介选项"
},
"theme": {
"system": """你是一位专业的小说创作顾问。
用户的原始想法:{initial_idea}
小说信息:
- 书名:{title}
- 简介:{description}
请生成6个深刻的主题选项,要求:
1. 必须与用户的原始想法保持高度一致
2. 符合书名和简介的风格
3. 有深度和思想性
4. 每个50-150字
5. 涵盖不同角度(如:成长、复仇、救赎、探索等),但都围绕用户的核心构思
返回JSON格式:
{{"prompt":"这本书的核心主题是什么?","options":["主题1","主题2","主题3","主题4","主题5","主题6"]}}
只返回纯JSON,不要有其他文字,不要换行。""",
"user": "原始想法:{initial_idea}\n书名:{title}\n简介:{description}\n请生成6个主题选项"
},
"genre": {
"system": """你是一位专业的小说创作顾问。
用户的原始想法:{initial_idea}
小说信息:
- 书名:{title}
- 简介:{description}
- 主题:{theme}
请生成6个合适的类型标签(每个2-4字),要求:
1. 必须符合用户原始想法中暗示的类型倾向
2. 符合小说整体风格
3. 可以多选组合
常见类型:玄幻、都市、科幻、武侠、仙侠、历史、言情、悬疑、奇幻、修仙等
返回JSON格式:
{{"prompt":"选择类型标签(可多选):","options":["类型1","类型2","类型3","类型4","类型5","类型6"]}}
只返回紧凑的纯JSON,不要换行,不要有其他文字。""",
"user": "原始想法:{initial_idea}\n书名:{title}\n简介:{description}\n主题:{theme}\n请生成6个类型标签"
}
}
# 不同阶段的temperature设置(递减以保持一致性)
TEMPERATURE_SETTINGS = {
"title": 0.8, # 书名阶段可以更有创意
@@ -153,6 +69,8 @@ def validate_options_response(result: Dict[str, Any], step: str, max_retries: in
@router.post("/generate-options")
async def generate_options(
data: Dict[str, Any],
http_request: Request,
db: AsyncSession = Depends(get_db),
ai_service: AIService = Depends(get_user_ai_service)
) -> Dict[str, Any]:
"""
@@ -183,28 +101,49 @@ async def generate_options(
logger.info(f"灵感模式:生成{step}阶段的选项(第{attempt + 1}次尝试)")
# 获取对应的提示词模板
if step not in INSPIRATION_PROMPTS:
# 获取用户ID
user_id = getattr(http_request.state, 'user_id', None)
# 获取对应的提示词模板(根据step确定模板key)
template_key_map = {
"title": "INSPIRATION_TITLE",
"description": "INSPIRATION_DESCRIPTION",
"theme": "INSPIRATION_THEME",
"genre": "INSPIRATION_GENRE"
}
template_key = template_key_map.get(step)
if not template_key:
return {
"error": f"不支持的步骤: {step}",
"prompt": "",
"options": []
}
prompt_template = INSPIRATION_PROMPTS[step]
# 获取自定义提示词模板
prompt_template_str = await PromptService.get_template(template_key, user_id, db)
# 准备格式化参数(提供默认值避免KeyError
# 关键改进:保持initial_idea在所有阶段传递,确保内容关联性
# 准备格式化参数
format_params = {
"initial_idea": context.get("initial_idea", context.get("description", "")), # 优先使用initial_idea,兼容旧数据
"initial_idea": context.get("initial_idea", context.get("description", "")),
"title": context.get("title", ""),
"description": context.get("description", ""),
"theme": context.get("theme", "")
}
# 格式化系统提示词
system_prompt = prompt_template["system"].format(**format_params)
user_prompt = prompt_template["user"].format(**format_params)
# 格式化提示词(灵感模式的模板是特殊格式,包含system和user两部分)
# 尝试解析为JSON格式的字典
try:
prompt_template = json.loads(prompt_template_str)
system_prompt = prompt_template["system"].format(**format_params)
user_prompt = prompt_template["user"].format(**format_params)
except (json.JSONDecodeError, KeyError):
# 如果不是JSON格式,降级使用原有方法
prompt_template = prompt_service.get_inspiration_prompt(step)
if not prompt_template:
return {"error": f"无法获取提示词模板: {step}", "prompt": "", "options": []}
system_prompt = prompt_template["system"].format(**format_params)
user_prompt = prompt_template["user"].format(**format_params)
# 如果是重试,在提示词中强调格式要求
if attempt > 0:
@@ -302,6 +241,8 @@ async def generate_options(
@router.post("/quick-generate")
async def quick_generate(
data: Dict[str, Any],
http_request: Request,
db: AsyncSession = Depends(get_db),
ai_service: AIService = Depends(get_user_ai_service)
) -> Dict[str, Any]:
"""
@@ -326,6 +267,9 @@ async def quick_generate(
try:
logger.info("灵感模式:智能补全")
# 获取用户ID
user_id = getattr(http_request.state, 'user_id', None)
# 构建补全提示词
existing_info = []
if data.get("title"):
@@ -339,35 +283,23 @@ async def quick_generate(
existing_text = "\n".join(existing_info) if existing_info else "暂无信息"
system_prompt = """你是一位专业的小说创作顾问。用户提供了部分小说信息,请补全缺失的字段。
用户已提供的信息:
{existing}
请生成完整的小说方案,包含:
1. title: 书名(3-6字,如果用户已提供则保持原样)
2. description: 简介(50-100字,必须基于用户提供的信息,不要偏离原意)
3. theme: 核心主题(30-50字,必须与用户提供的信息保持一致)
4. genre: 类型标签数组(2-3个)
重要:所有补全的内容都必须与用户提供的信息保持高度关联,确保前后一致性。
返回JSON格式:
{{
"title": "书名",
"description": "简介内容...",
"theme": "主题内容...",
"genre": ["类型1", "类型2"]
}}
只返回纯JSON,不要有其他文字。"""
# 获取自定义提示词模板
prompt_template_str = await PromptService.get_template("INSPIRATION_QUICK_COMPLETE", user_id, db)
user_prompt = "请补全小说信息"
# 格式化提示词
try:
prompts = json.loads(prompt_template_str)
# 格式化参数
prompts["system"] = prompts["system"].replace("{existing}", existing_text)
prompts["user"] = prompts["user"].replace("{existing}", existing_text)
except (json.JSONDecodeError, KeyError):
# 降级使用原有方法
prompts = prompt_service.get_inspiration_quick_complete_prompt(existing=existing_text)
# 调用AI
response = await ai_service.generate_text(
prompt=user_prompt,
system_prompt=system_prompt.format(existing=existing_text),
prompt=prompts["user"],
system_prompt=prompts["system"],
temperature=0.7
)
+11 -4
View File
@@ -24,7 +24,7 @@ from app.schemas.relationship import (
)
from app.schemas.character import CharacterResponse
from app.services.ai_service import AIService
from app.services.prompt_service import prompt_service
from app.services.prompt_service import prompt_service, PromptService
from app.logger import get_logger
from app.api.settings import get_user_ai_service
@@ -496,8 +496,11 @@ async def generate_organization(
- 其他要求:{gen_request.requirements or ''}
"""
# 使用统一的提示词服务
prompt = prompt_service.get_single_organization_prompt(
# 获取自定义提示词模板
template = await PromptService.get_template("SINGLE_ORGANIZATION", user_id, db)
# 格式化提示词
prompt = PromptService.format_prompt(
template,
project_context=project_context,
user_input=user_input
)
@@ -689,7 +692,11 @@ async def generate_organization_stream(
yield await SSEResponse.send_progress("构建AI提示词...", 20)
prompt = prompt_service.get_single_organization_prompt(
# 获取自定义提示词模板
template = await PromptService.get_template("SINGLE_ORGANIZATION", user_id, db)
# 格式化提示词
prompt = PromptService.format_prompt(
template,
project_context=project_context,
user_input=user_input
)
+19 -9
View File
@@ -25,7 +25,7 @@ from app.schemas.outline import (
CreateChaptersFromPlansResponse
)
from app.services.ai_service import AIService
from app.services.prompt_service import prompt_service
from app.services.prompt_service import prompt_service, PromptService
from app.services.memory_service import memory_service
from app.services.plot_expansion_service import PlotExpansionService
from app.logger import get_logger
@@ -477,8 +477,10 @@ async def _generate_new_outline(
logger.warning(f"⚠️ MCP工具调用失败,降级为基础模式: {str(e)}")
mcp_reference_materials = ""
# 使用完整提示词(插入MCP参考资料)
prompt = prompt_service.get_complete_outline_prompt(
# 使用完整提示词(插入MCP参考资料,支持自定义
template = await PromptService.get_template("COMPLETE_OUTLINE_GENERATION", user_id, db)
prompt = PromptService.format_prompt(
template,
title=project.title,
theme=request.theme or project.theme or "未设定",
genre=request.genre or project.genre or "通用",
@@ -797,8 +799,10 @@ async def _continue_outline(
logger.warning(f"⚠️ 第{batch_num + 1}批MCP工具调用失败,降级为基础模式: {str(e)}")
mcp_reference_materials = ""
# 使用标准续写提示词模板(支持记忆+MCP增强)
prompt = prompt_service.get_outline_continue_prompt(
# 使用标准续写提示词模板(支持记忆+MCP增强+自定义
template = await PromptService.get_template("OUTLINE_CONTINUE_GENERATION", user_id, db)
prompt = PromptService.format_prompt(
template,
title=project.title,
theme=request.theme or project.theme or "未设定",
genre=request.genre or project.genre or "通用",
@@ -814,6 +818,7 @@ async def _continue_outline(
recent_plot=recent_plot,
plot_stage_instruction=stage_instruction,
start_chapter=current_start_chapter,
end_chapter=current_start_chapter + current_batch_size - 1,
story_direction=request.story_direction or "自然延续",
requirements=request.requirements or "",
memory_context=memory_context,
@@ -1084,9 +1089,11 @@ async def new_outline_generator(
logger.warning(f"⚠️ MCP工具调用失败,降级为基础模式: {str(e)}")
mcp_reference_materials = ""
# 使用完整提示词(插入MCP参考资料)
# 使用完整提示词(插入MCP参考资料,支持自定义
yield await SSEResponse.send_progress("准备AI提示词...", 20)
prompt = prompt_service.get_complete_outline_prompt(
template = await PromptService.get_template("COMPLETE_OUTLINE_GENERATION", user_id_for_mcp, db)
prompt = PromptService.format_prompt(
template,
title=project.title,
theme=data.get("theme") or project.theme or "未设定",
genre=data.get("genre") or project.genre or "通用",
@@ -1412,8 +1419,10 @@ async def continue_outline_generator(
batch_progress + 5
)
# 使用标准续写提示词模板(支持记忆+MCP增强)
prompt = prompt_service.get_outline_continue_prompt(
# 使用标准续写提示词模板(支持记忆+MCP增强+自定义
template = await PromptService.get_template("OUTLINE_CONTINUE_GENERATION", user_id, db)
prompt = PromptService.format_prompt(
template,
title=project.title,
theme=data.get("theme") or project.theme or "未设定",
genre=data.get("genre") or project.genre or "通用",
@@ -1429,6 +1438,7 @@ async def continue_outline_generator(
recent_plot=recent_plot,
plot_stage_instruction=stage_instruction,
start_chapter=current_start_chapter,
end_chapter=current_start_chapter + current_batch_size - 1,
story_direction=data.get("story_direction", "自然延续"),
requirements=data.get("requirements", ""),
memory_context=memory_context,
+19 -5
View File
@@ -1,12 +1,12 @@
"""AI去味API - 核心特色功能"""
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends, HTTPException, Request
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
from app.models.generation_history import GenerationHistory
from app.schemas.polish import PolishRequest, PolishResponse
from app.services.ai_service import AIService
from app.services.prompt_service import prompt_service
from app.services.prompt_service import prompt_service, PromptService
from app.logger import get_logger
from app.api.settings import get_user_ai_service
@@ -17,6 +17,7 @@ logger = get_logger(__name__)
@router.post("", response_model=PolishResponse, summary="AI去味")
async def polish_text(
request: PolishRequest,
http_request: Request,
db: AsyncSession = Depends(get_db),
user_ai_service: AIService = Depends(get_user_ai_service)
):
@@ -32,8 +33,14 @@ async def polish_text(
这是本项目的核心特色功能!
"""
try:
# 构建AI去味提示词
prompt = prompt_service.get_denoising_prompt(
# 获取用户ID
user_id = getattr(http_request.state, 'user_id', None)
# 获取自定义提示词模板
template = await PromptService.get_template("DENOISING", user_id, db)
# 格式化提示词
prompt = PromptService.format_prompt(
template,
original_text=request.original_text
)
@@ -85,6 +92,7 @@ async def polish_batch(
project_id: int = None,
provider: str = None,
model: str = None,
http_request: Request = None,
db: AsyncSession = Depends(get_db),
user_ai_service: AIService = Depends(get_user_ai_service)
):
@@ -94,12 +102,18 @@ async def polish_batch(
适用于一次性处理多个章节或段落
"""
try:
# 获取用户ID
user_id = getattr(http_request.state, 'user_id', None) if http_request else None
results = []
for idx, text in enumerate(texts):
logger.info(f"处理第 {idx+1}/{len(texts)} 个文本")
prompt = prompt_service.get_denoising_prompt(original_text=text)
# 获取自定义提示词模板
template = await PromptService.get_template("DENOISING", user_id, db)
# 格式化提示词
prompt = PromptService.format_prompt(template, original_text=text)
polished_text = await user_ai_service.generate_text(
prompt=prompt,
+478
View File
@@ -0,0 +1,478 @@
"""提示词模板管理 API"""
from fastapi import APIRouter, HTTPException, Depends, Query, Request
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, delete
from typing import List, Optional
from datetime import datetime
import json
from app.database import get_db
from app.models.prompt_template import PromptTemplate
from app.schemas.prompt_template import (
PromptTemplateCreate,
PromptTemplateUpdate,
PromptTemplateResponse,
PromptTemplateListResponse,
PromptTemplateCategoryResponse,
PromptTemplateExport,
PromptTemplatePreviewRequest
)
from app.services.prompt_service import PromptService
from app.logger import get_logger
logger = get_logger(__name__)
router = APIRouter(prefix="/prompt-templates", tags=["提示词模板管理"])
@router.get("", response_model=PromptTemplateListResponse)
async def get_all_templates(
request: Request,
category: Optional[str] = Query(None, description="按分类筛选"),
is_active: Optional[bool] = Query(None, description="按启用状态筛选"),
db: AsyncSession = Depends(get_db)
):
"""
获取用户所有提示词模板
"""
# 从认证中间件获取用户ID
user_id = getattr(request.state, 'user_id', None)
if not user_id:
raise HTTPException(status_code=401, detail="未登录")
query = select(PromptTemplate).where(PromptTemplate.user_id == user_id)
if category:
query = query.where(PromptTemplate.category == category)
if is_active is not None:
query = query.where(PromptTemplate.is_active == is_active)
query = query.order_by(PromptTemplate.category, PromptTemplate.template_key)
result = await db.execute(query)
templates = result.scalars().all()
# 获取所有分类
categories_result = await db.execute(
select(PromptTemplate.category)
.where(PromptTemplate.user_id == user_id)
.distinct()
)
categories = [c for c in categories_result.scalars().all() if c]
return PromptTemplateListResponse(
templates=templates,
total=len(templates),
categories=sorted(categories)
)
@router.get("/categories", response_model=List[PromptTemplateCategoryResponse])
async def get_templates_by_category(
request: Request,
db: AsyncSession = Depends(get_db)
):
"""
按分类获取提示词模板(合并用户自定义和系统默认)
"""
# 从认证中间件获取用户ID
user_id = getattr(request.state, 'user_id', None)
if not user_id:
raise HTTPException(status_code=401, detail="未登录")
# 1. 查询用户自定义模板
result = await db.execute(
select(PromptTemplate)
.where(PromptTemplate.user_id == user_id)
.order_by(PromptTemplate.category, PromptTemplate.template_key)
)
user_templates = result.scalars().all()
# 2. 获取所有系统默认模板
system_templates = PromptService.get_all_system_templates()
# 3. 构建用户自定义模板的键集合
user_template_keys = {t.template_key for t in user_templates}
# 4. 合并模板:用户自定义的 + 未自定义的系统默认
all_templates = []
current_time = datetime.now()
# 添加用户自定义的模板
for user_template in user_templates:
user_template.is_system_default = False # 标记为已自定义
all_templates.append(user_template)
# 添加未自定义的系统默认模板
for sys_template in system_templates:
if sys_template['template_key'] not in user_template_keys:
# 这个系统模板用户还没有自定义,创建临时对象
template_obj = PromptTemplate(
id=sys_template['template_key'], # 使用template_key作为临时ID
user_id=user_id,
template_key=sys_template['template_key'],
template_name=sys_template['template_name'],
template_content=sys_template['content'],
description=sys_template['description'],
category=sys_template['category'],
parameters=json.dumps(sys_template['parameters']),
is_active=True,
is_system_default=True,
created_at=current_time,
updated_at=current_time
)
all_templates.append(template_obj)
# 5. 按分类分组
category_dict = {}
for template in all_templates:
cat = template.category or "未分类"
if cat not in category_dict:
category_dict[cat] = []
category_dict[cat].append(template)
# 6. 构建响应
response = []
for category, temps in sorted(category_dict.items()):
# 按template_key排序,确保顺序一致
temps.sort(key=lambda t: t.template_key)
response.append(PromptTemplateCategoryResponse(
category=category,
count=len(temps),
templates=temps
))
return response
@router.get("/system-defaults")
async def get_system_defaults(
request: Request
):
"""
获取所有系统默认提示词模板
"""
# 从认证中间件获取用户ID
user_id = getattr(request.state, 'user_id', None)
if not user_id:
raise HTTPException(status_code=401, detail="未登录")
# 从PromptService获取所有系统默认模板
system_templates = PromptService.get_all_system_templates()
return {
"templates": system_templates,
"total": len(system_templates)
}
@router.get("/{template_key}", response_model=PromptTemplateResponse)
async def get_template(
template_key: str,
request: Request,
db: AsyncSession = Depends(get_db)
):
"""
获取指定的提示词模板
"""
# 从认证中间件获取用户ID
user_id = getattr(request.state, 'user_id', None)
if not user_id:
raise HTTPException(status_code=401, detail="未登录")
result = await db.execute(
select(PromptTemplate).where(
PromptTemplate.user_id == user_id,
PromptTemplate.template_key == template_key
)
)
template = result.scalar_one_or_none()
if not template:
raise HTTPException(status_code=404, detail=f"模板 {template_key} 不存在")
return template
@router.post("", response_model=PromptTemplateResponse)
async def create_or_update_template(
data: PromptTemplateCreate,
request: Request,
db: AsyncSession = Depends(get_db)
):
"""
创建或更新提示词模板(Upsert)
"""
# 从认证中间件获取用户ID
user_id = getattr(request.state, 'user_id', None)
if not user_id:
raise HTTPException(status_code=401, detail="未登录")
# 查找现有模板
result = await db.execute(
select(PromptTemplate).where(
PromptTemplate.user_id == user_id,
PromptTemplate.template_key == data.template_key
)
)
template = result.scalar_one_or_none()
if template:
# 更新现有模板
for key, value in data.model_dump(exclude_unset=True).items():
setattr(template, key, value)
logger.info(f"用户 {user_id} 更新模板 {data.template_key}")
else:
# 创建新模板
template = PromptTemplate(
user_id=user_id,
**data.model_dump()
)
db.add(template)
logger.info(f"用户 {user_id} 创建模板 {data.template_key}")
await db.commit()
await db.refresh(template)
return template
@router.put("/{template_key}", response_model=PromptTemplateResponse)
async def update_template(
template_key: str,
data: PromptTemplateUpdate,
request: Request,
db: AsyncSession = Depends(get_db)
):
"""
更新提示词模板
"""
# 从认证中间件获取用户ID
user_id = getattr(request.state, 'user_id', None)
if not user_id:
raise HTTPException(status_code=401, detail="未登录")
result = await db.execute(
select(PromptTemplate).where(
PromptTemplate.user_id == user_id,
PromptTemplate.template_key == template_key
)
)
template = result.scalar_one_or_none()
if not template:
raise HTTPException(status_code=404, detail=f"模板 {template_key} 不存在")
# 更新模板
update_data = data.model_dump(exclude_unset=True)
for key, value in update_data.items():
setattr(template, key, value)
await db.commit()
await db.refresh(template)
logger.info(f"用户 {user_id} 更新模板 {template_key}")
return template
@router.delete("/{template_key}")
async def delete_template(
template_key: str,
request: Request,
db: AsyncSession = Depends(get_db)
):
"""
删除自定义提示词模板
"""
# 从认证中间件获取用户ID
user_id = getattr(request.state, 'user_id', None)
if not user_id:
raise HTTPException(status_code=401, detail="未登录")
result = await db.execute(
select(PromptTemplate).where(
PromptTemplate.user_id == user_id,
PromptTemplate.template_key == template_key
)
)
template = result.scalar_one_or_none()
if not template:
raise HTTPException(status_code=404, detail=f"模板 {template_key} 不存在")
await db.delete(template)
await db.commit()
logger.info(f"用户 {user_id} 删除模板 {template_key}")
return {"message": "模板已删除", "template_key": template_key}
@router.post("/{template_key}/reset")
async def reset_to_default(
template_key: str,
request: Request,
db: AsyncSession = Depends(get_db)
):
"""
重置为系统默认模板(删除用户自定义版本)
"""
# 从认证中间件获取用户ID
user_id = getattr(request.state, 'user_id', None)
if not user_id:
raise HTTPException(status_code=401, detail="未登录")
# 验证系统默认模板是否存在
system_template = PromptService.get_system_template_info(template_key)
if not system_template:
raise HTTPException(status_code=404, detail=f"系统默认模板 {template_key} 不存在")
# 查找并删除用户的自定义模板
result = await db.execute(
select(PromptTemplate).where(
PromptTemplate.user_id == user_id,
PromptTemplate.template_key == template_key
)
)
template = result.scalar_one_or_none()
if template:
await db.delete(template)
await db.commit()
logger.info(f"用户 {user_id} 删除自定义模板 {template_key},恢复为系统默认")
return {"message": "已重置为系统默认", "template_key": template_key}
else:
# 用户本来就没有自定义,已经是系统默认状态
logger.info(f"用户 {user_id} 的模板 {template_key} 本来就是系统默认")
return {"message": "已是系统默认状态", "template_key": template_key}
@router.post("/export", response_model=PromptTemplateExport)
async def export_templates(
request: Request,
db: AsyncSession = Depends(get_db)
):
"""
导出用户所有自定义模板
"""
# 从认证中间件获取用户ID
user_id = getattr(request.state, 'user_id', None)
if not user_id:
raise HTTPException(status_code=401, detail="未登录")
result = await db.execute(
select(PromptTemplate).where(PromptTemplate.user_id == user_id)
)
templates = result.scalars().all()
# 转换为导出格式
export_data = [
{
"template_key": t.template_key,
"template_name": t.template_name,
"template_content": t.template_content,
"description": t.description,
"category": t.category,
"parameters": t.parameters,
"is_active": t.is_active
}
for t in templates
]
logger.info(f"用户 {user_id} 导出了 {len(export_data)} 个模板")
return PromptTemplateExport(
templates=export_data,
export_time=datetime.now()
)
@router.post("/import")
async def import_templates(
data: PromptTemplateExport,
request: Request,
db: AsyncSession = Depends(get_db)
):
"""
导入提示词模板
"""
# 从认证中间件获取用户ID
user_id = getattr(request.state, 'user_id', None)
if not user_id:
raise HTTPException(status_code=401, detail="未登录")
imported_count = 0
updated_count = 0
for template_data in data.templates:
# 查找是否已存在
result = await db.execute(
select(PromptTemplate).where(
PromptTemplate.user_id == user_id,
PromptTemplate.template_key == template_data.template_key
)
)
existing = result.scalar_one_or_none()
if existing:
# 更新现有模板
for key, value in template_data.model_dump().items():
setattr(existing, key, value)
updated_count += 1
else:
# 创建新模板
new_template = PromptTemplate(
user_id=user_id,
**template_data.model_dump()
)
db.add(new_template)
imported_count += 1
await db.commit()
logger.info(f"用户 {user_id} 导入了 {imported_count} 个新模板,更新了 {updated_count} 个模板")
return {
"message": "导入成功",
"imported": imported_count,
"updated": updated_count,
"total": imported_count + updated_count
}
@router.post("/{template_key}/preview")
async def preview_template(
template_key: str,
data: PromptTemplatePreviewRequest,
request: Request
):
"""
预览提示词模板(渲染变量)
"""
# 从认证中间件获取用户ID
user_id = getattr(request.state, 'user_id', None)
if not user_id:
raise HTTPException(status_code=401, detail="未登录")
try:
# 使用PromptService的format_prompt方法
rendered = PromptService.format_prompt(
data.template_content,
**data.parameters
)
return {
"success": True,
"rendered_content": rendered,
"parameters_used": list(data.parameters.keys())
}
except KeyError as e:
return {
"success": False,
"error": f"缺少必需的参数: {str(e)}",
"rendered_content": None
}
except Exception as e:
return {
"success": False,
"error": f"渲染失败: {str(e)}",
"rendered_content": None
}
+19 -8
View File
@@ -16,7 +16,7 @@ from app.models.writing_style import WritingStyle
from app.models.project_default_style import ProjectDefaultStyle
from app.services.ai_service import AIService
from app.services.mcp_tool_service import MCPToolService
from app.services.prompt_service import prompt_service
from app.services.prompt_service import prompt_service, PromptService
from app.services.plot_expansion_service import PlotExpansionService
from app.logger import get_logger
from app.utils.sse_response import SSEResponse, create_sse_response
@@ -57,12 +57,14 @@ async def world_building_generator(
yield await SSEResponse.send_error("title、description、theme 和 genre 是必需的参数", 400)
return
# 获取基础提示词
# 获取基础提示词(支持自定义)
yield await SSEResponse.send_progress("准备AI提示词...", 15)
base_prompt = prompt_service.get_world_building_prompt(
template = await PromptService.get_template("WORLD_BUILDING", user_id, db)
base_prompt = PromptService.format_prompt(
template,
title=title,
theme=theme,
genre=genre
genre=genre or "通用类型"
)
# MCP工具增强:收集参考资料
@@ -455,8 +457,11 @@ async def characters_generator(
else:
batch_requirements += "\n主要是配角(supporting)和反派(antagonist)"
# 获取自定义提示词模板
template = await PromptService.get_template("CHARACTERS_BATCH", user_id, db)
# 构建基础提示词
base_prompt = prompt_service.get_characters_batch_prompt(
base_prompt = PromptService.format_prompt(
template,
count=current_batch_size, # 传递精确数量
time_period=world_context.get("time_period", ""),
location=world_context.get("location", ""),
@@ -954,7 +959,10 @@ async def outline_generator(
outline_requirements += "4. 不要试图完结故事,这只是开始部分\n"
outline_requirements += "5. 不要在JSON字符串值中使用中文引号(""''),请使用【】或《》标记\n"
outline_prompt = prompt_service.get_complete_outline_prompt(
# 获取自定义提示词模板
template = await PromptService.get_template("COMPLETE_OUTLINE_GENERATION", user_id, db)
outline_prompt = PromptService.format_prompt(
template,
title=project.title,
theme=project.theme or "未设定",
genre=project.genre or "通用",
@@ -966,6 +974,7 @@ async def outline_generator(
atmosphere=project.world_atmosphere or "未设定",
rules=project.world_rules or "未设定",
characters_info=characters_info or "暂无角色信息",
mcp_references="",
requirements=outline_requirements
)
@@ -1150,9 +1159,11 @@ async def world_building_regenerate_generator(
enable_mcp = data.get("enable_mcp", True)
user_id = data.get("user_id")
# 获取基础提示词
# 获取基础提示词(支持自定义)
yield await SSEResponse.send_progress("准备AI提示词...", 15)
base_prompt = prompt_service.get_world_building_prompt(
template = await PromptService.get_template("WORLD_BUILDING", user_id, db)
base_prompt = PromptService.format_prompt(
template,
title=project.title,
theme=project.theme or "未设定",
genre=project.genre or "通用"
+2 -1
View File
@@ -142,7 +142,7 @@ from app.api import (
projects, outlines, characters, chapters,
wizard_stream, relationships, organizations,
auth, users, settings, writing_styles, memories,
mcp_plugins, admin, inspiration
mcp_plugins, admin, inspiration, prompt_templates
)
app.include_router(auth.router, prefix="/api")
@@ -161,6 +161,7 @@ app.include_router(organizations.router, prefix="/api")
app.include_router(writing_styles.router, prefix="/api")
app.include_router(memories.router) # 记忆管理API (已包含/api前缀)
app.include_router(mcp_plugins.router, prefix="/api") # MCP插件管理API
app.include_router(prompt_templates.router, prefix="/api") # 提示词模板管理API
static_dir = Path(__file__).parent.parent / "static"
if static_dir.exists():
+30
View File
@@ -0,0 +1,30 @@
"""提示词模板数据模型"""
from sqlalchemy import Column, String, Text, Boolean, DateTime, Index
from sqlalchemy.sql import func
from app.database import Base
import uuid
class PromptTemplate(Base):
"""提示词模板表"""
__tablename__ = "prompt_templates"
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
user_id = Column(String(50), nullable=False, index=True, comment="用户ID")
template_key = Column(String(100), nullable=False, comment="模板键名")
template_name = Column(String(200), nullable=False, comment="模板显示名称")
template_content = Column(Text, nullable=False, comment="模板内容")
description = Column(Text, comment="模板描述")
category = Column(String(50), comment="模板分类")
parameters = Column(Text, comment="模板参数定义(JSON)")
is_active = Column(Boolean, default=True, comment="是否启用")
is_system_default = Column(Boolean, default=False, comment="是否为系统默认模板")
created_at = Column(DateTime, server_default=func.now(), comment="创建时间")
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment="更新时间")
__table_args__ = (
Index('idx_user_template', 'user_id', 'template_key', unique=True),
)
def __repr__(self):
return f"<PromptTemplate(id={self.id}, user_id={self.user_id}, template_key={self.template_key})>"
+68
View File
@@ -0,0 +1,68 @@
"""提示词模板相关的Pydantic模型"""
from pydantic import BaseModel, Field, ConfigDict
from typing import Optional, List
from datetime import datetime
class PromptTemplateBase(BaseModel):
"""提示词模板基础模型"""
template_key: str = Field(..., description="模板键名")
template_name: str = Field(..., description="模板显示名称")
template_content: str = Field(..., description="模板内容")
description: Optional[str] = Field(None, description="模板描述")
category: Optional[str] = Field(None, description="模板分类")
parameters: Optional[str] = Field(None, description="模板参数定义(JSON)")
is_active: bool = Field(True, description="是否启用")
class PromptTemplateCreate(PromptTemplateBase):
"""创建提示词模板请求模型"""
pass
class PromptTemplateUpdate(BaseModel):
"""更新提示词模板请求模型"""
template_name: Optional[str] = Field(None, description="模板显示名称")
template_content: Optional[str] = Field(None, description="模板内容")
description: Optional[str] = Field(None, description="模板描述")
category: Optional[str] = Field(None, description="模板分类")
parameters: Optional[str] = Field(None, description="模板参数定义(JSON)")
is_active: Optional[bool] = Field(None, description="是否启用")
class PromptTemplateResponse(PromptTemplateBase):
"""提示词模板响应模型"""
model_config = ConfigDict(from_attributes=True)
id: str
user_id: str
is_system_default: bool
created_at: datetime
updated_at: datetime
class PromptTemplateListResponse(BaseModel):
"""提示词模板列表响应"""
templates: List[PromptTemplateResponse]
total: int
categories: List[str]
class PromptTemplateCategoryResponse(BaseModel):
"""提示词模板分类响应"""
category: str
count: int
templates: List[PromptTemplateResponse]
class PromptTemplateExport(BaseModel):
"""提示词模板导出模型"""
templates: List[PromptTemplateBase]
export_time: datetime
version: str = "1.0"
class PromptTemplatePreviewRequest(BaseModel):
"""提示词模板预览请求"""
template_content: str = Field(..., description="模板内容")
parameters: dict = Field(..., description="参数字典")
+30 -117
View File
@@ -1,7 +1,8 @@
"""章节重新生成服务"""
from typing import Dict, Any, AsyncGenerator, Optional, List
from sqlalchemy.ext.asyncio import AsyncSession
from app.services.ai_service import AIService
from app.services.prompt_service import prompt_service
from app.services.prompt_service import prompt_service, PromptService
from app.models.chapter import Chapter
from app.models.memory import PlotAnalysis
from app.schemas.regeneration import ChapterRegenerateRequest, PreserveElementsConfig
@@ -24,7 +25,9 @@ class ChapterRegenerator:
analysis: Optional[PlotAnalysis],
regenerate_request: ChapterRegenerateRequest,
project_context: Dict[str, Any],
style_content: str = ""
style_content: str = "",
user_id: str = None,
db: AsyncSession = None
) -> AsyncGenerator[Dict[str, Any], None]:
"""
根据反馈重新生成章节(流式)
@@ -34,6 +37,9 @@ class ChapterRegenerator:
analysis: 分析结果(可选)
regenerate_request: 重新生成请求参数
project_context: 项目上下文(项目信息、角色、大纲等)
style_content: 写作风格
user_id: 用户ID(用于获取自定义提示词)
db: 数据库会话(用于查询自定义提示词)
Yields:
包含类型和数据的字典: {'type': 'progress'/'chunk', 'data': ...}
@@ -52,12 +58,14 @@ class ChapterRegenerator:
# 2. 构建完整提示词
yield {'type': 'progress', 'progress': 10, 'message': '正在构建生成提示词...'}
full_prompt = self._build_regeneration_prompt(
full_prompt = await self._build_regeneration_prompt(
chapter=chapter,
modification_instructions=modification_instructions,
project_context=project_context,
regenerate_request=regenerate_request,
style_content=style_content
style_content=style_content,
user_id=user_id,
db=db
)
logger.info(f"🎯 提示词构建完成,开始AI生成")
@@ -158,126 +166,31 @@ class ChapterRegenerator:
return "\n".join(instructions)
def _build_regeneration_prompt(
async def _build_regeneration_prompt(
self,
chapter: Chapter,
modification_instructions: str,
project_context: Dict[str, Any],
regenerate_request: ChapterRegenerateRequest,
style_content: str = ""
style_content: str = "",
user_id: str = None,
db: AsyncSession = None
) -> str:
"""构建完整的重新生成提示词"""
prompt_parts = []
# 系统角色
prompt_parts.append("""你是一位经验丰富的专业小说编辑和作家。现在需要根据反馈意见重新创作一个章节。
你的任务是:
1. 仔细理解原章节的内容和意图
2. 认真分析所有的修改要求
3. 在保持故事连贯性的前提下,创作一个改进后的新版本
4. 确保新版本在艺术性和可读性上都有明显提升
---
""")
# 原始章节信息
prompt_parts.append(f"""## 📖 原始章节信息
**章节**:第{chapter.chapter_number}
**标题**{chapter.title}
**字数**{chapter.word_count}
**原始内容**
{chapter.content}
---
""")
# 修改指令
prompt_parts.append(modification_instructions)
prompt_parts.append("\n---\n")
# 项目背景信息
prompt_parts.append(f"""## 🌍 项目背景信息
**小说标题**{project_context.get('project_title', '未知')}
**题材**{project_context.get('genre', '未设定')}
**主题**{project_context.get('theme', '未设定')}
**叙事视角**{project_context.get('narrative_perspective', '第三人称')}
**世界观设定**
- 时代背景:{project_context.get('time_period', '未设定')}
- 地理位置:{project_context.get('location', '未设定')}
- 氛围基调:{project_context.get('atmosphere', '未设定')}
---
""")
# 角色信息
if project_context.get('characters_info'):
prompt_parts.append(f"""## 👥 角色信息
{project_context['characters_info']}
---
""")
# 章节大纲
if project_context.get('chapter_outline'):
prompt_parts.append(f"""## 📝 本章大纲
{project_context['chapter_outline']}
---
""")
# 前置章节上下文
if project_context.get('previous_context'):
prompt_parts.append(f"""## 📚 前置章节上下文
{project_context['previous_context']}
---
""")
# 写作风格要求(如果提供)
if style_content:
prompt_parts.append(f"""## 🎨 写作风格要求
{style_content}
请在重新创作时严格遵循上述写作风格。
---
""")
# 创作要求
prompt_parts.append(f"""## ✨ 创作要求
1. **解决问题**:针对上述修改指令中提到的所有问题进行改进
2. **保持连贯**:确保与前后章节的情节、人物、风格保持一致
3. **提升质量**:在节奏、情感、描写等方面明显优于原版
4. **保留精华**:保持原章节中优秀的部分和关键情节
5. **字数控制**:目标字数约{regenerate_request.target_word_count}字(可适当浮动±20%
{f'6. **风格一致**:严格按照上述写作风格进行创作' if style_content else ''}
---
## 🎬 开始创作
请现在开始创作改进后的新版本章节内容。
**重要提示**
- 直接输出章节正文内容,从故事内容开始写
- **不要**输出章节标题(如"第X章""第X章:XXX"等)
- **不要**输出任何额外的说明、注释或元数据
- 只需要纯粹的故事正文内容
现在开始:
""")
return "\n".join(prompt_parts)
# 获取自定义提示词模板
template = await PromptService.get_template("CHAPTER_REGENERATION", user_id, db)
# 格式化提示词
return PromptService.format_prompt(
template,
chapter_number=chapter.chapter_number,
title=chapter.title,
word_count=chapter.word_count,
content=chapter.content,
modification_instructions=modification_instructions,
project_context=project_context,
style_content=style_content,
target_word_count=regenerate_request.target_word_count
)
def calculate_content_diff(
self,
+4 -18
View File
@@ -13,6 +13,7 @@ from app.models.settings import Settings as UserSettings
from app.mcp.registry import mcp_registry
from app.services.ai_service import create_user_ai_service
from app.schemas.mcp_plugin import MCPTestResult
from app.services.prompt_service import prompt_service
from app.logger import get_logger
from app.user_manager import User
@@ -168,26 +169,11 @@ class MCPTestService:
logger.debug(f"📋 OpenAI工具列表: {[t['function']['name'] for t in openai_tools]}")
# 调用AI选择工具
prompt = f"""你是MCP插件测试助手,需要测试插件 '{plugin.plugin_name}' 的功能。
⚠️ 重要规则:生成参数时,必须严格使用工具 schema 中定义的原始参数名称,不要转换为 snake_case 或其他格式。
例如:如果 schema 中是 'nextThoughtNeeded',就必须使用 'nextThoughtNeeded',不能改成 'next_thought_needed'
请选择一个合适的工具进行测试,优先选择搜索、查询类工具。
生成真实有效的测试参数(例如搜索"人工智能最新进展"而不是"test")。
现在开始测试这个插件。"""
system_prompt = """你是专业的API测试工具。当给定工具列表时,选择一个工具并使用合适的参数调用它。
⚠️ 关键规则:调用工具时,必须严格使用 schema 中定义的原始参数名,不要自行转换命名风格。
- 如果参数名是 camelCase(如 nextThoughtNeeded),就使用 camelCase
- 如果参数名是 snake_case(如 next_thought),就使用 snake_case
- 保持与 schema 中定义的完全一致,包括大小写和命名风格"""
prompts = prompt_service.get_mcp_tool_test_prompts(plugin.plugin_name)
ai_response = await ai_service.generate_text(
prompt=prompt,
system_prompt=system_prompt,
prompt=prompts["user"],
system_prompt=prompts["system"],
tools=openai_tools,
tool_choice="required"
)
+12 -166
View File
@@ -1,6 +1,8 @@
"""剧情分析服务 - 自动分析章节的钩子、伏笔、冲突等元素"""
from typing import Dict, Any, List, Optional
from sqlalchemy.ext.asyncio import AsyncSession
from app.services.ai_service import AIService
from app.services.prompt_service import prompt_service, PromptService
from app.logger import get_logger
import json
import re
@@ -11,169 +13,6 @@ logger = get_logger(__name__)
class PlotAnalyzer:
"""剧情分析器 - 使用AI分析章节内容"""
# AI分析提示词模板
ANALYSIS_PROMPT = """你是一位专业的小说编辑和剧情分析师。请深度分析以下章节内容:
**章节信息:**
- 章节: 第{chapter_number}
- 标题: {title}
- 字数: {word_count}
**章节内容:**
{content}
---
**分析任务:**
请从专业编辑的角度,全面分析这一章节:
### 1. 剧情钩子 (Hooks) - 吸引读者的元素
识别能够吸引读者继续阅读的关键元素:
- **悬念钩子**: 未解之谜、疑问、谜团
- **情感钩子**: 引发共鸣的情感点、触动心弦的时刻
- **冲突钩子**: 矛盾对抗、紧张局势
- **认知钩子**: 颠覆认知的信息、惊人真相
每个钩子需要:
- 类型分类
- 具体内容描述
- 强度评分(1-10)
- 出现位置(开头/中段/结尾)
- **关键词**: 【必填】从章节原文中逐字复制一段关键文本(8-25字),必须是原文中真实存在的连续文字,用于在文本中精确定位。不要概括或改写,必须原样复制!
### 2. 伏笔分析 (Foreshadowing)
- **埋下的新伏笔**: 描述内容、预期作用、隐藏程度(1-10)
- **回收的旧伏笔**: 呼应哪一章、回收效果评分
- **伏笔质量**: 巧妙性和合理性评估
- **关键词**: 【必填】从章节原文中逐字复制一段关键文本(8-25字),必须是原文中真实存在的连续文字,用于在文本中精确定位。不要概括或改写,必须原样复制!
### 3. 冲突分析 (Conflict)
- 冲突类型: 人与人/人与己/人与环境/人与社会
- 冲突各方及其立场
- 冲突强度评分(1-10)
- 冲突解决进度(0-100%)
### 4. 情感曲线 (Emotional Arc)
- 主导情绪: 紧张/温馨/悲伤/激昂/平静等
- 情感强度(1-10)
- 情绪变化轨迹描述
### 5. 角色状态追踪 (Character Development)
对每个出场角色分析:
- 心理状态变化(前→后)
- 关系变化
- 关键行动和决策
- 成长或退步
### 6. 关键情节点 (Plot Points)
列出3-5个核心情节点:
- 情节内容
- 类型(revelation/conflict/resolution/transition)
- 重要性(0.0-1.0)
- 对故事的影响
- **关键词**: 【必填】从章节原文中逐字复制一段关键文本(8-25字),必须是原文中真实存在的连续文字,用于在文本中精确定位。不要概括或改写,必须原样复制!
### 7. 场景与节奏
- 主要场景
- 叙事节奏(快/中/慢)
- 对话与描写的比例
### 8. 质量评分
- 节奏把控: 1-10分
- 吸引力: 1-10分
- 连贯性: 1-10分
- 整体质量: 1-10分
### 9. 改进建议
提供3-5条具体的改进建议
---
**输出格式(纯JSON,不要markdown标记):**
{{
"hooks": [
{{
"type": "悬念",
"content": "具体描述",
"strength": 8,
"position": "中段",
"keyword": "必须从原文逐字复制的文本片段"
}}
],
"foreshadows": [
{{
"content": "伏笔内容",
"type": "planted",
"strength": 7,
"subtlety": 8,
"reference_chapter": null,
"keyword": "必须从原文逐字复制的文本片段"
}}
],
"conflict": {{
"types": ["人与人", "人与己"],
"parties": ["主角-复仇", "反派-维护现状"],
"level": 8,
"description": "冲突描述",
"resolution_progress": 0.3
}},
"emotional_arc": {{
"primary_emotion": "紧张",
"intensity": 8,
"curve": "平静→紧张→高潮→释放",
"secondary_emotions": ["期待", "焦虑"]
}},
"character_states": [
{{
"character_name": "张三",
"state_before": "犹豫",
"state_after": "坚定",
"psychological_change": "心理变化描述",
"key_event": "触发事件",
"relationship_changes": {{"李四": "关系改善"}}
}}
],
"plot_points": [
{{
"content": "情节点描述",
"type": "revelation",
"importance": 0.9,
"impact": "推动故事发展",
"keyword": "必须从原文逐字复制的文本片段"
}}
],
"scenes": [
{{
"location": "地点",
"atmosphere": "氛围",
"duration": "时长估计"
}}
],
"pacing": "varied",
"dialogue_ratio": 0.4,
"description_ratio": 0.3,
"scores": {{
"pacing": 8,
"engagement": 9,
"coherence": 8,
"overall": 8.5
}},
"plot_stage": "发展",
"suggestions": [
"具体建议1",
"具体建议2"
]
}}
**重要提示:**
1. 每个钩子、伏笔、情节点的keyword字段是必填的,不能为空
2. keyword必须是从章节原文中逐字复制的文本,长度8-25字
3. keyword用于在前端标注文本位置,所以必须能在原文中精确找到
4. 不要使用概括性语句或改写后的文字作为keyword
只返回JSON,不要其他说明。"""
def __init__(self, ai_service: AIService):
"""
初始化剧情分析器
@@ -189,7 +28,9 @@ class PlotAnalyzer:
chapter_number: int,
title: str,
content: str,
word_count: int
word_count: int,
user_id: str = None,
db: AsyncSession = None
) -> Optional[Dict[str, Any]]:
"""
分析单章内容
@@ -199,6 +40,8 @@ class PlotAnalyzer:
title: 章节标题
content: 章节内容
word_count: 字数
user_id: 用户ID(用于获取自定义提示词)
db: 数据库会话(用于查询自定义提示词)
Returns:
分析结果字典,失败返回None
@@ -209,8 +52,11 @@ class PlotAnalyzer:
# 如果内容过长,截取前8000字(避免超token)
analysis_content = content[:8000] if len(content) > 8000 else content
# 构建提示词
prompt = self.ANALYSIS_PROMPT.format(
# 获取自定义提示词模板
template = await PromptService.get_template("PLOT_ANALYSIS", user_id, db)
# 格式化提示词
prompt = PromptService.format_prompt(
template,
chapter_number=chapter_number,
title=title,
word_count=word_count,
+55 -268
View File
@@ -9,6 +9,7 @@ from app.models.project import Project
from app.models.character import Character
from app.models.chapter import Chapter
from app.services.ai_service import AIService
from app.services.prompt_service import prompt_service, PromptService
from app.logger import get_logger
logger = get_logger(__name__)
@@ -107,15 +108,27 @@ class PlotExpansionService:
# 获取大纲上下文(前后大纲)
context_info = await self._get_outline_context(outline, project.id, db)
# 构建分析提示词
prompt = self._build_expansion_prompt(
outline=outline,
project=project,
characters_info=characters_info,
# 获取自定义提示词模板
template = await PromptService.get_template("PLOT_EXPANSION_SINGLE_BATCH", project.user_id, db)
# 格式化提示词
prompt = PromptService.format_prompt(
template,
project_title=project.title,
project_genre=project.genre or '通用',
project_theme=project.theme or '未设定',
project_narrative_perspective=project.narrative_perspective or '第三人称',
project_world_time_period=project.world_time_period or '未设定',
project_world_location=project.world_location or '未设定',
project_world_atmosphere=project.world_atmosphere or '未设定',
characters_info=characters_info or '暂无角色',
outline_order_index=outline.order_index,
outline_title=outline.title,
outline_content=outline.content,
context_info=context_info,
strategy_instruction=expansion_strategy,
target_chapter_count=target_chapter_count,
expansion_strategy=expansion_strategy,
enable_scene_analysis=enable_scene_analysis
scene_instruction="", # 暂时为空
scene_field="" # 暂时为空
)
# 调用AI生成章节规划
@@ -182,17 +195,43 @@ class PlotExpansionService:
await progress_callback(batch_num + 1, total_batches, current_start_index, current_batch_size)
# 构建当前批次的提示词(包含已生成章节的上下文)
prompt = self._build_batch_expansion_prompt(
outline=outline,
project=project,
characters_info=characters_info,
previous_context = ""
if all_chapter_plans:
previous_summaries = []
for ch in all_chapter_plans[-3:]: # 只显示最近3章
previous_summaries.append(
f"{ch['sub_index']}节《{ch['title']}》: {ch['plot_summary'][:100]}..."
)
previous_context = f"""
【已生成章节概要】(接续生成,注意衔接)
{chr(10).join(previous_summaries)}
⚠️ 当前是第{current_start_index}-{current_start_index + current_batch_size - 1}节(共{target_chapter_count}节中的一部分)
"""
# 获取自定义提示词模板
template = await PromptService.get_template("PLOT_EXPANSION_MULTI_BATCH", project.user_id, db)
# 格式化提示词
prompt = PromptService.format_prompt(
template,
project_title=project.title,
project_genre=project.genre or '通用',
project_theme=project.theme or '未设定',
project_narrative_perspective=project.narrative_perspective or '第三人称',
project_world_time_period=project.world_time_period or '未设定',
project_world_location=project.world_location or '未设定',
project_world_atmosphere=project.world_atmosphere or '未设定',
characters_info=characters_info or '暂无角色',
outline_order_index=outline.order_index,
outline_title=outline.title,
outline_content=outline.content,
context_info=context_info,
target_chapter_count=current_batch_size,
expansion_strategy=expansion_strategy,
enable_scene_analysis=enable_scene_analysis,
previous_context=previous_context,
strategy_instruction=expansion_strategy,
start_index=current_start_index,
previous_chapters=all_chapter_plans,
total_chapters=target_chapter_count
end_index=current_start_index + current_batch_size - 1,
target_chapter_count=current_batch_size,
scene_instruction="", # 暂时为空
scene_field="" # 暂时为空
)
# 调用AI生成当前批次
@@ -452,258 +491,6 @@ class PlotExpansionService:
return context if context else "(无前后文)"
def _build_expansion_prompt(
self,
outline: Outline,
project: Project,
characters_info: str,
context_info: str,
target_chapter_count: int,
expansion_strategy: str,
enable_scene_analysis: bool
) -> str:
"""构建大纲展开提示词"""
strategy_desc = {
"balanced": "均衡展开:每章剧情量相当,节奏平稳",
"climax": "高潮重点:重点章节剧情丰富,其他章节简洁过渡",
"detail": "细节丰富:每章都深入描写,场景和情感细腻"
}
strategy_instruction = strategy_desc.get(expansion_strategy, strategy_desc["balanced"])
# 场景字段(避免f-string中的反斜杠)
scene_field = ',\n "main_scenes": ["场景1", "场景2"]' if enable_scene_analysis else ''
scene_instruction = ""
if enable_scene_analysis:
scene_instruction = """
5. 场景分析(每章需包含):
- 主要场景地点
- 场景氛围
- 关键道具/环境元素
"""
prompt = f"""你是专业的小说情节架构师。请分析以下大纲节点,将其展开为 {target_chapter_count} 个章节的详细规划。
【项目信息】
小说名称:{project.title}
类型:{project.genre or '通用'}
主题:{project.theme or '未设定'}
叙事视角:{project.narrative_perspective or '第三人称'}
【世界观背景】
时间背景:{project.world_time_period or '未设定'}
地理位置:{project.world_location or '未设定'}
氛围基调:{project.world_atmosphere or '未设定'}
【角色信息】
{characters_info or '暂无角色'}
【当前大纲节点 - 展开对象】
序号:第 {outline.order_index}
标题:{outline.title}
内容:{outline.content}
【上下文参考】
{context_info}
【展开策略】
{strategy_instruction}
【⚠️ 重要约束 - 必须严格遵守】
1. **内容边界约束**
- ✅ 只能展开【当前大纲节点】中明确描述的内容
- ❌ 绝对不能推进到后续大纲的内容(如果有【后一节】信息)
- ❌ 不要让剧情快速推进,要深化而非跨越
2. **展开原则**
- 将当前大纲的单一事件拆解为多个细节丰富的章节
- 深入挖掘情感、心理、环境、对话等细节
- 放慢叙事节奏,让读者充分体验当前阶段的剧情
- 每个章节都应该是当前大纲内容的不同侧面或阶段
3. **如何避免剧情越界**
- 如果当前大纲描述"主角遇到困境",展开时应详写困境的发现、分析、情感冲击等
- 不要直接写到"解决困境",除非原大纲明确包含解决过程
- 如果看到【后一节】的内容,那些是禁区,绝不提前展开
【任务要求】
1. 深度分析该大纲的剧情容量和叙事节奏
2. 识别关键剧情点、冲突点和情感转折点(仅限当前大纲范围内)
3. 将大纲拆解为 {target_chapter_count} 个章节,每章需包含:
- sub_index: 子章节序号(1, 2, 3...
- title: 章节标题(体现该章核心冲突或情感)
- plot_summary: 剧情摘要(200-300字,详细描述该章发生的事件,仅限当前大纲内容)
- key_events: 关键事件列表(3-5个关键剧情点,必须在当前大纲范围内)
- character_focus: 角色焦点(主要涉及的角色名称)
- emotional_tone: 情感基调(如:紧张、温馨、悲伤、激动等)
- narrative_goal: 叙事目标(该章要达成的叙事效果)
- conflict_type: 冲突类型(如:内心挣扎、人际冲突、环境挑战等)
- estimated_words: 预计字数(建议2000-5000字)
{scene_instruction}
4. 确保章节间:
- 衔接自然流畅
- 剧情递进合理(但不超出当前大纲边界)
- 节奏张弛有度
- 每章都有明确的叙事价值
- 最后一章结束时,剧情发展程度应恰好完成当前大纲描述的内容,不多不少
【输出格式】
请严格按照以下JSON数组格式输出,不要添加任何其他文字:
[
{{
"sub_index": 1,
"title": "章节标题",
"plot_summary": "该章详细剧情摘要...",
"key_events": ["关键事件1", "关键事件2", "关键事件3"],
"character_focus": ["角色A", "角色B"],
"emotional_tone": "情感基调",
"narrative_goal": "叙事目标",
"conflict_type": "冲突类型",
"estimated_words": 3000{scene_field}
}}
]
请开始分析并生成章节规划:
"""
return prompt
def _build_batch_expansion_prompt(
self,
outline: Outline,
project: Project,
characters_info: str,
context_info: str,
target_chapter_count: int,
expansion_strategy: str,
enable_scene_analysis: bool,
start_index: int,
previous_chapters: List[Dict[str, Any]],
total_chapters: int
) -> str:
"""构建分批展开提示词"""
strategy_desc = {
"balanced": "均衡展开:每章剧情量相当,节奏平稳",
"climax": "高潮重点:重点章节剧情丰富,其他章节简洁过渡",
"detail": "细节丰富:每章都深入描写,场景和情感细腻"
}
strategy_instruction = strategy_desc.get(expansion_strategy, strategy_desc["balanced"])
# 场景字段
scene_field = ',\n "main_scenes": ["场景1", "场景2"]' if enable_scene_analysis else ''
scene_instruction = ""
if enable_scene_analysis:
scene_instruction = """
5. 场景分析(每章需包含):
- 主要场景地点
- 场景氛围
- 关键道具/环境元素
"""
# 构建已生成章节的摘要
previous_context = ""
if previous_chapters:
previous_summaries = []
for ch in previous_chapters[-3:]: # 只显示最近3章
previous_summaries.append(
f"{ch['sub_index']}节《{ch['title']}》: {ch['plot_summary'][:100]}..."
)
previous_context = f"""
【已生成章节概要】(接续生成,注意衔接)
{chr(10).join(previous_summaries)}
⚠️ 当前是第{start_index}-{start_index + target_chapter_count - 1}节(共{total_chapters}节中的一部分)
"""
prompt = f"""你是专业的小说情节架构师。请继续分析以下大纲节点,将其展开为第{start_index}-{start_index + target_chapter_count - 1}节(共{target_chapter_count}个章节)的详细规划。
【项目信息】
小说名称:{project.title}
类型:{project.genre or '通用'}
主题:{project.theme or '未设定'}
叙事视角:{project.narrative_perspective or '第三人称'}
【世界观背景】
时间背景:{project.world_time_period or '未设定'}
地理位置:{project.world_location or '未设定'}
氛围基调:{project.world_atmosphere or '未设定'}
【角色信息】
{characters_info or '暂无角色'}
【当前大纲节点 - 展开对象】
序号:第 {outline.order_index}
标题:{outline.title}
内容:{outline.content}
【上下文参考】
{context_info}
{previous_context}
【展开策略】
{strategy_instruction}
【⚠️ 重要约束 - 必须严格遵守】
1. **内容边界约束**
- ✅ 只能展开【当前大纲节点】中明确描述的内容
- ❌ 绝对不能推进到后续大纲的内容(如果有【后一节】信息)
- ❌ 不要让剧情快速推进,要深化而非跨越
2. **分批连续性约束**
- 这是第{start_index}-{start_index + target_chapter_count - 1}节,是整个展开的一部分
- 必须与前面已生成的章节自然衔接
- 从第{start_index}节开始编号(sub_index从{start_index}开始)
- 继续深化当前大纲的内容,保持叙事连贯性
3. **展开原则**
- 将当前大纲的单一事件拆解为多个细节丰富的章节
- 深入挖掘情感、心理、环境、对话等细节
- 放慢叙事节奏,让读者充分体验当前阶段的剧情
- 每个章节都应该是当前大纲内容的不同侧面或阶段
【任务要求】
1. 深度分析该大纲的剧情容量和叙事节奏
2. 识别关键剧情点、冲突点和情感转折点(仅限当前大纲范围内)
3. 生成第{start_index}-{start_index + target_chapter_count - 1}节的章节规划,每章需包含:
- sub_index: 子章节序号(从{start_index}开始)
- title: 章节标题(体现该章核心冲突或情感)
- plot_summary: 剧情摘要(200-300字,详细描述该章发生的事件)
- key_events: 关键事件列表(3-5个关键剧情点)
- character_focus: 角色焦点(主要涉及的角色名称)
- emotional_tone: 情感基调(如:紧张、温馨、悲伤、激动等)
- narrative_goal: 叙事目标(该章要达成的叙事效果)
- conflict_type: 冲突类型(如:内心挣扎、人际冲突、环境挑战等)
- estimated_words: 预计字数(建议2000-5000字)
{scene_instruction}
4. 确保章节间:
- 与前面章节衔接自然流畅
- 剧情递进合理(但不超出当前大纲边界)
- 节奏张弛有度
- 每章都有明确的叙事价值
【输出格式】
请严格按照以下JSON数组格式输出,不要添加任何其他文字:
[
{{
"sub_index": {start_index},
"title": "章节标题",
"plot_summary": "该章详细剧情摘要...",
"key_events": ["关键事件1", "关键事件2", "关键事件3"],
"character_focus": ["角色A", "角色B"],
"emotional_tone": "情感基调",
"narrative_goal": "叙事目标",
"conflict_type": "冲突类型",
"estimated_words": 3000{scene_field}
}}
]
请开始分析并生成第{start_index}-{start_index + target_chapter_count - 1}节的章节规划:
"""
return prompt
def _parse_expansion_response(
self,
+913
View File
@@ -851,7 +851,510 @@ class PromptService:
1. 只返回纯JSON对象不要有```json```这样的标记
2. 所有内容描述中严禁使用任何特殊符号
3. 不要有任何额外的文字说明"""
# 情节分析提示词
PLOT_ANALYSIS = """你是一位专业的小说编辑和剧情分析师。请深度分析以下章节内容:
**章节信息:**
- 章节: {chapter_number}
- 标题: {title}
- 字数: {word_count}
**章节内容:**
{content}
---
**分析任务:**
请从专业编辑的角度,全面分析这一章节:
### 1. 剧情钩子 (Hooks) - 吸引读者的元素
识别能够吸引读者继续阅读的关键元素:
- **悬念钩子**: 未解之谜疑问谜团
- **情感钩子**: 引发共鸣的情感点触动心弦的时刻
- **冲突钩子**: 矛盾对抗紧张局势
- **认知钩子**: 颠覆认知的信息惊人真相
每个钩子需要:
- 类型分类
- 具体内容描述
- 强度评分(1-10)
- 出现位置(开头/中段/结尾)
- **关键词**: 必填从章节原文中逐字复制一段关键文本(8-25)必须是原文中真实存在的连续文字用于在文本中精确定位不要概括或改写必须原样复制
### 2. 伏笔分析 (Foreshadowing)
- **埋下的新伏笔**: 描述内容预期作用隐藏程度(1-10)
- **回收的旧伏笔**: 呼应哪一章回收效果评分
- **伏笔质量**: 巧妙性和合理性评估
- **关键词**: 必填从章节原文中逐字复制一段关键文本(8-25)必须是原文中真实存在的连续文字用于在文本中精确定位不要概括或改写必须原样复制
### 3. 冲突分析 (Conflict)
- 冲突类型: 人与人/人与己/人与环境/人与社会
- 冲突各方及其立场
- 冲突强度评分(1-10)
- 冲突解决进度(0-100%)
### 4. 情感曲线 (Emotional Arc)
- 主导情绪: 紧张/温馨/悲伤/激昂/平静等
- 情感强度(1-10)
- 情绪变化轨迹描述
### 5. 角色状态追踪 (Character Development)
对每个出场角色分析:
- 心理状态变化()
- 关系变化
- 关键行动和决策
- 成长或退步
### 6. 关键情节点 (Plot Points)
列出3-5个核心情节点:
- 情节内容
- 类型(revelation/conflict/resolution/transition)
- 重要性(0.0-1.0)
- 对故事的影响
- **关键词**: 必填从章节原文中逐字复制一段关键文本(8-25)必须是原文中真实存在的连续文字用于在文本中精确定位不要概括或改写必须原样复制
### 7. 场景与节奏
- 主要场景
- 叙事节奏(//)
- 对话与描写的比例
### 8. 质量评分
- 节奏把控: 1-10
- 吸引力: 1-10
- 连贯性: 1-10
- 整体质量: 1-10
### 9. 改进建议
提供3-5条具体的改进建议
---
**输出格式(纯JSON,不要markdown标记):**
{{
"hooks": [
{{
"type": "悬念",
"content": "具体描述",
"strength": 8,
"position": "中段",
"keyword": "必须从原文逐字复制的文本片段"
}}
],
"foreshadows": [
{{
"content": "伏笔内容",
"type": "planted",
"strength": 7,
"subtlety": 8,
"reference_chapter": null,
"keyword": "必须从原文逐字复制的文本片段"
}}
],
"conflict": {{
"types": ["人与人", "人与己"],
"parties": ["主角-复仇", "反派-维护现状"],
"level": 8,
"description": "冲突描述",
"resolution_progress": 0.3
}},
"emotional_arc": {{
"primary_emotion": "紧张",
"intensity": 8,
"curve": "平静→紧张→高潮→释放",
"secondary_emotions": ["期待", "焦虑"]
}},
"character_states": [
{{
"character_name": "张三",
"state_before": "犹豫",
"state_after": "坚定",
"psychological_change": "心理变化描述",
"key_event": "触发事件",
"relationship_changes": {{"李四": "关系改善"}}
}}
],
"plot_points": [
{{
"content": "情节点描述",
"type": "revelation",
"importance": 0.9,
"impact": "推动故事发展",
"keyword": "必须从原文逐字复制的文本片段"
}}
],
"scenes": [
{{
"location": "地点",
"atmosphere": "氛围",
"duration": "时长估计"
}}
],
"pacing": "varied",
"dialogue_ratio": 0.4,
"description_ratio": 0.3,
"scores": {{
"pacing": 8,
"engagement": 9,
"coherence": 8,
"overall": 8.5
}},
"plot_stage": "发展",
"suggestions": [
"具体建议1",
"具体建议2"
]
}}
**重要提示:**
1. 每个钩子伏笔情节点的keyword字段是必填的不能为空
2. keyword必须是从章节原文中逐字复制的文本长度8-25
3. keyword用于在前端标注文本位置所以必须能在原文中精确找到
4. 不要使用概括性语句或改写后的文字作为keyword
只返回JSON,不要其他说明"""
# 大纲单批次展开提示词
PLOT_EXPANSION_SINGLE_BATCH = """你是专业的小说情节架构师。请分析以下大纲节点,将其展开为 {target_chapter_count} 个章节的详细规划。
项目信息
小说名称{project_title}
类型{project_genre}
主题{project_theme}
叙事视角{project_narrative_perspective}
世界观背景
时间背景{project_world_time_period}
地理位置{project_world_location}
氛围基调{project_world_atmosphere}
角色信息
{characters_info}
当前大纲节点 - 展开对象
序号 {outline_order_index}
标题{outline_title}
内容{outline_content}
上下文参考
{context_info}
展开策略
{strategy_instruction}
重要约束 - 必须严格遵守
1. **内容边界约束**
- 只能展开当前大纲节点中明确描述的内容
- 绝对不能推进到后续大纲的内容如果有后一节信息
- 不要让剧情快速推进要深化而非跨越
2. **展开原则**
- 将当前大纲的单一事件拆解为多个细节丰富的章节
- 深入挖掘情感心理环境对话等细节
- 放慢叙事节奏让读者充分体验当前阶段的剧情
- 每个章节都应该是当前大纲内容的不同侧面或阶段
3. **如何避免剧情越界**
- 如果当前大纲描述"主角遇到困境"展开时应详写困境的发现分析情感冲击等
- 不要直接写到"解决困境"除非原大纲明确包含解决过程
- 如果看到后一节的内容那些是禁区绝不提前展开
任务要求
1. 深度分析该大纲的剧情容量和叙事节奏
2. 识别关键剧情点冲突点和情感转折点仅限当前大纲范围内
3. 将大纲拆解为 {target_chapter_count} 个章节每章需包含
- sub_index: 子章节序号1, 2, 3...
- title: 章节标题体现该章核心冲突或情感
- plot_summary: 剧情摘要200-300详细描述该章发生的事件仅限当前大纲内容
- key_events: 关键事件列表3-5个关键剧情点必须在当前大纲范围内
- character_focus: 角色焦点主要涉及的角色名称
- emotional_tone: 情感基调紧张温馨悲伤激动等
- narrative_goal: 叙事目标该章要达成的叙事效果
- conflict_type: 冲突类型内心挣扎人际冲突环境挑战等
- estimated_words: 预计字数建议2000-5000
{scene_instruction}
4. 确保章节间
- 衔接自然流畅
- 剧情递进合理但不超出当前大纲边界
- 节奏张弛有度
- 每章都有明确的叙事价值
- 最后一章结束时剧情发展程度应恰好完成当前大纲描述的内容不多不少
输出格式
请严格按照以下JSON数组格式输出不要添加任何其他文字
[
{{
"sub_index": 1,
"title": "章节标题",
"plot_summary": "该章详细剧情摘要...",
"key_events": ["关键事件1", "关键事件2", "关键事件3"],
"character_focus": ["角色A", "角色B"],
"emotional_tone": "情感基调",
"narrative_goal": "叙事目标",
"conflict_type": "冲突类型",
"estimated_words": 3000{scene_field}
}}
]
请开始分析并生成章节规划
"""
# 大纲分批展开提示词
PLOT_EXPANSION_MULTI_BATCH = """你是专业的小说情节架构师。请继续分析以下大纲节点,将其展开为第{start_index}-{end_index}节(共{target_chapter_count}个章节)的详细规划。
项目信息
小说名称{project_title}
类型{project_genre}
主题{project_theme}
叙事视角{project_narrative_perspective}
世界观背景
时间背景{project_world_time_period}
地理位置{project_world_location}
氛围基调{project_world_atmosphere}
角色信息
{characters_info}
当前大纲节点 - 展开对象
序号 {outline_order_index}
标题{outline_title}
内容{outline_content}
上下文参考
{context_info}
{previous_context}
展开策略
{strategy_instruction}
重要约束 - 必须严格遵守
1. **内容边界约束**
- 只能展开当前大纲节点中明确描述的内容
- 绝对不能推进到后续大纲的内容如果有后一节信息
- 不要让剧情快速推进要深化而非跨越
2. **分批连续性约束**
- 这是第{start_index}-{end_index}是整个展开的一部分
- 必须与前面已生成的章节自然衔接
- 从第{start_index}节开始编号sub_index从{start_index}开始
- 继续深化当前大纲的内容保持叙事连贯性
3. **展开原则**
- 将当前大纲的单一事件拆解为多个细节丰富的章节
- 深入挖掘情感心理环境对话等细节
- 放慢叙事节奏让读者充分体验当前阶段的剧情
- 每个章节都应该是当前大纲内容的不同侧面或阶段
任务要求
1. 深度分析该大纲的剧情容量和叙事节奏
2. 识别关键剧情点冲突点和情感转折点仅限当前大纲范围内
3. 生成第{start_index}-{end_index}节的章节规划每章需包含
- sub_index: 子章节序号{start_index}开始
- title: 章节标题体现该章核心冲突或情感
- plot_summary: 剧情摘要200-300详细描述该章发生的事件
- key_events: 关键事件列表3-5个关键剧情点
- character_focus: 角色焦点主要涉及的角色名称
- emotional_tone: 情感基调紧张温馨悲伤激动等
- narrative_goal: 叙事目标该章要达成的叙事效果
- conflict_type: 冲突类型内心挣扎人际冲突环境挑战等
- estimated_words: 预计字数建议2000-5000
{scene_instruction}
4. 确保章节间
- 与前面章节衔接自然流畅
- 剧情递进合理但不超出当前大纲边界
- 节奏张弛有度
- 每章都有明确的叙事价值
输出格式
请严格按照以下JSON数组格式输出不要添加任何其他文字
[
{{
"sub_index": {start_index},
"title": "章节标题",
"plot_summary": "该章详细剧情摘要...",
"key_events": ["关键事件1", "关键事件2", "关键事件3"],
"character_focus": ["角色A", "角色B"],
"emotional_tone": "情感基调",
"narrative_goal": "叙事目标",
"conflict_type": "冲突类型",
"estimated_words": 3000{scene_field}
}}
]
请开始分析并生成第{start_index}-{end_index}节的章节规划
"""
# 章节重写系统提示词
CHAPTER_REGENERATION_SYSTEM = """你是一位经验丰富的专业小说编辑和作家。现在需要根据反馈意见重新创作一个章节。
你的任务是
1. 仔细理解原始章节的内容和意图
2. 认真分析所有的修改要求
3. 在保持故事连贯性的前提下创作一个改进后的新版本
4. 确保新版本在艺术性和可读性上都有明显提升
---
"""
# MCP工具测试提示词
MCP_TOOL_TEST = """你是MCP插件测试助手,需要测试插件 '{plugin_name}' 的功能。
重要规则生成参数时必须严格使用工具 schema 中定义的原始参数名称不要转换为 snake_case 或其他格式
例如如果 schema 中是 'nextThoughtNeeded'就必须使用 'nextThoughtNeeded'不能改成 'next_thought_needed'
请选择一个合适的工具进行测试优先选择搜索查询类工具
生成真实有效的测试参数例如搜索"人工智能最新进展"而不是"test"
现在开始测试这个插件"""
MCP_TOOL_TEST_SYSTEM = """你是专业的API测试工具。当给定工具列表时,选择一个工具并使用合适的参数调用它。
关键规则调用工具时必须严格使用 schema 中定义的原始参数名不要自行转换命名风格
- 如果参数名是 camelCase nextThoughtNeeded就使用 camelCase
- 如果参数名是 snake_case next_thought就使用 snake_case
- 保持与 schema 中定义的完全一致包括大小写和命名风格"""
# 灵感模式提示词字典
INSPIRATION_PROMPTS = {
"title": {
"system": """你是一位专业的小说创作顾问。
用户的原始想法{initial_idea}
请根据用户的想法生成6个吸引人的书名建议要求
1. 紧扣用户的原始想法和核心故事构思
2. 富有创意和吸引力
3. 涵盖不同的风格倾向
返回JSON格式
{{
"prompt": "根据你的想法,我为你准备了几个书名建议:",
"options": ["书名1", "书名2", "书名3", "书名4", "书名5", "书名6"]
}}
只返回纯JSON不要有其他文字""",
"user": "用户的想法:{initial_idea}\n请生成6个书名建议"
},
"description": {
"system": """你是一位专业的小说创作顾问。
用户的原始想法{initial_idea}
已确定的书名{title}
请生成6个精彩的小说简介要求
1. 必须紧扣用户的原始想法确保简介是原始想法的具体展开
2. 符合已确定的书名风格
3. 简洁有力每个50-100
4. 包含核心冲突
5. 涵盖不同的故事走向但都基于用户的原始构思
返回JSON格式
{{"prompt":"选择一个简介:","options":["简介1","简介2","简介3","简介4","简介5","简介6"]}}
只返回纯JSON不要有其他文字不要换行""",
"user": "原始想法:{initial_idea}\n书名:{title}\n请生成6个简介选项"
},
"theme": {
"system": """你是一位专业的小说创作顾问。
用户的原始想法{initial_idea}
小说信息
- 书名{title}
- 简介{description}
请生成6个深刻的主题选项要求
1. 必须与用户的原始想法保持高度一致
2. 符合书名和简介的风格
3. 有深度和思想性
4. 每个50-150
5. 涵盖不同角度成长复仇救赎探索等但都围绕用户的核心构思
返回JSON格式
{{"prompt":"这本书的核心主题是什么?","options":["主题1","主题2","主题3","主题4","主题5","主题6"]}}
只返回纯JSON不要有其他文字不要换行""",
"user": "原始想法:{initial_idea}\n书名:{title}\n简介:{description}\n请生成6个主题选项"
},
"genre": {
"system": """你是一位专业的小说创作顾问。
用户的原始想法{initial_idea}
小说信息
- 书名{title}
- 简介{description}
- 主题{theme}
请生成6个合适的类型标签每个2-4要求
1. 必须符合用户原始想法中暗示的类型倾向
2. 符合小说整体风格
3. 可以多选组合
常见类型玄幻都市科幻武侠仙侠历史言情悬疑奇幻修仙等
返回JSON格式
{{"prompt":"选择类型标签(可多选):","options":["类型1","类型2","类型3","类型4","类型5","类型6"]}}
只返回紧凑的纯JSON不要换行不要有其他文字""",
"user": "原始想法:{initial_idea}\n书名:{title}\n简介:{description}\n主题:{theme}\n请生成6个类型标签"
}
}
# 灵感模式智能补全提示词
INSPIRATION_QUICK_COMPLETE = """你是一位专业的小说创作顾问。用户提供了部分小说信息,请补全缺失的字段。
用户已提供的信息
{existing}
请生成完整的小说方案包含
1. title: 书名3-6如果用户已提供则保持原样
2. description: 简介50-100必须基于用户提供的信息不要偏离原意
3. theme: 核心主题30-50必须与用户提供的信息保持一致
4. genre: 类型标签数组2-3
重要所有补全的内容都必须与用户提供的信息保持高度关联确保前后一致性
返回JSON格式
{{
"title": "书名",
"description": "简介内容...",
"theme": "主题内容...",
"genre": ["类型1", "类型2"]
}}
只返回纯JSON不要有其他文字"""
# 世界观资料收集提示词(MCP增强用)
MCP_WORLD_BUILDING_PLANNING = """你正在为小说《{title}》设计世界观。
小说信息
- 题材{genre}
- 主题{theme}
- 简介{description}
任务
请使用可用工具搜索相关背景资料帮助构建更真实更有深度的世界观设定
你可以查询
1. 历史背景如果是历史题材
2. 地理环境和文化特征
3. 相关领域的专业知识
4. 类似作品的设定参考
请查询最关键的1个问题不要超过1个"""
# 角色资料收集提示词(MCP增强用)
MCP_CHARACTER_PLANNING = """你正在为小说《{title}》设计角色。
小说信息
- 题材{genre}
- 主题{theme}
- 时代背景{time_period}
- 地理位置{location}
任务
请使用可用工具搜索相关参考资料帮助设计更真实更有深度的角色
你可以查询
1. 该时代/地域的真实历史人物特征
2. 文化背景和社会习俗
3. 职业特点和生活方式
4. 相关领域的人物原型
请查询最关键的1个问题不要超过1个"""
# 大纲展开为多章节的提示词
OUTLINE_EXPANSION = """你是专业的小说情节架构师。请分析以下大纲节点,将其展开为 {target_chapters} 个章节的详细规划。
@@ -1339,7 +1842,417 @@ class PromptService:
scene_instruction=scene_instruction,
scene_field=scene_field
)
@classmethod
def get_plot_analysis_prompt(cls, chapter_number: int, title: str,
content: str, word_count: int) -> str:
"""获取章节剧情分析提示词"""
return cls.format_prompt(
cls.PLOT_ANALYSIS,
chapter_number=chapter_number,
title=title,
content=content,
word_count=word_count
)
@classmethod
def get_plot_expansion_single_batch_prompt(cls, project_title: str, project_genre: str, project_theme: str,
project_narrative_perspective: str, project_world_time_period: str,
project_world_location: str, project_world_atmosphere: str,
characters_info: str, outline_order_index: int, outline_title: str,
outline_content: str, context_info: str, strategy_instruction: str,
target_chapter_count: int, scene_instruction: str, scene_field: str) -> str:
"""获取大纲单批次展开提示词"""
return cls.format_prompt(
cls.PLOT_EXPANSION_SINGLE_BATCH,
project_title=project_title, project_genre=project_genre, project_theme=project_theme,
project_narrative_perspective=project_narrative_perspective, project_world_time_period=project_world_time_period,
project_world_location=project_world_location, project_world_atmosphere=project_world_atmosphere,
characters_info=characters_info, outline_order_index=outline_order_index, outline_title=outline_title,
outline_content=outline_content, context_info=context_info, strategy_instruction=strategy_instruction,
target_chapter_count=target_chapter_count, scene_instruction=scene_instruction, scene_field=scene_field
)
@classmethod
def get_plot_expansion_multi_batch_prompt(cls, project_title: str, project_genre: str, project_theme: str,
project_narrative_perspective: str, project_world_time_period: str,
project_world_location: str, project_world_atmosphere: str,
characters_info: str, outline_order_index: int, outline_title: str,
outline_content: str, context_info: str, previous_context: str,
strategy_instruction: str, start_index: int, end_index: int,
target_chapter_count: int, scene_instruction: str, scene_field: str) -> str:
"""获取大纲分批展开提示词"""
return cls.format_prompt(
cls.PLOT_EXPANSION_MULTI_BATCH,
project_title=project_title, project_genre=project_genre, project_theme=project_theme,
project_narrative_perspective=project_narrative_perspective, project_world_time_period=project_world_time_period,
project_world_location=project_world_location, project_world_atmosphere=project_world_atmosphere,
characters_info=characters_info, outline_order_index=outline_order_index, outline_title=outline_title,
outline_content=outline_content, context_info=context_info, previous_context=previous_context,
strategy_instruction=strategy_instruction, start_index=start_index, end_index=end_index,
target_chapter_count=target_chapter_count, scene_instruction=scene_instruction, scene_field=scene_field
)
@classmethod
def get_chapter_regeneration_prompt(cls, chapter_number: int, title: str, word_count: int, content: str,
modification_instructions: str, project_context: Dict[str, Any],
style_content: str, target_word_count: int) -> str:
"""获取章节重写提示词"""
prompt_parts = [cls.CHAPTER_REGENERATION_SYSTEM]
# 原始章节信息
prompt_parts.append(f"""## 📖 原始章节信息
**章节**{chapter_number}
**标题**{title}
**字数**{word_count}
**原始内容**
{content}
---
""")
# 修改指令
prompt_parts.append(modification_instructions)
prompt_parts.append("\n---\n")
# 项目背景信息
prompt_parts.append(f"""## 🌍 项目背景信息
**小说标题**{project_context.get('project_title', '未知')}
**题材**{project_context.get('genre', '未设定')}
**主题**{project_context.get('theme', '未设定')}
**叙事视角**{project_context.get('narrative_perspective', '第三人称')}
**世界观设定**
- 时代背景{project_context.get('time_period', '未设定')}
- 地理位置{project_context.get('location', '未设定')}
- 氛围基调{project_context.get('atmosphere', '未设定')}
---
""")
# 角色信息
if project_context.get('characters_info'):
prompt_parts.append(f"""## 👥 角色信息
{project_context['characters_info']}
---
""")
# 章节大纲
if project_context.get('chapter_outline'):
prompt_parts.append(f"""## 📝 本章大纲
{project_context['chapter_outline']}
---
""")
# 前置章节上下文
if project_context.get('previous_context'):
prompt_parts.append(f"""## 📚 前置章节上下文
{project_context['previous_context']}
---
""")
# 写作风格要求
if style_content:
prompt_parts.append(f"""## 🎨 写作风格要求
{style_content}
请在重新创作时严格遵循上述写作风格
---
""")
# 创作要求
prompt_parts.append(f"""## ✨ 创作要求
1. **解决问题**针对上述修改指令中提到的所有问题进行改进
2. **保持连贯**确保与前后章节的情节人物风格保持一致
3. **提升质量**在节奏情感描写等方面明显优于原版
4. **保留精华**保持原章节中优秀的部分和关键情节
5. **字数控制**目标字数约{target_word_count}可适当浮动±20%
{f'6. **风格一致**:严格按照上述写作风格进行创作' if style_content else ''}
---
## 🎬 开始创作
请现在开始创作改进后的新版本章节内容
**重要提示**
- 直接输出章节正文内容从故事内容开始写
- **不要**输出章节标题"第X章""第X章:XXX"
- **不要**输出任何额外的说明注释或元数据
- 只需要纯粹的故事正文内容
现在开始
""")
return "\n".join(prompt_parts)
@classmethod
def get_inspiration_prompt(cls, step: str) -> Optional[Dict[str, str]]:
"""获取灵感模式指定步骤的提示词"""
return cls.INSPIRATION_PROMPTS.get(step)
@classmethod
def get_inspiration_quick_complete_prompt(cls, existing: str) -> Dict[str, str]:
"""获取灵感模式智能补全的提示词"""
return {
"system": cls.format_prompt(cls.INSPIRATION_QUICK_COMPLETE, existing=existing),
"user": "请补全小说信息"
}
@classmethod
def get_mcp_tool_test_prompts(cls, plugin_name: str) -> Dict[str, str]:
"""获取MCP工具测试的提示词"""
return {
"user": cls.format_prompt(cls.MCP_TOOL_TEST, plugin_name=plugin_name),
"system": cls.MCP_TOOL_TEST_SYSTEM
}
# 创建全局提示词服务实例
# ========== 自定义提示词支持 ==========
@classmethod
async def get_template_with_fallback(cls,
template_key: str,
user_id: str = None,
db = None) -> str:
"""
获取提示词模板优先用户自定义支持降级
Args:
template_key: 模板键名
user_id: 用户ID可选如果不提供则直接返回系统默认
db: 数据库会话可选
Returns:
提示词模板内容
"""
# 如果没有提供user_id或db,直接返回系统默认
if not user_id or not db:
return getattr(cls, template_key, None)
# 尝试获取用户自定义模板
return await cls.get_template(template_key, user_id, db)
@classmethod
async def get_template(cls,
template_key: str,
user_id: str,
db) -> str:
"""
获取提示词模板优先用户自定义
Args:
template_key: 模板键名
user_id: 用户ID
db: 数据库会话
Returns:
提示词模板内容
"""
from sqlalchemy import select
from app.models.prompt_template import PromptTemplate
from app.logger import get_logger
logger = get_logger(__name__)
# 1. 尝试从数据库获取用户自定义模板
result = await db.execute(
select(PromptTemplate).where(
PromptTemplate.user_id == user_id,
PromptTemplate.template_key == template_key,
PromptTemplate.is_active == True
)
)
custom_template = result.scalar_one_or_none()
if custom_template:
logger.info(f"✅ 使用用户自定义提示词: user_id={user_id}, template_key={template_key}, template_name={custom_template.template_name}")
return custom_template.template_content
# 2. 降级到系统默认模板
logger.info(f"⚪ 使用系统默认提示词: user_id={user_id}, template_key={template_key} (未找到自定义模板)")
return getattr(cls, template_key, None)
@classmethod
def get_all_system_templates(cls) -> list:
"""
获取所有系统默认模板的信息
Returns:
系统模板列表
"""
templates = []
# 定义所有模板及其元信息
template_definitions = {
"WORLD_BUILDING": {
"name": "世界构建",
"category": "世界构建",
"description": "用于生成小说世界观设定,包括时间背景、地理位置、氛围基调和世界规则",
"parameters": ["title", "theme", "genre"]
},
"CHARACTERS_BATCH_GENERATION": {
"name": "批量角色生成",
"category": "角色生成",
"description": "批量生成多个角色和组织,建立角色关系网络",
"parameters": ["count", "time_period", "location", "atmosphere", "rules", "theme", "genre", "requirements"]
},
"SINGLE_CHARACTER_GENERATION": {
"name": "单个角色生成",
"category": "角色生成",
"description": "生成单个角色的详细设定",
"parameters": ["project_context", "user_input"]
},
"SINGLE_ORGANIZATION_GENERATION": {
"name": "组织生成",
"category": "角色生成",
"description": "生成组织/势力的详细设定",
"parameters": ["project_context", "user_input"]
},
"COMPLETE_OUTLINE_GENERATION": {
"name": "完整大纲生成",
"category": "大纲生成",
"description": "根据项目信息生成完整的章节大纲",
"parameters": ["title", "theme", "genre", "chapter_count", "narrative_perspective", "target_words",
"time_period", "location", "atmosphere", "rules", "characters_info", "requirements", "mcp_references"]
},
"OUTLINE_CONTINUE_GENERATION": {
"name": "大纲续写",
"category": "大纲生成",
"description": "基于已有章节续写大纲",
"parameters": ["title", "theme", "genre", "narrative_perspective", "chapter_count", "time_period",
"location", "atmosphere", "rules", "characters_info", "current_chapter_count",
"all_chapters_brief", "recent_plot", "memory_context", "mcp_references",
"plot_stage_instruction", "start_chapter", "end_chapter", "story_direction", "requirements"]
},
"OUTLINE_GENERATION": {
"name": "基础大纲生成",
"category": "大纲生成",
"description": "生成基础章节大纲框架",
"parameters": ["genre", "theme", "target_words", "requirements"]
},
"OUTLINE_EXPANSION": {
"name": "大纲展开",
"category": "大纲生成",
"description": "将单个大纲节点展开为多个章节",
"parameters": ["title", "genre", "theme", "narrative_perspective", "time_period", "location",
"atmosphere", "rules", "characters_info", "outline_order", "outline_title",
"outline_content", "context_info", "strategy_instruction", "target_chapters",
"scene_instruction", "scene_field"]
},
"CHAPTER_GENERATION": {
"name": "章节创作",
"category": "章节创作",
"description": "根据大纲创作章节内容",
"parameters": ["title", "theme", "genre", "narrative_perspective", "time_period", "location",
"atmosphere", "rules", "characters_info", "outlines_context", "chapter_number",
"chapter_title", "chapter_outline", "target_word_count", "max_word_count"]
},
"CHAPTER_GENERATION_WITH_CONTEXT": {
"name": "章节创作(带上下文)",
"category": "章节创作",
"description": "基于前置章节内容创作新章节",
"parameters": ["title", "theme", "genre", "narrative_perspective", "time_period", "location",
"atmosphere", "rules", "characters_info", "outlines_context", "previous_content",
"memory_context", "chapter_number", "chapter_title", "chapter_outline",
"target_word_count", "max_word_count"]
},
"CHAPTER_REGENERATION_SYSTEM": {
"name": "章节重写系统提示",
"category": "章节重写",
"description": "用于章节重写的系统提示词",
"parameters": ["chapter_number", "title", "word_count", "content", "modification_instructions",
"project_context", "style_content", "target_word_count"]
},
"AI_DENOISING": {
"name": "AI去味",
"category": "辅助功能",
"description": "将AI生成的文本改写得更自然",
"parameters": ["original_text"]
},
"PLOT_ANALYSIS": {
"name": "情节分析",
"category": "情节分析",
"description": "深度分析章节的剧情、钩子、伏笔等",
"parameters": ["chapter_number", "title", "content", "word_count"]
},
"PLOT_EXPANSION_SINGLE_BATCH": {
"name": "大纲单批次展开",
"category": "情节展开",
"description": "将大纲节点展开为详细章节规划(单批次)",
"parameters": ["project_title", "project_genre", "project_theme", "project_narrative_perspective",
"project_world_time_period", "project_world_location", "project_world_atmosphere",
"characters_info", "outline_order_index", "outline_title", "outline_content",
"context_info", "strategy_instruction", "target_chapter_count", "scene_instruction", "scene_field"]
},
"PLOT_EXPANSION_MULTI_BATCH": {
"name": "大纲分批展开",
"category": "情节展开",
"description": "将大纲节点展开为详细章节规划(分批)",
"parameters": ["project_title", "project_genre", "project_theme", "project_narrative_perspective",
"project_world_time_period", "project_world_location", "project_world_atmosphere",
"characters_info", "outline_order_index", "outline_title", "outline_content",
"context_info", "previous_context", "strategy_instruction", "start_index",
"end_index", "target_chapter_count", "scene_instruction", "scene_field"]
},
"MCP_TOOL_TEST": {
"name": "MCP工具测试",
"category": "MCP测试",
"description": "用于测试MCP插件功能",
"parameters": ["plugin_name"]
},
"MCP_WORLD_BUILDING_PLANNING": {
"name": "MCP世界观规划",
"category": "MCP增强",
"description": "使用MCP工具搜索资料辅助世界观设计",
"parameters": ["title", "genre", "theme", "description"]
},
"MCP_CHARACTER_PLANNING": {
"name": "MCP角色规划",
"category": "MCP增强",
"description": "使用MCP工具搜索资料辅助角色设计",
"parameters": ["title", "genre", "theme", "time_period", "location"]
}
}
for key, info in template_definitions.items():
template_content = getattr(cls, key, None)
if template_content:
templates.append({
"template_key": key,
"template_name": info["name"],
"category": info["category"],
"description": info["description"],
"parameters": info["parameters"],
"content": template_content
})
return templates
@classmethod
def get_system_template_info(cls, template_key: str) -> dict:
"""
获取指定系统模板的信息
Args:
template_key: 模板键名
Returns:
模板信息字典
"""
all_templates = cls.get_all_system_templates()
for template in all_templates:
if template["template_key"] == template_key:
return template
return None
prompt_service = PromptService()
@@ -0,0 +1,34 @@
-- 创建提示词模板表
CREATE TABLE IF NOT EXISTS prompt_templates (
id VARCHAR(36) PRIMARY KEY,
user_id VARCHAR(50) NOT NULL,
template_key VARCHAR(100) NOT NULL,
template_name VARCHAR(200) NOT NULL,
template_content TEXT NOT NULL,
description TEXT,
category VARCHAR(50),
parameters TEXT,
is_active BOOLEAN DEFAULT TRUE,
is_system_default BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uk_user_template UNIQUE (user_id, template_key)
);
-- 创建索引
CREATE INDEX IF NOT EXISTS idx_user_template ON prompt_templates(user_id, template_key);
CREATE INDEX IF NOT EXISTS idx_user_id ON prompt_templates(user_id);
CREATE INDEX IF NOT EXISTS idx_category ON prompt_templates(category);
-- 添加注释
COMMENT ON TABLE prompt_templates IS '提示词模板表';
COMMENT ON COLUMN prompt_templates.user_id IS '用户ID';
COMMENT ON COLUMN prompt_templates.template_key IS '模板键名';
COMMENT ON COLUMN prompt_templates.template_name IS '模板显示名称';
COMMENT ON COLUMN prompt_templates.template_content IS '模板内容';
COMMENT ON COLUMN prompt_templates.description IS '模板描述';
COMMENT ON COLUMN prompt_templates.category IS '模板分类';
COMMENT ON COLUMN prompt_templates.parameters IS '模板参数定义(JSON)';
COMMENT ON COLUMN prompt_templates.is_active IS '是否启用';
COMMENT ON COLUMN prompt_templates.is_system_default IS '是否为系统默认模板';