From 0ffa0ec4b5fe8662585b55993c22815ea5849b25 Mon Sep 17 00:00:00 2001 From: xiamuceer Date: Thu, 1 Jan 2026 17:30:46 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E7=AB=A0?= =?UTF-8?q?=E8=8A=82=E4=B8=8A=E4=B8=8B=E6=96=87=E6=9E=84=E5=BB=BA=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=EF=BC=8C=E4=BD=BF=E7=94=A8=E6=96=B0=E7=9A=84ChapterCo?= =?UTF-8?q?ntextBuilder=E4=BC=98=E5=8C=96=E4=B8=8A=E4=B8=8B=E6=96=87?= =?UTF-8?q?=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/api/chapters.py | 287 +-- .../app/services/chapter_context_service.py | 745 ++++++ backend/app/services/prompt_service.py | 1993 +++++++++-------- 3 files changed, 1888 insertions(+), 1137 deletions(-) create mode 100644 backend/app/services/chapter_context_service.py diff --git a/backend/app/api/chapters.py b/backend/app/api/chapters.py index edbda2c..7e52559 100644 --- a/backend/app/api/chapters.py +++ b/backend/app/api/chapters.py @@ -10,6 +10,7 @@ from datetime import datetime from asyncio import Queue, Lock from app.database import get_db +from app.services.chapter_context_service import ChapterContextBuilder, FocusedMemoryRetriever from app.models.chapter import Chapter from app.models.project import Project from app.models.outline import Outline @@ -1314,86 +1315,23 @@ async def generate_chapter_content_stream( else: logger.info("未指定写作风格,使用原始提示词") - # 🚀 使用智能上下文构建(支持海量章节) - smart_context = await build_smart_chapter_context( - db=db_session, - project_id=project.id, - current_chapter_number=current_chapter.chapter_number, - user_id=current_user_id + # 🚀 使用新的优化上下文构建器 + logger.info(f"🔧 使用优化的章节上下文构建器(V2)") + context_builder = ChapterContextBuilder() + chapter_context = await context_builder.build( + chapter=current_chapter, + project=project, + outline=outline, + user_id=current_user_id, + db=db_session ) - # 组装上下文 - previous_content = "" - if smart_context['story_skeleton']: - previous_content += smart_context['story_skeleton'] + "\n\n" - if smart_context['relevant_history']: - previous_content += smart_context['relevant_history'] + "\n\n" - if smart_context['recent_summary']: - previous_content += smart_context['recent_summary'] + "\n\n" - if smart_context['recent_full']: - previous_content += smart_context['recent_full'] - - # 🔧 修复1-n模式重复问题: 提取上一章结尾作为精确衔接点 - if current_chapter.chapter_number > 1: - recent_chapters_parts = smart_context['recent_full'].split('===') - if len(recent_chapters_parts) >= 2: - # 提取最后一章(recent_full包含最近3章,最后一个是上一章) - last_chapter_content = recent_chapters_parts[-1].strip() - # 提取结尾500字 - last_chapter_ending = last_chapter_content[-600:] if len(last_chapter_content) > 600 else last_chapter_content - - previous_content += f"\n\n{'='*50}\n" - previous_content += f"【⚠️ 上一章结尾内容(必读,用于衔接)】\n" - previous_content += f"以下是上一章(第{current_chapter.chapter_number-1}章)的结尾部分:\n\n" - previous_content += last_chapter_ending + "\n" - previous_content += f"\n{'='*50}\n" - previous_content += f"【本章({current_chapter.chapter_number}章)创作要求】\n" - previous_content += f"1. 必须自然承接上述结尾的场景/情节/对话\n" - previous_content += f"2. 不要重复叙述上一章已经发生的事件\n" - previous_content += f"3. 从新的情节点、新的场景或新的时间点开始\n" - previous_content += f"4. 角色状态要延续,不要重新介绍已出场角色\n" - previous_content += f"{'='*50}\n" - # 日志输出统计信息 - stats = smart_context['stats'] - logger.info(f"📊 智能上下文统计:") - logger.info(f" - 前置章节总数: {stats.get('total_previous', 0)}") - logger.info(f" - 故事骨架采样: {stats.get('skeleton_samples', 0)}章") - logger.info(f" - 相关历史检索: {stats.get('relevant_history', 0)}章") - logger.info(f" - 近期章节概要: {stats.get('recent_summaries', 0)}章") - logger.info(f" - 最近完整内容: {stats.get('recent_full', 0)}章") - logger.info(f" - 上下文总长度: {stats.get('total_length', 0)}字符") - - # 🧠 构建记忆增强上下文 - logger.info(f"🧠 开始构建记忆增强上下文...") - memory_context = await memory_service.build_context_for_generation( - user_id=current_user_id, - project_id=project.id, - current_chapter=current_chapter.chapter_number, - chapter_outline=outline.content if outline else current_chapter.summary or "", - character_names=[c.name for c in characters] if characters else None - ) - - # 计算各部分的字符长度 - context_lengths = { - 'recent_context': len(memory_context.get('recent_context', '')), - 'relevant_memories': len(memory_context.get('relevant_memories', '')), - 'foreshadows': len(memory_context.get('foreshadows', '')), - 'character_states': len(memory_context.get('character_states', '')), - 'plot_points': len(memory_context.get('plot_points', '')) - } - total_memory_length = sum(context_lengths.values()) - - logger.info(f"✅ 记忆上下文构建完成: {memory_context['stats']}") - logger.info(f"📏 记忆上下文长度统计:") - logger.info(f" - 最近章节记忆: {context_lengths['recent_context']} 字符") - logger.info(f" - 语义相关记忆: {context_lengths['relevant_memories']} 字符") - logger.info(f" - 未完结伏笔: {context_lengths['foreshadows']} 字符") - logger.info(f" - 角色状态记忆: {context_lengths['character_states']} 字符") - logger.info(f" - 重要情节点: {context_lengths['plot_points']} 字符") - logger.info(f" - 记忆总长度: {total_memory_length} 字符") - logger.info(f" - 前置章节上下文长度: {len(previous_content)} 字符") - logger.info(f" - 总上下文长度(估算): {total_memory_length + len(previous_content) + 2000} 字符") + logger.info(f"📊 优化上下文统计:") + logger.info(f" - 章节序号: {current_chapter.chapter_number}") + logger.info(f" - 衔接锚点长度: {len(chapter_context.continuation_point or '')} 字符") + logger.info(f" - 相关记忆: {chapter_context.context_stats.get('memory_count', 0)} 条") + logger.info(f" - 总上下文长度: {chapter_context.context_stats.get('total_length', 0)} 字符") # 发送开始事件 yield f"data: {json.dumps({'type': 'start', 'message': '开始AI创作...'}, ensure_ascii=False)}\n\n" @@ -1520,74 +1458,55 @@ async def generate_chapter_content_stream( chapter_outline_content = outline.content if outline else current_chapter.summary or '暂无大纲' logger.warning(f"⚠️ 一对多模式但无expansion_plan,使用大纲内容") - # 根据是否有前置内容选择不同的提示词,并应用写作风格、记忆增强和MCP参考资料(支持自定义) - if previous_content: - template = await PromptService.get_template("CHAPTER_GENERATION_WITH_CONTEXT", current_user_id, db_session) + # 🚀 使用 V2 优化模板构建提示词 + if chapter_context.continuation_point: + # 有前置内容,使用 WITH_CONTEXT 模板 + template = await PromptService.get_template("CHAPTER_GENERATION_V2_WITH_CONTEXT", current_user_id, db_session) base_prompt = PromptService.format_prompt( template, - title=project.title, - theme=project.theme or '', - genre=project.genre or '', - narrative_perspective=chapter_perspective, - time_period=project.world_time_period or '未设定', - location=project.world_location or '未设定', - atmosphere=project.world_atmosphere or '未设定', - rules=project.world_rules or '未设定', - characters_info=characters_info or '暂无角色信息', - outlines_context=outlines_context, - previous_content=previous_content, + # P0 核心参数 + project_title=project.title, chapter_number=current_chapter.chapter_number, chapter_title=current_chapter.title, chapter_outline=chapter_outline_content, target_word_count=target_word_count, - 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 "暂无相关记忆" + continuation_point=chapter_context.continuation_point, + # P1 重要参数 + genre=project.genre or '未设定', + narrative_perspective=chapter_perspective, + characters_info=characters_info or '暂无角色信息', + # P2 参考参数(动态裁剪后的) + story_skeleton=chapter_context.story_skeleton or '', + relevant_memories=chapter_context.relevant_memories or '' ) - # 插入模式说明和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: - template = await PromptService.get_template("CHAPTER_GENERATION", current_user_id, db_session) + # 第一章,使用无前置内容模板 + template = await PromptService.get_template("CHAPTER_GENERATION_V2", current_user_id, db_session) base_prompt = PromptService.format_prompt( template, - title=project.title, - theme=project.theme or '', - genre=project.genre or '', - narrative_perspective=chapter_perspective, - time_period=project.world_time_period or '未设定', - location=project.world_location or '未设定', - atmosphere=project.world_atmosphere or '未设定', - rules=project.world_rules or '未设定', - characters_info=characters_info or '暂无角色信息', - outlines_context=outlines_context, + # P0 核心参数 + project_title=project.title, chapter_number=current_chapter.chapter_number, chapter_title=current_chapter.title, chapter_outline=chapter_outline_content, target_word_count=target_word_count, - max_word_count=target_word_count + 1000 + # P1 重要参数 + genre=project.genre or '未设定', + narrative_perspective=chapter_perspective, + characters_info=characters_info or '暂无角色信息' ) - # 插入模式说明和记忆、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 + + # 添加 MCP 参考资料(如果有) + if mcp_reference_materials: + mcp_section = f"\n\n\n{mcp_reference_materials}\n" + base_prompt = base_prompt.replace("", f"{mcp_section}\n") + logger.info(f"📖 已整合MCP参考资料({len(mcp_reference_materials)}字符)") + + # 应用写作风格 + 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)}字符)到章节生成提示词") @@ -2774,34 +2693,24 @@ async def generate_single_chapter_for_batch( if style.user_id is None or style.user_id == user_id: style_content = style.prompt_content or "" - # 构建智能上下文 - smart_context = await build_smart_chapter_context( - db=db_session, - project_id=project.id, - current_chapter_number=chapter.chapter_number, - user_id=user_id - ) - - # 组装上下文 - previous_content = "" - if smart_context['story_skeleton']: - previous_content += smart_context['story_skeleton'] + "\n\n" - if smart_context['relevant_history']: - previous_content += smart_context['relevant_history'] + "\n\n" - if smart_context['recent_summary']: - previous_content += smart_context['recent_summary'] + "\n\n" - if smart_context['recent_full']: - previous_content += smart_context['recent_full'] - - # 构建记忆增强上下文 - memory_context = await memory_service.build_context_for_generation( + # 🚀 使用新的优化上下文构建器 + logger.info(f"🔧 批量生成 - 使用优化的章节上下文构建器(V2)") + context_builder = ChapterContextBuilder() + chapter_context = await context_builder.build( + chapter=chapter, + project=project, + outline=outline, user_id=user_id, - project_id=project.id, - current_chapter=chapter.chapter_number, - chapter_outline=outline.content if outline else chapter.summary or "", - character_names=[c.name for c in characters] if characters else None + db=db_session ) + # 日志输出统计信息 + logger.info(f"📊 批量生成 - 优化上下文统计:") + logger.info(f" - 章节序号: {chapter.chapter_number}") + logger.info(f" - 衔接锚点长度: {len(chapter_context.continuation_point or '')} 字符") + logger.info(f" - 相关记忆: {chapter_context.context_stats.get('memory_count', 0)} 条") + logger.info(f" - 总上下文长度: {chapter_context.context_stats.get('total_length', 0)} 字符") + # 📋 根据大纲模式构建差异化的章节大纲上下文 chapter_outline_content = "" if outline_mode == 'one-to-one': @@ -2844,61 +2753,49 @@ 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: - # 获取自定义提示词模板 - template = await PromptService.get_template("CHAPTER_GENERATION_WITH_CONTEXT", user_id, db_session) + # 🚀 使用 V2 优化模板构建提示词(批量生成) + if chapter_context.continuation_point: + # 有前置内容,使用 WITH_CONTEXT 模板 + template = await PromptService.get_template("CHAPTER_GENERATION_V2_WITH_CONTEXT", user_id, db_session) base_prompt = PromptService.format_prompt( template, - title=project.title, - theme=project.theme or '', - genre=project.genre or '', - narrative_perspective=project.narrative_perspective or '第三人称', - time_period=project.world_time_period or '未设定', - location=project.world_location or '未设定', - atmosphere=project.world_atmosphere or '未设定', - rules=project.world_rules or '未设定', - characters_info=characters_info or '暂无角色信息', - outlines_context=outlines_context, - previous_content=previous_content, + # P0 核心参数 + project_title=project.title, chapter_number=chapter.chapter_number, chapter_title=chapter.title, chapter_outline=chapter_outline_content, target_word_count=target_word_count, - 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 "暂无相关记忆" + continuation_point=chapter_context.continuation_point, + # P1 重要参数 + genre=project.genre or '未设定', + narrative_perspective=project.narrative_perspective or '第三人称', + characters_info=characters_info or '暂无角色信息', + # P2 参考参数(动态裁剪后的) + story_skeleton=chapter_context.story_skeleton or '', + relevant_memories=chapter_context.relevant_memories or '' ) - # 应用写作风格 - if style_content: - prompt = WritingStyleManager.apply_style_to_prompt(base_prompt, style_content) - else: - prompt = base_prompt else: - # 获取自定义提示词模板 - template = await PromptService.get_template("CHAPTER_GENERATION", user_id, db_session) + # 第一章,使用无前置内容模板 + template = await PromptService.get_template("CHAPTER_GENERATION_V2", user_id, db_session) base_prompt = PromptService.format_prompt( template, - title=project.title, - theme=project.theme or '', - genre=project.genre or '', - narrative_perspective=project.narrative_perspective or '第三人称', - time_period=project.world_time_period or '未设定', - location=project.world_location or '未设定', - atmosphere=project.world_atmosphere or '未设定', - rules=project.world_rules or '未设定', - characters_info=characters_info or '暂无角色信息', - outlines_context=outlines_context, + # P0 核心参数 + project_title=project.title, chapter_number=chapter.chapter_number, chapter_title=chapter.title, chapter_outline=chapter_outline_content, target_word_count=target_word_count, - max_word_count=target_word_count + 1000 + # P1 重要参数 + genre=project.genre or '未设定', + narrative_perspective=project.narrative_perspective or '第三人称', + characters_info=characters_info or '暂无角色信息' ) - # 应用写作风格 - if style_content: - prompt = WritingStyleManager.apply_style_to_prompt(base_prompt, style_content) - else: - prompt = base_prompt + + # 应用写作风格 + if style_content: + prompt = WritingStyleManager.apply_style_to_prompt(base_prompt, style_content) + else: + prompt = base_prompt # 🎨 方案一:将写作风格注入到系统提示词(批量生成) system_prompt_with_style = None diff --git a/backend/app/services/chapter_context_service.py b/backend/app/services/chapter_context_service.py new file mode 100644 index 0000000..805bde0 --- /dev/null +++ b/backend/app/services/chapter_context_service.py @@ -0,0 +1,745 @@ +"""章节上下文构建服务 - 实现RTCO框架的智能上下文构建""" + +from dataclasses import dataclass, field +from typing import Dict, Any, Optional, List +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select +import json + +from app.models.chapter import Chapter +from app.models.project import Project +from app.models.outline import Outline +from app.models.character import Character +from app.models.career import Career, CharacterCareer +from app.models.memory import StoryMemory +from app.logger import get_logger + +logger = get_logger(__name__) + + +@dataclass +class ChapterContext: + """ + 章节上下文数据结构 + + 采用RTCO框架的分层设计: + - P0-核心(必须):大纲、衔接点、字数要求 + - P1-重要(按需):角色、情感基调、风格 + - P2-参考(条件触发):记忆、故事骨架、MCP资料 + """ + + # === P0-核心信息(必须包含)=== + chapter_outline: str = "" # 本章大纲 + continuation_point: Optional[str] = None # 衔接锚点(上一章结尾) + target_word_count: int = 3000 # 目标字数 + min_word_count: int = 2500 # 最小字数 + max_word_count: int = 4000 # 最大字数 + narrative_perspective: str = "第三人称" # 叙事视角 + + # === 本章基本信息 === + chapter_number: int = 1 # 章节序号 + chapter_title: str = "" # 章节标题 + + # === 项目基本信息 === + title: str = "" # 书名 + genre: str = "" # 类型 + theme: str = "" # 主题 + + # === P1-重要信息(按需包含)=== + chapter_characters: str = "" # 本章涉及角色(精简) + emotional_tone: str = "" # 情感基调 + style_instruction: str = "" # 写作风格指令(摘要化) + + # === P2-参考信息(条件触发)=== + relevant_memories: Optional[str] = None # 相关记忆(精简版) + story_skeleton: Optional[str] = None # 故事骨架(50章+启用) + mcp_references: Optional[str] = None # MCP参考资料 + + # === 元信息 === + context_stats: Dict[str, Any] = field(default_factory=dict) # 统计信息 + + def get_total_context_length(self) -> int: + """计算总上下文长度""" + total = 0 + for field_name in ['chapter_outline', 'continuation_point', 'chapter_characters', + 'relevant_memories', 'story_skeleton', 'style_instruction']: + value = getattr(self, field_name, None) + if value: + total += len(value) + return total + + +class ChapterContextBuilder: + """ + 章节上下文构建器 + + 实现动态裁剪逻辑,根据章节序号自动调整上下文复杂度: + - 第1章:无前置上下文,仅提供大纲和角色 + - 第2-10章:上一章结尾300字 + 涉及角色 + - 第11-50章:上一章结尾500字 + 相关记忆3条 + - 第51章+:上一章结尾500字 + 故事骨架 + 智能记忆5条 + """ + + # 配置常量 + ENDING_LENGTH_SHORT = 300 # 1-10章:短衔接 + ENDING_LENGTH_NORMAL = 500 # 11章+:标准衔接 + MEMORY_COUNT_LIGHT = 3 # 11-50章:轻量记忆 + MEMORY_COUNT_FULL = 5 # 51章+:完整记忆 + SKELETON_THRESHOLD = 50 # 启用故事骨架的章节阈值 + SKELETON_SAMPLE_INTERVAL = 10 # 故事骨架采样间隔 + MEMORY_IMPORTANCE_THRESHOLD = 0.7 # 记忆重要性阈值 + STYLE_MAX_LENGTH = 200 # 风格描述最大长度 + MAX_CONTEXT_LENGTH = 3000 # 总上下文最大字符数 + + def __init__(self, memory_service=None): + """ + 初始化构建器 + + Args: + memory_service: 记忆服务实例(可选,用于检索相关记忆) + """ + self.memory_service = memory_service + + async def build( + self, + chapter: Chapter, + project: Project, + outline: Optional[Outline], + user_id: str, + db: AsyncSession, + style_content: Optional[str] = None, + target_word_count: int = 3000, + temp_narrative_perspective: Optional[str] = None + ) -> ChapterContext: + """ + 构建章节生成所需的上下文 + + Args: + chapter: 章节对象 + project: 项目对象 + outline: 大纲对象(可选) + user_id: 用户ID + db: 数据库会话 + style_content: 写作风格内容(可选) + target_word_count: 目标字数 + temp_narrative_perspective: 临时叙事视角(可选,覆盖项目默认) + + Returns: + ChapterContext: 结构化的上下文对象 + """ + chapter_number = chapter.chapter_number + logger.info(f"📝 开始构建章节上下文: 第{chapter_number}章") + + # 确定叙事视角 + narrative_perspective = ( + temp_narrative_perspective or + project.narrative_perspective or + "第三人称" + ) + + # 初始化上下文 + context = ChapterContext( + chapter_number=chapter_number, + chapter_title=chapter.title or "", + title=project.title or "", + genre=project.genre or "", + theme=project.theme or "", + target_word_count=target_word_count, + min_word_count=max(500, target_word_count - 500), + max_word_count=target_word_count + 1000, + narrative_perspective=narrative_perspective + ) + + # === P0-核心信息(始终构建)=== + context.chapter_outline = await self._build_chapter_outline( + chapter, outline, project.outline_mode + ) + + # === 衔接锚点(根据章节调整长度)=== + if chapter_number == 1: + context.continuation_point = None + logger.info(" ✅ 第1章无需衔接锚点") + elif chapter_number <= 10: + context.continuation_point = await self._get_last_ending( + chapter, db, self.ENDING_LENGTH_SHORT + ) + logger.info(f" ✅ 衔接锚点(短): {len(context.continuation_point or '')}字符") + else: + context.continuation_point = await self._get_last_ending( + chapter, db, self.ENDING_LENGTH_NORMAL + ) + logger.info(f" ✅ 衔接锚点(标准): {len(context.continuation_point or '')}字符") + + # === P1-重要信息 === + context.chapter_characters = await self._build_chapter_characters( + chapter, project, outline, db + ) + context.emotional_tone = self._extract_emotional_tone(chapter, outline) + + # 写作风格(摘要化) + if style_content: + context.style_instruction = self._summarize_style(style_content) + + # === P2-参考信息(条件触发)=== + if chapter_number > 10 and self.memory_service: + memory_limit = ( + self.MEMORY_COUNT_LIGHT if chapter_number <= 50 + else self.MEMORY_COUNT_FULL + ) + context.relevant_memories = await self._get_relevant_memories( + user_id, project.id, chapter_number, + context.chapter_outline, + limit=memory_limit + ) + logger.info(f" ✅ 相关记忆: {len(context.relevant_memories or '')}字符") + + # 故事骨架(50章+) + if chapter_number > self.SKELETON_THRESHOLD: + context.story_skeleton = await self._build_story_skeleton( + project.id, chapter_number, db + ) + logger.info(f" ✅ 故事骨架: {len(context.story_skeleton or '')}字符") + + # === 统计信息 === + context.context_stats = { + "chapter_number": chapter_number, + "has_continuation": context.continuation_point is not None, + "continuation_length": len(context.continuation_point or ""), + "characters_length": len(context.chapter_characters), + "memories_length": len(context.relevant_memories or ""), + "skeleton_length": len(context.story_skeleton or ""), + "total_length": context.get_total_context_length() + } + + logger.info(f"📊 上下文构建完成: 总长度 {context.context_stats['total_length']} 字符") + + return context + + async def _build_chapter_outline( + self, + chapter: Chapter, + outline: Optional[Outline], + outline_mode: str + ) -> str: + """ + 构建本章大纲内容 + + Args: + chapter: 章节对象 + outline: 大纲对象 + outline_mode: 大纲模式(one-to-one/one-to-many) + + Returns: + 本章大纲文本 + """ + if outline_mode == 'one-to-one': + # 一对一模式:使用大纲的 content + return outline.content if outline else chapter.summary or '暂无大纲' + else: + # 一对多模式:优先使用 expansion_plan 的详细规划 + if chapter.expansion_plan: + try: + plan = json.loads(chapter.expansion_plan) + outline_content = f"""剧情摘要:{plan.get('plot_summary', '无')} + +关键事件: +{chr(10).join(f'- {event}' for event in plan.get('key_events', []))} + +角色焦点:{', '.join(plan.get('character_focus', []))} +情感基调:{plan.get('emotional_tone', '未设定')} +叙事目标:{plan.get('narrative_goal', '未设定')} +冲突类型:{plan.get('conflict_type', '未设定')}""" + return outline_content + except json.JSONDecodeError: + pass + + # 回退到大纲内容 + return outline.content if outline else chapter.summary or '暂无大纲' + + async def _get_last_ending( + self, + chapter: Chapter, + db: AsyncSession, + max_length: int + ) -> Optional[str]: + """ + 获取上一章结尾内容作为衔接锚点 + + Args: + chapter: 当前章节 + db: 数据库会话 + max_length: 最大长度 + + Returns: + 上一章结尾内容 + """ + if chapter.chapter_number <= 1: + return None + + # 查询上一章 + result = await db.execute( + select(Chapter) + .where(Chapter.project_id == chapter.project_id) + .where(Chapter.chapter_number == chapter.chapter_number - 1) + ) + prev_chapter = result.scalar_one_or_none() + + if not prev_chapter or not prev_chapter.content: + return None + + # 提取结尾内容 + content = prev_chapter.content.strip() + if len(content) <= max_length: + return content + + return content[-max_length:] + + async def _build_chapter_characters( + self, + chapter: Chapter, + project: Project, + outline: Optional[Outline], + db: AsyncSession + ) -> str: + """ + 构建本章涉及的角色信息(精简版) + + 只返回本章相关的角色,而非全部角色 + + Args: + chapter: 章节对象 + project: 项目对象 + outline: 大纲对象 + db: 数据库会话 + + Returns: + 本章角色信息文本 + """ + # 获取所有角色 + characters_result = await db.execute( + select(Character).where(Character.project_id == project.id) + ) + characters = characters_result.scalars().all() + + if not characters: + return "暂无角色信息" + + # 提取本章相关角色名单 + filter_character_names = None + + # 从大纲或扩展计划中提取角色 + if project.outline_mode == 'one-to-one': + if outline and outline.structure: + try: + structure = json.loads(outline.structure) + filter_character_names = structure.get('characters', []) + except json.JSONDecodeError: + pass + else: + if chapter.expansion_plan: + try: + plan = json.loads(chapter.expansion_plan) + filter_character_names = plan.get('character_focus', []) + except json.JSONDecodeError: + pass + + # 筛选角色 + if filter_character_names: + characters = [c for c in characters if c.name in filter_character_names] + + if not characters: + return "暂无相关角色" + + # 构建精简的角色信息(每个角色最多100字符) + char_lines = [] + for c in characters[:10]: # 最多10个角色 + role_type = "主角" if c.role_type == "protagonist" else ( + "反派" if c.role_type == "antagonist" else "配角" + ) + + # 性格摘要(最多50字符) + personality_brief = "" + if c.personality: + personality_brief = c.personality[:50] + if len(c.personality) > 50: + personality_brief += "..." + + char_lines.append(f"- {c.name}({role_type}): {personality_brief}") + + return "\n".join(char_lines) + + def _extract_emotional_tone( + self, + chapter: Chapter, + outline: Optional[Outline] + ) -> str: + """ + 提取本章情感基调 + + Args: + chapter: 章节对象 + outline: 大纲对象 + + Returns: + 情感基调描述 + """ + # 尝试从扩展计划中提取 + if chapter.expansion_plan: + try: + plan = json.loads(chapter.expansion_plan) + tone = plan.get('emotional_tone') + if tone: + return tone + except json.JSONDecodeError: + pass + + # 尝试从大纲结构中提取 + if outline and outline.structure: + try: + structure = json.loads(outline.structure) + tone = structure.get('emotion') or structure.get('emotional_tone') + if tone: + return tone + except json.JSONDecodeError: + pass + + return "未设定" + + def _summarize_style(self, style_content: str) -> str: + """ + 将风格描述压缩为关键要点 + + Args: + style_content: 完整风格描述 + + Returns: + 摘要化的风格描述 + """ + if not style_content: + return "" + + if len(style_content) <= self.STYLE_MAX_LENGTH: + return style_content + + # 简单截断(后续可以用AI提取关键词) + return style_content[:self.STYLE_MAX_LENGTH] + "..." + + async def _get_relevant_memories( + self, + user_id: str, + project_id: str, + chapter_number: int, + chapter_outline: str, + limit: int = 3 + ) -> Optional[str]: + """ + 获取与本章最相关的记忆(精简版) + + 策略: + 1. 仅检索与大纲语义最相关的记忆 + 2. 提高重要性阈值,过滤低质量记忆 + 3. 优先返回未回收的伏笔 + + Args: + user_id: 用户ID + project_id: 项目ID + chapter_number: 当前章节号 + chapter_outline: 本章大纲 + limit: 返回数量限制 + + Returns: + 格式化的记忆文本 + """ + if not self.memory_service: + return None + + try: + # 1. 语义检索相关记忆(提高阈值) + relevant = await self.memory_service.search_memories( + user_id=user_id, + project_id=project_id, + query=chapter_outline, + limit=limit, + min_importance=self.MEMORY_IMPORTANCE_THRESHOLD + ) + + # 2. 检查即将到期的伏笔 + foreshadows = await self._get_due_foreshadows( + user_id, project_id, chapter_number, + lookahead=5 # 仅看5章内需要回收的 + ) + + # 3. 合并并格式化 + return self._format_memories(relevant, foreshadows, max_length=500) + + except Exception as e: + logger.error(f"❌ 获取相关记忆失败: {str(e)}") + return None + + async def _get_due_foreshadows( + self, + user_id: str, + project_id: str, + chapter_number: int, + lookahead: int = 5 + ) -> List[Dict[str, Any]]: + """ + 获取即将需要回收的伏笔 + + Args: + user_id: 用户ID + project_id: 项目ID + chapter_number: 当前章节号 + lookahead: 往前看的章节数 + + Returns: + 待回收伏笔列表 + """ + if not self.memory_service: + return [] + + try: + foreshadows = await self.memory_service.find_unresolved_foreshadows( + user_id, project_id, chapter_number + ) + + # 过滤:只保留埋下时间较长(超过lookahead章)的伏笔 + due_foreshadows = [] + for fs in foreshadows: + meta = fs.get('metadata', {}) + fs_chapter = meta.get('chapter_number', 0) + if chapter_number - fs_chapter >= lookahead: + due_foreshadows.append({ + 'chapter': fs_chapter, + 'content': fs.get('content', '')[:60], + 'importance': meta.get('importance', 0.5) + }) + + return due_foreshadows[:2] # 最多2条 + + except Exception as e: + logger.error(f"❌ 获取待回收伏笔失败: {str(e)}") + return [] + + def _format_memories( + self, + relevant: List[Dict[str, Any]], + foreshadows: List[Dict[str, Any]], + max_length: int = 500 + ) -> str: + """ + 格式化记忆为简洁文本,严格限制长度 + + Args: + relevant: 相关记忆列表 + foreshadows: 待回收伏笔列表 + max_length: 最大长度 + + Returns: + 格式化的记忆文本 + """ + lines = [] + current_length = 0 + + # 优先添加待回收伏笔 + if foreshadows: + lines.append("【待回收伏笔】") + for fs in foreshadows[:2]: + text = f"- 第{fs['chapter']}章埋下:{fs['content']}" + if current_length + len(text) > max_length: + break + lines.append(text) + current_length += len(text) + + # 添加相关记忆 + if relevant and current_length < max_length: + lines.append("【相关记忆】") + for mem in relevant: + content = mem.get('content', '')[:80] + text = f"- {content}" + if current_length + len(text) > max_length: + break + lines.append(text) + current_length += len(text) + + return "\n".join(lines) if lines else None + + async def _build_story_skeleton( + self, + project_id: str, + chapter_number: int, + db: AsyncSession + ) -> Optional[str]: + """ + 构建故事骨架(每N章采样) + + Args: + project_id: 项目ID + chapter_number: 当前章节号 + db: 数据库会话 + + Returns: + 故事骨架文本 + """ + try: + # 获取所有已完成章节的摘要 + result = await db.execute( + select(Chapter.chapter_number, Chapter.title) + .where(Chapter.project_id == project_id) + .where(Chapter.chapter_number < chapter_number) + .where(Chapter.content != None) + .where(Chapter.content != "") + .order_by(Chapter.chapter_number) + ) + chapters = result.all() + + if not chapters: + return None + + # 采样:每N章取一个 + skeleton_lines = ["【故事骨架】"] + for i, (ch_num, ch_title) in enumerate(chapters): + if i % self.SKELETON_SAMPLE_INTERVAL == 0: + # 尝试获取章节摘要 + summary_result = await db.execute( + select(StoryMemory.content) + .where(StoryMemory.project_id == project_id) + .where(StoryMemory.story_timeline == ch_num) + .where(StoryMemory.memory_type == 'chapter_summary') + .limit(1) + ) + summary = summary_result.scalar_one_or_none() + + if summary: + skeleton_lines.append(f"第{ch_num}章《{ch_title}》:{summary[:100]}") + else: + skeleton_lines.append(f"第{ch_num}章《{ch_title}》") + + if len(skeleton_lines) <= 1: + return None + + return "\n".join(skeleton_lines) + + except Exception as e: + logger.error(f"❌ 构建故事骨架失败: {str(e)}") + return None + + +class FocusedMemoryRetriever: + """ + 精简记忆检索器 + + 相比原有的memory_service,提供更精准、更简洁的记忆检索 + """ + + def __init__(self, memory_service): + """ + 初始化检索器 + + Args: + memory_service: 基础记忆服务实例 + """ + self.memory_service = memory_service + + async def get_relevant_memories( + self, + user_id: str, + project_id: str, + chapter_number: int, + chapter_outline: str, + limit: int = 3 + ) -> str: + """ + 获取与本章最相关的记忆 + + 策略: + 1. 仅检索与大纲语义最相关的记忆 + 2. 提高重要性阈值,过滤低质量记忆 + 3. 优先返回未回收的伏笔 + + Args: + user_id: 用户ID + project_id: 项目ID + chapter_number: 当前章节号 + chapter_outline: 本章大纲 + limit: 返回数量限制 + + Returns: + 格式化的记忆文本 + """ + # 1. 语义检索相关记忆(提高阈值) + relevant = await self.memory_service.search_memories( + user_id=user_id, + project_id=project_id, + query=chapter_outline, + limit=limit, + min_importance=0.7 # 从0.4提高到0.7 + ) + + # 2. 检查即将到期的伏笔 + due_foreshadows = await self._get_due_foreshadows( + user_id, project_id, chapter_number, + lookahead=5 # 仅看5章内需要回收的 + ) + + # 3. 合并并格式化 + return self._format_memories(relevant, due_foreshadows, max_length=500) + + async def _get_due_foreshadows( + self, + user_id: str, + project_id: str, + chapter_number: int, + lookahead: int = 5 + ) -> List[Dict[str, Any]]: + """获取即将需要回收的伏笔""" + foreshadows = await self.memory_service.find_unresolved_foreshadows( + user_id, project_id, chapter_number + ) + + # 过滤:只保留埋下时间较长的伏笔 + due_foreshadows = [] + for fs in foreshadows: + meta = fs.get('metadata', {}) + fs_chapter = meta.get('chapter_number', 0) + if chapter_number - fs_chapter >= lookahead: + due_foreshadows.append({ + 'chapter': fs_chapter, + 'content': fs.get('content', '')[:60], + 'importance': meta.get('importance', 0.5) + }) + + return due_foreshadows[:2] # 最多2条 + + def _format_memories( + self, + relevant: List[Dict[str, Any]], + foreshadows: List[Dict[str, Any]], + max_length: int = 500 + ) -> str: + """格式化为简洁文本,严格限制长度""" + lines = [] + current_length = 0 + + # 优先添加待回收伏笔 + if foreshadows: + lines.append("【待回收伏笔】") + for fs in foreshadows[:2]: + text = f"- 第{fs['chapter']}章埋下:{fs['content']}" + if current_length + len(text) > max_length: + break + lines.append(text) + current_length += len(text) + + # 添加相关记忆 + if relevant and current_length < max_length: + lines.append("【相关记忆】") + for mem in relevant: + content = mem.get('content', '')[:80] + text = f"- {content}" + if current_length + len(text) > max_length: + break + lines.append(text) + current_length += len(text) + + return "\n".join(lines) if lines else "" \ No newline at end of file diff --git a/backend/app/services/prompt_service.py b/backend/app/services/prompt_service.py index 40580dc..6662716 100644 --- a/backend/app/services/prompt_service.py +++ b/backend/app/services/prompt_service.py @@ -25,288 +25,309 @@ class WritingStyleManager: class PromptService: """提示词模板管理""" - # 世界构建提示词 - WORLD_BUILDING = """你是一位资深的世界观设计师。基于以下输入信息,构建一个高度原创、深度自洽、充满戏剧冲突的小说世界观。 + # ========== V2版本提示词模板(RTCO框架)========== + + # 世界构建提示词 V2(RTCO框架) + WORLD_BUILDING = """ +你是资深的世界观设计师,擅长为{genre}类型的小说构建真实、自洽的世界观。 + -# 输入信息 + +【设计任务】 +为小说《{title}》构建完整的世界观设定。 + +【核心要求】 +- 主题契合:世界观必须支撑主题"{theme}" +- 简介匹配:为简介中的情节提供合理背景 +- 类型适配:符合{genre}类型的特征 +- 规模适当:根据题材选择合适的设定尺度 + + + +【项目信息】 书名:{title} -主题:{theme} 类型:{genre} +主题:{theme} 简介:{description} + -# 核心要求 -* **简介契合性**:世界观设定必须能够支撑简介中描述的故事情节和核心矛盾 -* **类型适配性**:世界观必须符合小说类型的特征,不要生成不匹配的设定 -* **主题贴合性**:时代背景要能有效支撑和体现小说主题 -* **原创性**:在类型框架内发挥创意,创造独特但合理的世界设定 -* **具象化**:避免空洞概念,用具体可感的细节描述世界 -* **逻辑自洽**:确保所有设定相互支撑,形成完整体系 -* **戏剧张力**:设定要能为故事冲突提供支撑,尤其要为简介中的故事线索创造合适的环境 - -# 类型指导原则 -根据小说类型选择适当的设定风格: + +【类型指导原则】 **现代都市/言情/青春**: -- 时间设定:当代现实社会(2020年代)或近未来(2030-2050年) -- 避免使用:大崩解、纪元、末日、重生等宏大概念 -- 重点描述:具体的城市环境、社会现状、文化氛围 -- 例如:一线城市的竞争压力、职场文化、代际冲突、社交媒体影响等 +- 时间:当代社会(2020年代)或近未来(2030-2050年) +- 避免:大崩解、纪元、末日等宏大概念 +- 重点:具体城市环境、职场文化、社会现状 **历史/古代**: -- 时间设定:明确的历史朝代或虚构但有历史感的古代社会 -- 避免使用:科技元素、未来概念 -- 重点描述:时代特征、礼教制度、阶级分化 +- 时间:明确的历史朝代或虚构古代 +- 重点:时代特征、礼教制度、阶级分化 **玄幻/仙侠/修真**: -- 时间设定:修炼文明的特定时期,可以有门派兴衰、修炼体系变革 -- 可以使用宏大设定,但要与修炼体系紧密结合 -- 重点描述:修炼规则、灵气环境、门派势力 +- 时间:修炼文明的特定时期 +- 重点:修炼规则、灵气环境、门派势力 **科幻**: -- 时间设定:未来某个明确时期(如2150年、星际时代初期等) -- 可以有文明转折,但要具体说明科技水平和社会形态 -- 避免空泛的纪元名称,多用具体的科技特征描述 +- 时间:未来明确时期(如2150年、星际时代初期) +- 重点:科技水平、社会形态、文明转折 **奇幻/魔法**: -- 时间设定:魔法文明的特定阶段 -- 重点描述:魔法体系、种族关系、大陆格局 +- 时间:魔法文明的特定阶段 +- 重点:魔法体系、种族关系、大陆格局 -**悬疑/推理/惊悚**: -- 时间设定:当代或历史某个时期 -- 重点描述:案件背景、社会环境、人际关系网 +**设定尺度控制**: +- 现代都市:聚焦某个城市、行业、阶层 +- 校园青春:学校环境、学生生活、成长困境 +- 职场言情:公司文化、行业特点、职业压力 +- 史诗题材:才需要宏大的世界观架构 + -**军事/战争**: -- 时间设定:战争时期的具体年代 -- 重点描述:战争形势、阵营对立、军事科技水平 - -# 设定尺度控制 -**切记:不要为所有类型都生成宏大的世界观!** - -- 如果是现代都市题材,就写现实社会的某个城市、某个行业、某个阶层 -- 如果是校园青春,就写学校环境、学生生活、成长困境 -- 如果是职场言情,就写公司文化、行业特点、职业压力 -- 只有史诗级题材(玄幻、科幻、奇幻等)才需要宏大的世界观架构 - -# 输出要求 -生成包含以下四个字段的JSON对象,每个字段用300-500字的连贯段落描述: + +【输出格式】 +生成包含以下四个字段的JSON对象,每个字段300-500字: 1. **time_period**(时间背景与社会状态) - - **重要**:根据类型和主题,设定合适规模的时间背景 - - 现代题材:描述当前社会的具体特征(如:2024年的北京,互联网行业高速发展...) - - 历史题材:明确朝代和历史阶段(如:明朝嘉靖年间,海禁政策下的沿海地区...) - - 幻想题材:描述文明发展阶段,但要具体而非空泛(如:大陆诸国林立的战国时代,而非"XX纪元") - - 阐明时代核心矛盾和社会焦虑(要贴合主题) + - 根据类型设定合适规模的时间背景 + - 现代题材:具体社会特征(如:2024年北京,互联网行业高速发展) + - 历史题材:明确朝代和阶段(如:明朝嘉靖年间,海禁政策下的沿海) + - 幻想题材:文明发展阶段,具体而非空泛 + - 阐明时代核心矛盾和社会焦虑 2. **location**(空间环境与地理特征) - - 描绘故事主要发生的空间环境(具体的城市、地区、场所) - - 现代题材:具体城市名或城市类型(一线城市、沿海城市、内陆小城等) - - 说明环境如何影响居民的生存方式 - - 刻画能代表世界独特性的标志性场景 + - 故事主要发生的空间环境 + - 现代题材:具体城市名或类型 + - 环境如何影响居民生存方式 + - 标志性场景描述 3. **atmosphere**(感官体验与情感基调) - - 描述身临其境的感官细节(视觉、听觉、嗅觉等) - - 阐述世界的美学风格和色彩基调 - - 刻画居民普遍的心理状态和情绪氛围 - - **要与主题情感呼应**(如竞争焦虑、成长迷茫、爱情憧憬等) + - 身临其境的感官细节(视觉、听觉、嗅觉) + - 美学风格和色彩基调 + - 居民心理状态和情绪氛围 + - 与主题情感呼应 4. **rules**(世界规则与社会结构) - - 阐明世界运行的核心法则和底层逻辑 - - 现代题材:社会规则、行业潜规则、人际交往法则 - - 幻想题材:力量体系、社会等级、资源分配 - - 描述权力结构和利益格局 - - 揭示社会禁忌及违反后的后果 + - 世界运行的核心法则 + - 现代题材:社会规则、行业潜规则、人际法则 + - 幻想题材:力量体系、社会等级、资源分配 + - 权力结构和利益格局 + - 社会禁忌及后果 -# 格式规范 -1. **纯JSON输出**:只输出JSON对象,以左花括号开始、右花括号结束 -2. **无额外标记**:不要包含markdown标记、代码块符号或任何说明文字 -3. **纯文本值**:每个字段值必须是完整的段落文本,不使用嵌套结构 -4. **无特殊符号**:文本中不使用引号、方括号等特殊符号包裹内容 -5. **丰富细节**:每个字段提供充实的原创内容,避免模板化表达 - -请根据输入的书名、类型、主题和简介,生成**规模适当、风格匹配、能够支撑故事发展**的世界观设定。 - -**特别提醒**: -- 简介是故事的核心概括,世界观必须为简介中描述的情节提供合理的发生背景 -- 所有设定都应该能够自然地承载简介中的故事线 -- 如果简介中有特定的场景、冲突或设定,世界观要与之呼应 - -# JSON格式示例 +【格式规范】 +- 纯JSON输出,以{{开始、}}结束 +- 无markdown标记、代码块符号 +- 字段值为完整段落文本 +- 不使用特殊符号包裹内容 +- 提供充实原创内容 +【JSON示例】 {{ "time_period": "时间背景与社会状态的详细描述(300-500字)", "location": "空间环境与地理特征的详细描述(300-500字)", "atmosphere": "感官体验与情感基调的详细描述(300-500字)", "rules": "世界规则与社会结构的详细描述(300-500字)" -}}""" +}} + - # 批量角色生成提示词 - CHARACTERS_BATCH_GENERATION = """你是一位专业的角色设定师。请根据以下世界观和要求,生成{count}个立体丰满的角色和组织: + +【必须遵守】 +✅ 简介契合:为简介情节提供合理背景 +✅ 类型适配:符合{genre}的特征 +✅ 主题贴合:支撑主题"{theme}" +✅ 具象化:用具体细节而非空洞概念 +✅ 逻辑自洽:所有设定相互支撑 -世界观信息: -- 时间背景:{time_period} -- 地理位置:{location} -- 氛围基调:{atmosphere} -- 世界规则:{rules} +【禁止事项】 +❌ 生成与类型不匹配的设定 +❌ 为小规模题材使用宏大世界观 +❌ 使用模板化、空泛的表达 +❌ 输出markdown或代码块标记 +""" + + # 批量角色生成提示词 V2(RTCO框架) + CHARACTERS_BATCH_GENERATION = """ +你是专业的角色设定师,擅长为{genre}类型的小说创建立体丰满的角色。 + + + +【生成任务】 +生成{count}个角色和组织实体。 + +【数量要求 - 严格遵守】 +数组中必须精确包含{count}个对象,不多不少。 + +【实体类型分配】 +- 至少1个主角(protagonist) +- 多个配角(supporting) +- 可包含反派(antagonist) +- 可包含1-2个高影响力组织(power_level: 70-95) + + + +【世界观信息】 +时间背景:{time_period} +地理位置:{location} +氛围基调:{atmosphere} +世界规则:{rules} 主题:{theme} 类型:{genre} -特殊要求:{requirements} + -【数量要求 - 必须严格遵守】 -请精确生成{count}个实体,不多不少。数组中必须包含且仅包含{count}个对象。 + +【特殊要求】 +{requirements} + -实体类型分配: -- 至少1个主角(protagonist) -- 多个配角(supporting) -- 可以包含反派(antagonist) -- 可以包含1-2个**高影响力的重要组织**(势力等级应在70-95之间) + +【输出格式】 +返回纯JSON数组,每个对象包含: -要求: -- 角色要符合世界观设定 -- 性格和背景要有深度 -- 角色之间要有关系网络 -- 组织要有存在的合理性 -- 所有实体要为故事服务 +**角色对象**: +{{ + "name": "角色姓名", + "age": 25, + "gender": "男/女/其他", + "is_organization": false, + "role_type": "protagonist/supporting/antagonist", + "personality": "性格特点(100-200字):核心性格、优缺点、特殊习惯", + "background": "背景故事(100-200字):家庭背景、成长经历、重要转折", + "appearance": "外貌描述(50-100字):身高、体型、面容、着装风格", + "traits": ["特长1", "特长2", "特长3"], + "relationships_array": [ + {{ + "target_character_name": "已生成的角色名称", + "relationship_type": "关系类型", + "intimacy_level": 75, + "description": "关系描述" + }} + ], + "organization_memberships": [ + {{ + "organization_name": "已生成的组织名称", + "position": "职位", + "rank": 5, + "loyalty": 80 + }} + ] +}} -**重要格式要求:** -1. 只返回纯JSON数组格式,不要包含任何markdown标记、代码块标记或其他说明文字 -2. JSON字符串值的内容描述中严禁使用任何特殊符号(包括中文引号、英文引号、方括号、书名号等) -3. 所有专有名词、地点、人物、组织名称等直接书写,不使用任何符号包裹 +**组织对象**: +{{ + "name": "组织名称", + "is_organization": true, + "role_type": "supporting", + "personality": "组织特性(100-200字):运作方式、核心理念、行事风格", + "background": "组织背景(100-200字):建立历史、发展历程、重要事件", + "appearance": "外在表现(50-100字):总部位置、标志性建筑", + "organization_type": "组织类型", + "organization_purpose": "组织目的", + "organization_members": ["成员1", "成员2"], + "power_level": 85, + "location": "所在地或主要活动区域", + "motto": "组织格言、口号或宗旨", + "color": "代表颜色", + "traits": [] +}} -请严格按照以下JSON数组格式返回(每个角色为数组中的一个对象): -[ - {{ - "name": "角色姓名", - "age": 25, - "gender": "男/女/其他", - "is_organization": false, - "role_type": "protagonist/supporting/antagonist", - "personality": "性格特点的详细描述(100-200字),包括核心性格、优缺点、特殊习惯", - "background": "背景故事的详细描述(100-200字),包括家庭背景、成长经历、重要转折", - "appearance": "外貌描述(50-100字),包括身高、体型、面容、着装风格", - "traits": ["特长1", "特长2", "特长3"], - "relationships_array": [ - {{ - "target_character_name": "已生成的角色名称", - "relationship_type": "关系类型(师父/朋友/敌人/父亲/母亲等)", - "intimacy_level": 75, - "description": "关系描述" - }} - ], - "organization_memberships": [ - {{ - "organization_name": "已生成的组织名称", - "position": "职位", - "rank": 5, - "loyalty": 80 - }} - ] - }}, - {{ - "name": "组织名称", - "is_organization": true, - "role_type": "supporting", - "personality": "组织特性描述(100-200字),包括运作方式、核心理念、行事风格", - "background": "组织背景(100-200字),包括建立历史、发展历程、重要事件", - "appearance": "组织外在表现(50-100字),如总部位置、标志性建筑等", - "organization_type": "组织类型", - "organization_purpose": "组织目的", - "organization_members": ["成员1", "成员2"], - "power_level": 85, - "location": "组织所在地或主要活动区域", - "motto": "组织格言、口号或宗旨", - "color": "组织代表颜色(如:深红色、金色、黑色等)", - "traits": [] - }} -] - -**组织生成要求(重要):** -- 组织必须是对故事有重大影响的势力 -- power_level应在70-95之间(高影响力组织) -- 不要生成无关紧要的小组织或普通社团 -- 组织应该是推动剧情发展的关键力量 -- 可以是正派势力、中立势力或反派势力,但一定要有存在感 - -**关系类型参考(从中选择或自定义):** +【关系类型参考】 - 家族:父亲、母亲、兄弟、姐妹、子女、配偶、恋人 - 社交:师父、徒弟、朋友、同学、同事、邻居、知己 - 职业:上司、下属、合作伙伴 - 敌对:敌人、仇人、竞争对手、宿敌 -**重要说明:** -1. **数量控制**:数组中必须精确包含{count}个对象,不能多也不能少 -2. **关系约束**:relationships_array只能引用本批次中已经出现的角色名称 -3. **组织约束**:organization_memberships只能引用本批次中is_organization=true的实体名称 -4. **禁止幻觉**:不要引用任何不存在的角色或组织,如果没有可引用的就留空数组[] -5. **数值范围约束**: - - intimacy_level:-100到100的整数(负值表示敌对仇恨关系) - - loyalty:0到100的整数 - - **rank:0到10的整数**(职位等级,0最低,10最高) -6. 角色之间要形成合理的关系网络 +【数值范围】 +- intimacy_level:-100到100(负值表示敌对) +- loyalty:0到100 +- rank:0到10(职位等级) +- power_level:70到95(组织影响力) + -**示例说明**: -- 如果生成了角色A、组织B、角色C,则角色A的organization_memberships只能是[组织B],不能是其他组织 -- 如果角色A在数组第一位,它的relationships_array必须为空[],因为还没有其他角色 -- 如果角色C在数组第三位,它的relationships_array可以引用角色A,但不能引用不存在的角色D + +【必须遵守】 +✅ 数量精确:数组必须包含{count}个对象 +✅ 符合世界观:角色设定与世界观一致 +✅ 有深度:性格和背景要立体 +✅ 关系网络:角色间形成合理关系 +✅ 组织合理:组织是推动剧情的关键力量 -再次强调: -1. 只返回纯JSON数组,不要有```json```这样的标记 -2. 数组中必须精确包含{count}个对象 -3. 不要引用任何本批次中不存在的角色或组织名称 -4. 所有内容描述中严禁使用任何特殊符号,包括但不限于中文引号、英文引号、方括号、书名号等""" +【关系约束】 +✅ relationships_array只能引用本批次已出现的角色 +✅ organization_memberships只能引用本批次的组织 +✅ 第一个角色的relationships_array必须为空[] +✅ 禁止幻觉:不引用不存在的角色或组织 - # 向导大纲生成提示词 - OUTLINE_CREATE = """你是一位经验丰富的小说作家和编剧。请根据以下信息为小说生成**开篇{chapter_count}章**的大纲: +【格式约束】 +✅ 纯JSON数组输出,无markdown标记 +✅ 内容描述中严禁使用特殊符号(引号、方括号、书名号等) +✅ 专有名词直接书写,不使用符号包裹 + +【禁止事项】 +❌ 生成数量不符(多于或少于{count}个) +❌ 引用不存在的角色或组织 +❌ 生成低影响力的无关紧要组织 +❌ 使用markdown或代码块标记 +❌ 在描述中使用特殊符号 +""" + + # 大纲生成提示词 V2(RTCO框架) + OUTLINE_CREATE = """ +你是经验丰富的小说作家和编剧,擅长为{genre}类型的小说设计精彩开篇。 + + + +【创作任务】 +为小说《{title}》生成开篇{chapter_count}章的大纲。 【重要说明】 -本次任务是为项目初始化生成开头部分的大纲,而不是整本书的完整大纲。这些章节应该: -- 完成故事的**开局设定**和**世界观展示** +这是项目初始化的开头部分,不是完整大纲: +- 完成开局设定和世界观展示 - 引入主要角色,建立初始关系 - 埋下核心矛盾和悬念钩子 - 为后续剧情发展打下基础 -- **不需要完整的故事闭环**,结尾应该为续写留出空间 +- 不需要完整闭环,为续写留空间 + -基本信息: -- 书名:{title} -- 主题:{theme} -- 类型:{genre} -- 开篇章节数:{chapter_count} -- 叙事视角:{narrative_perspective} -- 全书目标字数:{target_words} + +【项目信息】 +书名:{title} +主题:{theme} +类型:{genre} +开篇章节数:{chapter_count} +叙事视角:{narrative_perspective} +全书目标字数:{target_words} + -世界观: -- 时间背景:{time_period} -- 地理位置:{location} -- 氛围基调:{atmosphere} -- 世界规则:{rules} + +【世界观】 +时间背景:{time_period} +地理位置:{location} +氛围基调:{atmosphere} +世界规则:{rules} + -角色信息: + +【角色信息】 {characters_info} + + {mcp_references} + -其他要求:{requirements} + +【其他要求】 +{requirements} + -开篇大纲要求: -- **开局设定**:前几章完成世界观呈现、主角登场、初始状态建立 -- **矛盾引入**:引出核心冲突或故事主线,但不急于展开 -- **角色亮相**:主要角色依次登场,展示性格特点和相互关系 -- **节奏控制**:开篇不宜过快,给读者适应和代入的时间 -- **悬念设置**:埋下伏笔和钩子,为后续续写大纲预留发展空间 -- **视角统一**:采用{narrative_perspective}视角叙事 -- **留白艺术**:结尾不要收束过紧,要为后续剧情留出足够的发展空间 + +【输出格式】 +返回包含{chapter_count}个章节对象的JSON数组: -**重要格式要求:** -1. 只返回纯JSON数组格式,不要包含任何markdown标记、代码块标记或其他说明文字 -2. JSON字符串值的内容描述中严禁使用任何特殊符号(包括中文引号、英文引号、方括号、书名号等) -3. 所有专有名词、事件名等直接书写,不使用任何符号包裹 - -请严格按照以下JSON数组格式返回(共{chapter_count}个章节对象): [ {{ "chapter_number": 1, "title": "章节标题", - "summary": "章节概要的详细描述(100-200字),包含主要情节、冲突、转折等", + "summary": "章节概要(300-500字):主要情节、冲突、转折", "scenes": ["场景1描述", "场景2描述", "场景3描述"], "characters": ["角色1", "角色2"], "key_points": ["情节要点1", "情节要点2"], @@ -325,69 +346,105 @@ class PromptService: }} ] -再次强调: -1. 只返回纯JSON数组,不要有```json```这样的标记 -2. 数组中要包含{chapter_count}个章节对象 -3. 所有内容描述中严禁使用任何特殊符号""" +【格式规范】 +- 纯JSON数组输出,无markdown标记 +- 内容描述中严禁使用特殊符号(引号、方括号、书名号等) +- 专有名词、事件名直接书写 + + + +【开篇大纲要求】 +✅ 开局设定:前几章完成世界观呈现、主角登场、初始状态 +✅ 矛盾引入:引出核心冲突,但不急于展开 +✅ 角色亮相:主要角色依次登场,展示性格和关系 +✅ 节奏控制:开篇不宜过快,给读者适应时间 +✅ 悬念设置:埋下伏笔和钩子,为续写预留空间 +✅ 视角统一:采用{narrative_perspective}视角 +✅ 留白艺术:结尾不收束过紧,留发展空间 + +【必须遵守】 +✅ 数量精确:数组包含{chapter_count}个章节对象 +✅ 符合类型:情节符合{genre}类型特征 +✅ 主题贴合:体现主题"{theme}" +✅ 开篇定位:是开局而非完整故事 + +【禁止事项】 +❌ 输出markdown或代码块标记 +❌ 在描述中使用特殊符号 +❌ 试图在开篇完结故事 +❌ 节奏过快,信息过载 +""" - # 大纲续写提示词(记忆增强版) - OUTLINE_CONTINUE = """你是一位经验丰富的小说作家和编剧。请基于以下信息续写小说大纲: + # 大纲续写提示词 V2(RTCO框架 + 记忆增强) + OUTLINE_CONTINUE = """ +你是经验丰富的小说作家和编剧,擅长续写{genre}类型的小说大纲。 + + +【续写任务】 +基于已有{current_chapter_count}章内容,续写第{start_chapter}章到第{end_chapter}章的大纲(共{chapter_count}章)。 + +【当前情节阶段】 +{plot_stage_instruction} + +【故事发展方向】 +{story_direction} + + + 【项目信息】 -- 书名:{title} -- 主题:{theme} -- 类型:{genre} -- 叙事视角:{narrative_perspective} -- 续写章节数:{chapter_count}章 +书名:{title} +主题:{theme} +类型:{genre} +叙事视角:{narrative_perspective} + + 【世界观】 -- 时间背景:{time_period} -- 地理位置:{location} -- 氛围基调:{atmosphere} -- 世界规则:{rules} +时间背景:{time_period} +地理位置:{location} +氛围基调:{atmosphere} +世界规则:{rules} + + 【角色信息】 {characters_info} + + 【已有章节概览】(共{current_chapter_count}章) {all_chapters_brief} 【最近剧情】 {recent_plot} + + 【🧠 智能记忆系统 - 续写参考】 -以下是从故事记忆库中检索到的相关信息,请在续写大纲时参考: +以下是从故事记忆库中检索到的相关信息: {memory_context} + + {mcp_references} + -【续写指导】 -- 当前情节阶段:{plot_stage_instruction} -- 起始章节编号:第{start_chapter}章 -- 故事发展方向:{story_direction} -- 其他要求:{requirements} + +【其他要求】 +{requirements} + -请生成第{start_chapter}章到第{end_chapter}章的大纲。 -要求: -- **剧情连贯性**:与前文自然衔接,保持故事连贯性 -- **记忆参考**:适当参考记忆系统中的伏笔、钩子和情节点 -- **伏笔回收**:可以考虑回收未完结的伏笔,制造呼应 -- **角色发展**:遵循角色在前文中的成长轨迹 -- **情节阶段**:遵循情节阶段的发展要求 -- **风格一致**:保持与已有章节相同的风格和详细程度 + +【输出格式】 +返回第{start_chapter}到第{end_chapter}章的JSON数组(共{chapter_count}个对象): -**重要格式要求:** -1. 只返回纯JSON数组格式,不要包含任何markdown标记、代码块标记或其他说明文字 -2. JSON字符串值的内容描述中严禁使用任何特殊符号(包括中文引号、英文引号、方括号、书名号等) -3. 所有专有名词直接书写,不使用任何符号包裹 - -请严格按照以下JSON数组格式返回(共{chapter_count}个章节对象): [ {{ "chapter_number": {start_chapter}, "title": "章节标题", - "summary": "章节概要的详细描述(100-200字),包含主要情节、角色互动、关键事件、冲突与转折", + "summary": "章节概要(300-500字):主要情节、角色互动、关键事件、冲突与转折", "scenes": ["场景1描述", "场景2描述", "场景3描述"], "characters": ["涉及角色1", "涉及角色2"], "key_points": ["情节要点1", "情节要点2"], @@ -406,244 +463,185 @@ class PromptService: }} ] -再次强调: -1. 只返回纯JSON数组,不要有```json```这样的标记 -2. 数组中要包含{chapter_count}个章节对象 -3. 每个summary必须是100-200字的详细描述 -4. 确保字段结构与已有章节完全一致 -5. 所有内容描述中严禁使用任何特殊符号""" +【格式规范】 +- 纯JSON数组输出,无markdown标记 +- 内容描述中严禁使用特殊符号 +- 专有名词直接书写 +- 字段结构与已有章节完全一致 + + + +【续写要求】 +✅ 剧情连贯:与前文自然衔接,保持连贯性 +✅ 记忆参考:适当参考记忆中的伏笔、钩子、情节点 +✅ 伏笔回收:考虑回收未完结伏笔,制造呼应 +✅ 角色发展:遵循角色成长轨迹 +✅ 情节阶段:遵循{plot_stage_instruction}的要求 +✅ 风格一致:保持与已有章节相同风格和详细程度 + +【必须遵守】 +✅ 数量精确:数组包含{chapter_count}个章节 +✅ 编号正确:从第{start_chapter}章开始 +✅ 描述详细:每个summary 100-200字 +✅ 承上启下:自然衔接前文 + +【禁止事项】 +❌ 输出markdown或代码块标记 +❌ 在描述中使用特殊符号 +❌ 与前文矛盾或脱节 +❌ 忽略已有角色发展 +""" - # AI去味提示词(核心特色功能) - AI_DENOISING = """你是一位追求自然写作风格的编辑。你的任务是将AI生成的文本改写得更像人类作家的手笔。 + # 章节生成V2 - 无前置章节版本(用于第1章) + CHAPTER_GENERATION_V2 = """ +你是《{project_title}》的作者,一位专注于{genre}类型的网络小说家。 + -原文: -{original_text} + +【创作任务】 +撰写第{chapter_number}章《{chapter_title}》的完整正文。 -修改要求: -1. 去除AI痕迹: - - 删除过于工整的排比句 - - 减少重复的修辞手法 - - 去掉刻意的对称结构 - - 避免机械式的总结陈词 - -2. 增加人性化: - - 使用更口语化的表达 - - 添加不完美的细节 - - 保留适度的随意性 - - 增加真实的情感波动 - -3. 优化叙事: - - 让节奏更自然不做作 - - 用简单词汇替换华丽辞藻 - - 保持叙述的松弛感 - - 让对话更生活化 - -4. 保持原意: - - 不改变核心情节 - - 保留关键信息点 - - 维持角色性格 - - 确保逻辑连贯 - -修改风格: -- 像是一个喜欢讲故事的普通人写的 -- 有点粗糙但很真诚 -- 自然流畅不刻意 -- 让人读起来很舒服 - -请直接输出修改后的文本,无需解释。""" - - # 章节完整创作提示词 - CHAPTER_GENERATION = """你是一位专业的小说作家。请根据以下信息创作本章内容: - -项目信息: -- 书名:{title} -- 主题:{theme} -- 类型:{genre} +【基本要求】 +- 目标字数:{target_word_count}字(允许±200字浮动) - 叙事视角:{narrative_perspective} + -世界观: -- 时间背景:{time_period} -- 地理位置:{location} -- 氛围基调:{atmosphere} -- 世界规则:{rules} + +【本章大纲 - 必须遵循】 +{chapter_outline} + -角色信息: + +【本章角色】 {characters_info} + -全书大纲: -{outlines_context} + +【必须遵守】 +✅ 严格按照大纲推进情节 +✅ 保持角色性格、说话方式一致 +✅ 字数控制在目标范围内 -本章信息: -- 章节序号:第{chapter_number}章 -- 章节标题:{chapter_title} -- 章节大纲:{chapter_outline} +【禁止事项】 +❌ 输出章节标题、序号等元信息 +❌ 使用"总之"、"综上所述"等AI常见总结语 +❌ 在结尾处使用开放式反问 +❌ 添加作者注释或创作说明 + -创作要求: -1. 严格按照大纲内容展开情节 -2. 保持与前后章节的连贯性 -3. 符合角色性格设定 -4. 体现世界观特色 -5. 使用{narrative_perspective}视角 -6. 字数要求:目标{target_word_count}字,不得低于{target_word_count}字,必须严格控制在{target_word_count}至{max_word_count}字之间 -7. 语言自然流畅,避免AI痕迹 + +【输出规范】 +直接输出小说正文内容,从故事场景或动作开始。 +无需任何前言、后记或解释性文字。 -请直接输出章节正文内容,不要包含章节标题和其他说明文字。""" +现在开始创作: +""" - # 章节完整创作提示词(带前置章节上下文和记忆增强) - CHAPTER_GENERATION_WITH_CONTEXT = """你是一位专业的小说作家。请根据以下信息创作本章内容: + # 章节生成V2 - 带前置章节版本(用于第2章及以后) + CHAPTER_GENERATION_V2_WITH_CONTEXT = """ +你是《{project_title}》的作者,一位专注于{genre}类型的网络小说家。 + -项目信息: -- 书名:{title} -- 主题:{theme} -- 类型:{genre} + +【创作任务】 +撰写第{chapter_number}章《{chapter_title}》的完整正文。 + +【基本要求】 +- 目标字数:{target_word_count}字(允许±500字浮动) - 叙事视角:{narrative_perspective} + -世界观: -- 时间背景:{time_period} -- 地理位置:{location} -- 氛围基调:{atmosphere} -- 世界规则:{rules} + +【本章大纲 - 必须遵循】 +{chapter_outline} + -角色信息: + +【衔接锚点 - 必须承接】 +上一章结尾: +「{continuation_point}」 + +⚠️ 要求:从此处自然续写,不得重复上述内容 + + + +【本章角色】 {characters_info} + -全书大纲: -{outlines_context} + +【相关记忆 - 参考】 +{relevant_memories} + -【已完成的前置章节内容】 -{previous_content} + +【故事骨架 - 背景】 +{story_skeleton} + -【🧠 智能记忆系统 - 重要参考】 -以下是从故事记忆库中检索到的相关信息,请在创作时适当参考和呼应: + +【必须遵守】 +✅ 严格按照大纲推进情节 +✅ 自然承接上一章结尾,不重复已发生事件 +✅ 保持角色性格、说话方式一致 +✅ 字数控制在目标范围内 -{memory_context} +【禁止事项】 +❌ 输出章节标题、序号等元信息 +❌ 使用"总之"、"综上所述"等AI常见总结语 +❌ 在结尾处使用开放式反问 +❌ 添加作者注释或创作说明 +❌ 重复叙述上一章已发生的事件 + -本章信息: -- 章节序号:第{chapter_number}章 -- 章节标题:{chapter_title} -- 章节大纲:{chapter_outline} + +【输出规范】 +直接输出小说正文内容,从故事场景或动作开始。 +无需任何前言、后记或解释性文字。 -创作要求: -1. **剧情连贯性(最重要)**: -- 必须承接前面章节的剧情发展 -- 注意角色状态、情节进展、时间线的连续性 -- 不能出现与前文矛盾的内容 -- 自然过渡,避免突兀的跳跃 +现在开始创作: +""" -2. **🔴 防止内容重复(关键)**: -- ⚠️ 仔细阅读【上一章结尾内容】,绝对不要重复叙述已经发生的事件 -- ⚠️ 本章必须从新的情节点开始,不要重新描述上一章的场景或对话 -- ⚠️ 如果上一章以某个动作或对话结束,本章应该从紧接着的下一个动作或反应开始 -- ⚠️ 角色状态应该延续而非重置,不要让角色重新经历上一章已经经历的心理过程 -- ⚠️ 场景转换要明确,如果是同一场景的延续,要从不同的视角或新的细节切入 + # 单个角色生成提示词 V2(RTCO框架) + SINGLE_CHARACTER_GENERATION = """ +你是专业的角色设定师,擅长创建立体饱满的小说角色。 + -3. **情节推进**: -- 严格按照本章大纲(expansion_plan)展开情节 -- 推动故事向前发展,不要原地踏步 -- 保持与全书大纲的一致性 -- 确保本章有独特的叙事价值,而非前章内容的重复 - -4. **角色一致性**: -- 符合角色性格设定 -- 延续角色在前文中的成长和变化 -- 保持角色关系的连贯性 - -5. **写作风格**: -- 使用{narrative_perspective}视角 -- 字数要求:目标{target_word_count}字,不得低于{target_word_count}字,必须严格控制在{target_word_count}至{max_word_count}字之间 -- 语言自然流畅,避免AI痕迹 -- 体现世界观特色 - -6. **承上启下**: - - 开头自然衔接上一章结尾(但不重复上一章内容) - -7. **记忆系统使用指南**: - - **最近章节记忆**:保持情节连贯,注意角色状态和剧情发展 - - **语义相关记忆**:参考相似情节的处理方式 - - **未完结伏笔**:适当时机可以回收伏笔,制造呼应效果 - - **角色状态记忆**:确保角色行为符合其发展轨迹 - - **重要情节点**:与关键剧情保持一致 - -请直接输出章节正文内容,不要包含章节标题和其他说明文字。""" - - - # 单个角色生成提示词 - SINGLE_CHARACTER_GENERATION = """你是一位专业的角色设定师。请根据以下信息创建一个立体饱满的小说角色。 + +【设计任务】 +根据用户需求和项目上下文,创建一个完整的角色设定。 + + +【项目上下文】 {project_context} +【用户需求】 {user_input} + -请生成一个完整的角色卡片,包含以下所有信息: + +【输出格式】 +生成完整的角色卡片JSON对象: -1. **基本信息**: - - 姓名:如果用户未提供,请生成一个符合世界观的名字 - - 年龄:具体数字或年龄段 - - 性别:男/女/其他 - -2. **外貌特征**(100-150字): - - 身高体型、面容特征、着装风格 - - 要符合角色定位和世界观设定 - -3. **性格特点**(150-200字): - - 核心性格特质(至少3个) - - 优点和缺点 - - 特殊习惯或癖好 - - 性格要有复杂性和矛盾性 - -4. **背景故事**(200-300字): - - 家庭背景 - - 成长经历 - - 重要转折事件 - - 如何与项目主题关联 - - 融入用户提供的背景设定 - -5. **人际关系**: - - 与现有角色的关系(如果有) - - 重要的人际纽带 - - 社会地位和人脉 - -6. **特殊能力/特长**: - - 擅长的领域 - - 特殊技能或知识 - - 符合世界观设定 - -7. **职业信息**(重要 - 如果项目上下文中包含职业列表): - - 仔细查看项目上下文中的"可用主职业"和"可用副职业"列表 - - 主职业:必须从"可用主职业"列表中选择一个最符合角色设定的职业,填写其职业名称(name字段) - - 主职业阶段:根据职业的阶段信息和角色实力,设定合理的当前阶段(1到职业的max_stage) - - 副职业:可以从"可用副职业"列表中选择0-2个,每个包含职业名称和阶段 - - 如果项目没有职业列表,则不需要填写career_info字段 - - 职业选择必须与角色的背景故事、能力特点和故事定位高度契合 - - ⚠️ 重要:请填写职业的名称而非ID,系统会自动匹配 - -**重要格式要求:** -1. 只返回纯JSON格式,不要包含任何markdown标记、代码块标记或其他说明文字 -2. JSON字符串值的内容描述中严禁使用任何特殊符号(包括中文引号、英文引号、方括号、书名号等) -3. 所有专有名词直接书写,不使用任何符号包裹 - -请严格按照以下JSON格式返回: {{ - "name": "角色姓名", - "age": "年龄", - "gender": "性别", - "appearance": "外貌描述(100-150字)", - "personality": "性格特点(150-200字)", - "background": "背景故事(200-300字)", + "name": "角色姓名(如用户未提供则生成符合世界观的名字)", + "age": "年龄(具体数字或年龄段)", + "gender": "男/女/其他", + "appearance": "外貌描述(100-150字):身高体型、面容特征、着装风格", + "personality": "性格特点(150-200字):核心性格特质、优缺点、特殊习惯", + "background": "背景故事(200-300字):家庭背景、成长经历、重要转折、与主题关联", "traits": ["特长1", "特长2", "特长3"], - - "relationships_text": "人际关系的文字描述(用于显示)", - + "relationships_text": "人际关系的自然语言描述", "relationships": [ {{ "target_character_name": "已存在的角色名称", - "relationship_type": "关系类型(如:师父、朋友、敌人、父亲、母亲等)", + "relationship_type": "关系类型", "intimacy_level": 75, - "description": "这段关系的详细描述", + "description": "关系的详细描述", "started_at": "关系开始的故事时间点(可选)" }} ], - "organization_memberships": [ {{ "organization_name": "已存在的组织名称", @@ -654,214 +652,262 @@ class PromptService: "status": "active" }} ], - "career_info": {{ - "main_career_name": "从项目上下文的可用主职业列表中复制的职业名称", + "main_career_name": "从可用主职业列表中选择的职业名称", "main_career_stage": 5, "sub_careers": [ {{ - "career_name": "从项目上下文的可用副职业列表中复制的职业名称", + "career_name": "从可用副职业列表中选择的职业名称", "stage": 3 }} ] }} }} -**关系类型参考(请从中选择或自定义):** -- 家族关系:父亲、母亲、兄弟、姐妹、子女、配偶、恋人 -- 社交关系:师父、徒弟、朋友、同学、同事、邻居、知己 -- 职业关系:上司、下属、合作伙伴 -- 敌对关系:敌人、仇人、竞争对手、宿敌 +【职业信息说明】 +如果项目上下文包含职业列表: +- 主职业:从"可用主职业"列表中选择最符合角色的职业 +- 主职业阶段:根据角色实力设定合理阶段(1到max_stage) +- 副职业:可选择0-2个副职业 +- ⚠️ 填写职业名称而非ID,系统会自动匹配 +- 职业选择必须与角色背景、能力和定位高度契合 -**重要说明:** -1. relationships数组:只包含与上面列出的已存在角色的关系,通过target_character_name匹配 -2. organization_memberships数组:只包含与上面列出的已存在组织的关系 -3. intimacy_level是-100到100的整数(负值表示敌对、仇恨等关系),loyalty是0-100的整数 -4. 如果没有关系或组织,对应数组为空[] -5. relationships_text是自然语言描述,用于展示给用户看 +【关系类型参考】 +- 家族:父亲、母亲、兄弟、姐妹、子女、配偶、恋人 +- 社交:师父、徒弟、朋友、同学、同事、邻居、知己 +- 职业:上司、下属、合作伙伴 +- 敌对:敌人、仇人、竞争对手、宿敌 -**角色设定要求:** -- 角色要符合项目的世界观和主题 -- 如果是主角,要有明确的成长空间和目标动机 -- 如果是反派,要有合理的动机,不能脸谱化 -- 配角要有独特性,不能是工具人 -- 所有设定要为故事服务 +【数值范围】 +- intimacy_level:-100到100(负值表示敌对) +- loyalty:0到100 +- rank:0到10 + -再次强调: -1. 只返回纯JSON对象,不要有```json```这样的标记 -2. 所有内容描述中严禁使用任何特殊符号 -3. 不要有任何额外的文字说明""" + +【必须遵守】 +✅ 符合世界观:角色设定与项目世界观一致 +✅ 主题关联:背景故事与项目主题关联 +✅ 立体饱满:性格复杂有矛盾性,不脸谱化 +✅ 为故事服务:设定要推动剧情发展 +✅ 职业匹配:职业选择与角色高度契合 - # 单个组织生成提示词 - SINGLE_ORGANIZATION_GENERATION = """你是一位专业的组织设定师。请根据以下信息创建一个完整的组织/势力设定。 +【角色定位要求】 +✅ 主角:有成长空间和目标动机 +✅ 反派:有合理动机,不脸谱化 +✅ 配角:有独特性,不是工具人 +【关系约束】 +✅ relationships只引用已存在的角色 +✅ organization_memberships只引用已存在的组织 +✅ 无关系或组织时对应数组为空[] + +【格式约束】 +✅ 纯JSON对象输出,无markdown标记 +✅ 内容描述中严禁使用特殊符号 +✅ 专有名词直接书写 + +【禁止事项】 +❌ 输出markdown或代码块标记 +❌ 在描述中使用特殊符号(引号、方括号等) +❌ 引用不存在的角色或组织 +❌ 脸谱化的角色设定 +""" + + # 单个组织生成提示词 V2(RTCO框架) + SINGLE_ORGANIZATION_GENERATION = """ +你是专业的组织设定师,擅长创建完整的组织/势力设定。 + + + +【设计任务】 +根据用户需求和项目上下文,创建一个完整的组织/势力设定。 + + + +【项目上下文】 {project_context} +【用户需求】 {user_input} + -请生成一个完整的组织设定,包含以下所有信息: + +【输出格式】 +生成完整的组织设定JSON对象: -1. **基本信息**: - - 组织名称:如果用户未提供,请生成一个符合世界观的名称 - - 组织类型:如帮派、公司、门派、学院、政府机构、宗教组织等 - - 成立时间:具体时间或时间段 - -2. **组织特性**(150-200字): - - 组织的核心理念和行事风格 - - 组织文化和价值观 - - 运作方式和管理模式 - - 特殊传统或规矩 - -3. **组织背景**(200-300字): - - 建立历史和起源 - - 发展历程和重要事件 - - 目前的地位和影响力 - - 如何与项目主题关联 - - 融入用户提供的背景设定 - -4. **外在表现**(100-150字): - - 总部或主要据点位置 - - 标志性建筑或场所 - - 组织标志、徽章、制服等 - - 可辨识的外在特征 - -5. **组织目的/宗旨**: - - 明确的组织目标 - - 长期愿景 - - 行动准则 - -6. **势力等级**: - - 在世界中的影响力(0-100) - - 综合实力评估 - -7. **所在地点**: - - 主要活动区域 - - 势力范围 - -**重要格式要求:** -1. 只返回纯JSON格式,不要包含任何markdown标记、代码块标记或其他说明文字 -2. JSON字符串值的内容描述中严禁使用任何特殊符号(包括中文引号、英文引号、方括号、书名号等) -3. 所有专有名词直接书写,不使用任何符号包裹 - -请严格按照以下JSON格式返回: {{ - "name": "组织名称", + "name": "组织名称(如用户未提供则生成符合世界观的名称)", "is_organization": true, - "organization_type": "组织类型", - "personality": "组织特性(150-200字)", - "background": "组织背景(200-300字)", - "appearance": "外在表现(100-150字)", - "organization_purpose": "组织目的和宗旨", + "organization_type": "组织类型(帮派/公司/门派/学院/政府机构/宗教组织等)", + "personality": "组织特性(150-200字):核心理念、行事风格、文化价值观、运作方式", + "background": "组织背景(200-300字):建立历史、发展历程、重要事件、当前地位", + "appearance": "外在表现(100-150字):总部位置、标志性建筑、组织标志、制服等", + "organization_purpose": "组织目的和宗旨:明确目标、长期愿景、行动准则", "power_level": 75, - "location": "所在地点", + "location": "所在地点:主要活动区域、势力范围", "motto": "组织格言或口号", "traits": ["特征1", "特征2", "特征3"], "color": "组织代表颜色(如:深红色、金色、黑色等)", "organization_members": ["重要成员1", "重要成员2", "重要成员3"] }} -**组织设定要求:** -- 组织要符合项目的世界观和主题 -- 目标和行动要合理,不能过于理想化或脸谱化 -- 要有存在的必要性,能推动故事发展 -- 内部要有层级和结构 -- 与其他势力要有互动关系 +【字段说明】 +- power_level:0-100的整数,表示在世界中的影响力 +- organization_members:组织内重要成员名字列表(可关联已有角色) +- 成立时间:在background中描述 + -**说明**: -1. power_level是0-100的整数,表示组织在世界中的影响力 -2. organization_members是组织内重要成员的名字列表(如果已有角色,可以关联) -3. 所有文本描述要详细具体,避免空泛 + +【必须遵守】 +✅ 符合世界观:组织设定与项目世界观一致 +✅ 主题关联:背景与项目主题关联 +✅ 推动剧情:组织能推动故事发展 +✅ 有层级结构:内部有明确的层级和结构 +✅ 势力互动:与其他势力有互动关系 -再次强调: -1. 只返回纯JSON对象,不要有```json```这样的标记 -2. 所有内容描述中严禁使用任何特殊符号 -3. 不要有任何额外的文字说明""" +【组织定位要求】 +✅ 有存在必要性:不是可有可无的背景板 +✅ 目标合理:不过于理想化或脸谱化 +✅ 具体细节:描述详细具体,避免空泛 - # 情节分析提示词 - PLOT_ANALYSIS = """你是一位专业的小说编辑和剧情分析师。请深度分析以下章节内容: +【格式约束】 +✅ 纯JSON对象输出,无markdown标记 +✅ 内容描述中严禁使用特殊符号 +✅ 专有名词直接书写 -**章节信息:** -- 章节: 第{chapter_number}章 -- 标题: {title} -- 字数: {word_count}字 +【禁止事项】 +❌ 输出markdown或代码块标记 +❌ 在描述中使用特殊符号(引号、方括号等) +❌ 过于理想化或脸谱化的设定 +❌ 空泛的描述 +""" -**章节内容:** + # 情节分析提示词 V2(RTCO框架) + PLOT_ANALYSIS = """ +你是专业的小说编辑和剧情分析师,擅长深度剖析章节内容。 + + + +【分析任务】 +全面分析第{chapter_number}章《{title}》的剧情要素、钩子、伏笔、冲突和角色发展。 + + + +【章节信息】 +章节:第{chapter_number}章 +标题:{title} +字数:{word_count}字 + +【章节内容】 {content} + ---- + +【分析维度】 -**分析任务:** -请从专业编辑的角度,全面分析这一章节: +**1. 剧情钩子 (Hooks)** +识别吸引读者的关键元素: +- 悬念钩子:未解之谜、疑问、谜团 +- 情感钩子:引发共鸣的情感点 +- 冲突钩子:矛盾对抗、紧张局势 +- 认知钩子:颠覆认知的信息 -### 1. 剧情钩子 (Hooks) - 吸引读者的元素 -识别能够吸引读者继续阅读的关键元素: -- **悬念钩子**: 未解之谜、疑问、谜团 -- **情感钩子**: 引发共鸣的情感点、触动心弦的时刻 -- **冲突钩子**: 矛盾对抗、紧张局势 -- **认知钩子**: 颠覆认知的信息、惊人真相 - -每个钩子需要: +每个钩子需要: - 类型分类 - 具体内容描述 - 强度评分(1-10) - 出现位置(开头/中段/结尾) -- **关键词**: 【必填】从章节原文中逐字复制一段关键文本(8-25字),必须是原文中真实存在的连续文字,用于在文本中精确定位。不要概括或改写,必须原样复制! +- **关键词**:【必填】从原文逐字复制8-25字的文本片段,用于精确定位 -### 2. 伏笔分析 (Foreshadowing) -- **埋下的新伏笔**: 描述内容、预期作用、隐藏程度(1-10) -- **回收的旧伏笔**: 呼应哪一章、回收效果评分 -- **伏笔质量**: 巧妙性和合理性评估 -- **关键词**: 【必填】从章节原文中逐字复制一段关键文本(8-25字),必须是原文中真实存在的连续文字,用于在文本中精确定位。不要概括或改写,必须原样复制! +**2. 伏笔分析 (Foreshadowing)** +- 埋下的新伏笔:内容、预期作用、隐藏程度(1-10) +- 回收的旧伏笔:呼应哪一章、回收效果 +- 伏笔质量:巧妙性和合理性 +- **关键词**:【必填】从原文逐字复制8-25字 -### 3. 冲突分析 (Conflict) -- 冲突类型: 人与人/人与己/人与环境/人与社会 -- 冲突各方及其立场 -- 冲突强度评分(1-10) -- 冲突解决进度(0-100%) +**3. 冲突分析 (Conflict)** +- 冲突类型:人与人/人与己/人与环境/人与社会 +- 冲突各方及立场 +- 冲突强度(1-10) +- 解决进度(0-100%) -### 4. 情感曲线 (Emotional Arc) -- 主导情绪(最多10个字): 紧张/温馨/悲伤/激昂/平静/压抑/欢快/恐惧/期待/失落等 +**4. 情感曲线 (Emotional Arc)** +- 主导情绪(最多10字) - 情感强度(1-10) -- 情绪变化轨迹描述 +- 情绪变化轨迹 -### 5. 角色状态追踪 (Character Development) -对每个出场角色分析: +**5. 角色状态追踪 (Character Development)** +对每个出场角色分析: - 心理状态变化(前→后) - 关系变化 - 关键行动和决策 - 成长或退步 -- **💼 职业变化(重要 - 新增)**: - - 如果角色在本章有职业相关的进展或突破,请详细分析 - - 主职业阶段变化: 是否晋级、突破或降级(用整数表示变化量,如: +1表示晋升一阶段,-1表示退步一阶段,0表示无变化) - - 副职业变化: 是否学习新的副职业或副职业有所精进 - - 职业突破描述: 具体的突破过程、原因和标志性事件 - - 注意:只有当章节中明确描述了职业相关的成长、突破或变化时才填写此项 +- **💼 职业变化(可选)**: + - 仅当章节明确描述职业进展时填写 + - main_career_stage_change: 整数(+1晋升/-1退步/0无变化) + - sub_career_changes: 副职业变化数组 + - new_careers: 新获得职业 + - career_breakthrough: 突破过程描述 -### 6. 关键情节点 (Plot Points) -列出3-5个核心情节点: +**6. 关键情节点 (Plot Points)** +列出3-5个核心情节点: - 情节内容 - 类型(revelation/conflict/resolution/transition) - 重要性(0.0-1.0) - 对故事的影响 -- **关键词**: 【必填】从章节原文中逐字复制一段关键文本(8-25字),必须是原文中真实存在的连续文字,用于在文本中精确定位。不要概括或改写,必须原样复制! +- **关键词**:【必填】从原文逐字复制8-25字 -### 7. 场景与节奏 +**7. 场景与节奏** - 主要场景 - 叙事节奏(快/中/慢) -- 对话与描写的比例 +- 对话与描写比例 -### 8. 质量评分 -- 节奏把控: 1-10分 -- 吸引力: 1-10分 -- 连贯性: 1-10分 -- 整体质量: 1-10分 +**8. 质量评分(支持小数,严格区分度)** +评分范围:1.0-10.0,支持一位小数(如 6.5、7.8) +每个维度必须根据以下标准严格评分,避免所有内容都打中等分数: -### 9. 改进建议 -提供3-5条具体的改进建议 +**节奏把控 (pacing)**: +- 1.0-3.9(差):节奏混乱,该快不快该慢不慢;场景切换生硬;大段无意义描写拖沓 +- 4.0-5.9(中下):节奏基本可读但有明显问题;部分场景过于冗长或仓促 +- 6.0-7.9(中上):节奏整体流畅,偶有小问题;张弛有度但不够精妙 +- 8.0-9.4(优秀):节奏把控精准,高潮迭起;场景切换自然,详略得当 +- 9.5-10.0(完美):节奏大师级,每个段落都恰到好处 ---- +**吸引力 (engagement)**: +- 1.0-3.9(差):内容乏味,缺乏钩子;读者难以继续阅读 +- 4.0-5.9(中下):有基本情节但缺乏亮点;钩子设置生硬或缺失 +- 6.0-7.9(中上):有一定吸引力,钩子有效但不够巧妙 +- 8.0-9.4(优秀):引人入胜,钩子设置精妙;让人欲罢不能 +- 9.5-10.0(完美):极具吸引力,每个段落都有阅读动力 -**输出格式(纯JSON,不要markdown标记):** +**连贯性 (coherence)**: +- 1.0-3.9(差):逻辑混乱,前后矛盾;角色行为不合理 +- 4.0-5.9(中下):基本连贯但有明显漏洞;部分情节衔接生硬 +- 6.0-7.9(中上):整体连贯,偶有小瑕疵;角色行为基本合理 +- 8.0-9.4(优秀):逻辑严密,衔接自然;角色行为高度一致 +- 9.5-10.0(完美):无懈可击的连贯性 + +**整体质量 (overall)**: +- 计算公式:(pacing + engagement + coherence) / 3,保留一位小数 +- 可根据综合印象±0.5调整,必须与各项分数保持一致性 + +**9. 改进建议(与分数关联)** +建议数量必须与整体质量分数关联: +- overall < 4.0:必须提供4-5条具体改进建议,指出严重问题 +- overall 4.0-5.9:必须提供3-4条改进建议,指出主要问题 +- overall 6.0-7.9:提供1-2条优化建议,指出可提升之处 +- overall ≥ 8.0:提供0-1条锦上添花的建议 + +每条建议必须: +- 指出具体问题位置或类型 +- 说明为什么是问题 +- 给出明确的改进方向 + + + +【输出格式】 +返回纯JSON对象(无markdown标记): {{ "hooks": [ @@ -870,7 +916,7 @@ class PromptService: "content": "具体描述", "strength": 8, "position": "中段", - "keyword": "必须从原文逐字复制的文本片段" + "keyword": "从原文逐字复制的8-25字文本" }} ], "foreshadows": [ @@ -880,7 +926,7 @@ class PromptService: "strength": 7, "subtlety": 8, "reference_chapter": null, - "keyword": "必须从原文逐字复制的文本片段" + "keyword": "从原文逐字复制的8-25字文本" }} ], "conflict": {{ @@ -903,7 +949,13 @@ class PromptService: "state_after": "坚定", "psychological_change": "心理变化描述", "key_event": "触发事件", - "relationship_changes": {{"李四": "关系改善"}} + "relationship_changes": {{"李四": "关系改善"}}, + "career_changes": {{ + "main_career_stage_change": 1, + "sub_career_changes": [{{"career_name": "炼丹", "stage_change": 1}}], + "new_careers": [], + "career_breakthrough": "突破描述" + }} }} ], "plot_points": [ @@ -912,7 +964,7 @@ class PromptService: "type": "revelation", "importance": 0.9, "impact": "推动故事发展", - "keyword": "必须从原文逐字复制的文本片段" + "keyword": "从原文逐字复制的8-25字文本" }} ], "scenes": [ @@ -926,36 +978,61 @@ class PromptService: "dialogue_ratio": 0.4, "description_ratio": 0.3, "scores": {{ - "pacing": 8, - "engagement": 9, - "coherence": 8, - "overall": 8.5 + "pacing": 6.5, + "engagement": 5.8, + "coherence": 7.2, + "overall": 6.5, + "score_justification": "节奏整体流畅但中段略显拖沓;钩子设置有效但不够巧妙;逻辑连贯无明显漏洞" }}, "plot_stage": "发展", "suggestions": [ - "具体建议1", - "具体建议2" + "【节奏问题】第三场景的心理描写过长(约500字),建议精简至200字以内,保留核心情感即可", + "【吸引力不足】章节中段缺乏有效钩子,建议在主角发现线索后增加一个小悬念" ] }} + -**重要提示:** -1. 每个钩子、伏笔、情节点的keyword字段是必填的,不能为空 -2. keyword必须是从章节原文中逐字复制的文本,长度8-25字 -3. keyword用于在前端标注文本位置,所以必须能在原文中精确找到 -4. 不要使用概括性语句或改写后的文字作为keyword -5. **职业变化字段说明**: - - career_changes是可选字段,只有当章节中明确描述了职业相关变化时才填写 - - main_career_stage_change: 整数,表示主职业阶段变化量(+1=晋升一阶,-1=退步一阶,0=无变化) - - sub_career_changes: 数组,包含副职业的变化,每项包含career_name(职业名称)和stage_change(阶段变化量) - - new_careers: 数组,包含新获得的职业名称(如果有) - - career_breakthrough: 字符串,描述职业突破的具体过程和标志性事件 - - 如果角色没有职业变化,可以不填写career_changes字段或设为空对象 + +【必须遵守】 +✅ keyword字段必填:钩子、伏笔、情节点的keyword不能为空 +✅ 逐字复制:keyword必须从原文复制,长度8-25字 +✅ 精确定位:keyword能在原文中精确找到 +✅ 职业变化可选:仅当章节明确描述时填写 -只返回JSON,不要其他说明。""" +【评分约束 - 严格执行】 +✅ 严格按评分标准打分,支持小数(如6.5、7.2、8.3) +✅ 不要默认给7.0-8.0分,差的内容必须给低分(1.0-5.0),好的内容才给高分(8.0-10.0) +✅ score_justification必填:简要说明各项评分的依据 +✅ 建议数量必须与overall分数关联: + - overall≤4.0 → 4-5条建议 + - overall 4.0-6.0 → 3-4条建议 + - overall 6.0-8.0 → 1-2条建议 + - overall≥8.0 → 0-1条建议 +✅ 每条建议必须标注问题类型(如【节奏问题】【描写不足】等) - # 大纲单批次展开提示词 - OUTLINE_EXPAND_SINGLE = """你是专业的小说情节架构师。请分析以下大纲节点,将其展开为 {target_chapter_count} 个章节的详细规划。 +【禁止事项】 +❌ keyword使用概括或改写的文字 +❌ 输出markdown标记 +❌ 遗漏必填的keyword字段 +❌ 无根据地添加职业变化 +❌ 所有章节都打7-8分的"安全分" +❌ 高分章节给大量建议,或低分章节不给建议 +""" + # 大纲单批次展开提示词 V2(RTCO框架) + OUTLINE_EXPAND_SINGLE = """ +你是专业的小说情节架构师,擅长将大纲节点展开为详细章节规划。 + + + +【展开任务】 +将第{outline_order_index}节大纲《{outline_title}》展开为{target_chapter_count}个章节的详细规划。 + +【展开策略】 +{strategy_instruction} + + + 【项目信息】 小说名称:{project_title} 类型:{project_genre} @@ -966,90 +1043,105 @@ class PromptService: 时间背景:{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. **如何避免剧情越界**: - - 如果当前大纲描述"主角遇到困境",展开时应详写困境的发现、分析、情感冲击等 - - 不要直接写到"解决困境",除非原大纲明确包含解决过程 - - 如果看到【后一节】的内容,那些是禁区,绝不提前展开 - -4. **🔴 相邻章节差异化约束(重要 - 防止内容重复)**: - - 每个章节必须有独特的开场方式(不同的场景、时间点、角色状态) - - 每个章节必须有独特的结束方式(不同的悬念、转折、情感收尾) - - key_events在相邻章节间绝不允许重叠,每章的关键事件必须完全不同 - - plot_summary必须描述该章的独特内容,不能与其他章节雷同 - - 即使是同一事件的不同阶段,也要明确区分"前、中、后"的具体内容 - - 例如:第1章可以是"发现线索",第2章必须是"追踪调查"而非再次"发现线索" - -【任务要求】 -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} -5. 确保章节间: - - 衔接自然流畅(每章从不同的起点开始) - - 剧情递进合理(但不超出当前大纲边界) - - 节奏张弛有度 - - 每章都有明确且独特的叙事价值(不重复前一章的内容) - - 最后一章结束时,剧情发展程度应恰好完成当前大纲描述的内容,不多不少 - - **关键事件无重叠**:仔细检查相邻章节的key_events,确保没有任何重复或雷同 - + 【输出格式】 -请严格按照以下JSON数组格式输出,不要添加任何其他文字: +返回{target_chapter_count}个章节规划的JSON数组: + [ {{ "sub_index": 1, - "title": "章节标题", - "plot_summary": "该章详细剧情摘要...", + "title": "章节标题(体现核心冲突或情感)", + "plot_summary": "剧情摘要(200-300字):详细描述该章发生的事件,仅限当前大纲内容", "key_events": ["关键事件1", "关键事件2", "关键事件3"], "character_focus": ["角色A", "角色B"], - "emotional_tone": "情感基调", - "narrative_goal": "叙事目标", - "conflict_type": "冲突类型", + "emotional_tone": "情感基调(如:紧张、温馨、悲伤)", + "narrative_goal": "叙事目标(该章要达成的叙事效果)", + "conflict_type": "冲突类型(如:内心挣扎、人际冲突)", "estimated_words": 3000{scene_field} }} ] -请开始分析并生成章节规划: -""" +【格式规范】 +- 纯JSON数组输出,无其他文字 +- 内容描述中严禁使用特殊符号 + - # 大纲分批展开提示词 - OUTLINE_EXPAND_MULTI = """你是专业的小说情节架构师。请继续分析以下大纲节点,将其展开为第{start_index}-{end_index}节(共{target_chapter_count}个章节)的详细规划。 + +【⚠️ 内容边界约束 - 必须严格遵守】 +✅ 只能展开当前大纲节点的内容 +✅ 深化当前大纲,而非跨越到后续 +✅ 放慢叙事节奏,充分体验当前阶段 +❌ 绝对不能推进到后续大纲内容 +❌ 不要让剧情快速推进 +❌ 不要提前展开【后一节】的内容 + +【展开原则】 +✅ 将单一事件拆解为多个细节丰富的章节 +✅ 深入挖掘情感、心理、环境、对话 +✅ 每章是当前大纲内容的不同侧面或阶段 + +【🔴 相邻章节差异化约束(防止重复)】 +✅ 每章有独特的开场方式(不同场景、时间点、角色状态) +✅ 每章有独特的结束方式(不同悬念、转折、情感收尾) +✅ key_events在相邻章节间绝不重叠 +✅ plot_summary描述该章独特内容,不与其他章雷同 +✅ 同一事件的不同阶段要明确区分"前、中、后" + +【章节间要求】 +✅ 衔接自然流畅(每章从不同起点开始) +✅ 剧情递进合理(但不超出当前大纲边界) +✅ 节奏张弛有度 +✅ 每章有明确且独特的叙事价值 +✅ 最后一章结束时恰好完成当前大纲内容 +✅ 关键事件无重叠:检查相邻章节key_events + +【禁止事项】 +❌ 输出非JSON格式 +❌ 剧情越界到后续大纲 +❌ 相邻章节内容重复 +❌ 关键事件雷同 +""" + + # 大纲分批展开提示词 V2(RTCO框架) + OUTLINE_EXPAND_MULTI = """ +你是专业的小说情节架构师,擅长分批展开大纲节点。 + + + +【展开任务】 +继续展开第{outline_order_index}节大纲《{outline_title}》,生成第{start_index}-{end_index}节(共{target_chapter_count}个章节)的详细规划。 + +【分批说明】 +- 这是整个展开的一部分 +- 必须与前面已生成的章节自然衔接 +- 从第{start_index}节开始编号 +- 继续深化当前大纲内容 + +【展开策略】 +{strategy_instruction} + + + 【项目信息】 小说名称:{project_title} 类型:{project_genre} @@ -1060,76 +1152,37 @@ class PromptService: 时间背景:{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. **展开原则**: - - 将当前大纲的单一事件拆解为多个细节丰富的章节 - - 深入挖掘情感、心理、环境、对话等细节 - - 放慢叙事节奏,让读者充分体验当前阶段的剧情 - - 每个章节都应该是当前大纲内容的不同侧面或阶段 - -4. **🔴 相邻章节差异化约束(重要 - 防止内容重复)**: - - 每个章节必须有独特的开场方式(不同的场景、时间点、角色状态) - - 每个章节必须有独特的结束方式(不同的悬念、转折、情感收尾) - - key_events在相邻章节间绝不允许重叠,每章的关键事件必须完全不同 - - plot_summary必须描述该章的独特内容,不能与其他章节雷同 - - 特别注意与【已生成的前序章节】的差异化,避免重复已有内容 - - 即使是同一事件的不同阶段,也要明确区分"前、中、后"的具体内容 - -【任务要求】 -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} -5. 确保章节间: - - 与前面章节衔接自然流畅(每章从不同的起点开始) - - 剧情递进合理(但不超出当前大纲边界) - - 节奏张弛有度 - - 每章都有明确且独特的叙事价值(不重复前面章节的内容) - - **关键事件无重叠**:仔细检查本批次章节的key_events,以及与前序章节的key_events,确保没有任何重复或雷同 - + 【输出格式】 -请严格按照以下JSON数组格式输出,不要添加任何其他文字: +返回第{start_index}-{end_index}节章节规划的JSON数组(共{target_chapter_count}个对象): + [ {{ "sub_index": {start_index}, "title": "章节标题", - "plot_summary": "该章详细剧情摘要...", + "plot_summary": "剧情摘要(200-300字):详细描述该章发生的事件", "key_events": ["关键事件1", "关键事件2", "关键事件3"], "character_focus": ["角色A", "角色B"], "emotional_tone": "情感基调", @@ -1139,19 +1192,85 @@ class PromptService: }} ] -请开始分析并生成第{start_index}-{end_index}节的章节规划: -""" +【格式规范】 +- 纯JSON数组输出,无其他文字 +- 内容描述中严禁使用特殊符号 +- sub_index从{start_index}开始 + - # 章节重写系统提示词 - CHAPTER_REGENERATION_SYSTEM = """你是一位经验丰富的专业小说编辑和作家。现在需要根据反馈意见重新创作一个章节。 + +【⚠️ 内容边界约束】 +✅ 只能展开当前大纲节点的内容 +✅ 深化当前大纲,而非跨越到后续 +✅ 放慢叙事节奏 -你的任务是: -1. 仔细理解原始章节的内容和意图 -2. 认真分析所有的修改要求 -3. 在保持故事连贯性的前提下,创作一个改进后的新版本 -4. 确保新版本在艺术性和可读性上都有明显提升 +❌ 绝对不能推进到后续大纲内容 +❌ 不要让剧情快速推进 ---- +【分批连续性约束】 +✅ 与前面已生成章节自然衔接 +✅ 从第{start_index}节开始编号 +✅ 保持叙事连贯性 + +【🔴 相邻章节差异化约束(防止重复)】 +✅ 每章有独特的开场和结束方式 +✅ key_events在相邻章节间绝不重叠 +✅ plot_summary描述该章独特内容 +✅ 特别注意与前序章节的差异化 +✅ 避免重复已有内容 + +【章节间要求】 +✅ 与前面章节衔接自然流畅 +✅ 剧情递进合理(但不超出当前大纲边界) +✅ 节奏张弛有度 +✅ 每章有明确且独特的叙事价值 +✅ 关键事件无重叠:检查本批次和前序章节的key_events + +【禁止事项】 +❌ 输出非JSON格式 +❌ 剧情越界到后续大纲 +❌ 相邻章节内容重复 +❌ 与前序章节key_events雷同 +""" + + # 章节重写系统提示词 V2(RTCO框架) + CHAPTER_REGENERATION_SYSTEM = """ +你是经验丰富的专业小说编辑和作家,擅长根据反馈意见重新创作章节。 +你的任务是根据修改指令,对原始章节进行深度改写和优化。 + + + +【重写任务】 +1. 仔细理解原始章节的内容、情节走向和叙事意图 +2. 认真分析所有的修改要求,包括AI分析建议和用户自定义指令 +3. 针对每一条修改建议,在新版本中进行具体改进 +4. 在保持故事连贯性和角色一致性的前提下,创作改进后的新版本 +5. 确保新版本在艺术性、可读性和叙事质量上都有明显提升 + + + +【改写原则】 +- **问题导向**:针对修改指令中指出的每个问题进行改进 +- **保持精华**:保留原章节中优秀的描写、对话和情节设计 +- **深化细节**:增强场景描写、情感渲染和人物刻画 +- **节奏优化**:调整叙事节奏,避免拖沓或过快 +- **风格一致**:如果提供了写作风格要求,必须严格遵循 + +【重点关注】 +- 如果修改指令提到"节奏"问题,重点调整叙事速度和场景切换 +- 如果修改指令提到"情感"问题,重点深化人物内心戏和情感表达 +- 如果修改指令提到"描写"问题,重点丰富环境和动作细节 +- 如果修改指令提到"对话"问题,重点让对话更自然、更有个性 +- 如果修改指令提到"冲突"问题,重点强化矛盾和戏剧张力 + + + +【输出规范】 +直接输出重写后的章节正文内容。 +- 不要包含章节标题、序号或其他元信息 +- 不要输出任何解释、注释或创作说明 +- 从故事内容直接开始,保持叙事的连贯性 + """ # MCP工具测试提示词 MCP_TOOL_TEST = """你是MCP插件测试助手,需要测试插件 '{plugin_name}' 的功能。 @@ -1317,19 +1436,32 @@ class PromptService: 请查询最关键的1个问题(不要超过1个)。""" - # 自动角色引入 - 预测性分析提示词(方案A) - AUTO_CHARACTER_ANALYSIS = """你是专业的小说角色设计顾问。请根据即将续写的剧情方向,预测是否需要引入新角色。 + # 自动角色引入 - 预测性分析提示词 V2(RTCO框架) + AUTO_CHARACTER_ANALYSIS = """ +你是专业的小说角色设计顾问,擅长预测剧情发展对角色的需求。 + + +【分析任务】 +预测在接下来的{chapter_count}章续写中,根据剧情发展方向和阶段,是否需要引入新角色。 + +【重要说明】 +这是预测性分析,而非基于已生成内容的事后分析。 + + + 【项目信息】 -- 书名:{title} -- 类型:{genre} -- 主题:{theme} +书名:{title} +类型:{genre} +主题:{theme} 【世界观】 -- 时间背景:{time_period} -- 地理位置:{location} -- 氛围基调:{atmosphere} +时间背景:{time_period} +地理位置:{location} +氛围基调:{atmosphere} + + 【已有角色】 {existing_characters} @@ -1341,42 +1473,48 @@ class PromptService: - 续写数量:{chapter_count}章 - 剧情阶段:{plot_stage} - 发展方向:{story_direction} + -【预测性分析任务】 -请预测在接下来的{chapter_count}章中,根据剧情发展方向和阶段,是否需要引入新角色。 + +【预测分析维度】 -**分析要点:** -1. **剧情需求预测**:根据发展方向,哪些场景、冲突需要新角色参与 -2. **角色充分性**:现有角色是否足以支撑即将发生的剧情 -3. **引入时机**:新角色应该在哪个章节登场最合适 -4. **重要性判断**:新角色对后续剧情的影响程度 +**1. 剧情需求预测** +根据发展方向,哪些场景、冲突需要新角色参与? -**预测依据:** +**2. 角色充分性** +现有角色是否足以支撑即将发生的剧情? + +**3. 引入时机** +新角色应该在哪个章节登场最合适? + +**4. 重要性判断** +新角色对后续剧情的影响程度如何? + +【预测依据】 - 剧情阶段的典型角色需求(如:高潮阶段可能需要强力对手) - 故事发展方向的逻辑需要(如:进入新地点需要当地角色) - 冲突升级的角色需求(如:更强的反派、意外的盟友) - 世界观扩展的需要(如:新组织、新势力的代表) + -**如果需要新角色,请详细说明:** -- 角色定位和作用 -- 建议的角色类型和重要性 -- 预计登场时机 -- 与现有角色的潜在关系 + +【输出格式】 +返回纯JSON对象(两种情况之一): -**输出格式(纯JSON):** +**情况A:需要新角色** {{ "needs_new_characters": true, "reason": "预测分析原因(150-200字),说明为什么即将的剧情需要新角色", "character_count": 2, "character_specifications": [ {{ - "name": "建议的角色名字(可选,如果有明确想法)", + "name": "建议的角色名字(可选)", "role_description": "角色在剧情中的定位和作用(100-150字)", "suggested_role_type": "supporting/antagonist/protagonist", "importance": "high/medium/low", "appearance_chapter": {start_chapter}, "key_abilities": ["能力1", "能力2"], - "plot_function": "在剧情中的具体功能(如:作为主要对手、提供关键信息等)", + "plot_function": "在剧情中的具体功能", "relationship_suggestions": [ {{ "target_character": "现有角色名", @@ -1388,34 +1526,51 @@ class PromptService: ] }} -或者如果不需要新角色: +**情况B:不需要新角色** {{ "needs_new_characters": false, "reason": "现有角色足以支撑即将的剧情发展,说明理由" }} + -**重要提示:** -- 这是预测性分析,不是基于已生成内容的事后分析 -- 要考虑剧情的自然发展和节奏 -- 不要为了引入角色而引入,确保必要性 -- 优先考虑角色的长期作用,而非一次性功能 + +【必须遵守】 +✅ 这是预测性分析,面向未来剧情 +✅ 考虑剧情的自然发展和节奏 +✅ 确保引入必要性,不为引入而引入 +✅ 优先考虑角色的长期作用 -只返回纯JSON,不要有markdown标记或其他文字。""" +【禁止事项】 +❌ 输出markdown标记 +❌ 基于已生成内容做事后分析 +❌ 为了引入角色而强行引入 +❌ 设计一次性功能角色 +""" - # 自动角色引入 - 生成提示词 - AUTO_CHARACTER_GENERATION = """你是专业的角色设定师。请根据以下信息,为小说生成新角色的完整设定。 + # 自动角色引入 - 生成提示词 V2(RTCO框架) + AUTO_CHARACTER_GENERATION = """ +你是专业的角色设定师,擅长根据剧情需求创建完整的角色设定。 + + +【生成任务】 +为小说生成新角色的完整设定,包括基本信息、性格背景、关系网络和职业信息。 + + + 【项目信息】 -- 书名:{title} -- 类型:{genre} -- 主题:{theme} +书名:{title} +类型:{genre} +主题:{theme} 【世界观】 -- 时间背景:{time_period} -- 地理位置:{location} -- 氛围基调:{atmosphere} -- 世界规则:{rules} +时间背景:{time_period} +地理位置:{location} +氛围基调:{atmosphere} +世界规则:{rules} + + 【已有角色】 {existing_characters} @@ -1424,40 +1579,43 @@ class PromptService: 【角色规格要求】 {character_specification} + + 【MCP工具参考】 {mcp_references} + -【生成要求】 + +【核心要求】 1. 角色必须符合剧情需求和世界观设定 2. **必须分析新角色与已有角色的关系**,至少建立1-3个有意义的关系 3. 性格、背景要有深度和独特性 4. 外貌描写要具体生动 5. 特长和能力要符合角色定位 -6. **如果【已有角色】中包含职业列表,必须为角色设定职业**(参考下方职业信息要求) +6. **如果【已有角色】中包含职业列表,必须为角色设定职业** -**关系建立指导(非常重要):** +【关系建立指导】 - 仔细审视【已有角色】列表,思考新角色与哪些现有角色有联系 -- 根据剧情需求,建立合理的角色关系(如:主角的新朋友、反派的手下、某角色的亲属等) +- 根据剧情需求,建立合理的角色关系 - 每个关系都要有明确的类型、亲密度和描述 -- 关系应该服务于剧情发展,推动故事前进 +- 关系应该服务于剧情发展 - 如果新角色是组织成员,记得填写organization_memberships -**重要格式要求:** -1. 只返回纯JSON格式,不要包含任何markdown标记或其他说明文字 -2. JSON字符串值中严禁使用特殊符号(引号、方括号、书名号等) -3. 所有专有名词直接书写,不使用任何符号包裹 - -【职业信息要求(重要)】 -如果【已有角色】部分包含"可用主职业列表"或"可用副职业列表",则必须: +【职业信息要求】 +如果【已有角色】部分包含"可用主职业列表"或"可用副职业列表": - 仔细查看可用的主职业和副职业列表 - 根据角色的背景、能力、故事定位,选择最合适的职业 -- 主职业:从"可用主职业列表"中选择一个,填写职业名称(name字段) +- 主职业:从"可用主职业列表"中选择一个,填写职业名称 - 主职业阶段:根据职业的阶段信息和角色实力,设定合理的当前阶段 -- 副职业:可选择0-2个副职业,每个包含职业名称和阶段 -- ⚠️ 重要:必须填写职业的名称而非ID,系统会自动匹配 +- 副职业:可选择0-2个副职业 +- ⚠️ 重要:必须填写职业的名称而非ID + + + +【输出格式】 +返回纯JSON对象: -请严格按照以下JSON格式返回: {{ "name": "角色姓名", "age": 25, @@ -1472,9 +1630,9 @@ class PromptService: "relationships": [ {{ "target_character_name": "已存在的角色名称", - "relationship_type": "关系类型(如:朋友、师父、敌人、父亲等)", + "relationship_type": "关系类型", "intimacy_level": 75, - "description": "关系的具体描述,说明他们如何认识、关系如何发展", + "description": "关系的具体描述", "status": "active" }} ], @@ -1499,83 +1657,92 @@ class PromptService: }} }} -**关系类型参考(从中选择或自定义):** -- 家族关系:父亲、母亲、兄弟、姐妹、子女、配偶、恋人、亲戚 -- 社交关系:师父、徒弟、朋友、挚友、同学、同事、邻居、知己、酒友 -- 职业关系:上司、下属、合作伙伴、客户、雇主、员工 -- 敌对关系:敌人、仇人、竞争对手、宿敌、死敌 +【关系类型参考】 +家族、社交、职业、敌对等各类关系 -**重要说明:** -1. **relationships数组必填**:至少要有1-3个与已有角色的关系(除非确实没有合理的关联) -2. **target_character_name必须精确匹配**:只能引用【已有角色】列表中的角色名称 -3. organization_memberships只能引用已存在的组织名称 -4. **数值范围约束**: - - intimacy_level:-100到100的整数 - * 80-100:至亲、挚友、深爱 - * 50-79:亲密、友好 - * 0-49:一般、普通 - * -1到-49:不和、敌视 - * -50到-100:仇恨、死敌 - - loyalty:0到100的整数(仅用于组织成员) - - **rank:0到10的整数**(职位等级,0最低,10最高) -5. status默认为"active",表示当前关系状态 +【数值范围】 +- intimacy_level:-100到100(负值表示敌对) +- loyalty:0到100 +- rank:0到10 + -**关系建立示例:** -- 如果新角色是主角的新队友,应该与主角建立"队友"或"朋友"关系 -- 如果新角色是反派的手下,应该与反派建立"上司-下属"关系 -- 如果新角色与某角色有血缘,应该建立家族关系 + +【必须遵守】 +✅ 符合剧情需求和世界观设定 +✅ relationships数组必填:至少1-3个关系 +✅ target_character_name必须精确匹配【已有角色】 +✅ organization_memberships只能引用已存在的组织 +✅ 职业选择必须从可用列表中选择 -只返回纯JSON对象,不要有```json```这样的标记。""" +【禁止事项】 +❌ 输出markdown标记 +❌ 在描述中使用特殊符号 +❌ 引用不存在的角色或组织 +❌ 使用职业ID而非职业名称 +""" - # 职业体系生成提示词 - CAREER_SYSTEM_GENERATION = """你是专业的游戏/小说职业体系设计师。请根据以下世界观信息,设计一个完整且合理的职业体系。 + # 职业体系生成提示词 V2(RTCO框架) + CAREER_SYSTEM_GENERATION = """ +你是专业的职业体系设计师,擅长为不同世界观设计完整的职业体系。 + + +【设计任务】 +根据世界观信息,设计一个完整且合理的职业体系,包括主职业和副职业。 + + + 【项目信息】 -- 书名:{title} -- 类型:{genre} -- 主题:{theme} -- 时间背景:{time_period} -- 地理位置:{location} -- 氛围基调:{atmosphere} -- 世界规则:{rules} +书名:{title} +类型:{genre} +主题:{theme} +时间背景:{time_period} +地理位置:{location} +氛围基调:{atmosphere} +世界规则:{rules} + + 【设计要求】 -1. **主职业(main_careers)**: - - 根据世界观特点,决定需要多少个主职业 - - 主职业是角色的核心发展方向,直接影响战斗力或核心能力 - - 必须严格符合世界观规则,体现核心能力体系 - - 每个主职业的阶段数量可以不同:根据职业的复杂度、重要性、修炼难度等因素,为不同职业设定不同的max_stage -2. **副职业(sub_careers)**: - - 根据世界需要,决定需要多少个副职业 - - 副职业包含生产、辅助、特殊技能类,丰富角色的能力维度 - - 每个副职业的阶段数量可以不同:简单的副职业可能只有3-5个阶段,复杂的可能有6-10个阶段 - - 不要让所有副职业都是相同的阶段数 +**1. 主职业(main_careers)** +- 根据世界观特点,决定需要多少个主职业 +- 主职业是角色的核心发展方向 +- 必须严格符合世界观规则 +- 每个主职业的阶段数量可以不同(体现职业复杂度差异) -3. **阶段设计(stages)**: - - 每个职业的stages数组长度必须等于max_stage - - 阶段名称要符合世界观文化背景和时代特征 - - 阶段描述要体现明确的能力提升和成长路径 - - 重要:确保职业间的阶段数量有差异,体现职业的多样性 +**2. 副职业(sub_careers)** +- 根据世界需要,决定需要多少个副职业 +- 副职业包含生产、辅助、特殊技能类 +- 每个副职业的阶段数量可以不同 +- 不要让所有副职业都是相同的阶段数 -【JSON格式】 +**3. 阶段设计(stages)** +- 每个职业的stages数组长度必须等于max_stage +- 阶段名称要符合世界观文化背景 +- 阶段描述要体现明确的能力提升路径 +- 确保职业间的阶段数量有差异 + + + +【输出格式】 +返回纯JSON对象: {{ "main_careers": [ {{ "name": "职业名称", - "description": "职业描述(100-150字),说明职业特点和定位", - "category": "职业分类(如:战斗系、法术系、体修系等)", + "description": "职业描述(100-150字)", + "category": "职业分类", "stages": [ {{"level": 1, "name": "阶段1名称", "description": "阶段描述"}}, - {{"level": 2, "name": "阶段2名称", "description": "阶段描述"}}, - ...数组长度应等于max_stage... + {{"level": 2, "name": "阶段2名称", "description": "阶段描述"}} ], - "max_stage": 根据职业复杂度自行决定的整数, + "max_stage": 整数, "requirements": "职业要求和前置条件", - "special_abilities": "职业特殊能力和特色", + "special_abilities": "职业特殊能力", "worldview_rules": "与世界观规则的关联", - "attribute_bonuses": {{"strength": "+10%", "intelligence": "+5%"}} + "attribute_bonuses": {{"strength": "+10%"}} }} ], "sub_careers": [ @@ -1584,27 +1751,30 @@ class PromptService: "description": "职业描述(80-120字)", "category": "生产系/辅助系/特殊系", "stages": [ - {{"level": 1, "name": "阶段1名称", "description": "阶段描述"}}, - ...数组长度应等于max_stage... + {{"level": 1, "name": "阶段1名称", "description": "阶段描述"}} ], - "max_stage": 根据职业特性自行决定的整数, + "max_stage": 整数, "requirements": "职业要求", "special_abilities": "特殊能力" }} ] }} + -【重要提示】 -- 职业的数量、类型完全由你根据世界观自行决定,不要受任何数字限制 -- **阶段数量多样性(关键)**: - - 不同职业的max_stage必须不同,不要所有职业都是相同的阶段数 - - 主职业的阶段数建议范围:5-15个阶段(根据职业重要性和复杂度灵活设定) - - 副职业的阶段数建议范围:3-10个阶段(根据职业特性灵活设定) - - 例如:剑修可能有12个阶段,炼丹师可能有8个阶段,体修可能有10个阶段 -- 确保职业体系与世界观高度契合,符合该世界的逻辑和文化 -- 只返回纯JSON,不要添加markdown标记或其他解释文字 + +【必须遵守】 +✅ 职业数量和类型根据世界观自行决定 +✅ 不同职业的max_stage必须不同 +✅ 主职业阶段数建议:5-15个 +✅ 副职业阶段数建议:3-10个 +✅ stages数组长度必须等于max_stage +✅ 确保职业体系与世界观高度契合 -请让每个职业的阶段数有所不同,体现职业的独特性和多样性!""" +【禁止事项】 +❌ 所有职业使用相同的阶段数 +❌ 输出markdown标记 +❌ 职业设计与世界观脱节 +""" @staticmethod def format_prompt(template: str, **kwargs) -> str: @@ -1752,38 +1922,6 @@ class PromptService: return "\n".join(prompt_parts) - @classmethod - def get_inspiration_prompt(cls, step: str) -> Optional[Dict[str, str]]: - """获取灵感模式指定步骤的提示词""" - # 根据步骤名称返回对应的system和user提示词 - step_map = { - "title": { - "system": cls.INSPIRATION_TITLE_SYSTEM, - "user": cls.INSPIRATION_TITLE_USER - }, - "description": { - "system": cls.INSPIRATION_DESCRIPTION_SYSTEM, - "user": cls.INSPIRATION_DESCRIPTION_USER - }, - "theme": { - "system": cls.INSPIRATION_THEME_SYSTEM, - "user": cls.INSPIRATION_THEME_USER - }, - "genre": { - "system": cls.INSPIRATION_GENRE_SYSTEM, - "user": cls.INSPIRATION_GENRE_USER - } - } - return step_map.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 async def get_mcp_tool_test_prompts( cls, @@ -1818,7 +1956,6 @@ class PromptService: "user": cls.format_prompt(user_template, plugin_name=plugin_name), "system": system_template } -# 创建全局提示词服务实例 # ========== 自定义提示词支持 ========== @@ -1884,14 +2021,7 @@ class PromptService: # 2. 降级到系统默认模板 logger.info(f"⚪ 使用系统默认提示词: user_id={user_id}, template_key={template_key} (未找到自定义模板)") - # 特殊处理灵感模式的提示词(直接从类属性获取) - if template_key.startswith("INSPIRATION_"): - # 直接从类属性获取 - template_content = getattr(cls, template_key, None) - if template_content: - return template_content - - # 其他模板直接从类属性获取 + # 直接从类属性获取系统默认模板 template_content = getattr(cls, template_key, None) if template_content is None: @@ -1951,37 +2081,20 @@ class PromptService: "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": "章节创作", + "CHAPTER_GENERATION_V2": { + "name": "章节创作V2(首章)", "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"] + "description": "根据大纲创作章节内容(用于第1章,无前置章节)", + "parameters": ["project_title", "genre", "chapter_number", "chapter_title", "chapter_outline", + "target_word_count", "narrative_perspective", "characters_info"] }, - "CHAPTER_GENERATION_WITH_CONTEXT": { - "name": "章节创作(带上下文)", + "CHAPTER_GENERATION_V2_WITH_CONTEXT": { + "name": "章节创作V2(续章)", "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"] + "description": "基于前置章节内容创作新章节(用于第2章及以后)", + "parameters": ["project_title", "genre", "chapter_number", "chapter_title", "chapter_outline", + "target_word_count", "narrative_perspective", "characters_info", "continuation_point", + "relevant_memories", "story_skeleton"] }, "CHAPTER_REGENERATION_SYSTEM": { "name": "章节重写系统提示", @@ -1990,12 +2103,6 @@ class PromptService: "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": "情节分析", @@ -2151,4 +2258,6 @@ class PromptService: if template["template_key"] == template_key: return template return None + +# ========== 全局实例 ========== prompt_service = PromptService() \ No newline at end of file