2026-03-27 17:42:41 +08:00
|
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
"""
|
|
|
|
|
|
冲突检测和常识校验模块
|
|
|
|
|
|
检测角色冲突和常识错误
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
import re
|
|
|
|
|
|
import json
|
|
|
|
|
|
from typing import Dict, List, Optional, Tuple, Set, Any
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
import logging
|
|
|
|
|
|
from dataclasses import dataclass, field
|
|
|
|
|
|
from enum import Enum
|
|
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ConflictSeverity(Enum):
|
|
|
|
|
|
"""冲突严重程度"""
|
|
|
|
|
|
INFO = "info" # 信息提示
|
|
|
|
|
|
WARNING = "warning" # 警告
|
|
|
|
|
|
ERROR = "error" # 错误
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ConflictType(Enum):
|
|
|
|
|
|
"""冲突类型"""
|
|
|
|
|
|
DUPLICATE_NAME = "duplicate_name" # 重复姓名
|
|
|
|
|
|
SIMILAR_CHARACTER = "similar_character" # 相似角色
|
|
|
|
|
|
TIMELINE_CONFLICT = "timeline_conflict" # 时间线冲突
|
|
|
|
|
|
RELATIONSHIP_CONFLICT = "relationship_conflict" # 关系冲突
|
|
|
|
|
|
AGE_INCONSISTENCY = "age_inconsistency" # 年龄不一致
|
|
|
|
|
|
COMMON_SENSE_ERROR = "common_sense_error" # 常识错误
|
|
|
|
|
|
LOGIC_CONFLICT = "logic_conflict" # 逻辑冲突
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
|
class Conflict:
|
|
|
|
|
|
"""冲突信息"""
|
|
|
|
|
|
type: ConflictType
|
|
|
|
|
|
severity: ConflictSeverity
|
|
|
|
|
|
message: str
|
|
|
|
|
|
character_name: str
|
|
|
|
|
|
details: Dict[str, Any] = field(default_factory=dict)
|
|
|
|
|
|
related_characters: List[str] = field(default_factory=list)
|
|
|
|
|
|
suggested_fixes: List[str] = field(default_factory=list)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
|
class ValidationRule:
|
|
|
|
|
|
"""校验规则"""
|
|
|
|
|
|
id: str
|
|
|
|
|
|
name: str
|
|
|
|
|
|
description: str
|
|
|
|
|
|
pattern: Optional[str] = None
|
|
|
|
|
|
condition: Optional[str] = None
|
|
|
|
|
|
severity: ConflictSeverity = ConflictSeverity.WARNING
|
|
|
|
|
|
enabled: bool = True
|
|
|
|
|
|
category: str = "common_sense"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ConflictDetector:
|
|
|
|
|
|
"""冲突检测器"""
|
|
|
|
|
|
|
|
|
|
|
|
# 预定义常识规则
|
|
|
|
|
|
DEFAULT_COMMON_SENSE_RULES = [
|
|
|
|
|
|
ValidationRule(
|
|
|
|
|
|
id="age_realistic",
|
|
|
|
|
|
name="年龄合理性",
|
|
|
|
|
|
description="检查年龄是否在合理范围内(0-150岁)",
|
|
|
|
|
|
condition="age.isdigit() and not (0 <= int(age) <= 150)",
|
|
|
|
|
|
severity=ConflictSeverity.WARNING,
|
|
|
|
|
|
category="common_sense"
|
|
|
|
|
|
),
|
|
|
|
|
|
ValidationRule(
|
|
|
|
|
|
id="age_format",
|
|
|
|
|
|
name="年龄格式",
|
|
|
|
|
|
description="年龄应为数字或数字范围",
|
|
|
|
|
|
pattern=r"^\d+(\s*-\s*\d+)?$",
|
|
|
|
|
|
severity=ConflictSeverity.INFO,
|
|
|
|
|
|
category="common_sense"
|
|
|
|
|
|
),
|
|
|
|
|
|
ValidationRule(
|
|
|
|
|
|
id="timeline_consistency",
|
|
|
|
|
|
name="时间线一致性",
|
|
|
|
|
|
description="检查关键事件时间线是否合理",
|
|
|
|
|
|
condition="'background' in character and 'timeline_events' in character",
|
|
|
|
|
|
severity=ConflictSeverity.WARNING,
|
|
|
|
|
|
category="timeline"
|
|
|
|
|
|
),
|
|
|
|
|
|
ValidationRule(
|
|
|
|
|
|
id="relationship_consistency",
|
|
|
|
|
|
name="关系一致性",
|
|
|
|
|
|
description="检查角色关系是否相互一致",
|
|
|
|
|
|
condition="'relationships' in character",
|
|
|
|
|
|
severity=ConflictSeverity.WARNING,
|
|
|
|
|
|
category="relationships"
|
|
|
|
|
|
),
|
|
|
|
|
|
ValidationRule(
|
|
|
|
|
|
id="duplicate_detection",
|
|
|
|
|
|
name="重复检测",
|
|
|
|
|
|
description="检测是否有重复角色",
|
|
|
|
|
|
condition="True", # 始终启用
|
|
|
|
|
|
severity=ConflictSeverity.ERROR,
|
|
|
|
|
|
category="duplicate"
|
|
|
|
|
|
)
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, rules_file: Optional[str] = None):
|
|
|
|
|
|
"""初始化冲突检测器
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
rules_file: 规则配置文件路径
|
|
|
|
|
|
"""
|
|
|
|
|
|
self.rules = self._load_rules(rules_file)
|
|
|
|
|
|
self.character_index = {} # 角色信息索引
|
|
|
|
|
|
|
|
|
|
|
|
def _load_rules(self, rules_file: Optional[str]) -> List[ValidationRule]:
|
|
|
|
|
|
"""加载校验规则
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
rules_file: 规则文件路径
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
规则列表
|
|
|
|
|
|
"""
|
|
|
|
|
|
rules = []
|
|
|
|
|
|
|
|
|
|
|
|
# 先加载默认规则
|
|
|
|
|
|
rules.extend(self.DEFAULT_COMMON_SENSE_RULES)
|
|
|
|
|
|
|
|
|
|
|
|
# 如果指定了规则文件,从文件加载
|
|
|
|
|
|
if rules_file and Path(rules_file).exists():
|
|
|
|
|
|
try:
|
|
|
|
|
|
with open(rules_file, 'r', encoding='utf-8') as f:
|
|
|
|
|
|
rule_data = json.load(f)
|
|
|
|
|
|
|
|
|
|
|
|
for rule_item in rule_data.get("rules", []):
|
|
|
|
|
|
rule = ValidationRule(
|
|
|
|
|
|
id=rule_item.get("id"),
|
|
|
|
|
|
name=rule_item.get("name"),
|
|
|
|
|
|
description=rule_item.get("description"),
|
|
|
|
|
|
pattern=rule_item.get("pattern"),
|
|
|
|
|
|
condition=rule_item.get("condition"),
|
|
|
|
|
|
severity=ConflictSeverity(rule_item.get("severity", "warning")),
|
|
|
|
|
|
enabled=rule_item.get("enabled", True),
|
|
|
|
|
|
category=rule_item.get("category", "common_sense")
|
|
|
|
|
|
)
|
|
|
|
|
|
rules.append(rule)
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(f"从文件加载了 {len(rule_data.get('rules', []))} 条规则")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"加载规则文件失败: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
return rules
|
|
|
|
|
|
|
|
|
|
|
|
def set_character_index(self, character_index: Dict):
|
|
|
|
|
|
"""设置角色信息索引
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
character_index: 角色索引字典
|
|
|
|
|
|
"""
|
|
|
|
|
|
self.character_index = character_index
|
|
|
|
|
|
|
|
|
|
|
|
def detect_conflicts(self, new_character: Dict, existing_characters: List[Dict] = None) -> List[Conflict]:
|
|
|
|
|
|
"""检测新角色与现有角色的冲突
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
new_character: 新角色信息
|
|
|
|
|
|
existing_characters: 现有角色列表,为None时使用character_index
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
冲突列表
|
|
|
|
|
|
"""
|
|
|
|
|
|
conflicts = []
|
|
|
|
|
|
|
|
|
|
|
|
# 获取现有角色
|
|
|
|
|
|
if existing_characters is None:
|
|
|
|
|
|
existing_characters = self.character_index.get("characters", [])
|
|
|
|
|
|
|
|
|
|
|
|
# 1. 检测重复姓名
|
|
|
|
|
|
duplicate_conflicts = self._detect_duplicate_names(new_character, existing_characters)
|
|
|
|
|
|
conflicts.extend(duplicate_conflicts)
|
|
|
|
|
|
|
|
|
|
|
|
# 2. 检测相似角色
|
|
|
|
|
|
similar_conflicts = self._detect_similar_characters(new_character, existing_characters)
|
|
|
|
|
|
conflicts.extend(similar_conflicts)
|
|
|
|
|
|
|
|
|
|
|
|
# 3. 检测常识错误
|
|
|
|
|
|
common_sense_conflicts = self._check_common_sense(new_character)
|
|
|
|
|
|
conflicts.extend(common_sense_conflicts)
|
|
|
|
|
|
|
|
|
|
|
|
# 4. 检测时间线冲突
|
|
|
|
|
|
timeline_conflicts = self._detect_timeline_conflicts(new_character, existing_characters)
|
|
|
|
|
|
conflicts.extend(timeline_conflicts)
|
|
|
|
|
|
|
|
|
|
|
|
# 5. 检测关系冲突
|
|
|
|
|
|
relationship_conflicts = self._detect_relationship_conflicts(new_character, existing_characters)
|
|
|
|
|
|
conflicts.extend(relationship_conflicts)
|
|
|
|
|
|
|
|
|
|
|
|
return conflicts
|
|
|
|
|
|
|
|
|
|
|
|
def _detect_duplicate_names(self, new_char: Dict, existing_chars: List[Dict]) -> List[Conflict]:
|
|
|
|
|
|
"""检测重复姓名
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
new_char: 新角色
|
|
|
|
|
|
existing_chars: 现有角色
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
冲突列表
|
|
|
|
|
|
"""
|
|
|
|
|
|
conflicts = []
|
|
|
|
|
|
new_name = new_char.get("name", "").strip().lower()
|
|
|
|
|
|
|
|
|
|
|
|
if not new_name:
|
|
|
|
|
|
return conflicts
|
|
|
|
|
|
|
|
|
|
|
|
for existing_char in existing_chars:
|
|
|
|
|
|
existing_name = existing_char.get("name", "").strip().lower()
|
|
|
|
|
|
|
|
|
|
|
|
if existing_name and existing_name == new_name:
|
|
|
|
|
|
conflict = Conflict(
|
|
|
|
|
|
type=ConflictType.DUPLICATE_NAME,
|
|
|
|
|
|
severity=ConflictSeverity.ERROR,
|
|
|
|
|
|
message=f"角色姓名重复: '{new_char.get('name')}' 已存在",
|
|
|
|
|
|
character_name=new_char.get("name", "未知"),
|
|
|
|
|
|
details={
|
|
|
|
|
|
"new_character": new_char.get("name"),
|
|
|
|
|
|
"existing_character": existing_char.get("name"),
|
|
|
|
|
|
"existing_file": existing_char.get("file_path", "")
|
|
|
|
|
|
},
|
|
|
|
|
|
related_characters=[existing_char.get("name", "未知")],
|
|
|
|
|
|
suggested_fixes=[
|
|
|
|
|
|
f"修改新角色姓名为 '{new_char.get('name')}_新'",
|
|
|
|
|
|
f"合并两个角色的设定",
|
|
|
|
|
|
f"删除或重命名现有角色"
|
|
|
|
|
|
]
|
|
|
|
|
|
)
|
|
|
|
|
|
conflicts.append(conflict)
|
|
|
|
|
|
|
|
|
|
|
|
return conflicts
|
|
|
|
|
|
|
|
|
|
|
|
def _detect_similar_characters(self, new_char: Dict, existing_chars: List[Dict]) -> List[Conflict]:
|
|
|
|
|
|
"""检测相似角色
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
new_char: 新角色
|
|
|
|
|
|
existing_chars: 现有角色
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
冲突列表
|
|
|
|
|
|
"""
|
|
|
|
|
|
conflicts = []
|
|
|
|
|
|
new_name = new_char.get("name", "").lower()
|
|
|
|
|
|
|
|
|
|
|
|
if not new_name:
|
|
|
|
|
|
return conflicts
|
|
|
|
|
|
|
|
|
|
|
|
# 相似度检测阈值
|
|
|
|
|
|
similarity_threshold = 0.7
|
|
|
|
|
|
|
|
|
|
|
|
for existing_char in existing_chars:
|
|
|
|
|
|
existing_name = existing_char.get("name", "").lower()
|
|
|
|
|
|
|
|
|
|
|
|
# 计算姓名相似度
|
|
|
|
|
|
similarity = self._calculate_name_similarity(new_name, existing_name)
|
|
|
|
|
|
|
|
|
|
|
|
if similarity > similarity_threshold:
|
|
|
|
|
|
conflict = Conflict(
|
|
|
|
|
|
type=ConflictType.SIMILAR_CHARACTER,
|
|
|
|
|
|
severity=ConflictSeverity.WARNING,
|
|
|
|
|
|
message=f"角色姓名相似: '{new_char.get('name')}' 与 '{existing_char.get('name')}' 相似度较高",
|
|
|
|
|
|
character_name=new_char.get("name", "未知"),
|
|
|
|
|
|
details={
|
|
|
|
|
|
"new_character": new_char.get("name"),
|
|
|
|
|
|
"existing_character": existing_char.get("name"),
|
|
|
|
|
|
"similarity_score": similarity,
|
|
|
|
|
|
"existing_file": existing_char.get("file_path", "")
|
|
|
|
|
|
},
|
|
|
|
|
|
related_characters=[existing_char.get("name", "未知")],
|
|
|
|
|
|
suggested_fixes=[
|
|
|
|
|
|
f"确认是否为不同角色",
|
|
|
|
|
|
f"修改其中一个角色的姓名以增加区分度",
|
|
|
|
|
|
f"检查角色设定是否重复"
|
|
|
|
|
|
]
|
|
|
|
|
|
)
|
|
|
|
|
|
conflicts.append(conflict)
|
|
|
|
|
|
|
|
|
|
|
|
# 检查其他相似特征
|
|
|
|
|
|
similarity_features = self._check_feature_similarity(new_char, existing_char)
|
|
|
|
|
|
if similarity_features:
|
|
|
|
|
|
conflict = Conflict(
|
|
|
|
|
|
type=ConflictType.SIMILAR_CHARACTER,
|
|
|
|
|
|
severity=ConflictSeverity.INFO,
|
|
|
|
|
|
message=f"角色特征相似: '{new_char.get('name')}' 与 '{existing_char.get('name')}' 有相似特征",
|
|
|
|
|
|
character_name=new_char.get("name", "未知"),
|
|
|
|
|
|
details={
|
|
|
|
|
|
"new_character": new_char.get("name"),
|
|
|
|
|
|
"existing_character": existing_char.get("name"),
|
|
|
|
|
|
"similar_features": similarity_features,
|
|
|
|
|
|
"existing_file": existing_char.get("file_path", "")
|
|
|
|
|
|
},
|
|
|
|
|
|
related_characters=[existing_char.get("name", "未知")],
|
|
|
|
|
|
suggested_fixes=[
|
|
|
|
|
|
f"区分角色特征",
|
|
|
|
|
|
f"强化角色独特性",
|
|
|
|
|
|
f"考虑角色合并"
|
|
|
|
|
|
]
|
|
|
|
|
|
)
|
|
|
|
|
|
conflicts.append(conflict)
|
|
|
|
|
|
|
|
|
|
|
|
return conflicts
|
|
|
|
|
|
|
|
|
|
|
|
def _calculate_name_similarity(self, name1: str, name2: str) -> float:
|
|
|
|
|
|
"""计算姓名相似度
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
name1: 姓名1
|
|
|
|
|
|
name2: 姓名2
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
相似度得分 (0-1)
|
|
|
|
|
|
"""
|
|
|
|
|
|
if not name1 or not name2:
|
|
|
|
|
|
return 0.0
|
|
|
|
|
|
|
|
|
|
|
|
# 简单的相似度计算:共同字符比例
|
|
|
|
|
|
set1 = set(name1.replace(' ', ''))
|
|
|
|
|
|
set2 = set(name2.replace(' ', ''))
|
|
|
|
|
|
|
|
|
|
|
|
if not set1 or not set2:
|
|
|
|
|
|
return 0.0
|
|
|
|
|
|
|
|
|
|
|
|
intersection = len(set1.intersection(set2))
|
|
|
|
|
|
union = len(set1.union(set2))
|
|
|
|
|
|
|
|
|
|
|
|
return intersection / union if union > 0 else 0.0
|
|
|
|
|
|
|
|
|
|
|
|
def _check_feature_similarity(self, char1: Dict, char2: Dict) -> List[str]:
|
|
|
|
|
|
"""检查特征相似性
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
char1: 角色1
|
|
|
|
|
|
char2: 角色2
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
相似特征列表
|
|
|
|
|
|
"""
|
|
|
|
|
|
similar_features = []
|
|
|
|
|
|
|
|
|
|
|
|
# 检查年龄
|
|
|
|
|
|
age1 = char1.get("age", "")
|
|
|
|
|
|
age2 = char2.get("age", "")
|
|
|
|
|
|
if age1 and age2 and age1 == age2:
|
|
|
|
|
|
similar_features.append(f"年龄相同: {age1}")
|
|
|
|
|
|
|
|
|
|
|
|
# 检查职业
|
|
|
|
|
|
occupation1 = char1.get("occupation", "")
|
|
|
|
|
|
occupation2 = char2.get("occupation", "")
|
|
|
|
|
|
if occupation1 and occupation2 and occupation1.lower() == occupation2.lower():
|
|
|
|
|
|
similar_features.append(f"职业相同: {occupation1}")
|
|
|
|
|
|
|
|
|
|
|
|
# 检查角色类型
|
|
|
|
|
|
type1 = char1.get("character_type", "")
|
|
|
|
|
|
type2 = char2.get("character_type", "")
|
|
|
|
|
|
if type1 and type2 and type1.lower() == type2.lower():
|
|
|
|
|
|
similar_features.append(f"角色类型相同: {type1}")
|
|
|
|
|
|
|
|
|
|
|
|
return similar_features
|
|
|
|
|
|
|
|
|
|
|
|
def _check_common_sense(self, character: Dict) -> List[Conflict]:
|
|
|
|
|
|
"""检查常识错误
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
character: 角色信息
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
冲突列表
|
|
|
|
|
|
"""
|
|
|
|
|
|
conflicts = []
|
|
|
|
|
|
|
|
|
|
|
|
for rule in self.rules:
|
|
|
|
|
|
if not rule.enabled or rule.category != "common_sense":
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
# 根据规则类型进行检查
|
|
|
|
|
|
if rule.pattern:
|
|
|
|
|
|
# 模式匹配规则
|
|
|
|
|
|
for field_name, field_value in character.items():
|
|
|
|
|
|
if isinstance(field_value, str):
|
|
|
|
|
|
if not re.match(rule.pattern, field_value):
|
|
|
|
|
|
conflict = Conflict(
|
|
|
|
|
|
type=ConflictType.COMMON_SENSE_ERROR,
|
|
|
|
|
|
severity=rule.severity,
|
|
|
|
|
|
message=f"{rule.name}: {field_name} '{field_value}' 不符合格式要求",
|
|
|
|
|
|
character_name=character.get("name", "未知"),
|
|
|
|
|
|
details={
|
|
|
|
|
|
"rule_id": rule.id,
|
|
|
|
|
|
"rule_name": rule.name,
|
|
|
|
|
|
"field": field_name,
|
|
|
|
|
|
"value": field_value,
|
|
|
|
|
|
"expected_pattern": rule.pattern
|
|
|
|
|
|
},
|
|
|
|
|
|
suggested_fixes=[
|
|
|
|
|
|
f"修改 {field_name} 为符合格式: {rule.pattern}",
|
|
|
|
|
|
f"检查 {field_name} 是否正确"
|
|
|
|
|
|
]
|
|
|
|
|
|
)
|
|
|
|
|
|
conflicts.append(conflict)
|
|
|
|
|
|
|
|
|
|
|
|
elif rule.condition:
|
|
|
|
|
|
# 条件规则(简化实现)
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 这里实现简化的条件检查
|
|
|
|
|
|
if rule.id == "age_realistic":
|
|
|
|
|
|
age_str = character.get("age", "")
|
|
|
|
|
|
if age_str.isdigit():
|
|
|
|
|
|
age = int(age_str)
|
|
|
|
|
|
if not (0 <= age <= 150):
|
|
|
|
|
|
conflict = Conflict(
|
|
|
|
|
|
type=ConflictType.COMMON_SENSE_ERROR,
|
|
|
|
|
|
severity=rule.severity,
|
|
|
|
|
|
message=f"{rule.name}: 年龄 {age} 不在合理范围 (0-150)",
|
|
|
|
|
|
character_name=character.get("name", "未知"),
|
|
|
|
|
|
details={
|
|
|
|
|
|
"rule_id": rule.id,
|
|
|
|
|
|
"rule_name": rule.name,
|
|
|
|
|
|
"field": "age",
|
|
|
|
|
|
"value": age,
|
|
|
|
|
|
"expected_range": "0-150"
|
|
|
|
|
|
},
|
|
|
|
|
|
suggested_fixes=[
|
|
|
|
|
|
"调整年龄到合理范围",
|
|
|
|
|
|
"如果是特殊设定(如非人类),请明确说明"
|
|
|
|
|
|
]
|
|
|
|
|
|
)
|
|
|
|
|
|
conflicts.append(conflict)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.debug(f"规则检查失败 {rule.id}: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
return conflicts
|
|
|
|
|
|
|
|
|
|
|
|
def _detect_timeline_conflicts(self, new_char: Dict, existing_chars: List[Dict]) -> List[Conflict]:
|
|
|
|
|
|
"""检测时间线冲突
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
new_char: 新角色
|
|
|
|
|
|
existing_chars: 现有角色
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
冲突列表
|
|
|
|
|
|
"""
|
|
|
|
|
|
conflicts = []
|
|
|
|
|
|
# 这里实现时间线冲突检测逻辑
|
|
|
|
|
|
# 需要从角色信息中提取时间线事件
|
|
|
|
|
|
return conflicts
|
|
|
|
|
|
|
|
|
|
|
|
def _detect_relationship_conflicts(self, new_char: Dict, existing_chars: List[Dict]) -> List[Conflict]:
|
|
|
|
|
|
"""检测关系冲突
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
new_char: 新角色
|
|
|
|
|
|
existing_chars: 现有角色
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
冲突列表
|
|
|
|
|
|
"""
|
|
|
|
|
|
conflicts = []
|
|
|
|
|
|
# 这里实现关系冲突检测逻辑
|
|
|
|
|
|
# 需要从角色信息中提取关系信息
|
|
|
|
|
|
return conflicts
|
|
|
|
|
|
|
|
|
|
|
|
def validate_character(self, character: Dict) -> Tuple[bool, List[Conflict]]:
|
|
|
|
|
|
"""验证单个角色
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
character: 角色信息
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
(是否有效, 冲突列表)
|
|
|
|
|
|
"""
|
|
|
|
|
|
conflicts = self.detect_conflicts(character)
|
|
|
|
|
|
is_valid = all(conflict.severity != ConflictSeverity.ERROR for conflict in conflicts)
|
|
|
|
|
|
|
|
|
|
|
|
return is_valid, conflicts
|
|
|
|
|
|
|
|
|
|
|
|
def generate_report(self, conflicts: List[Conflict], output_format: str = "text") -> str:
|
|
|
|
|
|
"""生成冲突报告
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
conflicts: 冲突列表
|
|
|
|
|
|
output_format: 输出格式,支持 'text', 'json', 'markdown'
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
报告字符串
|
|
|
|
|
|
"""
|
|
|
|
|
|
if output_format == "json":
|
|
|
|
|
|
report_data = []
|
|
|
|
|
|
for conflict in conflicts:
|
|
|
|
|
|
report_data.append({
|
|
|
|
|
|
"type": conflict.type.value,
|
|
|
|
|
|
"severity": conflict.severity.value,
|
|
|
|
|
|
"message": conflict.message,
|
|
|
|
|
|
"character_name": conflict.character_name,
|
|
|
|
|
|
"details": conflict.details,
|
|
|
|
|
|
"related_characters": conflict.related_characters,
|
|
|
|
|
|
"suggested_fixes": conflict.suggested_fixes
|
|
|
|
|
|
})
|
|
|
|
|
|
return json.dumps(report_data, ensure_ascii=False, indent=2)
|
|
|
|
|
|
|
|
|
|
|
|
elif output_format == "markdown":
|
|
|
|
|
|
report_lines = ["# 冲突检测报告", ""]
|
|
|
|
|
|
|
|
|
|
|
|
# 按严重程度分组
|
|
|
|
|
|
errors = [c for c in conflicts if c.severity == ConflictSeverity.ERROR]
|
|
|
|
|
|
warnings = [c for c in conflicts if c.severity == ConflictSeverity.WARNING]
|
|
|
|
|
|
infos = [c for c in conflicts if c.severity == ConflictSeverity.INFO]
|
|
|
|
|
|
|
|
|
|
|
|
if errors:
|
|
|
|
|
|
report_lines.append("## ❌ 错误")
|
|
|
|
|
|
for conflict in errors:
|
|
|
|
|
|
report_lines.append(f"### {conflict.message}")
|
|
|
|
|
|
report_lines.append(f"- **角色**: {conflict.character_name}")
|
|
|
|
|
|
report_lines.append(f"- **类型**: {conflict.type.value}")
|
|
|
|
|
|
if conflict.related_characters:
|
|
|
|
|
|
report_lines.append(f"- **相关角色**: {', '.join(conflict.related_characters)}")
|
|
|
|
|
|
if conflict.suggested_fixes:
|
|
|
|
|
|
report_lines.append(f"- **建议**:")
|
|
|
|
|
|
for fix in conflict.suggested_fixes:
|
|
|
|
|
|
report_lines.append(f" - {fix}")
|
|
|
|
|
|
report_lines.append("")
|
|
|
|
|
|
|
|
|
|
|
|
if warnings:
|
|
|
|
|
|
report_lines.append("## ⚠️ 警告")
|
|
|
|
|
|
for conflict in warnings:
|
|
|
|
|
|
report_lines.append(f"### {conflict.message}")
|
|
|
|
|
|
report_lines.append(f"- **角色**: {conflict.character_name}")
|
|
|
|
|
|
report_lines.append(f"- **类型**: {conflict.type.value}")
|
|
|
|
|
|
if conflict.suggested_fixes:
|
|
|
|
|
|
report_lines.append(f"- **建议**: {conflict.suggested_fixes[0]}")
|
|
|
|
|
|
report_lines.append("")
|
|
|
|
|
|
|
|
|
|
|
|
if infos:
|
|
|
|
|
|
report_lines.append("## ℹ️ 提示")
|
|
|
|
|
|
for conflict in infos:
|
|
|
|
|
|
report_lines.append(f"- {conflict.message}")
|
|
|
|
|
|
report_lines.append("")
|
|
|
|
|
|
|
|
|
|
|
|
return "\n".join(report_lines)
|
|
|
|
|
|
|
|
|
|
|
|
else: # text格式
|
|
|
|
|
|
report_lines = ["冲突检测报告", "=" * 40, ""]
|
|
|
|
|
|
|
|
|
|
|
|
# 统计
|
|
|
|
|
|
error_count = len([c for c in conflicts if c.severity == ConflictSeverity.ERROR])
|
|
|
|
|
|
warning_count = len([c for c in conflicts if c.severity == ConflictSeverity.WARNING])
|
|
|
|
|
|
info_count = len([c for c in conflicts if c.severity == ConflictSeverity.INFO])
|
|
|
|
|
|
|
|
|
|
|
|
report_lines.append(f"发现 {error_count} 个错误, {warning_count} 个警告, {info_count} 个提示")
|
|
|
|
|
|
report_lines.append("")
|
|
|
|
|
|
|
|
|
|
|
|
# 按角色分组显示
|
|
|
|
|
|
characters_conflicts = {}
|
|
|
|
|
|
for conflict in conflicts:
|
|
|
|
|
|
char_name = conflict.character_name
|
|
|
|
|
|
if char_name not in characters_conflicts:
|
|
|
|
|
|
characters_conflicts[char_name] = []
|
|
|
|
|
|
characters_conflicts[char_name].append(conflict)
|
|
|
|
|
|
|
|
|
|
|
|
for char_name, char_conflicts in characters_conflicts.items():
|
|
|
|
|
|
report_lines.append(f"角色: {char_name}")
|
|
|
|
|
|
report_lines.append("-" * 30)
|
|
|
|
|
|
|
|
|
|
|
|
for conflict in char_conflicts:
|
|
|
|
|
|
severity_icon = {
|
|
|
|
|
|
ConflictSeverity.ERROR: "[错误]",
|
|
|
|
|
|
ConflictSeverity.WARNING: "[警告]",
|
|
|
|
|
|
ConflictSeverity.INFO: "[提示]"
|
|
|
|
|
|
}.get(conflict.severity, "[未知]")
|
|
|
|
|
|
|
|
|
|
|
|
report_lines.append(f"{severity_icon} {conflict.message}")
|
|
|
|
|
|
|
|
|
|
|
|
if conflict.related_characters:
|
|
|
|
|
|
report_lines.append(f" 相关角色: {', '.join(conflict.related_characters)}")
|
|
|
|
|
|
|
|
|
|
|
|
if conflict.suggested_fixes:
|
|
|
|
|
|
report_lines.append(f" 建议: {conflict.suggested_fixes[0]}")
|
|
|
|
|
|
|
|
|
|
|
|
report_lines.append("")
|
|
|
|
|
|
|
|
|
|
|
|
return "\n".join(report_lines)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
|
"""命令行测试"""
|
|
|
|
|
|
import sys
|
|
|
|
|
|
|
|
|
|
|
|
if len(sys.argv) < 2:
|
|
|
|
|
|
print("用法: python conflict_detector.py <新角色JSON文件> [现有角色目录]")
|
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
# 加载新角色信息
|
|
|
|
|
|
new_char_file = sys.argv[1]
|
|
|
|
|
|
try:
|
|
|
|
|
|
with open(new_char_file, 'r', encoding='utf-8') as f:
|
|
|
|
|
|
new_character = json.load(f)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"加载新角色文件失败: {e}")
|
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建检测器
|
|
|
|
|
|
detector = ConflictDetector()
|
|
|
|
|
|
|
|
|
|
|
|
# 如果有现有角色目录,扫描现有角色
|
|
|
|
|
|
existing_characters = []
|
|
|
|
|
|
if len(sys.argv) > 2:
|
|
|
|
|
|
from lore_bible_manager import LoreBibleManager
|
|
|
|
|
|
try:
|
|
|
|
|
|
manager = LoreBibleManager(sys.argv[2])
|
|
|
|
|
|
existing_characters = manager.scan_existing_characters()
|
|
|
|
|
|
print(f"扫描到 {len(existing_characters)} 个现有角色")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"扫描现有角色失败: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
# 设置角色索引
|
|
|
|
|
|
if existing_characters:
|
|
|
|
|
|
detector.set_character_index({
|
|
|
|
|
|
"characters": existing_characters,
|
|
|
|
|
|
"total_count": len(existing_characters)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
# 检测冲突
|
|
|
|
|
|
conflicts = detector.detect_conflicts(new_character, existing_characters)
|
|
|
|
|
|
|
|
|
|
|
|
# 生成报告
|
|
|
|
|
|
report = detector.generate_report(conflicts, "text")
|
|
|
|
|
|
print(report)
|
|
|
|
|
|
|
|
|
|
|
|
# 检查是否有效
|
|
|
|
|
|
is_valid, _ = detector.validate_character(new_character)
|
|
|
|
|
|
if is_valid:
|
|
|
|
|
|
print("\n✓ 角色设定基本有效")
|
|
|
|
|
|
else:
|
|
|
|
|
|
print("\n✗ 角色设定存在错误")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2026-03-25 16:42:18 +08:00
|
|
|
|
main()
|