fix bug
This commit is contained in:
@@ -5,6 +5,7 @@ NPC AI 的类。
|
||||
from __future__ import annotations
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import TYPE_CHECKING
|
||||
import asyncio
|
||||
|
||||
from src.classes.world import World
|
||||
from src.classes.event import Event, NULL_EVENT
|
||||
@@ -35,8 +36,16 @@ class AI(ABC):
|
||||
"""
|
||||
results = {}
|
||||
max_decide_num = CONFIG.ai.max_decide_num
|
||||
|
||||
# 使用 asyncio.gather 并行执行多个批次的决策
|
||||
tasks = []
|
||||
for i in range(0, len(avatars_to_decide), max_decide_num):
|
||||
results.update(await self._decide(world, avatars_to_decide[i:i+max_decide_num]))
|
||||
tasks.append(self._decide(world, avatars_to_decide[i:i+max_decide_num]))
|
||||
|
||||
if tasks:
|
||||
batch_results_list = await asyncio.gather(*tasks)
|
||||
for batch_result in batch_results_list:
|
||||
results.update(batch_result)
|
||||
|
||||
for avatar, result in list(results.items()):
|
||||
action_name_params_pairs, avatar_thinking, short_term_objective = result # type: ignore
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import random
|
||||
import asyncio
|
||||
|
||||
from src.classes.calendar import Month, Year, MonthStamp
|
||||
from src.classes.avatar import Avatar, Gender
|
||||
@@ -118,9 +119,14 @@ class Simulator:
|
||||
events = []
|
||||
for avatar in self.world.avatar_manager.avatars.values():
|
||||
avatar.update_time_effect()
|
||||
for avatar in list(self.world.avatar_manager.avatars.values()):
|
||||
fortune_events = await try_trigger_fortune(avatar)
|
||||
events.extend(fortune_events)
|
||||
|
||||
# 使用 gather 并行触发奇遇
|
||||
tasks = [try_trigger_fortune(avatar) for avatar in self.world.avatar_manager.avatars.values()]
|
||||
results = await asyncio.gather(*tasks)
|
||||
for res in results:
|
||||
if res:
|
||||
events.extend(res)
|
||||
|
||||
return events
|
||||
|
||||
async def _phase_nickname_generation(self):
|
||||
@@ -129,11 +135,11 @@ class Simulator:
|
||||
"""
|
||||
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)
|
||||
# 并发执行
|
||||
tasks = [process_avatar_nickname(avatar) for avatar in self.world.avatar_manager.avatars.values()]
|
||||
results = await asyncio.gather(*tasks)
|
||||
|
||||
events = [e for e in results if e]
|
||||
return events
|
||||
|
||||
async def _phase_long_term_objective_thinking(self):
|
||||
@@ -141,11 +147,11 @@ class Simulator:
|
||||
长期目标思考阶段
|
||||
检查角色是否需要生成/更新长期目标
|
||||
"""
|
||||
events = []
|
||||
for avatar in list(self.world.avatar_manager.avatars.values()):
|
||||
event = await process_avatar_long_term_objective(avatar)
|
||||
if event:
|
||||
events.append(event)
|
||||
# 并发执行
|
||||
tasks = [process_avatar_long_term_objective(avatar) for avatar in self.world.avatar_manager.avatars.values()]
|
||||
results = await asyncio.gather(*tasks)
|
||||
|
||||
events = [e for e in results if e]
|
||||
return events
|
||||
|
||||
def _phase_update_celestial_phenomenon(self):
|
||||
|
||||
53
src/utils/ai_batch.py
Normal file
53
src/utils/ai_batch.py
Normal file
@@ -0,0 +1,53 @@
|
||||
"""
|
||||
通用 AI 任务批处理器。
|
||||
用于将串行的异步任务收集起来并行执行,优化 LLM 密集型场景的性能。
|
||||
"""
|
||||
import asyncio
|
||||
from typing import Coroutine, Any, List
|
||||
|
||||
class AITaskBatch:
|
||||
"""
|
||||
AI 任务批处理器。
|
||||
|
||||
使用示例:
|
||||
```python
|
||||
async with AITaskBatch() as batch:
|
||||
for item in items:
|
||||
batch.add(process_item(item))
|
||||
# with 块结束时,所有任务已并发执行完毕
|
||||
```
|
||||
"""
|
||||
def __init__(self):
|
||||
self.tasks: List[Coroutine[Any, Any, Any]] = []
|
||||
|
||||
def add(self, coro: Coroutine[Any, Any, Any]) -> None:
|
||||
"""
|
||||
添加一个协程任务到池中(不立即执行)。
|
||||
注意:传入的协程应该自行处理结果(如修改对象状态),或者通过外部变量收集结果。
|
||||
"""
|
||||
self.tasks.append(coro)
|
||||
|
||||
async def run(self) -> List[Any]:
|
||||
"""
|
||||
并行执行池中所有任务,并等待全部完成。
|
||||
返回所有任务的结果列表(顺序与添加顺序一致)。
|
||||
"""
|
||||
if not self.tasks:
|
||||
return []
|
||||
|
||||
# 使用 gather 并发执行
|
||||
results = await asyncio.gather(*self.tasks)
|
||||
|
||||
# 清空任务队列
|
||||
self.tasks = []
|
||||
return list(results)
|
||||
|
||||
async def __aenter__(self) -> "AITaskBatch":
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
|
||||
# 如果 with 块内部发生异常,不执行任务,直接抛出
|
||||
if exc_type:
|
||||
return
|
||||
await self.run()
|
||||
|
||||
@@ -14,7 +14,7 @@ paths:
|
||||
saves: assets/saves/
|
||||
|
||||
ai:
|
||||
max_decide_num: 4
|
||||
max_decide_num: 3
|
||||
max_parse_retries: 3
|
||||
|
||||
game:
|
||||
|
||||
@@ -65,7 +65,8 @@ export const useWorldStore = defineStore('world', () => {
|
||||
if (!rawEvents || rawEvents.length === 0) return;
|
||||
|
||||
// 转换 DTO -> Domain
|
||||
const newEvents: GameEvent[] = rawEvents.map(e => ({
|
||||
// 增加临时索引 _seq 记录原始逻辑顺序,用于同时间戳事件的排序
|
||||
const newEvents: GameEvent[] = rawEvents.map((e, index) => ({
|
||||
id: e.id,
|
||||
text: e.text,
|
||||
content: e.content,
|
||||
@@ -74,13 +75,32 @@ export const useWorldStore = defineStore('world', () => {
|
||||
timestamp: (e.year ?? year.value) * 12 + (e.month ?? month.value),
|
||||
relatedAvatarIds: e.related_avatar_ids || [],
|
||||
isMajor: e.is_major,
|
||||
isStory: e.is_story
|
||||
}));
|
||||
isStory: e.is_story,
|
||||
_seq: index
|
||||
} as GameEvent & { _seq: number }));
|
||||
|
||||
// 排序并保留最新的 N 条
|
||||
const MAX_EVENTS = 300;
|
||||
const combined = [...newEvents, ...events.value];
|
||||
combined.sort((a, b) => b.timestamp - a.timestamp); // 降序
|
||||
|
||||
combined.sort((a, b) => {
|
||||
// 1. 先按时间戳降序(最新的月在上面)
|
||||
const ta = a.timestamp;
|
||||
const tb = b.timestamp;
|
||||
if (tb !== ta) {
|
||||
return tb - ta;
|
||||
}
|
||||
|
||||
// 2. 时间相同时,按原始逻辑顺序降序(后发生的在上面)
|
||||
// 旧事件通常没有 _seq (undefined),视为最旧 (-1)
|
||||
const seqA = (a as any)._seq ?? -1;
|
||||
const seqB = (b as any)._seq ?? -1;
|
||||
|
||||
// 如果都是旧事件,保持相对顺序 (Stable)
|
||||
if (seqA === -1 && seqB === -1) return 0;
|
||||
|
||||
return seqB - seqA;
|
||||
});
|
||||
|
||||
events.value = combined.slice(0, MAX_EVENTS);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user