add thinking
This commit is contained in:
@@ -26,21 +26,21 @@ class AI(ABC):
|
||||
def __init__(self, avatar: Avatar):
|
||||
self.avatar = avatar
|
||||
|
||||
async def decide(self, world: World) -> tuple[ACTION_NAME, ACTION_PARAMS, Event]:
|
||||
async def decide(self, world: World) -> tuple[ACTION_NAME, ACTION_PARAMS, str, Event]:
|
||||
"""
|
||||
决定做什么,同时生成对应的事件
|
||||
"""
|
||||
# 先决定动作和参数
|
||||
action_name, action_params = await self._decide(world)
|
||||
action_name, action_params, avatar_thinking = await self._decide(world)
|
||||
|
||||
# 获取动作对象并生成事件
|
||||
action = self.avatar.create_action(action_name)
|
||||
event = action.get_event(**action_params)
|
||||
|
||||
return action_name, action_params, event
|
||||
return action_name, action_params, avatar_thinking, event
|
||||
|
||||
@abstractmethod
|
||||
async def _decide(self, world: World) -> ACTION_PAIR:
|
||||
async def _decide(self, world: World) -> tuple[ACTION_NAME, ACTION_PARAMS, str]:
|
||||
"""
|
||||
决策逻辑:决定执行什么动作和参数
|
||||
由子类实现具体的决策逻辑
|
||||
@@ -51,7 +51,7 @@ class RuleAI(AI):
|
||||
"""
|
||||
规则AI
|
||||
"""
|
||||
async def _decide(self, world: World) -> ACTION_PAIR:
|
||||
async def _decide(self, world: World) -> tuple[ACTION_NAME, ACTION_PARAMS, str]:
|
||||
"""
|
||||
决策逻辑:决定执行什么动作和参数
|
||||
先做一个简单的:
|
||||
@@ -62,15 +62,15 @@ class RuleAI(AI):
|
||||
5. 如果需要突破境界了,则突破境界
|
||||
"""
|
||||
if random.random() < 0.1:
|
||||
return "Play", {}
|
||||
return "Play", {}, ""
|
||||
best_region = self.get_best_region(list(world.map.regions.values()))
|
||||
if self.avatar.is_in_region(best_region):
|
||||
if self.avatar.cultivation_progress.can_break_through():
|
||||
return "Breakthrough", {}
|
||||
return "Breakthrough", {}, ""
|
||||
else:
|
||||
return "Cultivate", {}
|
||||
return "Cultivate", {}, ""
|
||||
else:
|
||||
return "MoveToRegion", {"region": best_region.name}
|
||||
return "MoveToRegion", {"region": best_region.name}, ""
|
||||
|
||||
def get_best_region(self, regions: list[Region]) -> Region:
|
||||
"""
|
||||
@@ -93,7 +93,7 @@ class LLMAI(AI):
|
||||
不能每个单步step都调用一次LLM来决定下一步做什么。这样子一方面动作一直乱变,另一方面也太费token了。
|
||||
decide的作用是,拉取既有的动作链(如果没有了就call_llm),再根据动作链决定动作,以及动作之间的衔接。
|
||||
"""
|
||||
async def _decide(self, world: World) -> ACTION_PAIR:
|
||||
async def _decide(self, world: World) -> tuple[ACTION_NAME, ACTION_PARAMS, str]:
|
||||
"""
|
||||
异步决策逻辑:通过LLM决定执行什么动作和参数
|
||||
"""
|
||||
@@ -108,5 +108,5 @@ class LLMAI(AI):
|
||||
"avatar_persona": avatar_persona
|
||||
}
|
||||
res = await get_ai_prompt_and_call_llm_async(dict_info)
|
||||
action_name, action_params = res["action_name"], res["action_params"]
|
||||
return action_name, action_params
|
||||
action_name, action_params, avatar_thinking = res["action_name"], res["action_params"], res["avatar_thinking"]
|
||||
return action_name, action_params, avatar_thinking
|
||||
@@ -54,6 +54,7 @@ class Avatar:
|
||||
ai: AI = None
|
||||
cur_action_pair: Optional[ACTION_PAIR] = None
|
||||
history_action_pairs: list[ACTION_PAIR] = field(default_factory=list)
|
||||
thinking: str = ""
|
||||
|
||||
def __post_init__(self):
|
||||
"""
|
||||
@@ -101,8 +102,9 @@ class Avatar:
|
||||
|
||||
if self.cur_action_pair is None:
|
||||
# 决定动作时生成事件
|
||||
action_name, action_args, event = await self.ai.decide(self.world)
|
||||
action_name, action_args, avatar_thinking, event = await self.ai.decide(self.world)
|
||||
action = self.create_action(action_name)
|
||||
self.thinking = avatar_thinking
|
||||
self.cur_action_pair = (action, action_args)
|
||||
|
||||
# 纯粹执行动作,不产生事件
|
||||
|
||||
@@ -8,6 +8,7 @@ from src.classes.world import World
|
||||
from src.classes.tile import TileType
|
||||
from src.classes.avatar import Avatar, Gender
|
||||
from src.classes.event import Event
|
||||
from src.utils.text_wrap import wrap_text
|
||||
|
||||
|
||||
class Front:
|
||||
@@ -155,10 +156,7 @@ class Front:
|
||||
|
||||
pygame.quit()
|
||||
|
||||
def _step_once(self):
|
||||
"""执行一步模拟(同步版本,已弃用)"""
|
||||
print("警告:_step_once已弃用,请使用异步版本")
|
||||
pass
|
||||
|
||||
|
||||
def _render(self):
|
||||
"""渲染主画面"""
|
||||
@@ -239,11 +237,6 @@ class Front:
|
||||
# 计算位置:放在操作指南右边,留适当间距
|
||||
x_pos = self.margin + self._guide_width + padding * 3
|
||||
self.screen.blit(ym_surf, (x_pos, y_pos))
|
||||
|
||||
def _get_month_number(self) -> int:
|
||||
"""获取月份数字(已弃用,保留向后兼容)"""
|
||||
return self.simulator.world.month_stamp.get_month().value
|
||||
|
||||
|
||||
|
||||
def _draw_map(self):
|
||||
@@ -445,6 +438,15 @@ class Front:
|
||||
lines.append("") # 空行分隔
|
||||
lines.append("历史动作:")
|
||||
lines.extend(avatar.get_history_action_pairs_str().split("\n"))
|
||||
|
||||
# 添加thinking信息
|
||||
if avatar.thinking:
|
||||
lines.append("") # 空行分隔
|
||||
lines.append("思考:")
|
||||
# 使用wrap_text函数将thinking信息按20字符换行
|
||||
thinking_lines = wrap_text(avatar.thinking, 20)
|
||||
lines.extend(thinking_lines)
|
||||
|
||||
self._draw_tooltip(lines, *self.pygame.mouse.get_pos(), self.tooltip_font)
|
||||
|
||||
def _draw_tooltip_for_region(self, region, mouse_x: int, mouse_y: int):
|
||||
@@ -545,11 +547,7 @@ class Front:
|
||||
self.avatar_images[avatar_id] = random.choice(self.female_avatars)
|
||||
# 如果没有可用的头像,则使用None,后续会画圆点作为fallback
|
||||
|
||||
def _create_fallback_surface(self, tile_type):
|
||||
"""创建默认的fallback surface"""
|
||||
fallback_surface = self.pygame.Surface((self.tile_size, self.tile_size))
|
||||
fallback_surface.fill((128, 128, 128)) # 灰色
|
||||
self.tile_images[tile_type] = fallback_surface
|
||||
|
||||
|
||||
def _create_font(self, size: int):
|
||||
"""创建字体"""
|
||||
|
||||
@@ -71,8 +71,6 @@ def get_prompt_and_call_llm(template_path: Path, infos: dict) -> str:
|
||||
prompt = get_prompt(template, infos)
|
||||
res = call_llm(prompt)
|
||||
json_res = parse_llm_response(res)
|
||||
# print(f"prompt = {prompt}")
|
||||
# print(f"res = {res}")
|
||||
return json_res
|
||||
|
||||
async def get_prompt_and_call_llm_async(template_path: Path, infos: dict) -> str:
|
||||
@@ -82,7 +80,7 @@ async def get_prompt_and_call_llm_async(template_path: Path, infos: dict) -> str
|
||||
template = read_txt(template_path)
|
||||
prompt = get_prompt(template, infos)
|
||||
res = await call_llm_async(prompt)
|
||||
print(f"res = {res}")
|
||||
# print(f"res = {res}")
|
||||
json_res = parse_llm_response(res)
|
||||
return json_res
|
||||
|
||||
|
||||
53
src/utils/text_wrap.py
Normal file
53
src/utils/text_wrap.py
Normal file
@@ -0,0 +1,53 @@
|
||||
def wrap_text(text: str, max_width: int = 20) -> list[str]:
|
||||
"""
|
||||
将长文本按指定宽度换行
|
||||
|
||||
Args:
|
||||
text: 要换行的文本
|
||||
max_width: 每行的最大字符数,默认20
|
||||
|
||||
Returns:
|
||||
换行后的文本行列表
|
||||
"""
|
||||
if not text:
|
||||
return []
|
||||
|
||||
lines = []
|
||||
current_line = ""
|
||||
|
||||
# 按换行符分割,处理已有的换行
|
||||
paragraphs = text.split('\n')
|
||||
|
||||
for paragraph in paragraphs:
|
||||
if not paragraph.strip():
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
words = paragraph.split()
|
||||
|
||||
for word in words:
|
||||
# 如果当前行加上新词会超过限制
|
||||
if len(current_line) + len(word) + 1 > max_width:
|
||||
if current_line:
|
||||
lines.append(current_line.strip())
|
||||
current_line = word
|
||||
else:
|
||||
# 如果单个词就超过限制,强制换行
|
||||
if len(word) > max_width:
|
||||
# 长词强制切分
|
||||
for i in range(0, len(word), max_width):
|
||||
lines.append(word[i:i + max_width])
|
||||
else:
|
||||
lines.append(word)
|
||||
else:
|
||||
if current_line:
|
||||
current_line += " " + word
|
||||
else:
|
||||
current_line = word
|
||||
|
||||
# 处理段落的最后一行
|
||||
if current_line:
|
||||
lines.append(current_line.strip())
|
||||
current_line = ""
|
||||
|
||||
return lines
|
||||
Reference in New Issue
Block a user