refactor: 重构章节上下文构建系统,使用新的ChapterContextBuilder优化上下文生成
This commit is contained in:
+92
-195
@@ -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<mcp_reference>\n{mcp_reference_materials}\n</mcp_reference>"
|
||||
base_prompt = base_prompt.replace("</task>", f"{mcp_section}\n</task>")
|
||||
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
|
||||
|
||||
@@ -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 ""
|
||||
+1051
-942
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user