add nickname

This commit is contained in:
bridge
2025-11-18 01:23:43 +08:00
parent 19f5faabf3
commit c1963beef2
5 changed files with 197 additions and 3 deletions

View File

@@ -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
View 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

View File

@@ -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:

View File

@@ -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

View File

@@ -0,0 +1,30 @@
你是一个仙侠世界的故事家,负责为修仙界人物起绰号。
绰号是修仙界中对一个人物的评价和称谓,要求:
1. 符合修仙世界观,具有仙侠风格
2. 体现角色的特点、行为、性格或事迹
3. 简洁有力朗朗上口2-4个字为佳
4. 要帅气、有意境
角色信息:
{avatar_info}
角色的长期事件(重大事迹):
{major_events}
角色的短期事件(近期经历):
{minor_events}
基于以上信息,为该角色起一个合适的修仙界绰号。
返回JSON格式
{{
"thinking": "分析角色特点、主要事迹、性格特质,思考什么绰号最能体现这个人物...",
"nickname": "绰号"
}}
注意:
- thinking是你的思考过程要详细分析
- nickname只返回绰号本身不要加引号或其他符号
- 绰号要符合修仙世界的风格