add nickname
This commit is contained in:
@@ -108,6 +108,8 @@ class Avatar(AvatarSaveMixin, AvatarLoadMixin):
|
||||
auxiliary: Optional[Auxiliary] = None
|
||||
# 灵兽:最多一个;若再次捕捉则覆盖
|
||||
spirit_animal: Optional[SpiritAnimal] = None
|
||||
# 绰号:江湖中对该角色的称谓,满足条件后生成,永久不变
|
||||
nickname: Optional[str] = None
|
||||
# 当月/当步新设动作标记:在 commit_next_plan 设为 True,首次 tick_action 后清为 False
|
||||
_new_action_set_this_step: bool = False
|
||||
# 动作冷却:记录动作类名 -> 上次完成月戳
|
||||
@@ -260,6 +262,9 @@ class Avatar(AvatarSaveMixin, AvatarLoadMixin):
|
||||
"兵器": weapon_info,
|
||||
"辅助装备": auxiliary_info,
|
||||
}
|
||||
# 绰号:仅在存在时显示
|
||||
if self.nickname is not None:
|
||||
info_dict["绰号"] = self.nickname
|
||||
# 灵兽:仅在存在时显示
|
||||
if self.spirit_animal is not None:
|
||||
info_dict["灵兽"] = spirit_animal_info
|
||||
@@ -574,7 +579,10 @@ class Avatar(AvatarSaveMixin, AvatarLoadMixin):
|
||||
|
||||
lines: list[str] = []
|
||||
# 基础信息
|
||||
lines.append(f"{self.name}")
|
||||
if self.nickname:
|
||||
lines.append(f"{self.name}「{self.nickname}」")
|
||||
else:
|
||||
lines.append(f"{self.name}")
|
||||
add_kv(lines, "性别", self.gender)
|
||||
add_kv(lines, "年龄", self.age)
|
||||
add_kv(lines, "外貌", self.appearance.get_info())
|
||||
|
||||
136
src/classes/nickname.py
Normal file
136
src/classes/nickname.py
Normal file
@@ -0,0 +1,136 @@
|
||||
"""
|
||||
绰号生成模块
|
||||
为满足条件的角色生成江湖绰号
|
||||
"""
|
||||
from typing import Optional, List, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from src.classes.avatar import Avatar
|
||||
from src.classes.world import World
|
||||
|
||||
from src.classes.event import Event
|
||||
from src.utils.config import CONFIG
|
||||
from src.utils.llm import get_prompt_and_call_llm_async
|
||||
from src.run.log import get_logger
|
||||
|
||||
logger = get_logger().logger
|
||||
|
||||
|
||||
def can_get_nickname(avatar: "Avatar") -> bool:
|
||||
"""
|
||||
检查角色是否满足获得绰号的条件
|
||||
|
||||
条件:
|
||||
1. 尚未拥有绰号(nickname为None)
|
||||
2. 长期事件数量 >= major_event_threshold
|
||||
3. 短期事件数量 >= minor_event_threshold
|
||||
|
||||
Args:
|
||||
avatar: 要检查的角色
|
||||
|
||||
Returns:
|
||||
是否满足条件
|
||||
"""
|
||||
# 已有绰号,不再生成
|
||||
if avatar.nickname is not None:
|
||||
return False
|
||||
|
||||
# 检查事件数量
|
||||
em = avatar.world.event_manager
|
||||
major_threshold = CONFIG.nickname.major_event_threshold
|
||||
minor_threshold = CONFIG.nickname.minor_event_threshold
|
||||
|
||||
major_events = em.get_major_events_by_avatar(avatar.id)
|
||||
minor_events = em.get_minor_events_by_avatar(avatar.id)
|
||||
|
||||
major_count = len(major_events)
|
||||
minor_count = len(minor_events)
|
||||
|
||||
# AND逻辑:两个条件都要满足
|
||||
return major_count >= major_threshold and minor_count >= minor_threshold
|
||||
|
||||
|
||||
async def generate_nickname(avatar: "Avatar") -> Optional[str]:
|
||||
"""
|
||||
为角色生成绰号
|
||||
|
||||
调用LLM基于角色信息和事件历史生成合适的绰号
|
||||
|
||||
Args:
|
||||
avatar: 要生成绰号的角色
|
||||
|
||||
Returns:
|
||||
生成的绰号,失败则返回None
|
||||
"""
|
||||
try:
|
||||
# 准备角色信息
|
||||
avatar_info = avatar.get_info(detailed=True)
|
||||
avatar_info_str = "\n".join([f"{k}: {v}" for k, v in avatar_info.items()])
|
||||
|
||||
# 获取事件历史(根据配置的阈值获取对应数量的事件)
|
||||
em = avatar.world.event_manager
|
||||
major_threshold = CONFIG.nickname.major_event_threshold
|
||||
minor_threshold = CONFIG.nickname.minor_event_threshold
|
||||
major_events = em.get_major_events_by_avatar(avatar.id, limit=major_threshold)
|
||||
minor_events = em.get_minor_events_by_avatar(avatar.id, limit=minor_threshold)
|
||||
|
||||
major_events_str = "\n".join([f"- {str(e)}" for e in major_events]) if major_events else "无"
|
||||
minor_events_str = "\n".join([f"- {str(e)}" for e in minor_events]) if minor_events else "无"
|
||||
|
||||
# 准备模板参数
|
||||
template_path = CONFIG.paths.templates / "nickname.txt"
|
||||
infos = {
|
||||
"avatar_info": avatar_info_str,
|
||||
"major_events": major_events_str,
|
||||
"minor_events": minor_events_str
|
||||
}
|
||||
|
||||
# 调用LLM并自动解析JSON
|
||||
response_data = await get_prompt_and_call_llm_async(template_path, infos, mode="normal")
|
||||
|
||||
nickname = response_data.get("nickname", "").strip()
|
||||
thinking = response_data.get("thinking", "")
|
||||
|
||||
if not nickname:
|
||||
logger.warning(f"为角色 {avatar.name} 生成绰号失败:返回空绰号")
|
||||
return None
|
||||
|
||||
logger.info(f"为角色 {avatar.name} 生成绰号:{nickname}")
|
||||
logger.debug(f"绰号生成思考过程:{thinking}")
|
||||
|
||||
return nickname
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"生成绰号时出错:{e}")
|
||||
return None
|
||||
|
||||
|
||||
async def process_avatar_nickname(avatar: "Avatar") -> Optional[Event]:
|
||||
"""
|
||||
处理单个角色的绰号生成
|
||||
|
||||
检查角色是否满足条件,满足则生成绰号并返回对应事件
|
||||
|
||||
Args:
|
||||
avatar: 要处理的角色
|
||||
|
||||
Returns:
|
||||
生成的事件,如果不满足条件或生成失败则返回None
|
||||
"""
|
||||
if not can_get_nickname(avatar):
|
||||
return None
|
||||
|
||||
nickname = await generate_nickname(avatar)
|
||||
if not nickname:
|
||||
return None
|
||||
|
||||
avatar.nickname = nickname
|
||||
# 生成事件:角色获得绰号
|
||||
event = Event(
|
||||
avatar.world.month_stamp,
|
||||
f"{avatar.name}在江湖中闯出名号,被人称为'{nickname}'。",
|
||||
related_avatars=[avatar.id],
|
||||
is_major=True
|
||||
)
|
||||
return event
|
||||
|
||||
@@ -122,6 +122,19 @@ class Simulator:
|
||||
events.extend(fortune_events)
|
||||
return events
|
||||
|
||||
async def _phase_nickname_generation(self):
|
||||
"""
|
||||
绰号生成阶段
|
||||
"""
|
||||
from src.classes.nickname import process_avatar_nickname
|
||||
|
||||
events = []
|
||||
for avatar in list(self.world.avatar_manager.avatars.values()):
|
||||
event = await process_avatar_nickname(avatar)
|
||||
if event:
|
||||
events.append(event)
|
||||
return events
|
||||
|
||||
def _phase_update_celestial_phenomenon(self):
|
||||
"""
|
||||
更新天地灵机:
|
||||
@@ -214,10 +227,13 @@ class Simulator:
|
||||
# 6. 被动结算(时间效果+奇遇)
|
||||
events.extend(await self._phase_passive_effects())
|
||||
|
||||
# 7. 更新天地灵机
|
||||
# 7. 绰号生成
|
||||
events.extend(await self._phase_nickname_generation())
|
||||
|
||||
# 8. 更新天地灵机
|
||||
events.extend(self._phase_update_celestial_phenomenon())
|
||||
|
||||
# 8. 日志
|
||||
# 9. 日志
|
||||
# 统一写入事件管理器
|
||||
if hasattr(self.world, "event_manager") and self.world.event_manager is not None:
|
||||
for e in events:
|
||||
|
||||
@@ -33,6 +33,10 @@ social:
|
||||
major_event_context_num: 10 # 大事(长期记忆)展示数量
|
||||
minor_event_context_num: 10 # 小事(短期记忆)展示数量
|
||||
|
||||
nickname:
|
||||
major_event_threshold: 10 # 获得绰号需要的长期事件数量
|
||||
minor_event_threshold: 50 # 获得绰号需要的短期事件数量
|
||||
|
||||
save:
|
||||
max_events_to_save: 1000
|
||||
|
||||
|
||||
30
static/templates/nickname.txt
Normal file
30
static/templates/nickname.txt
Normal file
@@ -0,0 +1,30 @@
|
||||
你是一个仙侠世界的故事家,负责为修仙界人物起绰号。
|
||||
|
||||
绰号是修仙界中对一个人物的评价和称谓,要求:
|
||||
1. 符合修仙世界观,具有仙侠风格
|
||||
2. 体现角色的特点、行为、性格或事迹
|
||||
3. 简洁有力,朗朗上口(2-4个字为佳)
|
||||
4. 要帅气、有意境
|
||||
|
||||
角色信息:
|
||||
{avatar_info}
|
||||
|
||||
角色的长期事件(重大事迹):
|
||||
{major_events}
|
||||
|
||||
角色的短期事件(近期经历):
|
||||
{minor_events}
|
||||
|
||||
基于以上信息,为该角色起一个合适的修仙界绰号。
|
||||
|
||||
返回JSON格式:
|
||||
{{
|
||||
"thinking": "分析角色特点、主要事迹、性格特质,思考什么绰号最能体现这个人物...",
|
||||
"nickname": "绰号"
|
||||
}}
|
||||
|
||||
注意:
|
||||
- thinking是你的思考过程,要详细分析
|
||||
- nickname只返回绰号本身,不要加引号或其他符号
|
||||
- 绰号要符合修仙世界的风格
|
||||
|
||||
Reference in New Issue
Block a user