From c3798e1a8d65256705d4c02be0908e9a4c5f1f6f Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Fri, 5 Dec 2025 13:46:09 +0800 Subject: [PATCH] =?UTF-8?q?fix=EF=BC=9A=E8=A7=A3=E6=9E=90=E5=8A=A8?= =?UTF-8?q?=E4=BD=9C=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/brain_chat/brain_chat.py | 2 - src/chat/brain_chat/brain_planner.py | 174 +++++++++++++++++++++------ 2 files changed, 139 insertions(+), 37 deletions(-) diff --git a/src/chat/brain_chat/brain_chat.py b/src/chat/brain_chat/brain_chat.py index 31e34ff2..9945b496 100644 --- a/src/chat/brain_chat/brain_chat.py +++ b/src/chat/brain_chat/brain_chat.py @@ -304,14 +304,12 @@ class BrainChatting: ) prompt_info = await self.action_planner.build_planner_prompt( - is_group_chat=is_group_chat, chat_target_info=chat_target_info, current_available_actions=available_actions, chat_content_block=chat_content_block, message_id_list=message_id_list, interest=global_config.personality.interest, prompt_key="brain_planner_prompt_react", - log_prompt=True, ) continue_flag, modified_message = await events_manager.handle_mai_events( EventType.ON_PLAN, None, prompt_info[0], None, self.chat_stream.stream_id diff --git a/src/chat/brain_chat/brain_planner.py b/src/chat/brain_chat/brain_planner.py index 1bfceec8..df143ead 100644 --- a/src/chat/brain_chat/brain_planner.py +++ b/src/chat/brain_chat/brain_planner.py @@ -148,6 +148,9 @@ class BrainPlanner: ) # 用于动作规划 self.last_obs_time_mark = 0.0 + + # 计划日志记录 + self.plan_log: List[Tuple[str, float, List[ActionPlannerInfo]]] = [] def find_message_by_id( self, message_id: str, message_id_list: List[Tuple[str, "DatabaseMessages"]] @@ -179,6 +182,7 @@ class BrainPlanner: try: action = action_json.get("action", "complete_talk") + logger.debug(f"{self.log_prefix}解析动作JSON: action={action}, json={action_json}") reasoning = action_json.get("reason", "未提供原因") action_data = {key: value for key, value in action_json.items() if key not in ["action", "reason"]} # 非complete_talk动作需要target_message_id @@ -201,6 +205,8 @@ class BrainPlanner: # 注意:listening 已合并到 wait 中,如果遇到 listening 则转换为 wait internal_action_names = ["complete_talk", "reply", "wait_time", "wait", "listening"] + logger.debug(f"{self.log_prefix}动作验证: action={action}, internal={internal_action_names}, available={available_action_names}") + # 将 listening 转换为 wait(向后兼容) if action == "listening": logger.debug(f"{self.log_prefix}检测到 listening 动作,已合并到 wait,自动转换") @@ -208,12 +214,13 @@ class BrainPlanner: if action not in internal_action_names and action not in available_action_names: logger.warning( - f"{self.log_prefix}LLM 返回了当前不可用或无效的动作: '{action}' (可用: {available_action_names}),将强制使用 'complete_talk'" + f"{self.log_prefix}LLM 返回了当前不可用或无效的动作: '{action}' (内部动作: {internal_action_names}, 可用插件动作: {available_action_names}),将强制使用 'complete_talk'" ) reasoning = ( f"LLM 返回了当前不可用的动作 '{action}' (可用: {available_action_names})。原始理由: {reasoning}" ) action = "complete_talk" + logger.warning(f"{self.log_prefix}动作已转换为 complete_talk") # 创建ActionPlannerInfo对象 # 将列表转换为字典格式 @@ -294,18 +301,16 @@ class BrainPlanner: prompt_key = "brain_planner_prompt_react" # 这里不记录日志,避免重复打印,由调用方按需控制 log_prompt prompt, message_id_list = await self.build_planner_prompt( - is_group_chat=is_group_chat, chat_target_info=chat_target_info, current_available_actions=filtered_actions, chat_content_block=chat_content_block, message_id_list=message_id_list, interest=global_config.personality.interest, prompt_key=prompt_key, - log_prompt=False, ) # 调用LLM获取决策 - actions = await self._execute_main_planner( + reasoning, actions = await self._execute_main_planner( prompt=prompt, message_id_list=message_id_list, filtered_actions=filtered_actions, @@ -313,18 +318,22 @@ class BrainPlanner: loop_start_time=loop_start_time, ) + # 记录和展示计划日志 + logger.info( + f"{self.log_prefix}Planner: {reasoning}。选择了{len(actions)}个动作: {' '.join([a.action_type for a in actions])}" + ) + self.add_plan_log(reasoning, actions) + return actions async def build_planner_prompt( self, - is_group_chat: bool, chat_target_info: Optional["TargetPersonInfo"], current_available_actions: Dict[str, ActionInfo], message_id_list: List[Tuple[str, "DatabaseMessages"]], chat_content_block: str = "", interest: str = "", prompt_key: str = "brain_planner_prompt_react", - log_prompt: bool = False, ) -> tuple[str, List[Tuple[str, "DatabaseMessages"]]]: """构建 Planner LLM 的提示词 (获取模板并填充数据)""" try: @@ -373,10 +382,6 @@ class BrainPlanner: plan_style=global_config.personality.private_plan_style, ) - # 调试:按需展示本次 Planner 使用的 Prompt - if log_prompt: - logger.info(f"{self.log_prefix} BrainPlanner Prompt [{prompt_key}]:\n{prompt}") - return prompt, message_id_list except Exception as e: logger.error(f"构建 Planner 提示词时出错: {e}") @@ -474,17 +479,18 @@ class BrainPlanner: filtered_actions: Dict[str, ActionInfo], available_actions: Dict[str, ActionInfo], loop_start_time: float, - ) -> List[ActionPlannerInfo]: + ) -> Tuple[str, List[ActionPlannerInfo]]: """执行主规划器""" llm_content = None actions: List[ActionPlannerInfo] = [] + extracted_reasoning = "" try: # 调用LLM llm_content, (reasoning_content, _, _) = await self.planner_llm.generate_response_async(prompt=prompt) - # logger.info(f"{self.log_prefix}规划器原始提示词: {prompt}") - # logger.info(f"{self.log_prefix}规划器原始响应: {llm_content}") + logger.info(f"{self.log_prefix}规划器原始提示词: {prompt}") + logger.info(f"{self.log_prefix}规划器原始响应: {llm_content}") if global_config.debug.show_planner_prompt: logger.info(f"{self.log_prefix}规划器原始提示词: {prompt}") @@ -499,10 +505,11 @@ class BrainPlanner: except Exception as req_e: logger.error(f"{self.log_prefix}LLM 请求执行失败: {req_e}") - return [ + extracted_reasoning = f"LLM 请求失败,模型出现问题: {req_e}" + return extracted_reasoning, [ ActionPlannerInfo( action_type="complete_talk", - reasoning=f"LLM 请求失败,模型出现问题: {req_e}", + reasoning=extracted_reasoning, action_data={}, action_message=None, available_actions=available_actions, @@ -512,22 +519,30 @@ class BrainPlanner: # 解析LLM响应 if llm_content: try: - if json_objects := self._extract_json_from_markdown(llm_content): - logger.debug(f"{self.log_prefix}从响应中提取到{len(json_objects)}个JSON对象") + json_objects, extracted_reasoning = self._extract_json_from_markdown(llm_content) + if json_objects: + logger.info(f"{self.log_prefix}从响应中提取到{len(json_objects)}个JSON对象") + for i, json_obj in enumerate(json_objects): + logger.info(f"{self.log_prefix}解析第{i+1}个JSON对象: {json_obj}") filtered_actions_list = list(filtered_actions.items()) for json_obj in json_objects: - actions.extend(self._parse_single_action(json_obj, message_id_list, filtered_actions_list)) + parsed_actions = self._parse_single_action(json_obj, message_id_list, filtered_actions_list) + logger.info(f"{self.log_prefix}解析后的动作: {[a.action_type for a in parsed_actions]}") + actions.extend(parsed_actions) else: # 尝试解析为直接的JSON logger.warning(f"{self.log_prefix}LLM没有返回可用动作: {llm_content}") - actions = self._create_complete_talk("LLM没有返回可用动作", available_actions) + extracted_reasoning = extracted_reasoning or "LLM没有返回可用动作" + actions = self._create_complete_talk(extracted_reasoning, available_actions) except Exception as json_e: logger.warning(f"{self.log_prefix}解析LLM响应JSON失败 {json_e}. LLM原始输出: '{llm_content}'") - actions = self._create_complete_talk(f"解析LLM响应JSON失败: {json_e}", available_actions) + extracted_reasoning = f"解析LLM响应JSON失败: {json_e}" + actions = self._create_complete_talk(extracted_reasoning, available_actions) traceback.print_exc() else: - actions = self._create_complete_talk("规划器没有获得LLM响应", available_actions) + extracted_reasoning = "规划器没有获得LLM响应" + actions = self._create_complete_talk(extracted_reasoning, available_actions) # 添加循环开始时间到所有动作 for action in actions: @@ -538,7 +553,7 @@ class BrainPlanner: f"{self.log_prefix}规划器决定执行{len(actions)}个动作: {' '.join([a.action_type for a in actions])}" ) - return actions + return extracted_reasoning, actions def _create_complete_talk(self, reasoning: str, available_actions: Dict[str, ActionInfo]) -> List[ActionPlannerInfo]: """创建complete_talk""" @@ -551,34 +566,123 @@ class BrainPlanner: available_actions=available_actions, ) ] + + def add_plan_log(self, reasoning: str, actions: List[ActionPlannerInfo]): + """添加计划日志""" + self.plan_log.append((reasoning, time.time(), actions)) + if len(self.plan_log) > 20: + self.plan_log.pop(0) - def _extract_json_from_markdown(self, content: str) -> List[dict]: + def _extract_json_from_markdown(self, content: str) -> Tuple[List[dict], str]: # sourcery skip: for-append-to-extend - """从Markdown格式的内容中提取JSON对象""" + """从Markdown格式的内容中提取JSON对象和推理内容""" json_objects = [] + reasoning_content = "" # 使用正则表达式查找```json包裹的JSON内容 json_pattern = r"```json\s*(.*?)\s*```" - matches = re.findall(json_pattern, content, re.DOTALL) + markdown_matches = re.findall(json_pattern, content, re.DOTALL) - for match in matches: + # 提取JSON之前的内容作为推理文本 + first_json_pos = len(content) + if markdown_matches: + # 找到第一个```json的位置 + first_json_pos = content.find("```json") + if first_json_pos > 0: + reasoning_content = content[:first_json_pos].strip() + # 清理推理内容中的注释标记 + reasoning_content = re.sub(r"^//\s*", "", reasoning_content, flags=re.MULTILINE) + reasoning_content = reasoning_content.strip() + + # 处理```json包裹的JSON + for match in markdown_matches: try: # 清理可能的注释和格式问题 json_str = re.sub(r"//.*?\n", "\n", match) # 移除单行注释 json_str = re.sub(r"/\*.*?\*/", "", json_str, flags=re.DOTALL) # 移除多行注释 if json_str := json_str.strip(): - json_obj = json.loads(repair_json(json_str)) - if isinstance(json_obj, dict): - json_objects.append(json_obj) - elif isinstance(json_obj, list): - for item in json_obj: - if isinstance(item, dict): - json_objects.append(item) + # 先尝试将整个块作为一个JSON对象或数组(适用于多行JSON) + try: + json_obj = json.loads(repair_json(json_str)) + if isinstance(json_obj, dict): + json_objects.append(json_obj) + elif isinstance(json_obj, list): + for item in json_obj: + if isinstance(item, dict): + json_objects.append(item) + except json.JSONDecodeError: + # 如果整个块解析失败,尝试按行分割(适用于多个单行JSON对象) + lines = [line.strip() for line in json_str.split("\n") if line.strip()] + for line in lines: + try: + # 尝试解析每一行作为独立的JSON对象 + json_obj = json.loads(repair_json(line)) + if isinstance(json_obj, dict): + json_objects.append(json_obj) + elif isinstance(json_obj, list): + for item in json_obj: + if isinstance(item, dict): + json_objects.append(item) + except json.JSONDecodeError: + # 单行解析失败,继续下一行 + continue except Exception as e: - logger.warning(f"解析JSON块失败: {e}, 块内容: {match[:100]}...") + logger.warning(f"{self.log_prefix}解析JSON块失败: {e}, 块内容: {match[:100]}...") continue - return json_objects + # 如果没有找到完整的```json```块,尝试查找不完整的代码块(缺少结尾```) + if not json_objects: + json_start_pos = content.find("```json") + if json_start_pos != -1: + # 找到```json之后的内容 + json_content_start = json_start_pos + 7 # ```json的长度 + # 提取从```json之后到内容结尾的所有内容 + incomplete_json_str = content[json_content_start:].strip() + + # 提取JSON之前的内容作为推理文本 + if json_start_pos > 0: + reasoning_content = content[:json_start_pos].strip() + reasoning_content = re.sub(r"^//\s*", "", reasoning_content, flags=re.MULTILINE) + reasoning_content = reasoning_content.strip() + + if incomplete_json_str: + try: + # 清理可能的注释和格式问题 + json_str = re.sub(r"//.*?\n", "\n", incomplete_json_str) + json_str = re.sub(r"/\*.*?\*/", "", json_str, flags=re.DOTALL) + json_str = json_str.strip() + + if json_str: + # 尝试按行分割,每行可能是一个JSON对象 + lines = [line.strip() for line in json_str.split("\n") if line.strip()] + for line in lines: + try: + json_obj = json.loads(repair_json(line)) + if isinstance(json_obj, dict): + json_objects.append(json_obj) + elif isinstance(json_obj, list): + for item in json_obj: + if isinstance(item, dict): + json_objects.append(item) + except json.JSONDecodeError: + pass + + # 如果按行解析没有成功,尝试将整个块作为一个JSON对象或数组 + if not json_objects: + try: + json_obj = json.loads(repair_json(json_str)) + if isinstance(json_obj, dict): + json_objects.append(json_obj) + elif isinstance(json_obj, list): + for item in json_obj: + if isinstance(item, dict): + json_objects.append(item) + except Exception as e: + logger.debug(f"尝试解析不完整的JSON代码块失败: {e}") + except Exception as e: + logger.debug(f"处理不完整的JSON代码块时出错: {e}") + + return json_objects, reasoning_content init_prompt()