feature:新增大纲生成后自动校验并补全缺失角色/组织功能,替代旧的预测确认机制
This commit is contained in:
@@ -898,7 +898,6 @@ async def characters_generator(
|
|||||||
relationships=relationships_text,
|
relationships=relationships_text,
|
||||||
organization_type=char_data.get("organization_type") if is_organization else None,
|
organization_type=char_data.get("organization_type") if is_organization else None,
|
||||||
organization_purpose=char_data.get("organization_purpose") if is_organization else None,
|
organization_purpose=char_data.get("organization_purpose") if is_organization else None,
|
||||||
organization_members=json.dumps(char_data.get("organization_members", []), ensure_ascii=False) if is_organization else None,
|
|
||||||
traits=json.dumps(char_data.get("traits", []), ensure_ascii=False) if char_data.get("traits") else None
|
traits=json.dumps(char_data.get("traits", []), ensure_ascii=False) if char_data.get("traits") else None
|
||||||
)
|
)
|
||||||
db.add(character)
|
db.add(character)
|
||||||
@@ -1201,10 +1200,10 @@ async def characters_generator(
|
|||||||
"personality": char.personality,
|
"personality": char.personality,
|
||||||
"background": char.background,
|
"background": char.background,
|
||||||
"appearance": char.appearance,
|
"appearance": char.appearance,
|
||||||
"relationships": char.relationships,
|
"relationships": "",
|
||||||
"organization_type": char.organization_type,
|
"organization_type": char.organization_type,
|
||||||
"organization_purpose": char.organization_purpose,
|
"organization_purpose": char.organization_purpose,
|
||||||
"organization_members": char.organization_members,
|
"organization_members": "",
|
||||||
"traits": char.traits,
|
"traits": char.traits,
|
||||||
"created_at": char.created_at.isoformat() if char.created_at else None,
|
"created_at": char.created_at.isoformat() if char.created_at else None,
|
||||||
"updated_at": char.updated_at.isoformat() if char.updated_at else None
|
"updated_at": char.updated_at.isoformat() if char.updated_at else None
|
||||||
@@ -1383,6 +1382,52 @@ async def outline_generator(
|
|||||||
|
|
||||||
logger.info(f"✅ 成功创建{len(created_outlines)}个大纲节点")
|
logger.info(f"✅ 成功创建{len(created_outlines)}个大纲节点")
|
||||||
|
|
||||||
|
# 🎭 角色校验:检查大纲structure中的characters是否存在对应角色
|
||||||
|
yield await tracker.saving("🎭 校验角色信息...", 0.5)
|
||||||
|
try:
|
||||||
|
from app.services.auto_character_service import get_auto_character_service
|
||||||
|
|
||||||
|
auto_char_service = get_auto_character_service(user_ai_service)
|
||||||
|
char_check_result = await auto_char_service.check_and_create_missing_characters(
|
||||||
|
project_id=project_id,
|
||||||
|
outline_data_list=outline_data[:outline_count],
|
||||||
|
db=db,
|
||||||
|
user_id=user_id,
|
||||||
|
enable_mcp=enable_mcp
|
||||||
|
)
|
||||||
|
if char_check_result["created_count"] > 0:
|
||||||
|
created_names = [c.name for c in char_check_result["created_characters"]]
|
||||||
|
logger.info(f"🎭 向导大纲:自动创建了 {char_check_result['created_count']} 个角色: {', '.join(created_names)}")
|
||||||
|
yield await tracker.saving(
|
||||||
|
f"🎭 自动创建了 {char_check_result['created_count']} 个角色: {', '.join(created_names)}",
|
||||||
|
0.6
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"⚠️ 向导大纲角色校验失败(不影响主流程): {e}")
|
||||||
|
|
||||||
|
# 🏛️ 组织校验:检查大纲structure中的characters(type=organization)是否存在对应组织
|
||||||
|
yield await tracker.saving("🏛️ 校验组织信息...", 0.55)
|
||||||
|
try:
|
||||||
|
from app.services.auto_organization_service import get_auto_organization_service
|
||||||
|
|
||||||
|
auto_org_service = get_auto_organization_service(user_ai_service)
|
||||||
|
org_check_result = await auto_org_service.check_and_create_missing_organizations(
|
||||||
|
project_id=project_id,
|
||||||
|
outline_data_list=outline_data[:outline_count],
|
||||||
|
db=db,
|
||||||
|
user_id=user_id,
|
||||||
|
enable_mcp=enable_mcp
|
||||||
|
)
|
||||||
|
if org_check_result["created_count"] > 0:
|
||||||
|
created_names = [c.name for c in org_check_result["created_organizations"]]
|
||||||
|
logger.info(f"🏛️ 向导大纲:自动创建了 {org_check_result['created_count']} 个组织: {', '.join(created_names)}")
|
||||||
|
yield await tracker.saving(
|
||||||
|
f"🏛️ 自动创建了 {org_check_result['created_count']} 个组织: {', '.join(created_names)}",
|
||||||
|
0.65
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"⚠️ 向导大纲组织校验失败(不影响主流程): {e}")
|
||||||
|
|
||||||
# 根据项目的大纲模式决定是否自动创建章节
|
# 根据项目的大纲模式决定是否自动创建章节
|
||||||
created_chapters = []
|
created_chapters = []
|
||||||
if project.outline_mode == 'one-to-one':
|
if project.outline_mode == 'one-to-one':
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""自动角色引入服务 - 在续写大纲时根据剧情推进自动引入新角色"""
|
"""自动角色服务 - 大纲生成后校验并自动补全缺失角色"""
|
||||||
from typing import List, Dict, Any, Optional, Callable, Awaitable
|
from typing import List, Dict, Any, Optional, Callable, Awaitable
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
@@ -20,270 +20,24 @@ class AutoCharacterService:
|
|||||||
def __init__(self, ai_service: AIService):
|
def __init__(self, ai_service: AIService):
|
||||||
self.ai_service = ai_service
|
self.ai_service = ai_service
|
||||||
|
|
||||||
async def analyze_and_create_characters(
|
|
||||||
self,
|
|
||||||
project_id: str,
|
|
||||||
outline_content: str,
|
|
||||||
existing_characters: List[Character],
|
|
||||||
db: AsyncSession,
|
|
||||||
user_id: str = None,
|
|
||||||
enable_mcp: bool = True,
|
|
||||||
all_chapters_brief: str = "",
|
|
||||||
start_chapter: int = 1,
|
|
||||||
chapter_count: int = 3,
|
|
||||||
plot_stage: str = "发展",
|
|
||||||
story_direction: str = "继续推进主线剧情",
|
|
||||||
preview_only: bool = False,
|
|
||||||
progress_callback: Optional[Callable[[str], Awaitable[None]]] = None
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
预测性分析并创建需要的新角色(方案A:先角色后大纲)
|
|
||||||
|
|
||||||
Args:
|
|
||||||
project_id: 项目ID
|
|
||||||
outline_content: 当前批次大纲内容(用于向后兼容,实际不使用)
|
|
||||||
existing_characters: 现有角色列表
|
|
||||||
db: 数据库会话
|
|
||||||
user_id: 用户ID(用于MCP和自定义提示词)
|
|
||||||
enable_mcp: 是否启用MCP增强
|
|
||||||
all_chapters_brief: 已有章节概览
|
|
||||||
start_chapter: 起始章节号
|
|
||||||
chapter_count: 续写章节数
|
|
||||||
plot_stage: 剧情阶段
|
|
||||||
story_direction: 故事发展方向
|
|
||||||
preview_only: 仅预测不创建(用于角色确认机制)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
{
|
|
||||||
"new_characters": [角色对象列表], # preview_only=True时为空
|
|
||||||
"relationships_created": [关系对象列表], # preview_only=True时为空
|
|
||||||
"character_count": 新增角色数量,
|
|
||||||
"analysis_result": AI分析结果,
|
|
||||||
"predicted_characters": [预测的角色数据] # 仅preview_only=True时返回
|
|
||||||
"needs_new_characters": bool,
|
|
||||||
"reason": str
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
logger.info(f"🎭 【方案A】预测性分析:检测是否需要引入新角色...")
|
|
||||||
logger.info(f" - 项目ID: {project_id}")
|
|
||||||
logger.info(f" - 续写计划: 第{start_chapter}章起,共{chapter_count}章")
|
|
||||||
logger.info(f" - 剧情阶段: {plot_stage}")
|
|
||||||
logger.info(f" - 发展方向: {story_direction}")
|
|
||||||
logger.info(f" - 现有角色数: {len(existing_characters)}")
|
|
||||||
|
|
||||||
# 1. 获取项目信息
|
|
||||||
project_result = await db.execute(
|
|
||||||
select(Project).where(Project.id == project_id)
|
|
||||||
)
|
|
||||||
project = project_result.scalar_one_or_none()
|
|
||||||
if not project:
|
|
||||||
raise ValueError("项目不存在")
|
|
||||||
|
|
||||||
# 2. 构建现有角色信息摘要
|
|
||||||
existing_chars_summary = self._build_character_summary(existing_characters)
|
|
||||||
|
|
||||||
# 3. AI预测性分析是否需要新角色
|
|
||||||
analysis_result = await self._analyze_character_needs(
|
|
||||||
project=project,
|
|
||||||
outline_content=outline_content, # 保留参数向后兼容
|
|
||||||
existing_chars_summary=existing_chars_summary,
|
|
||||||
db=db,
|
|
||||||
user_id=user_id,
|
|
||||||
enable_mcp=enable_mcp,
|
|
||||||
all_chapters_brief=all_chapters_brief,
|
|
||||||
start_chapter=start_chapter,
|
|
||||||
chapter_count=chapter_count,
|
|
||||||
plot_stage=plot_stage,
|
|
||||||
story_direction=story_direction
|
|
||||||
)
|
|
||||||
|
|
||||||
# 4. 判断是否需要创建角色
|
|
||||||
if not analysis_result or not analysis_result.get("needs_new_characters"):
|
|
||||||
logger.info("✅ AI判断:当前剧情不需要引入新角色")
|
|
||||||
return {
|
|
||||||
"new_characters": [],
|
|
||||||
"relationships_created": [],
|
|
||||||
"character_count": 0,
|
|
||||||
"analysis_result": analysis_result,
|
|
||||||
"predicted_characters": [],
|
|
||||||
"needs_new_characters": False,
|
|
||||||
"reason": analysis_result.get("reason", "当前剧情不需要新角色")
|
|
||||||
}
|
|
||||||
|
|
||||||
# 5. 如果是预览模式,仅返回预测结果,不创建角色
|
|
||||||
if preview_only:
|
|
||||||
character_specs = analysis_result.get("character_specifications", [])
|
|
||||||
logger.info(f"🔮 预览模式:预测到 {len(character_specs)} 个角色,不创建数据库记录")
|
|
||||||
return {
|
|
||||||
"new_characters": [],
|
|
||||||
"relationships_created": [],
|
|
||||||
"character_count": 0,
|
|
||||||
"analysis_result": analysis_result,
|
|
||||||
"predicted_characters": character_specs,
|
|
||||||
"needs_new_characters": True,
|
|
||||||
"reason": analysis_result.get("reason", "预测需要新角色")
|
|
||||||
}
|
|
||||||
|
|
||||||
# 6. 批量生成新角色(非预览模式)
|
|
||||||
new_characters = []
|
|
||||||
relationships_created = []
|
|
||||||
|
|
||||||
character_specs = analysis_result.get("character_specifications", [])
|
|
||||||
logger.info(f"🎯 AI建议引入 {len(character_specs)} 个新角色")
|
|
||||||
|
|
||||||
for idx, spec in enumerate(character_specs):
|
|
||||||
try:
|
|
||||||
spec_name = spec.get('name', spec.get('role_description', '未命名'))
|
|
||||||
logger.info(f" [{idx+1}/{len(character_specs)}] 生成角色规格: {spec_name}")
|
|
||||||
logger.debug(f" 角色规格内容: {json.dumps(spec, ensure_ascii=False)}")
|
|
||||||
|
|
||||||
if progress_callback:
|
|
||||||
await progress_callback(f"🎨 [{idx+1}/{len(character_specs)}] 生成角色详情: {spec_name}")
|
|
||||||
|
|
||||||
# 生成角色详细信息
|
|
||||||
character_data = await self._generate_character_details(
|
|
||||||
spec=spec,
|
|
||||||
project=project,
|
|
||||||
existing_characters=existing_characters + new_characters, # 包含新创建的
|
|
||||||
db=db,
|
|
||||||
user_id=user_id,
|
|
||||||
enable_mcp=enable_mcp
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.debug(f" AI生成的角色数据: {json.dumps(character_data, ensure_ascii=False)[:200]}")
|
|
||||||
|
|
||||||
if progress_callback:
|
|
||||||
await progress_callback(f"💾 [{idx+1}/{len(character_specs)}] 保存角色: {character_data.get('name', spec_name)}")
|
|
||||||
|
|
||||||
# 创建角色记录
|
|
||||||
character = await self._create_character_record(
|
|
||||||
project_id=project_id,
|
|
||||||
character_data=character_data,
|
|
||||||
db=db
|
|
||||||
)
|
|
||||||
|
|
||||||
new_characters.append(character)
|
|
||||||
logger.info(f" ✅ 创建新角色: {character.name} ({character.role_type}), ID: {character.id}")
|
|
||||||
|
|
||||||
if progress_callback:
|
|
||||||
await progress_callback(f"✅ [{idx+1}/{len(character_specs)}] 角色创建成功: {character.name}")
|
|
||||||
|
|
||||||
# 建立关系(兼容两种字段名)
|
|
||||||
relationships_data = character_data.get("relationships") or character_data.get("relationships_array", [])
|
|
||||||
logger.info(f" 🔍 检查关系数据:")
|
|
||||||
logger.info(f" - relationships字段: {character_data.get('relationships')}")
|
|
||||||
logger.info(f" - relationships_array字段: {character_data.get('relationships_array')}")
|
|
||||||
logger.info(f" - 最终使用的数据: {relationships_data}")
|
|
||||||
logger.info(f" - 关系数量: {len(relationships_data) if relationships_data else 0}")
|
|
||||||
|
|
||||||
if relationships_data:
|
|
||||||
logger.info(f" 🔗 开始创建 {len(relationships_data)} 条关系...")
|
|
||||||
for idx, rel in enumerate(relationships_data):
|
|
||||||
logger.info(f" [{idx+1}] {rel.get('target_character_name')} - {rel.get('relationship_type')}")
|
|
||||||
|
|
||||||
if progress_callback:
|
|
||||||
await progress_callback(f"🔗 [{idx+1}/{len(character_specs)}] 建立 {len(relationships_data)} 个关系")
|
|
||||||
else:
|
|
||||||
logger.warning(f" ⚠️ AI返回的角色数据中没有关系信息!")
|
|
||||||
logger.warning(f" 完整的character_data keys: {list(character_data.keys())}")
|
|
||||||
|
|
||||||
rels = await self._create_relationships(
|
|
||||||
new_character=character,
|
|
||||||
relationship_specs=relationships_data,
|
|
||||||
existing_characters=existing_characters + new_characters,
|
|
||||||
project_id=project_id,
|
|
||||||
db=db
|
|
||||||
)
|
|
||||||
|
|
||||||
relationships_created.extend(rels)
|
|
||||||
logger.info(f" ✅ 实际创建了 {len(rels)} 条关系记录")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f" ❌ 创建角色失败: {e}", exc_info=True)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 7. 提交事务(注意:这里只flush,让调用方commit)
|
|
||||||
await db.flush()
|
|
||||||
|
|
||||||
logger.info(f"🎉 自动角色引入完成: 新增{len(new_characters)}个角色, {len(relationships_created)}条关系")
|
|
||||||
|
|
||||||
return {
|
|
||||||
"new_characters": new_characters,
|
|
||||||
"relationships_created": relationships_created,
|
|
||||||
"character_count": len(new_characters),
|
|
||||||
"analysis_result": analysis_result
|
|
||||||
}
|
|
||||||
|
|
||||||
def _build_character_summary(self, characters: List[Character]) -> str:
|
def _build_character_summary(self, characters: List[Character]) -> str:
|
||||||
"""构建现有角色摘要"""
|
"""构建现有角色摘要信息"""
|
||||||
if not characters:
|
if not characters:
|
||||||
return "暂无角色"
|
return "暂无已有角色"
|
||||||
|
|
||||||
summary = []
|
lines = []
|
||||||
for char in characters:
|
for char in characters:
|
||||||
char_type = "组织" if char.is_organization else "角色"
|
parts = [f"- {char.name}"]
|
||||||
role_desc = char.role_type or "未知"
|
if char.role_type:
|
||||||
personality = (char.personality or "")[:50]
|
role_map = {"protagonist": "主角", "supporting": "配角", "antagonist": "反派"}
|
||||||
summary.append(f"- {char.name} ({char_type}, {role_desc}): {personality}")
|
parts.append(f"({role_map.get(char.role_type, char.role_type)})")
|
||||||
|
if char.personality:
|
||||||
|
parts.append(f"性格: {char.personality[:50]}")
|
||||||
|
if char.background:
|
||||||
|
parts.append(f"背景: {char.background[:50]}")
|
||||||
|
lines.append(" ".join(parts))
|
||||||
|
|
||||||
return "\n".join(summary[:20]) # 最多显示20个
|
return "\n".join(lines)
|
||||||
|
|
||||||
async def _analyze_character_needs(
|
|
||||||
self,
|
|
||||||
project: Project,
|
|
||||||
outline_content: str,
|
|
||||||
existing_chars_summary: str,
|
|
||||||
db: AsyncSession,
|
|
||||||
user_id: str,
|
|
||||||
enable_mcp: bool,
|
|
||||||
all_chapters_brief: str = "",
|
|
||||||
start_chapter: int = 1,
|
|
||||||
chapter_count: int = 3,
|
|
||||||
plot_stage: str = "发展",
|
|
||||||
story_direction: str = "继续推进主线剧情"
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""AI预测性分析是否需要新角色(方案A)"""
|
|
||||||
|
|
||||||
# 构建分析提示词
|
|
||||||
template = await PromptService.get_template(
|
|
||||||
"AUTO_CHARACTER_ANALYSIS",
|
|
||||||
user_id,
|
|
||||||
db
|
|
||||||
)
|
|
||||||
|
|
||||||
# 使用新的预测性分析参数
|
|
||||||
prompt = PromptService.format_prompt(
|
|
||||||
template,
|
|
||||||
title=project.title,
|
|
||||||
theme=project.theme or "未设定",
|
|
||||||
genre=project.genre or "未设定",
|
|
||||||
time_period=project.world_time_period or "未设定",
|
|
||||||
location=project.world_location or "未设定",
|
|
||||||
atmosphere=project.world_atmosphere or "未设定",
|
|
||||||
existing_characters=existing_chars_summary,
|
|
||||||
all_chapters_brief=all_chapters_brief,
|
|
||||||
start_chapter=start_chapter,
|
|
||||||
chapter_count=chapter_count,
|
|
||||||
plot_stage=plot_stage,
|
|
||||||
story_direction=story_direction
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 使用统一的JSON调用方法(支持自动MCP工具加载)
|
|
||||||
analysis = await self.ai_service.call_with_json_retry(
|
|
||||||
prompt=prompt,
|
|
||||||
max_retries=3,
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info(f" ✅ AI分析完成: needs_new_characters={analysis.get('needs_new_characters')}")
|
|
||||||
return analysis
|
|
||||||
|
|
||||||
except json.JSONDecodeError as e:
|
|
||||||
logger.error(f" ❌ 角色需求分析JSON解析失败: {e}")
|
|
||||||
return {"needs_new_characters": False}
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f" ❌ 角色需求分析失败: {e}")
|
|
||||||
return {"needs_new_characters": False}
|
|
||||||
|
|
||||||
async def _generate_character_details(
|
async def _generate_character_details(
|
||||||
self,
|
self,
|
||||||
@@ -447,7 +201,7 @@ class AutoCharacterService:
|
|||||||
else:
|
else:
|
||||||
logger.warning(f" ⚠️ AI返回的副职业名称未找到: {career_name}")
|
logger.warning(f" ⚠️ AI返回的副职业名称未找到: {career_name}")
|
||||||
|
|
||||||
# 创建角色
|
# 创建角色(不再写入 relationships 文本字段,关系统一由 character_relationships 表管理)
|
||||||
character = Character(
|
character = Character(
|
||||||
project_id=project_id,
|
project_id=project_id,
|
||||||
name=character_data.get("name", "未命名角色"),
|
name=character_data.get("name", "未命名角色"),
|
||||||
@@ -458,7 +212,6 @@ class AutoCharacterService:
|
|||||||
personality=character_data.get("personality", ""),
|
personality=character_data.get("personality", ""),
|
||||||
background=character_data.get("background", ""),
|
background=character_data.get("background", ""),
|
||||||
appearance=character_data.get("appearance", ""),
|
appearance=character_data.get("appearance", ""),
|
||||||
relationships=character_data.get("relationships_text", ""),
|
|
||||||
organization_type=character_data.get("organization_type") if is_organization else None,
|
organization_type=character_data.get("organization_type") if is_organization else None,
|
||||||
organization_purpose=character_data.get("organization_purpose") if is_organization else None,
|
organization_purpose=character_data.get("organization_purpose") if is_organization else None,
|
||||||
traits=json.dumps(character_data.get("traits", []), ensure_ascii=False) if character_data.get("traits") else None,
|
traits=json.dumps(character_data.get("traits", []), ensure_ascii=False) if character_data.get("traits") else None,
|
||||||
@@ -594,6 +347,204 @@ class AutoCharacterService:
|
|||||||
return relationships
|
return relationships
|
||||||
|
|
||||||
|
|
||||||
|
async def check_and_create_missing_characters(
|
||||||
|
self,
|
||||||
|
project_id: str,
|
||||||
|
outline_data_list: list,
|
||||||
|
db: AsyncSession,
|
||||||
|
user_id: str = None,
|
||||||
|
enable_mcp: bool = True,
|
||||||
|
progress_callback: Optional[Callable[[str], Awaitable[None]]] = None
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
根据大纲structure中的characters字段校验项目是否存在对应角色,
|
||||||
|
如果不存在则根据大纲摘要自动生成角色信息。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
project_id: 项目ID
|
||||||
|
outline_data_list: 大纲数据列表(每个元素包含 characters、summary 等字段)
|
||||||
|
db: 数据库会话
|
||||||
|
user_id: 用户ID
|
||||||
|
enable_mcp: 是否启用MCP
|
||||||
|
progress_callback: 进度回调
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
{
|
||||||
|
"created_characters": [角色对象列表],
|
||||||
|
"missing_names": [缺失的角色名称列表],
|
||||||
|
"created_count": 创建的角色数量
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
logger.info(f"🔍 【角色校验】开始校验大纲中提到的角色是否存在...")
|
||||||
|
|
||||||
|
# 1. 从所有大纲的structure中提取角色名称(兼容新旧格式)
|
||||||
|
all_character_names = set()
|
||||||
|
character_context = {} # 记录角色出现的上下文(大纲摘要)
|
||||||
|
|
||||||
|
for outline_item in outline_data_list:
|
||||||
|
if isinstance(outline_item, dict):
|
||||||
|
characters = outline_item.get("characters", [])
|
||||||
|
summary = outline_item.get("summary", "") or outline_item.get("content", "")
|
||||||
|
title = outline_item.get("title", "")
|
||||||
|
|
||||||
|
if isinstance(characters, list):
|
||||||
|
for char_entry in characters:
|
||||||
|
# 新格式:{"name": "xxx", "type": "character"/"organization"}
|
||||||
|
if isinstance(char_entry, dict):
|
||||||
|
entry_type = char_entry.get("type", "character")
|
||||||
|
entry_name = char_entry.get("name", "")
|
||||||
|
# 只处理 character 类型,跳过 organization
|
||||||
|
if entry_type == "organization" or not entry_name.strip():
|
||||||
|
continue
|
||||||
|
name = entry_name.strip()
|
||||||
|
# 旧格式:纯字符串
|
||||||
|
elif isinstance(char_entry, str) and char_entry.strip():
|
||||||
|
name = char_entry.strip()
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
all_character_names.add(name)
|
||||||
|
# 收集角色出现的上下文
|
||||||
|
if name not in character_context:
|
||||||
|
character_context[name] = []
|
||||||
|
character_context[name].append(f"《{title}》: {summary[:200]}")
|
||||||
|
|
||||||
|
if not all_character_names:
|
||||||
|
logger.info("🔍 【角色校验】大纲中未提到任何角色,跳过校验")
|
||||||
|
return {
|
||||||
|
"created_characters": [],
|
||||||
|
"missing_names": [],
|
||||||
|
"created_count": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(f"🔍 【角色校验】大纲中提到的角色: {', '.join(all_character_names)}")
|
||||||
|
|
||||||
|
# 2. 获取项目现有角色
|
||||||
|
existing_result = await db.execute(
|
||||||
|
select(Character).where(Character.project_id == project_id)
|
||||||
|
)
|
||||||
|
existing_characters = existing_result.scalars().all()
|
||||||
|
existing_names = {char.name for char in existing_characters}
|
||||||
|
|
||||||
|
# 3. 找出缺失的角色
|
||||||
|
missing_names = all_character_names - existing_names
|
||||||
|
|
||||||
|
if not missing_names:
|
||||||
|
logger.info("✅ 【角色校验】所有角色已存在,无需创建")
|
||||||
|
return {
|
||||||
|
"created_characters": [],
|
||||||
|
"missing_names": [],
|
||||||
|
"created_count": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(f"⚠️ 【角色校验】发现 {len(missing_names)} 个缺失角色: {', '.join(missing_names)}")
|
||||||
|
|
||||||
|
# 4. 获取项目信息
|
||||||
|
project_result = await db.execute(
|
||||||
|
select(Project).where(Project.id == project_id)
|
||||||
|
)
|
||||||
|
project = project_result.scalar_one_or_none()
|
||||||
|
if not project:
|
||||||
|
logger.error("❌ 【角色校验】项目不存在")
|
||||||
|
return {
|
||||||
|
"created_characters": [],
|
||||||
|
"missing_names": list(missing_names),
|
||||||
|
"created_count": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# 5. 为每个缺失的角色生成并创建角色信息
|
||||||
|
created_characters = []
|
||||||
|
|
||||||
|
for idx, char_name in enumerate(missing_names):
|
||||||
|
try:
|
||||||
|
if progress_callback:
|
||||||
|
await progress_callback(
|
||||||
|
f"🎭 [{idx+1}/{len(missing_names)}] 自动创建角色:{char_name}..."
|
||||||
|
)
|
||||||
|
|
||||||
|
# 构建角色规格(基于大纲上下文)
|
||||||
|
context_summaries = character_context.get(char_name, [])
|
||||||
|
context_text = "\n".join(context_summaries[:3]) # 最多3个上下文
|
||||||
|
|
||||||
|
spec = {
|
||||||
|
"name": char_name,
|
||||||
|
"role_description": f"在大纲中出现的角色,出现场景:\n{context_text}",
|
||||||
|
"suggested_role_type": "supporting",
|
||||||
|
"importance": "medium"
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(f" 🤖 [{idx+1}/{len(missing_names)}] 生成角色详情: {char_name}")
|
||||||
|
|
||||||
|
# 生成角色详细信息
|
||||||
|
character_data = await self._generate_character_details(
|
||||||
|
spec=spec,
|
||||||
|
project=project,
|
||||||
|
existing_characters=list(existing_characters) + created_characters,
|
||||||
|
db=db,
|
||||||
|
user_id=user_id,
|
||||||
|
enable_mcp=enable_mcp
|
||||||
|
)
|
||||||
|
|
||||||
|
# 确保使用大纲中的角色名称
|
||||||
|
character_data['name'] = char_name
|
||||||
|
|
||||||
|
if progress_callback:
|
||||||
|
await progress_callback(
|
||||||
|
f"💾 [{idx+1}/{len(missing_names)}] 保存角色:{char_name}..."
|
||||||
|
)
|
||||||
|
|
||||||
|
# 创建角色记录
|
||||||
|
character = await self._create_character_record(
|
||||||
|
project_id=project_id,
|
||||||
|
character_data=character_data,
|
||||||
|
db=db
|
||||||
|
)
|
||||||
|
|
||||||
|
created_characters.append(character)
|
||||||
|
logger.info(f" ✅ [{idx+1}/{len(missing_names)}] 角色创建成功: {character.name}")
|
||||||
|
|
||||||
|
# 建立关系
|
||||||
|
relationships_data = character_data.get("relationships") or character_data.get("relationships_array", [])
|
||||||
|
if relationships_data:
|
||||||
|
if progress_callback:
|
||||||
|
await progress_callback(
|
||||||
|
f"🔗 [{idx+1}/{len(missing_names)}] 建立 {len(relationships_data)} 个关系:{char_name}..."
|
||||||
|
)
|
||||||
|
|
||||||
|
await self._create_relationships(
|
||||||
|
new_character=character,
|
||||||
|
relationship_specs=relationships_data,
|
||||||
|
existing_characters=list(existing_characters) + created_characters,
|
||||||
|
project_id=project_id,
|
||||||
|
db=db
|
||||||
|
)
|
||||||
|
|
||||||
|
if progress_callback:
|
||||||
|
await progress_callback(
|
||||||
|
f"✅ [{idx+1}/{len(missing_names)}] 角色创建完成:{char_name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f" ❌ 创建角色 {char_name} 失败: {e}", exc_info=True)
|
||||||
|
if progress_callback:
|
||||||
|
await progress_callback(
|
||||||
|
f"⚠️ [{idx+1}/{len(missing_names)}] 角色 {char_name} 创建失败"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 6. flush 到数据库(让调用方 commit)
|
||||||
|
if created_characters:
|
||||||
|
await db.flush()
|
||||||
|
|
||||||
|
logger.info(f"🎉 【角色校验】完成: 发现 {len(missing_names)} 个缺失角色,成功创建 {len(created_characters)} 个")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"created_characters": created_characters,
|
||||||
|
"missing_names": list(missing_names),
|
||||||
|
"created_count": len(created_characters)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# 全局实例缓存
|
# 全局实例缓存
|
||||||
_auto_character_service_instance: Optional[AutoCharacterService] = None
|
_auto_character_service_instance: Optional[AutoCharacterService] = None
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""自动组织引入服务 - 在续写大纲时根据剧情推进自动引入新组织"""
|
"""自动组织服务 - 大纲生成后校验并自动补全缺失组织"""
|
||||||
from typing import List, Dict, Any, Optional, Callable, Awaitable
|
from typing import List, Dict, Any, Optional, Callable, Awaitable
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
@@ -20,293 +20,34 @@ class AutoOrganizationService:
|
|||||||
def __init__(self, ai_service: AIService):
|
def __init__(self, ai_service: AIService):
|
||||||
self.ai_service = ai_service
|
self.ai_service = ai_service
|
||||||
|
|
||||||
async def analyze_and_create_organizations(
|
def _build_character_summary(self, characters: List[Character]) -> str:
|
||||||
self,
|
"""构建现有角色摘要信息"""
|
||||||
project_id: str,
|
if not characters:
|
||||||
outline_content: str,
|
return "暂无已有角色"
|
||||||
existing_characters: List[Character],
|
|
||||||
existing_organizations: List[Dict[str, Any]],
|
|
||||||
db: AsyncSession,
|
|
||||||
user_id: str = None,
|
|
||||||
enable_mcp: bool = True,
|
|
||||||
all_chapters_brief: str = "",
|
|
||||||
start_chapter: int = 1,
|
|
||||||
chapter_count: int = 3,
|
|
||||||
plot_stage: str = "发展",
|
|
||||||
story_direction: str = "继续推进主线剧情",
|
|
||||||
preview_only: bool = False,
|
|
||||||
progress_callback: Optional[Callable[[str], Awaitable[None]]] = None
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
预测性分析并创建需要的新组织
|
|
||||||
|
|
||||||
Args:
|
lines = []
|
||||||
project_id: 项目ID
|
for char in characters:
|
||||||
outline_content: 当前批次大纲内容(用于向后兼容,实际不使用)
|
parts = [f"- {char.name}"]
|
||||||
existing_characters: 现有角色列表
|
if char.role_type:
|
||||||
existing_organizations: 现有组织列表
|
role_map = {"protagonist": "主角", "supporting": "配角", "antagonist": "反派"}
|
||||||
db: 数据库会话
|
parts.append(f"({role_map.get(char.role_type, char.role_type)})")
|
||||||
user_id: 用户ID(用于MCP和自定义提示词)
|
if char.personality:
|
||||||
enable_mcp: 是否启用MCP增强
|
parts.append(f"性格: {char.personality[:50]}")
|
||||||
all_chapters_brief: 已有章节概览
|
lines.append(" ".join(parts))
|
||||||
start_chapter: 起始章节号
|
|
||||||
chapter_count: 续写章节数
|
|
||||||
plot_stage: 剧情阶段
|
|
||||||
story_direction: 故事发展方向
|
|
||||||
preview_only: 仅预测不创建(用于组织确认机制)
|
|
||||||
|
|
||||||
Returns:
|
return "\n".join(lines)
|
||||||
{
|
|
||||||
"new_organizations": [组织对象列表], # preview_only=True时为空
|
|
||||||
"members_created": [成员关系列表], # preview_only=True时为空
|
|
||||||
"organization_count": 新增组织数量,
|
|
||||||
"analysis_result": AI分析结果,
|
|
||||||
"predicted_organizations": [预测的组织数据] # 仅preview_only=True时返回
|
|
||||||
"needs_new_organizations": bool,
|
|
||||||
"reason": str
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
logger.info(f"🏛️ 【组织引入】预测性分析:检测是否需要引入新组织...")
|
|
||||||
logger.info(f" - 项目ID: {project_id}")
|
|
||||||
logger.info(f" - 续写计划: 第{start_chapter}章起,共{chapter_count}章")
|
|
||||||
logger.info(f" - 剧情阶段: {plot_stage}")
|
|
||||||
logger.info(f" - 发展方向: {story_direction}")
|
|
||||||
logger.info(f" - 现有角色数: {len(existing_characters)}")
|
|
||||||
logger.info(f" - 现有组织数: {len(existing_organizations)}")
|
|
||||||
|
|
||||||
# 1. 获取项目信息
|
|
||||||
project_result = await db.execute(
|
|
||||||
select(Project).where(Project.id == project_id)
|
|
||||||
)
|
|
||||||
project = project_result.scalar_one_or_none()
|
|
||||||
if not project:
|
|
||||||
raise ValueError("项目不存在")
|
|
||||||
|
|
||||||
# 2. 构建现有组织信息摘要
|
|
||||||
existing_orgs_summary = self._build_organization_summary(existing_organizations)
|
|
||||||
existing_chars_summary = self._build_character_summary(existing_characters)
|
|
||||||
|
|
||||||
# 3. AI预测性分析是否需要新组织
|
|
||||||
if progress_callback:
|
|
||||||
await progress_callback("🤖 AI分析组织需求...")
|
|
||||||
|
|
||||||
analysis_result = await self._analyze_organization_needs(
|
|
||||||
project=project,
|
|
||||||
outline_content=outline_content,
|
|
||||||
existing_orgs_summary=existing_orgs_summary,
|
|
||||||
existing_chars_summary=existing_chars_summary,
|
|
||||||
db=db,
|
|
||||||
user_id=user_id,
|
|
||||||
enable_mcp=enable_mcp,
|
|
||||||
all_chapters_brief=all_chapters_brief,
|
|
||||||
start_chapter=start_chapter,
|
|
||||||
chapter_count=chapter_count,
|
|
||||||
plot_stage=plot_stage,
|
|
||||||
story_direction=story_direction
|
|
||||||
)
|
|
||||||
|
|
||||||
if progress_callback:
|
|
||||||
await progress_callback("✅ 组织需求分析完成")
|
|
||||||
|
|
||||||
# 4. 判断是否需要创建组织
|
|
||||||
if not analysis_result or not analysis_result.get("needs_new_organizations"):
|
|
||||||
logger.info("✅ AI判断:当前剧情不需要引入新组织")
|
|
||||||
return {
|
|
||||||
"new_organizations": [],
|
|
||||||
"members_created": [],
|
|
||||||
"organization_count": 0,
|
|
||||||
"analysis_result": analysis_result,
|
|
||||||
"predicted_organizations": [],
|
|
||||||
"needs_new_organizations": False,
|
|
||||||
"reason": analysis_result.get("reason", "当前剧情不需要新组织")
|
|
||||||
}
|
|
||||||
|
|
||||||
# 5. 如果是预览模式,仅返回预测结果,不创建组织
|
|
||||||
if preview_only:
|
|
||||||
organization_specs = analysis_result.get("organization_specifications", [])
|
|
||||||
logger.info(f"🔮 预览模式:预测到 {len(organization_specs)} 个组织,不创建数据库记录")
|
|
||||||
return {
|
|
||||||
"new_organizations": [],
|
|
||||||
"members_created": [],
|
|
||||||
"organization_count": 0,
|
|
||||||
"analysis_result": analysis_result,
|
|
||||||
"predicted_organizations": organization_specs,
|
|
||||||
"needs_new_organizations": True,
|
|
||||||
"reason": analysis_result.get("reason", "预测需要新组织")
|
|
||||||
}
|
|
||||||
|
|
||||||
# 6. 批量生成新组织(非预览模式)
|
|
||||||
new_organizations = []
|
|
||||||
members_created = []
|
|
||||||
|
|
||||||
organization_specs = analysis_result.get("organization_specifications", [])
|
|
||||||
logger.info(f"🎯 AI建议引入 {len(organization_specs)} 个新组织")
|
|
||||||
|
|
||||||
for idx, spec in enumerate(organization_specs):
|
|
||||||
try:
|
|
||||||
spec_name = spec.get('name', spec.get('organization_description', '未命名'))
|
|
||||||
logger.info(f" [{idx+1}/{len(organization_specs)}] 生成组织规格: {spec_name}")
|
|
||||||
logger.debug(f" 组织规格内容: {json.dumps(spec, ensure_ascii=False)}")
|
|
||||||
|
|
||||||
if progress_callback:
|
|
||||||
await progress_callback(f"🏛️ [{idx+1}/{len(organization_specs)}] 生成组织详情: {spec_name}")
|
|
||||||
|
|
||||||
# 生成组织详细信息
|
|
||||||
organization_data = await self._generate_organization_details(
|
|
||||||
spec=spec,
|
|
||||||
project=project,
|
|
||||||
existing_characters=existing_characters,
|
|
||||||
existing_organizations=existing_organizations,
|
|
||||||
db=db,
|
|
||||||
user_id=user_id,
|
|
||||||
enable_mcp=enable_mcp
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.debug(f" AI生成的组织数据: {json.dumps(organization_data, ensure_ascii=False)[:200]}")
|
|
||||||
|
|
||||||
if progress_callback:
|
|
||||||
await progress_callback(f"💾 [{idx+1}/{len(organization_specs)}] 保存组织: {organization_data.get('name', spec_name)}")
|
|
||||||
|
|
||||||
# 创建组织记录(先创建Character记录,再创建Organization记录)
|
|
||||||
character, organization = await self._create_organization_record(
|
|
||||||
project_id=project_id,
|
|
||||||
organization_data=organization_data,
|
|
||||||
db=db
|
|
||||||
)
|
|
||||||
|
|
||||||
new_organizations.append({
|
|
||||||
"character": character,
|
|
||||||
"organization": organization
|
|
||||||
})
|
|
||||||
logger.info(f" ✅ 创建新组织: {character.name}, ID: {organization.id}")
|
|
||||||
|
|
||||||
if progress_callback:
|
|
||||||
await progress_callback(f"✅ [{idx+1}/{len(organization_specs)}] 组织创建成功: {character.name}")
|
|
||||||
|
|
||||||
# 建立成员关系
|
|
||||||
members_data = organization_data.get("initial_members", [])
|
|
||||||
if members_data:
|
|
||||||
logger.info(f" 🔗 开始创建 {len(members_data)} 个成员关系...")
|
|
||||||
|
|
||||||
if progress_callback:
|
|
||||||
await progress_callback(f"🔗 [{idx+1}/{len(organization_specs)}] 建立 {len(members_data)} 个成员关系")
|
|
||||||
|
|
||||||
members = await self._create_member_relationships(
|
|
||||||
organization=organization,
|
|
||||||
member_specs=members_data,
|
|
||||||
existing_characters=existing_characters,
|
|
||||||
project_id=project_id,
|
|
||||||
db=db
|
|
||||||
)
|
|
||||||
members_created.extend(members)
|
|
||||||
logger.info(f" ✅ 实际创建了 {len(members)} 个成员关系记录")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f" ❌ 创建组织失败: {e}", exc_info=True)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 7. 提交事务(注意:这里只flush,让调用方commit)
|
|
||||||
await db.flush()
|
|
||||||
|
|
||||||
logger.info(f"🎉 自动组织引入完成: 新增{len(new_organizations)}个组织, {len(members_created)}个成员关系")
|
|
||||||
|
|
||||||
return {
|
|
||||||
"new_organizations": new_organizations,
|
|
||||||
"members_created": members_created,
|
|
||||||
"organization_count": len(new_organizations),
|
|
||||||
"analysis_result": analysis_result,
|
|
||||||
"predicted_organizations": [],
|
|
||||||
"needs_new_organizations": True,
|
|
||||||
"reason": analysis_result.get("reason", "")
|
|
||||||
}
|
|
||||||
|
|
||||||
def _build_organization_summary(self, organizations: List[Dict[str, Any]]) -> str:
|
def _build_organization_summary(self, organizations: List[Dict[str, Any]]) -> str:
|
||||||
"""构建现有组织摘要"""
|
"""构建现有组织摘要信息"""
|
||||||
if not organizations:
|
if not organizations:
|
||||||
return "暂无组织"
|
return "暂无已有组织"
|
||||||
|
|
||||||
summary = []
|
lines = []
|
||||||
for org in organizations:
|
for org in organizations:
|
||||||
org_name = org.get("name", "未知")
|
name = org.get("name", "未知") if isinstance(org, dict) else getattr(org, "name", "未知")
|
||||||
org_type = org.get("organization_type", "未知类型")
|
lines.append(f"- {name}")
|
||||||
power_level = org.get("power_level", 50)
|
|
||||||
purpose = (org.get("organization_purpose") or "")[:50]
|
|
||||||
summary.append(f"- {org_name} ({org_type}, 势力等级:{power_level}): {purpose}")
|
|
||||||
|
|
||||||
return "\n".join(summary[:15]) # 最多显示15个
|
return "\n".join(lines)
|
||||||
|
|
||||||
def _build_character_summary(self, characters: List[Character]) -> str:
|
|
||||||
"""构建现有角色摘要"""
|
|
||||||
if not characters:
|
|
||||||
return "暂无角色"
|
|
||||||
|
|
||||||
summary = []
|
|
||||||
for char in characters:
|
|
||||||
if not char.is_organization: # 只统计非组织角色
|
|
||||||
char_role = char.role_type or "未知"
|
|
||||||
personality = (char.personality or "")[:30]
|
|
||||||
summary.append(f"- {char.name} ({char_role}): {personality}")
|
|
||||||
|
|
||||||
return "\n".join(summary[:20]) # 最多显示20个
|
|
||||||
|
|
||||||
async def _analyze_organization_needs(
|
|
||||||
self,
|
|
||||||
project: Project,
|
|
||||||
outline_content: str,
|
|
||||||
existing_orgs_summary: str,
|
|
||||||
existing_chars_summary: str,
|
|
||||||
db: AsyncSession,
|
|
||||||
user_id: str,
|
|
||||||
enable_mcp: bool,
|
|
||||||
all_chapters_brief: str = "",
|
|
||||||
start_chapter: int = 1,
|
|
||||||
chapter_count: int = 3,
|
|
||||||
plot_stage: str = "发展",
|
|
||||||
story_direction: str = "继续推进主线剧情"
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""AI预测性分析是否需要新组织"""
|
|
||||||
|
|
||||||
# 构建分析提示词
|
|
||||||
template = await PromptService.get_template(
|
|
||||||
"AUTO_ORGANIZATION_ANALYSIS",
|
|
||||||
user_id,
|
|
||||||
db
|
|
||||||
)
|
|
||||||
|
|
||||||
# 使用新的预测性分析参数
|
|
||||||
prompt = PromptService.format_prompt(
|
|
||||||
template,
|
|
||||||
title=project.title,
|
|
||||||
theme=project.theme or "未设定",
|
|
||||||
genre=project.genre or "未设定",
|
|
||||||
time_period=project.world_time_period or "未设定",
|
|
||||||
location=project.world_location or "未设定",
|
|
||||||
atmosphere=project.world_atmosphere or "未设定",
|
|
||||||
existing_organizations=existing_orgs_summary,
|
|
||||||
existing_characters=existing_chars_summary,
|
|
||||||
all_chapters_brief=all_chapters_brief,
|
|
||||||
start_chapter=start_chapter,
|
|
||||||
chapter_count=chapter_count,
|
|
||||||
plot_stage=plot_stage,
|
|
||||||
story_direction=story_direction
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 使用统一的JSON调用方法(支持自动MCP工具加载)
|
|
||||||
analysis = await self.ai_service.call_with_json_retry(
|
|
||||||
prompt=prompt,
|
|
||||||
max_retries=3,
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info(f" ✅ AI分析完成: needs_new_organizations={analysis.get('needs_new_organizations')}")
|
|
||||||
return analysis
|
|
||||||
|
|
||||||
except json.JSONDecodeError as e:
|
|
||||||
logger.error(f" ❌ 组织需求分析JSON解析失败: {e}")
|
|
||||||
return {"needs_new_organizations": False}
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f" ❌ 组织需求分析失败: {e}")
|
|
||||||
return {"needs_new_organizations": False}
|
|
||||||
|
|
||||||
async def _generate_organization_details(
|
async def _generate_organization_details(
|
||||||
self,
|
self,
|
||||||
@@ -485,6 +226,233 @@ class AutoOrganizationService:
|
|||||||
return members
|
return members
|
||||||
|
|
||||||
|
|
||||||
|
async def check_and_create_missing_organizations(
|
||||||
|
self,
|
||||||
|
project_id: str,
|
||||||
|
outline_data_list: list,
|
||||||
|
db: AsyncSession,
|
||||||
|
user_id: str = None,
|
||||||
|
enable_mcp: bool = True,
|
||||||
|
progress_callback: Optional[Callable[[str], Awaitable[None]]] = None
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
根据大纲structure中的characters字段(type=organization)校验项目是否存在对应组织,
|
||||||
|
如果不存在则根据大纲摘要自动生成组织信息。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
project_id: 项目ID
|
||||||
|
outline_data_list: 大纲数据列表(每个元素包含 characters、summary 等字段)
|
||||||
|
db: 数据库会话
|
||||||
|
user_id: 用户ID
|
||||||
|
enable_mcp: 是否启用MCP
|
||||||
|
progress_callback: 进度回调
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
{
|
||||||
|
"created_organizations": [组织对象列表],
|
||||||
|
"missing_names": [缺失的组织名称列表],
|
||||||
|
"created_count": 创建的组织数量
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
logger.info(f"🔍 【组织校验】开始校验大纲中提到的组织是否存在...")
|
||||||
|
|
||||||
|
# 1. 从所有大纲的structure中提取组织名称(兼容新旧格式)
|
||||||
|
all_organization_names = set()
|
||||||
|
organization_context = {} # 记录组织出现的上下文(大纲摘要)
|
||||||
|
|
||||||
|
for outline_item in outline_data_list:
|
||||||
|
if isinstance(outline_item, dict):
|
||||||
|
characters = outline_item.get("characters", [])
|
||||||
|
summary = outline_item.get("summary", "") or outline_item.get("content", "")
|
||||||
|
title = outline_item.get("title", "")
|
||||||
|
|
||||||
|
if isinstance(characters, list):
|
||||||
|
for char_entry in characters:
|
||||||
|
# 新格式:{"name": "xxx", "type": "character"/"organization"}
|
||||||
|
if isinstance(char_entry, dict):
|
||||||
|
entry_type = char_entry.get("type", "character")
|
||||||
|
entry_name = char_entry.get("name", "")
|
||||||
|
# 只处理 organization 类型
|
||||||
|
if entry_type != "organization" or not entry_name.strip():
|
||||||
|
continue
|
||||||
|
name = entry_name.strip()
|
||||||
|
all_organization_names.add(name)
|
||||||
|
if name not in organization_context:
|
||||||
|
organization_context[name] = []
|
||||||
|
organization_context[name].append(f"《{title}》: {summary[:200]}")
|
||||||
|
# 旧格式:纯字符串,无法区分类型,跳过
|
||||||
|
|
||||||
|
if not all_organization_names:
|
||||||
|
logger.info("🔍 【组织校验】大纲中未提到任何组织,跳过校验")
|
||||||
|
return {
|
||||||
|
"created_organizations": [],
|
||||||
|
"missing_names": [],
|
||||||
|
"created_count": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(f"🔍 【组织校验】大纲中提到的组织: {', '.join(all_organization_names)}")
|
||||||
|
|
||||||
|
# 2. 获取项目现有组织(通过Character表的is_organization字段)
|
||||||
|
existing_result = await db.execute(
|
||||||
|
select(Character).where(
|
||||||
|
Character.project_id == project_id,
|
||||||
|
Character.is_organization == True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
existing_org_characters = existing_result.scalars().all()
|
||||||
|
existing_org_names = {char.name for char in existing_org_characters}
|
||||||
|
|
||||||
|
# 3. 找出缺失的组织
|
||||||
|
missing_names = all_organization_names - existing_org_names
|
||||||
|
|
||||||
|
if not missing_names:
|
||||||
|
logger.info("✅ 【组织校验】所有组织已存在,无需创建")
|
||||||
|
return {
|
||||||
|
"created_organizations": [],
|
||||||
|
"missing_names": [],
|
||||||
|
"created_count": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(f"⚠️ 【组织校验】发现 {len(missing_names)} 个缺失组织: {', '.join(missing_names)}")
|
||||||
|
|
||||||
|
# 4. 获取项目信息
|
||||||
|
project_result = await db.execute(
|
||||||
|
select(Project).where(Project.id == project_id)
|
||||||
|
)
|
||||||
|
project = project_result.scalar_one_or_none()
|
||||||
|
if not project:
|
||||||
|
logger.error("❌ 【组织校验】项目不存在")
|
||||||
|
return {
|
||||||
|
"created_organizations": [],
|
||||||
|
"missing_names": list(missing_names),
|
||||||
|
"created_count": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# 5. 获取现有角色和组织信息
|
||||||
|
all_chars_result = await db.execute(
|
||||||
|
select(Character).where(Character.project_id == project_id)
|
||||||
|
)
|
||||||
|
existing_characters = list(all_chars_result.scalars().all())
|
||||||
|
|
||||||
|
existing_organizations = []
|
||||||
|
for char in existing_org_characters:
|
||||||
|
org_result = await db.execute(
|
||||||
|
select(Organization).where(Organization.character_id == char.id)
|
||||||
|
)
|
||||||
|
org = org_result.scalar_one_or_none()
|
||||||
|
if org:
|
||||||
|
existing_organizations.append({
|
||||||
|
"name": char.name,
|
||||||
|
"organization_type": char.organization_type,
|
||||||
|
"organization_purpose": char.organization_purpose,
|
||||||
|
"power_level": org.power_level,
|
||||||
|
"location": org.location,
|
||||||
|
"motto": org.motto
|
||||||
|
})
|
||||||
|
|
||||||
|
# 6. 为每个缺失的组织生成并创建组织信息
|
||||||
|
created_organizations = []
|
||||||
|
|
||||||
|
for idx, org_name in enumerate(missing_names):
|
||||||
|
try:
|
||||||
|
if progress_callback:
|
||||||
|
await progress_callback(
|
||||||
|
f"🏛️ [{idx+1}/{len(missing_names)}] 自动创建组织:{org_name}..."
|
||||||
|
)
|
||||||
|
|
||||||
|
# 构建组织规格(基于大纲上下文)
|
||||||
|
context_summaries = organization_context.get(org_name, [])
|
||||||
|
context_text = "\n".join(context_summaries[:3])
|
||||||
|
|
||||||
|
spec = {
|
||||||
|
"name": org_name,
|
||||||
|
"organization_description": f"在大纲中出现的组织/势力,出现场景:\n{context_text}",
|
||||||
|
"organization_type": "未知",
|
||||||
|
"importance": "medium"
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(f" 🤖 [{idx+1}/{len(missing_names)}] 生成组织详情: {org_name}")
|
||||||
|
|
||||||
|
# 生成组织详细信息
|
||||||
|
organization_data = await self._generate_organization_details(
|
||||||
|
spec=spec,
|
||||||
|
project=project,
|
||||||
|
existing_characters=existing_characters,
|
||||||
|
existing_organizations=existing_organizations,
|
||||||
|
db=db,
|
||||||
|
user_id=user_id,
|
||||||
|
enable_mcp=enable_mcp
|
||||||
|
)
|
||||||
|
|
||||||
|
# 确保使用大纲中的组织名称
|
||||||
|
organization_data['name'] = org_name
|
||||||
|
|
||||||
|
if progress_callback:
|
||||||
|
await progress_callback(
|
||||||
|
f"💾 [{idx+1}/{len(missing_names)}] 保存组织:{org_name}..."
|
||||||
|
)
|
||||||
|
|
||||||
|
# 创建组织记录
|
||||||
|
org_character, organization = await self._create_organization_record(
|
||||||
|
project_id=project_id,
|
||||||
|
organization_data=organization_data,
|
||||||
|
db=db
|
||||||
|
)
|
||||||
|
|
||||||
|
created_organizations.append(org_character)
|
||||||
|
existing_characters.append(org_character)
|
||||||
|
existing_organizations.append({
|
||||||
|
"name": org_character.name,
|
||||||
|
"organization_type": org_character.organization_type,
|
||||||
|
"organization_purpose": org_character.organization_purpose,
|
||||||
|
"power_level": organization.power_level,
|
||||||
|
"location": organization.location,
|
||||||
|
"motto": organization.motto
|
||||||
|
})
|
||||||
|
logger.info(f" ✅ [{idx+1}/{len(missing_names)}] 组织创建成功: {org_character.name}")
|
||||||
|
|
||||||
|
# 建立成员关系
|
||||||
|
members_data = organization_data.get("initial_members", [])
|
||||||
|
if members_data:
|
||||||
|
if progress_callback:
|
||||||
|
await progress_callback(
|
||||||
|
f"🔗 [{idx+1}/{len(missing_names)}] 建立 {len(members_data)} 个成员关系:{org_name}..."
|
||||||
|
)
|
||||||
|
|
||||||
|
await self._create_member_relationships(
|
||||||
|
organization=organization,
|
||||||
|
member_specs=members_data,
|
||||||
|
existing_characters=existing_characters,
|
||||||
|
project_id=project_id,
|
||||||
|
db=db
|
||||||
|
)
|
||||||
|
|
||||||
|
if progress_callback:
|
||||||
|
await progress_callback(
|
||||||
|
f"✅ [{idx+1}/{len(missing_names)}] 组织创建完成:{org_name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f" ❌ 创建组织 {org_name} 失败: {e}", exc_info=True)
|
||||||
|
if progress_callback:
|
||||||
|
await progress_callback(
|
||||||
|
f"⚠️ [{idx+1}/{len(missing_names)}] 组织 {org_name} 创建失败"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 7. flush 到数据库(让调用方 commit)
|
||||||
|
if created_organizations:
|
||||||
|
await db.flush()
|
||||||
|
|
||||||
|
logger.info(f"🎉 【组织校验】完成: 发现 {len(missing_names)} 个缺失组织,成功创建 {len(created_organizations)} 个")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"created_organizations": created_organizations,
|
||||||
|
"missing_names": list(missing_names),
|
||||||
|
"created_count": len(created_organizations)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# 全局实例缓存
|
# 全局实例缓存
|
||||||
_auto_organization_service_instance: Optional[AutoOrganizationService] = None
|
_auto_organization_service_instance: Optional[AutoOrganizationService] = None
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user