Files
cultivation-world-simulator/src/classes/relation_resolver.py
2025-12-29 22:02:44 +08:00

146 lines
5.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
from typing import TYPE_CHECKING, List, Tuple, Optional
import asyncio
from src.classes.relation import (
Relation,
get_relation_rules_desc,
relation_display_names
)
from src.classes.relations import (
set_relation,
cancel_relation,
)
from src.classes.calendar import get_date_str
from src.classes.event import Event
from src.utils.llm import call_llm_with_task_name
from src.utils.config import CONFIG
if TYPE_CHECKING:
from src.classes.avatar import Avatar
class RelationResolver:
TEMPLATE_PATH = CONFIG.paths.templates / "relation_update.txt"
@staticmethod
def _build_prompt_data(avatar_a: "Avatar", avatar_b: "Avatar") -> dict:
# 1. 获取近期交互记录
# 优先使用 EventManager 的索引
event_manager = avatar_a.world.event_manager
# 获取已归档的历史事件 (取最近10条)
# get_events_between 返回的是按时间正序排列的
recent_events = event_manager.get_events_between(avatar_a.id, avatar_b.id, limit=10)
event_lines = [str(e) for e in recent_events]
recent_events_text = "\n".join(event_lines) if event_lines else "近期无显著交互记录。"
# 2. 获取当前关系描述
current_rel = avatar_a.get_relation(avatar_b)
rel_desc = ""
if current_rel:
rel_name = relation_display_names.get(current_rel, current_rel.value)
rel_desc = f"{rel_name}"
# 获取当前世界时间
current_time_str = get_date_str(avatar_a.world.month_stamp)
return {
"relation_rules_desc": get_relation_rules_desc(),
"avatar_a_name": avatar_a.name,
"avatar_a_info": str(avatar_a.get_info(detailed=True)),
"avatar_b_name": avatar_b.name,
"avatar_b_info": str(avatar_b.get_info(detailed=True)),
"current_relations": f"目前关系:{rel_desc}",
"recent_events_text": recent_events_text,
"current_time": current_time_str
}
@staticmethod
async def resolve_pair(avatar_a: "Avatar", avatar_b: "Avatar") -> Optional[Event]:
"""
处理一对角色的关系变化,返回产生的事件
"""
infos = RelationResolver._build_prompt_data(avatar_a, avatar_b)
result = await call_llm_with_task_name("relation_resolver", RelationResolver.TEMPLATE_PATH, infos)
changed = result.get("changed", False)
if not changed:
return None
month_stamp = avatar_a.world.month_stamp
c_type = result.get("change_type")
rel_name = result.get("relation")
reason = result.get("reason", "")
if not rel_name:
return None
# 解析关系枚举
try:
rel = Relation[rel_name]
except KeyError:
return None
display_name = relation_display_names.get(rel, rel_name)
event = None
if c_type == "ADD":
# 检查是否已有
# Prompt 定义:输出 MASTER 意味着 A 是 B 的师傅
# 代码逻辑set_relation(from, to, rel) -> from.relations[to] = rel (from 认为 to 是 rel)
# 因此,如果 LLM 输出 MASTER (A 是师傅),意味着 A 认为 B 是徒弟(APPRENTICE)B 认为 A 是师傅(MASTER)
# 所以我们要调用 set_relation(B, A, MASTER) 或者 set_relation(A, B, APPRENTICE)
# 统一逻辑:以“谁视谁为某个关系”来思考。
# 如果 rel 是 A 的身份(如 MASTER则 B 视 A 为 rel。
# 调用 set_relation(B, A, rel) 会设置 B.relations[A] = rel
# set_relation 内部会自动处理对偶关系,所以 A.relations[B] 也会被设为 APPRENTICE
# 但要注意对称关系LOVERS, FRIEND, ENEMY
# A 是 B 的朋友 -> B 视 A 为 FRIEND。 set_relation(B, A, FRIEND) -> A.relations[B] = FRIEND (正确)
# 结论:始终调用 set_relation(avatar_b, avatar_a, rel)
current_rel = avatar_b.get_relation(avatar_a)
if current_rel == rel:
return None
set_relation(avatar_b, avatar_a, rel)
event_text = f"因为{reason}{avatar_a.name}成为{avatar_b.name}{display_name}"
event = Event(month_stamp, event_text, related_avatars=[avatar_a.id, avatar_b.id], is_major=True)
elif c_type == "REMOVE":
# 同样反转调用
success = cancel_relation(avatar_b, avatar_a, rel)
if success:
event_text = f"因为{reason}{avatar_a.name}不再是{avatar_b.name}{display_name}"
event = Event(month_stamp, event_text, related_avatars=[avatar_a.id, avatar_b.id], is_major=True)
if event:
# 手动调用 add_event(to_sidebar=False) 来更新统计数据,但不加入 pending_events
# 因为事件将由 Simulator 统一处理
avatar_a.add_event(event, to_sidebar=False)
avatar_b.add_event(event, to_sidebar=False)
return event
return None
@staticmethod
async def run_batch(pairs: List[Tuple["Avatar", "Avatar"]]) -> List[Event]:
"""
批量并发处理,返回产生的所有事件
"""
if not pairs:
return []
# 并发执行所有任务
tasks = [RelationResolver.resolve_pair(a, b) for a, b in pairs]
results = await asyncio.gather(*tasks)
# 过滤掉 None 结果 (resolve_pair 失败或无变化时返回 None)
return [res for res in results if res]