diff --git a/src/classes/actions.py b/src/classes/actions.py index b70a7a2..b4b5358 100644 --- a/src/classes/actions.py +++ b/src/classes/actions.py @@ -21,6 +21,6 @@ ACTION_INFOS = { } for action in ALL_ACTUAL_ACTION_CLASSES } -ACTION_INFOS_STR = json.dumps(ACTION_INFOS, ensure_ascii=False) +ACTION_INFOS_STR = json.dumps(ACTION_INFOS, ensure_ascii=False, indent=2) diff --git a/src/classes/avatar.py b/src/classes/avatar.py index fe0ae4f..e233a6a 100644 --- a/src/classes/avatar.py +++ b/src/classes/avatar.py @@ -120,26 +120,14 @@ class Avatar: def __hash__(self) -> int: return hash(self.id) - def get_info(self, detailed: bool = False) -> str: + def get_info(self, detailed: bool = False) -> dict: """ - 获取avatar的信息,分详细和不详细两种。 + 获取 avatar 的信息,返回 dict;根据 detailed 控制信息粒度。 """ - region_info = "无" region = self.tile.region if self.tile is not None else None relations_info = self._get_relations_summary_str() magic_stone_info = str(self.magic_stone) - info = "Avatar:\n" - info += f"id={self.id}\n" - info += f"name={self.name}\n" - info += f"gender={self.gender}\n" - info += f"age={self.age}\n" - info += f"hp={self.hp}\n" - info += f"mp={self.mp}\n" - info += f"magic_stone={magic_stone_info}\n" - info += f"relations={relations_info}\n" - - # 接下来开始有区分了 if detailed: sect_info = self.sect.get_detailed_info() if self.sect is not None else "散修" alignment_info = self.alignment.get_detailed_info() if self.alignment is not None else "未知" @@ -152,24 +140,34 @@ class Avatar: else: sect_info = self.sect.get_info() if self.sect is not None else "散修" region_info = region.get_info() if region is not None else "无" - alignment_info = self.alignment.get_info() + alignment_info = self.alignment.get_info() if self.alignment is not None else "未知" root_info = self.root.get_info() - technique_info = self.technique.get_info() + technique_info = self.technique.get_info() if self.technique is not None else "无" cultivation_info = self.cultivation_progress.get_info() personas_info = ", ".join([p.get_info() for p in self.personas]) if self.personas else "无" items_info = ",".join([f"{item.get_info()}x{quantity}" for item, quantity in self.items.items()]) if self.items else "无" - info += f"sect={sect_info}\n" - info += f"alignment={alignment_info}\n" - info += f"region={region_info}\n" - info += f"root={root_info}\n" - info += f"technique={technique_info}\n" - info += f"cultivation={cultivation_info}\n" - info += f"personas={personas_info}\n" - info += f"items={items_info}\n" - return info + + return { + "id": self.id, + "名字": self.name, + "性别": str(self.gender), + "年龄": str(self.age), + "hp": str(self.hp), + "mp": str(self.mp), + "灵石": magic_stone_info, + "关系": relations_info, + "宗门": sect_info, + "阵营": alignment_info, + "地区": region_info, + "灵根": root_info, + "功法": technique_info, + "境界": cultivation_info, + "个性": personas_info, + "物品": items_info, + } def __str__(self) -> str: - return self.get_info() + return str(self.get_info(detailed=False)) def create_action(self, action_name: ACTION_NAME) -> Action: """ @@ -421,31 +419,28 @@ class Avatar: action_space = [action.name for action in doable_actions] return action_space - def get_prompt_info(self, co_region_avatars: Optional[List["Avatar"]] = None) -> str: + def get_prompt_info(self, co_region_avatars: Optional[List["Avatar"]] = None) -> dict: """ - 获取角色提示词信息 + 获取角色提示词信息,返回 dict。 """ info = self.get_info(detailed=False) - - # 观测范围内角色(沿用参数名保持兼容) - co_region_info = "" + + observed: list[str] = [] if co_region_avatars: - entries: list[str] = [] for other in co_region_avatars[:8]: - entries.append(f"{other.name}(境界:{other.cultivation_progress.get_info()})") - co_region_info = "\n观测范围内角色:" + (",".join(entries) if entries else "无") + observed.append(f"{other.name}(境界:{other.cultivation_progress.get_info()})") - # 历史事件摘要 if self.history_events: - history_lines = ";".join([str(e) for e in self.history_events[-8:]]) - history_info = f"历史事件:{history_lines}" + history_list = [str(e) for e in self.history_events[-8:]] else: - history_info = "历史事件:无" + history_list = [] - # 动作空间 - action_space = self.get_action_space_str() + action_space = self.get_action_space() - return f"{info}\n{history_info}\n{co_region_info}\n该角色的目前合法动作为:{action_space}" + info["动作空间"] = action_space + info["观察到的角色"] = observed + info["历史事件"] = history_list + return info def get_hover_info(self) -> list[str]: """ diff --git a/src/classes/mutual_action/mutual_action.py b/src/classes/mutual_action/mutual_action.py index c43115b..a91c040 100644 --- a/src/classes/mutual_action/mutual_action.py +++ b/src/classes/mutual_action/mutual_action.py @@ -42,6 +42,7 @@ class MutualAction(DefineAction, LLMAction, TargetingMixin): avatar_name_2 = target_avatar.name # avatar infos 仅放入与两人相关的提示,避免超长 avatar_infos = { + # 决策:使用非详细信息 avatar_name_1: self.avatar.get_info(detailed=False), avatar_name_2: target_avatar.get_info(detailed=False), } diff --git a/src/classes/story_teller.py b/src/classes/story_teller.py index a4ccc71..e5e89eb 100644 --- a/src/classes/story_teller.py +++ b/src/classes/story_teller.py @@ -12,12 +12,12 @@ class StoryTeller: """ @staticmethod - def build_avatar_infos(*avatars: "Avatar") -> Dict[str, str]: + def build_avatar_infos(*avatars: "Avatar") -> Dict[str, dict]: """ - 将若干角色信息组织为 {name: info} 映射,供故事模板使用。 - 战斗/小故事使用详细信息。 + 将若干角色信息组织为 {name: info_dict} 映射,供故事模板使用。 + 战斗/小故事使用详细信息(dict 版)。 """ - infos: Dict[str, str] = {} + infos: Dict[str, dict] = {} for av in avatars: try: infos[av.name] = av.get_info(detailed=True) @@ -26,7 +26,7 @@ class StoryTeller: return infos @staticmethod - def tell_story(avatar_infos: Dict[str, str], event: str, res: str) -> str: + def tell_story(avatar_infos: Dict[str, dict], event: str, res: str) -> str: """ 基于 `static/templates/story.txt` 模板生成小故事。 始终使用 fast 模式以提升速度。 diff --git a/src/utils/llm.py b/src/utils/llm.py index 824c871..ee495f3 100644 --- a/src/utils/llm.py +++ b/src/utils/llm.py @@ -8,13 +8,20 @@ import json5 from src.utils.config import CONFIG from src.utils.io import read_txt from src.run.log import log_llm_call +from src.utils.strings import intentify_prompt_infos def get_prompt(template: str, infos: dict) -> str: """ 根据模板,获取提示词 """ prompt_template = PromptTemplate(template=template) - return prompt_template.format(**infos) + # 将 dict/list 等结构化对象转为 JSON 字符串 + # 策略: + # - avatar_infos: 不包装 intent(模板里已经说明是 dict[Name, info]) + # - general_action_infos: 强制包装 intent 以凸显语义 + # - 其他容器类型:默认包装 intent + processed_infos = intentify_prompt_infos(infos) + return prompt_template.format(**processed_infos) def call_llm(prompt: str, mode="normal") -> str: diff --git a/src/utils/strings.py b/src/utils/strings.py index b9ca169..edc3527 100644 --- a/src/utils/strings.py +++ b/src/utils/strings.py @@ -1,10 +1,22 @@ -def to_snake_case(name: str) -> str: - """将驼峰/帕斯卡命名转换为蛇形命名。""" - chars = [] - for i, ch in enumerate(name): - if ch.isupper() and i > 0: - chars.append('_') - chars.append(ch.lower()) - return ''.join(chars) +def to_json_str_with_intent(data, unescape_newlines: bool = True) -> str: + """ + 将任意数据包装为带有 intent 的 JSON 字符串,便于在 Prompt 中明确语义。 + + 结构:{"intent": intent, "data": data} + - 使用缩进与 ensure_ascii=False,保证可读性与中文不转义 + """ + import json + s = json.dumps(data, ensure_ascii=False, indent=2) + if unescape_newlines: + s = s.replace("\\n", "\n") + return s +def intentify_prompt_infos(infos: dict) -> dict: + processed: dict = dict(infos or {}) + if "avatar_infos" in processed: + processed["avatar_infos"] = to_json_str_with_intent(processed["avatar_infos"]) + if "general_action_infos" in processed: + processed["general_action_infos"] = to_json_str_with_intent(processed["general_action_infos"]) + return processed +