update: 优化剧情分析与章节规划算法,集成伏笔上下文追踪;完善章节删除时的级联清理逻辑

This commit is contained in:
xiamuceer-j
2026-01-19 17:23:50 +08:00
parent dc3dbaaf2c
commit 927072d16f
3 changed files with 308 additions and 31 deletions
+175 -10
View File
@@ -12,6 +12,7 @@ from app.models.outline import Outline
from app.models.character import Character from app.models.character import Character
from app.models.career import Career, CharacterCareer from app.models.career import Career, CharacterCareer
from app.models.memory import StoryMemory from app.models.memory import StoryMemory
from app.models.foreshadow import Foreshadow
from app.logger import get_logger from app.logger import get_logger
logger = get_logger(__name__) logger = get_logger(__name__)
@@ -25,12 +26,14 @@ class ChapterContext:
采用RTCO框架的分层设计: 采用RTCO框架的分层设计:
- P0-核心(必须):大纲、衔接点、字数要求 - P0-核心(必须):大纲、衔接点、字数要求
- P1-重要(按需):角色、情感基调、风格 - P1-重要(按需):角色、情感基调、风格
- P2-参考(条件触发):记忆、故事骨架、MCP资料 - P2-参考(条件触发):记忆、故事骨架、MCP资料、伏笔提醒
""" """
# === P0-核心信息(必须包含)=== # === P0-核心信息(必须包含)===
chapter_outline: str = "" # 本章大纲 chapter_outline: str = "" # 本章大纲
continuation_point: Optional[str] = None # 衔接锚点(上一章结尾) continuation_point: Optional[str] = None # 衔接锚点(增强版:含上一章摘要和结尾)
previous_chapter_summary: Optional[str] = None # 🔧 新增:上一章剧情摘要
previous_chapter_events: Optional[List[str]] = None # 🔧 新增:上一章关键事件
target_word_count: int = 3000 # 目标字数 target_word_count: int = 3000 # 目标字数
min_word_count: int = 2500 # 最小字数 min_word_count: int = 2500 # 最小字数
max_word_count: int = 4000 # 最大字数 max_word_count: int = 4000 # 最大字数
@@ -54,6 +57,7 @@ class ChapterContext:
relevant_memories: Optional[str] = None # 相关记忆(精简版) relevant_memories: Optional[str] = None # 相关记忆(精简版)
story_skeleton: Optional[str] = None # 故事骨架(50章+启用) story_skeleton: Optional[str] = None # 故事骨架(50章+启用)
mcp_references: Optional[str] = None # MCP参考资料 mcp_references: Optional[str] = None # MCP参考资料
foreshadow_reminders: Optional[str] = None # 伏笔提醒(新增)
# === 元信息 === # === 元信息 ===
context_stats: Dict[str, Any] = field(default_factory=dict) # 统计信息 context_stats: Dict[str, Any] = field(default_factory=dict) # 统计信息
@@ -62,7 +66,8 @@ class ChapterContext:
"""计算总上下文长度""" """计算总上下文长度"""
total = 0 total = 0
for field_name in ['chapter_outline', 'continuation_point', 'chapter_characters', for field_name in ['chapter_outline', 'continuation_point', 'chapter_characters',
'relevant_memories', 'story_skeleton', 'style_instruction']: 'relevant_memories', 'story_skeleton', 'style_instruction',
'foreshadow_reminders', 'previous_chapter_summary']:
value = getattr(self, field_name, None) value = getattr(self, field_name, None)
if value: if value:
total += len(value) total += len(value)
@@ -91,14 +96,16 @@ class ChapterContextBuilder:
STYLE_MAX_LENGTH = 200 # 风格描述最大长度 STYLE_MAX_LENGTH = 200 # 风格描述最大长度
MAX_CONTEXT_LENGTH = 3000 # 总上下文最大字符数 MAX_CONTEXT_LENGTH = 3000 # 总上下文最大字符数
def __init__(self, memory_service=None): def __init__(self, memory_service=None, foreshadow_service=None):
""" """
初始化构建器 初始化构建器
Args: Args:
memory_service: 记忆服务实例(可选,用于检索相关记忆) memory_service: 记忆服务实例(可选,用于检索相关记忆)
foreshadow_service: 伏笔服务实例(可选,用于获取伏笔提醒)
""" """
self.memory_service = memory_service self.memory_service = memory_service
self.foreshadow_service = foreshadow_service
async def build( async def build(
self, self,
@@ -155,20 +162,28 @@ class ChapterContextBuilder:
chapter, outline, project.outline_mode chapter, outline, project.outline_mode
) )
# === 衔接锚点(根据章节调整长度)=== # === 衔接锚点(根据章节调整长度,增强版含摘要和事件===
if chapter_number == 1: if chapter_number == 1:
context.continuation_point = None context.continuation_point = None
context.previous_chapter_summary = None
context.previous_chapter_events = None
logger.info(" ✅ 第1章无需衔接锚点") logger.info(" ✅ 第1章无需衔接锚点")
elif chapter_number <= 10: elif chapter_number <= 10:
context.continuation_point = await self._get_last_ending( ending_info = await self._get_last_ending_enhanced(
chapter, db, self.ENDING_LENGTH_SHORT chapter, db, self.ENDING_LENGTH_SHORT
) )
logger.info(f" ✅ 衔接锚点(短): {len(context.continuation_point or '')}字符") context.continuation_point = ending_info.get('ending_text')
context.previous_chapter_summary = ending_info.get('summary')
context.previous_chapter_events = ending_info.get('key_events')
logger.info(f" ✅ 衔接锚点(短): {len(context.continuation_point or '')}字符, 摘要: {len(context.previous_chapter_summary or '')}字符")
else: else:
context.continuation_point = await self._get_last_ending( ending_info = await self._get_last_ending_enhanced(
chapter, db, self.ENDING_LENGTH_NORMAL chapter, db, self.ENDING_LENGTH_NORMAL
) )
logger.info(f" ✅ 衔接锚点(标准): {len(context.continuation_point or '')}字符") context.continuation_point = ending_info.get('ending_text')
context.previous_chapter_summary = ending_info.get('summary')
context.previous_chapter_events = ending_info.get('key_events')
logger.info(f" ✅ 衔接锚点(标准): {len(context.continuation_point or '')}字符, 摘要: {len(context.previous_chapter_summary or '')}字符")
# === P1-重要信息 === # === P1-重要信息 ===
context.chapter_characters = await self._build_chapter_characters( context.chapter_characters = await self._build_chapter_characters(
@@ -200,6 +215,14 @@ class ChapterContextBuilder:
) )
logger.info(f" ✅ 故事骨架: {len(context.story_skeleton or '')}字符") logger.info(f" ✅ 故事骨架: {len(context.story_skeleton or '')}字符")
# === P2-伏笔提醒(新增)===
if self.foreshadow_service:
context.foreshadow_reminders = await self._get_foreshadow_reminders(
project.id, chapter_number, db
)
if context.foreshadow_reminders:
logger.info(f" ✅ 伏笔提醒: {len(context.foreshadow_reminders)}字符")
# === 统计信息 === # === 统计信息 ===
context.context_stats = { context.context_stats = {
"chapter_number": chapter_number, "chapter_number": chapter_number,
@@ -208,6 +231,7 @@ class ChapterContextBuilder:
"characters_length": len(context.chapter_characters), "characters_length": len(context.chapter_characters),
"memories_length": len(context.relevant_memories or ""), "memories_length": len(context.relevant_memories or ""),
"skeleton_length": len(context.story_skeleton or ""), "skeleton_length": len(context.story_skeleton or ""),
"foreshadow_length": len(context.foreshadow_reminders or ""),
"total_length": context.get_total_context_length() "total_length": context.get_total_context_length()
} }
@@ -263,7 +287,7 @@ class ChapterContextBuilder:
max_length: int max_length: int
) -> Optional[str]: ) -> Optional[str]:
""" """
获取上一章结尾内容作为衔接锚点 获取上一章结尾内容作为衔接锚点(旧版本,保留兼容性)
Args: Args:
chapter: 当前章节 chapter: 当前章节
@@ -294,6 +318,105 @@ class ChapterContextBuilder:
return content[-max_length:] return content[-max_length:]
async def _get_last_ending_enhanced(
self,
chapter: Chapter,
db: AsyncSession,
max_length: int
) -> Dict[str, Any]:
"""
获取增强版衔接锚点(含上一章摘要和关键事件)
🔧 新增功能:
1. 提取上一章结尾文本
2. 获取上一章剧情摘要(从记忆或expansion_plan
3. 提取上一章关键事件
Args:
chapter: 当前章节
db: 数据库会话
max_length: 最大长度
Returns:
包含 ending_text, summary, key_events 的字典
"""
result_info = {
'ending_text': None,
'summary': None,
'key_events': []
}
if chapter.chapter_number <= 1:
return result_info
# 查询上一章
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:
return result_info
# 1. 提取结尾内容
if prev_chapter.content:
content = prev_chapter.content.strip()
if len(content) <= max_length:
result_info['ending_text'] = content
else:
result_info['ending_text'] = content[-max_length:]
# 2. 获取上一章摘要
# 优先从记忆中获取 chapter_summary
summary_result = await db.execute(
select(StoryMemory.content)
.where(StoryMemory.project_id == chapter.project_id)
.where(StoryMemory.chapter_id == prev_chapter.id)
.where(StoryMemory.memory_type == 'chapter_summary')
.limit(1)
)
summary_mem = summary_result.scalar_one_or_none()
if summary_mem:
result_info['summary'] = summary_mem[:300] # 限制长度
elif prev_chapter.summary:
# 回退到章节的summary字段
result_info['summary'] = prev_chapter.summary[:300]
elif prev_chapter.expansion_plan:
# 再回退到expansion_plan中的plot_summary
try:
plan = json.loads(prev_chapter.expansion_plan)
result_info['summary'] = plan.get('plot_summary', '')[:300]
except json.JSONDecodeError:
pass
# 3. 提取上一章关键事件
if prev_chapter.expansion_plan:
try:
plan = json.loads(prev_chapter.expansion_plan)
key_events = plan.get('key_events', [])
if key_events:
result_info['key_events'] = key_events[:5] # 最多5个事件
except json.JSONDecodeError:
pass
# 如果没有从expansion_plan获取到,尝试从记忆中获取
if not result_info['key_events']:
events_result = await db.execute(
select(StoryMemory.content)
.where(StoryMemory.project_id == chapter.project_id)
.where(StoryMemory.chapter_id == prev_chapter.id)
.where(StoryMemory.memory_type == 'plot_point')
.limit(5)
)
event_mems = events_result.scalars().all()
if event_mems:
result_info['key_events'] = [e[:100] for e in event_mems]
return result_info
async def _build_chapter_characters( async def _build_chapter_characters(
self, self,
chapter: Chapter, chapter: Chapter,
@@ -564,6 +687,48 @@ class ChapterContextBuilder:
return "\n".join(lines) if lines else None return "\n".join(lines) if lines else None
async def _get_foreshadow_reminders(
self,
project_id: str,
chapter_number: int,
db: AsyncSession
) -> Optional[str]:
"""
获取伏笔提醒信息用于章节生成
策略:
1. 获取计划在本章或之前回收但未回收的伏笔(超期提醒)
2. 获取已埋入且接近需要回收的伏笔(提前提醒)
3. 获取本章计划埋入的伏笔(埋入提醒)
Args:
project_id: 项目ID
chapter_number: 当前章节号
db: 数据库会话
Returns:
格式化的伏笔提醒文本
"""
if not self.foreshadow_service:
return None
try:
context_result = await self.foreshadow_service.build_chapter_context(
db=db,
project_id=project_id,
chapter_number=chapter_number,
include_pending=True,
include_overdue=True,
lookahead=5
)
context_text = context_result.get("context_text", "")
return context_text if context_text else None
except Exception as e:
logger.error(f"❌ 获取伏笔提醒失败: {str(e)}")
return None
async def _build_story_skeleton( async def _build_story_skeleton(
self, self,
project_id: str, project_id: str,
+58 -13
View File
@@ -163,7 +163,7 @@ class PlotExpansionService:
batch_size: int, batch_size: int,
progress_callback: Optional[callable] progress_callback: Optional[callable]
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
"""分批生成章节规划""" """分批生成章节规划(增强差异化版本)"""
# 计算批次数 # 计算批次数
total_batches = (target_chapter_count + batch_size - 1) // batch_size total_batches = (target_chapter_count + batch_size - 1) // batch_size
logger.info(f"分批生成计划: 总共{target_chapter_count}章,分{total_batches}批,每批{batch_size}") logger.info(f"分批生成计划: 总共{target_chapter_count}章,分{total_batches}批,每批{batch_size}")
@@ -184,6 +184,9 @@ class PlotExpansionService:
all_chapter_plans = [] all_chapter_plans = []
# 🔧 收集所有已使用的关键事件,用于防止重复
used_key_events = set()
for batch_num in range(total_batches): for batch_num in range(total_batches):
# 计算当前批次的章节数 # 计算当前批次的章节数
remaining_chapters = target_chapter_count - len(all_chapter_plans) remaining_chapters = target_chapter_count - len(all_chapter_plans)
@@ -196,20 +199,41 @@ class PlotExpansionService:
if progress_callback: if progress_callback:
await progress_callback(batch_num + 1, total_batches, current_start_index, current_batch_size) await progress_callback(batch_num + 1, total_batches, current_start_index, current_batch_size)
# 构建当前批次的提示词(包含已生成章节的上下文 # 🔧 增强的上下文构建(包含完整的差异化信息
previous_context = "" previous_context = ""
if all_chapter_plans: if all_chapter_plans:
# 构建完整的已生成章节摘要(包含关键事件)
previous_summaries = [] previous_summaries = []
for ch in all_chapter_plans[-3:]: # 显示最近3章 for ch in all_chapter_plans: # 显示所有已生成章节
key_events_str = "".join(ch.get('key_events', [])[:3]) if ch.get('key_events') else ""
previous_summaries.append( previous_summaries.append(
f"{ch['sub_index']}节《{ch['title']}》: {ch['plot_summary'][:100]}..." f"{ch['sub_index']}节《{ch['title']}》:\n"
f" - 剧情:{ch.get('plot_summary', '')[:150]}\n"
f" - 关键事件:{key_events_str}\n"
f" - 结尾方式:{ch.get('ending_type', '未知')}"
) )
previous_context = f"""
【已生成章节概要】(接续生成,注意衔接)
{chr(10).join(previous_summaries)}
⚠️ 当前是第{current_start_index}-{current_start_index + current_batch_size - 1}节(共{target_chapter_count}节中的一部分) # 提取所有已使用的关键事件
""" all_used_events = []
for ch in all_chapter_plans:
all_used_events.extend(ch.get('key_events', []))
used_events_str = "".join(all_used_events[-20:]) if all_used_events else "暂无"
previous_context = f"""
【🔴 已生成章节完整信息(必须参考以确保差异化)】
{chr(10).join(previous_summaries)}
【🔴 已使用的关键事件(本批次不可重复使用)】
{used_events_str}
【🔴 差异化强制要求】
⚠️ 当前是第{current_start_index}-{current_start_index + current_batch_size - 1}节(共{target_chapter_count}节中的第{batch_num + 1}批)
⚠️ 每个新章节必须有完全不同的:
1. 开场场景(不同地点/时间/人物状态)
2. 核心事件(不与已生成章节的关键事件重复)
3. 结尾悬念(不同类型的钩子)
⚠️ 新章节的key_events不得与上面【已使用的关键事件】中的任何事件相同或相似
"""
# 获取自定义提示词模板 # 获取自定义提示词模板
template = await PromptService.get_template("OUTLINE_EXPAND_MULTI", project.user_id, db) template = await PromptService.get_template("OUTLINE_EXPAND_MULTI", project.user_id, db)
# 格式化提示词 # 格式化提示词
@@ -501,7 +525,7 @@ class PlotExpansionService:
ai_response: str, ai_response: str,
outline_id: str outline_id: str
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
"""解析AI的展开响应(使用统一的JSON清洗方法)""" """解析AI的展开响应(使用统一的JSON清洗方法,增强差异化字段"""
try: try:
# 使用统一的JSON清洗方法 # 使用统一的JSON清洗方法
cleaned_text = self.ai_service._clean_json_response(ai_response) cleaned_text = self.ai_service._clean_json_response(ai_response)
@@ -513,11 +537,30 @@ class PlotExpansionService:
if not isinstance(chapter_plans, list): if not isinstance(chapter_plans, list):
chapter_plans = [chapter_plans] chapter_plans = [chapter_plans]
# 为每个章节规划添加outline_id # 为每个章节规划添加outline_id和差异化标识
for plan in chapter_plans: for idx, plan in enumerate(chapter_plans):
plan["outline_id"] = outline_id plan["outline_id"] = outline_id
logger.info(f"✅ 成功解析 {len(chapter_plans)} 个章节规划") # 🔧 确保有 ending_type 字段(用于差异化追踪)
if "ending_type" not in plan:
# 根据叙事目标推断结尾类型
narrative_goal = plan.get("narrative_goal", "")
if "悬念" in narrative_goal or "疑问" in narrative_goal:
plan["ending_type"] = "悬念"
elif "冲突" in narrative_goal or "对抗" in narrative_goal:
plan["ending_type"] = "冲突升级"
elif "转折" in narrative_goal:
plan["ending_type"] = "情节转折"
elif "情感" in narrative_goal or "情绪" in narrative_goal:
plan["ending_type"] = "情感收尾"
else:
plan["ending_type"] = f"自然过渡-{idx + 1}"
# 🔧 确保 key_events 是列表且非空
if not plan.get("key_events"):
plan["key_events"] = [f"章节{idx + 1}核心事件"]
logger.info(f"✅ 成功解析 {len(chapter_plans)} 个章节规划(含差异化标识)")
return chapter_plans return chapter_plans
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
@@ -533,6 +576,7 @@ class PlotExpansionService:
"emotional_tone": "未知", "emotional_tone": "未知",
"narrative_goal": "需要重新生成", "narrative_goal": "需要重新生成",
"conflict_type": "未知", "conflict_type": "未知",
"ending_type": "未知",
"estimated_words": 3000 "estimated_words": 3000
}] }]
except Exception as e: except Exception as e:
@@ -547,6 +591,7 @@ class PlotExpansionService:
"emotional_tone": "未知", "emotional_tone": "未知",
"narrative_goal": "需要重新生成", "narrative_goal": "需要重新生成",
"conflict_type": "未知", "conflict_type": "未知",
"ending_type": "未知",
"estimated_words": 3000 "estimated_words": 3000
}] }]
+75 -8
View File
@@ -561,7 +561,14 @@ class PromptService:
上一章结尾: 上一章结尾:
{continuation_point} {continuation_point}
⚠️ 要求:从此处自然续写,不得重复上述内容 【🔴 上一章已完成剧情(禁止重复!)】
{previous_chapter_summary}
⚠️ 严重警告:
1. 上述"已完成剧情""衔接锚点"是**已经写过的**内容
2. 本章必须推进到**新的情节点**,绝对不能重新叙述已经发生的事件
3. 如果锚点是对话结束,请描写对话后的动作或场景转换,不要重复对话
4. 如果锚点是场景描写,请直接开始人物行动,不要重复描写环境
</continuation> </continuation>
<characters priority="P1"> <characters priority="P1">
@@ -569,6 +576,11 @@ class PromptService:
{characters_info} {characters_info}
</characters> </characters>
<foreshadow_reminders priority="P1">
【🎯 伏笔提醒 - 需关注】
{foreshadow_reminders}
</foreshadow_reminders>
<memory priority="P2"> <memory priority="P2">
【相关记忆 - 参考】 【相关记忆 - 参考】
{relevant_memories} {relevant_memories}
@@ -585,13 +597,20 @@ class PromptService:
✅ 自然承接上一章结尾,不重复已发生事件 ✅ 自然承接上一章结尾,不重复已发生事件
✅ 保持角色性格、说话方式一致 ✅ 保持角色性格、说话方式一致
✅ 字数控制在目标范围内 ✅ 字数控制在目标范围内
✅ 如有伏笔提醒,请在本章中适当埋入或回收相应伏笔
【🔴 反重复特别指令】
✅ 检查本章开篇是否与"衔接锚点"内容重复
✅ 检查本章情节是否与"上一章已完成剧情"重复
✅ 确保本章推进到了大纲中规划的新事件
【禁止事项】 【禁止事项】
❌ 输出章节标题、序号等元信息 ❌ 输出章节标题、序号等元信息
❌ 使用"总之""综上所述"等AI常见总结语 ❌ 使用"总之""综上所述"等AI常见总结语
❌ 在结尾处使用开放式反问 ❌ 在结尾处使用开放式反问
❌ 添加作者注释或创作说明 ❌ 添加作者注释或创作说明
❌ 重复叙述上一章已发生的事件 ❌ 重复叙述上一章已发生的事件(包括环境描写、心理活动)
❌ 在开篇使用"接上回""书接上文"等套话
</constraints> </constraints>
<output> <output>
@@ -783,7 +802,7 @@ class PromptService:
❌ 空泛的描述 ❌ 空泛的描述
</constraints>""" </constraints>"""
# 情节分析提示词 V2(RTCO框架) # 情节分析提示词 V2RTCO框架 + 伏笔ID追踪
PLOT_ANALYSIS = """<system> PLOT_ANALYSIS = """<system>
你是专业的小说编辑和剧情分析师,擅长深度剖析章节内容。 你是专业的小说编辑和剧情分析师,擅长深度剖析章节内容。
</system> </system>
@@ -791,6 +810,12 @@ class PromptService:
<task> <task>
【分析任务】 【分析任务】
全面分析第{chapter_number}章《{title}》的剧情要素、钩子、伏笔、冲突和角色发展。 全面分析第{chapter_number}章《{title}》的剧情要素、钩子、伏笔、冲突和角色发展。
【🔴 伏笔追踪任务(重要)】
系统已提供【已埋入伏笔列表】,当你识别到章节中有回收伏笔时:
1. 必须从列表中找出对应的伏笔ID
2. 在 foreshadows 数组中使用 reference_foreshadow_id 字段关联
3. 如果无法确定是哪个伏笔,reference_foreshadow_id 填 null
</task> </task>
<chapter priority="P0"> <chapter priority="P0">
@@ -803,6 +828,13 @@ class PromptService:
{content} {content}
</chapter> </chapter>
<existing_foreshadows priority="P1">
【已埋入伏笔列表 - 用于回收匹配】
以下是本项目中已埋入但尚未回收的伏笔,分析时如发现章节内容回收了某个伏笔,请使用对应的ID:
{existing_foreshadows}
</existing_foreshadows>
<analysis_framework priority="P0"> <analysis_framework priority="P0">
【分析维度】 【分析维度】
@@ -820,12 +852,26 @@ class PromptService:
- 出现位置(开头/中段/结尾) - 出现位置(开头/中段/结尾)
- **关键词**:【必填】从原文逐字复制8-25字的文本片段,用于精确定位 - **关键词**:【必填】从原文逐字复制8-25字的文本片段,用于精确定位
**2. 伏笔分析 (Foreshadowing)** **2. 伏笔分析 (Foreshadowing) - 🔴 支持ID追踪**
- 埋下的新伏笔:内容、预期作用、隐藏程度(1-10) - 埋下的新伏笔:内容、预期作用、隐藏程度(1-10)
- 回收的旧伏笔:呼应哪一章、回收效果 - 回收的旧伏笔:【必须】从已埋入伏笔列表中匹配ID
- 伏笔质量:巧妙性和合理性 - 伏笔质量:巧妙性和合理性
- **关键词**:【必填】从原文逐字复制8-25字 - **关键词**:【必填】从原文逐字复制8-25字
每个伏笔需要:
- **title**:简洁标题(10-20字,概括伏笔核心)
- **content**:详细描述伏笔内容和预期作用
- **type**planted(埋下)或 resolved(回收)
- **strength**:强度1-10(对读者的吸引力)
- **subtlety**:隐藏度1-10(越高越隐蔽)
- **reference_chapter**:回收时引用的原埋入章节号,埋下时为null
- **reference_foreshadow_id**:【回收时必填】被回收伏笔的ID(从已埋入伏笔列表中选择),埋下时为null
- **keyword**:【必填】从原文逐字复制8-25字的定位文本
- **category**:分类(identity=身世/mystery=悬念/item=物品/relationship=关系/event=事件/ability=能力/prophecy=预言)
- **is_long_term**:是否长线伏笔(跨10章以上回收为true)
- **related_characters**:涉及的角色名列表
- **estimated_resolve_chapter**:预估回收章节号(埋下时预估,回收时为当前章节)
**3. 冲突分析 (Conflict)** **3. 冲突分析 (Conflict)**
- 冲突类型:人与人/人与己/人与环境/人与社会 - 冲突类型:人与人/人与己/人与环境/人与社会
- 冲突各方及立场 - 冲突各方及立场
@@ -921,12 +967,32 @@ class PromptService:
], ],
"foreshadows": [ "foreshadows": [
{{ {{
"content": "伏笔内容", "title": "伏笔简洁标题",
"content": "伏笔详细内容和预期作用",
"type": "planted", "type": "planted",
"strength": 7, "strength": 7,
"subtlety": 8, "subtlety": 8,
"reference_chapter": null, "reference_chapter": null,
"keyword": "从原文逐字复制的8-25字文本" "reference_foreshadow_id": null,
"keyword": "从原文逐字复制的8-25字文本",
"category": "mystery",
"is_long_term": false,
"related_characters": ["角色A", "角色B"],
"estimated_resolve_chapter": 15
}},
{{
"title": "回收的伏笔标题",
"content": "伏笔如何被回收的描述",
"type": "resolved",
"strength": 8,
"subtlety": 6,
"reference_chapter": 5,
"reference_foreshadow_id": "abc123-已埋入伏笔的ID",
"keyword": "从原文逐字复制的8-25字文本",
"category": "mystery",
"is_long_term": false,
"related_characters": ["角色A"],
"estimated_resolve_chapter": 10
}} }}
], ],
"conflict": {{ "conflict": {{
@@ -998,6 +1064,7 @@ class PromptService:
✅ 逐字复制:keyword必须从原文复制,长度8-25字 ✅ 逐字复制:keyword必须从原文复制,长度8-25字
✅ 精确定位:keyword能在原文中精确找到 ✅ 精确定位:keyword能在原文中精确找到
✅ 职业变化可选:仅当章节明确描述时填写 ✅ 职业变化可选:仅当章节明确描述时填写
✅ 【伏笔ID追踪】回收伏笔时,必须从【已埋入伏笔列表】中查找匹配的ID填入 reference_foreshadow_id
【评分约束 - 严格执行】 【评分约束 - 严格执行】
✅ 严格按评分标准打分,支持小数(如6.5、7.2、8.3) ✅ 严格按评分标准打分,支持小数(如6.5、7.2、8.3)
@@ -2336,7 +2403,7 @@ class PromptService:
"description": "基于前置章节内容创作新章节(用于第2章及以后)", "description": "基于前置章节内容创作新章节(用于第2章及以后)",
"parameters": ["project_title", "genre", "chapter_number", "chapter_title", "chapter_outline", "parameters": ["project_title", "genre", "chapter_number", "chapter_title", "chapter_outline",
"target_word_count", "narrative_perspective", "characters_info", "continuation_point", "target_word_count", "narrative_perspective", "characters_info", "continuation_point",
"relevant_memories", "story_skeleton"] "foreshadow_reminders", "relevant_memories", "story_skeleton", "previous_chapter_summary"]
}, },
"CHAPTER_REGENERATION_SYSTEM": { "CHAPTER_REGENERATION_SYSTEM": {
"name": "章节重写系统提示", "name": "章节重写系统提示",