decide only one avatar each time

This commit is contained in:
bridge
2025-12-08 22:08:53 +08:00
parent 33cf306e58
commit 303bffe413
9 changed files with 96 additions and 30 deletions

View File

@@ -66,22 +66,33 @@ class LLMAI(AI):
async def _decide(self, world: World, avatars_to_decide: list[Avatar]) -> dict[Avatar, tuple[ACTION_NAME_PARAMS_PAIRS, str, str]]:
"""
异步决策逻辑通过LLM决定执行什么动作和参数
改动:支持每个角色仅获取其已知区域的世界信息,并发调用 LLM。
"""
world_info = world.get_info()
# 在提示中包含处于角色观测范围内的其他角色
avatar_infos = {}
for avatar in avatars_to_decide:
observed = world.get_observable_avatars(avatar)
avatar_infos[avatar.name] = avatar.get_expanded_info(observed)
general_action_infos = ACTION_INFOS_STR
info = {
"avatar_infos": avatar_infos,
"world_info": world_info,
"general_action_infos": general_action_infos,
}
res = await call_ai_action(info)
async def decide_one(avatar: Avatar):
# 获取基于该角色已知区域的世界信息
world_info = world.get_info(known_region_ids=avatar.known_regions)
# 在提示中包含处于角色观测范围内的其他角色
observed = world.get_observable_avatars(avatar)
avatar_info = avatar.get_expanded_info(co_region_avatars=observed)
info = {
"avatar_info": avatar_info,
"world_info": world_info,
"general_action_infos": general_action_infos,
}
res = await call_ai_action(info)
return avatar, res
tasks = [decide_one(avatar) for avatar in avatars_to_decide]
results_list = await asyncio.gather(*tasks)
results: dict[Avatar, tuple[ACTION_NAME_PARAMS_PAIRS, str, str]] = {}
for avatar in avatars_to_decide:
for avatar, res in results_list:
if not res or avatar.name not in res:
continue
r = res[avatar.name]
# 仅接受 action_name_params_pairs不再支持单个 action_name/action_params
raw_pairs = r["action_name_params_pairs"]
@@ -94,6 +105,7 @@ class LLMAI(AI):
else:
# 跳过无法解析的项
continue
# 至少有一个
if not pairs:
raise ValueError(f"LLM未返回有效的action_name_params_pairs: {r}")
@@ -101,6 +113,7 @@ class LLMAI(AI):
avatar_thinking = r.get("avatar_thinking", r.get("thinking", ""))
short_term_objective = r.get("short_term_objective", "")
results[avatar] = (pairs, avatar_thinking, short_term_objective)
return results
llm_ai = LLMAI()

View File

@@ -125,6 +125,9 @@ class Avatar(AvatarSaveMixin, AvatarLoadMixin):
# 动作冷却:记录动作类名 -> 上次完成月戳
_action_cd_last_months: dict[str, int] = field(default_factory=dict)
# 不缓存 effects实时从宗门与功法合并
# 知道的区域 ID 集合
known_regions: set[int] = field(default_factory=set)
def join_sect(self, sect: Sect, rank: "SectRank") -> None:
"""加入宗门"""
@@ -216,6 +219,24 @@ class Avatar(AvatarSaveMixin, AvatarLoadMixin):
# 初始化时计算所有长期效果HP等
self.recalc_effects()
# 初始化已知区域
self._init_known_regions()
def _init_known_regions(self):
"""初始化已知区域:当前位置 + 宗门驻地"""
# 1. 当前位置
if self.tile and self.tile.region:
self.known_regions.add(self.tile.region.id)
# 2. 宗门驻地
if self.sect:
# 遍历地图寻找该宗门的驻地
# map.sect_regions 是 {region_id: SectRegion}
for r in self.world.map.sect_regions.values():
if r.sect_id == self.sect.id:
self.known_regions.add(r.id)
break
@property
def effects(self) -> dict[str, object]:
merged: dict[str, object] = defaultdict(str)
@@ -571,6 +592,11 @@ class Avatar(AvatarSaveMixin, AvatarLoadMixin):
if self.current_action is None:
# 当前无动作时才清除标记,避免清除新提交动作的标记
self._new_action_set_this_step = False
# 每次执行动作后,更新已知区域
if self.tile and self.tile.region:
self.known_regions.add(self.tile.region.id)
return events
def update_cultivation(self, new_level: int):

View File

@@ -78,8 +78,8 @@ async def generate_long_term_objective(avatar: "Avatar") -> Optional[LongTermObj
Returns:
生成的LongTermObjective对象失败则返回None
"""
# 准备世界信息
world_info = avatar.world.get_info()
# 准备世界信息(仅获取已知区域)
world_info = avatar.world.get_info(known_region_ids=avatar.known_regions)
# 获取 expanded_info包含详细信息和事件历史
expanded_info = avatar.get_expanded_info(detailed=True)

View File

@@ -80,9 +80,10 @@ class Map():
"""
return self.tiles[(x, y)].region
def get_info(self, detailed: bool = False) -> dict:
def get_info(self, detailed: bool = False, known_region_ids: Optional[set[int]] = None) -> dict:
"""
返回地图信息dict
known_region_ids: 如果提供仅返回这些ID对应的区域信息。
"""
# 动态分类(因为现在没有自动分类字典了)
# 或者我们简单点,不分类返回,只返回总览?
@@ -91,7 +92,10 @@ class Map():
from src.classes.region import NormalRegion, CultivateRegion, CityRegion
def filter_regions(cls):
return {rid: r for rid, r in self.regions.items() if isinstance(r, cls)}
return {
rid: r for rid, r in self.regions.items()
if rid in known_region_ids
}
def build_regions_info(regions_dict) -> list[str]:
if detailed:

View File

@@ -23,12 +23,13 @@ class World():
# 天地灵机开始年份(用于计算持续时间)
phenomenon_start_year: int = 0
def get_info(self, detailed: bool = False) -> dict:
def get_info(self, detailed: bool = False, known_region_ids: Optional[set[int]] = None) -> dict:
"""
返回世界信息dict其中包含地图信息dict
如果指定了 known_region_ids则只返回这些 ID 对应的区域信息。
"""
static_info = self.static_info
map_info = self.map.get_info(detailed=detailed)
map_info = self.map.get_info(detailed=detailed, known_region_ids=known_region_ids)
world_info = {**map_info, **static_info}
if self.current_phenomenon: