add thinking

This commit is contained in:
bridge
2025-09-03 21:56:38 +08:00
parent 8cfa2e5d0a
commit 9fd7dfd471
5 changed files with 81 additions and 30 deletions

View File

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

View File

@@ -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)
# 纯粹执行动作,不产生事件

View File

@@ -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):
"""创建字体"""

View File

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