mirror of https://github.com/Mai-with-u/MaiBot.git
fix:解析动作错误
parent
541799b1c2
commit
c3798e1a8d
|
|
@ -304,14 +304,12 @@ class BrainChatting:
|
||||||
)
|
)
|
||||||
|
|
||||||
prompt_info = await self.action_planner.build_planner_prompt(
|
prompt_info = await self.action_planner.build_planner_prompt(
|
||||||
is_group_chat=is_group_chat,
|
|
||||||
chat_target_info=chat_target_info,
|
chat_target_info=chat_target_info,
|
||||||
current_available_actions=available_actions,
|
current_available_actions=available_actions,
|
||||||
chat_content_block=chat_content_block,
|
chat_content_block=chat_content_block,
|
||||||
message_id_list=message_id_list,
|
message_id_list=message_id_list,
|
||||||
interest=global_config.personality.interest,
|
interest=global_config.personality.interest,
|
||||||
prompt_key="brain_planner_prompt_react",
|
prompt_key="brain_planner_prompt_react",
|
||||||
log_prompt=True,
|
|
||||||
)
|
)
|
||||||
continue_flag, modified_message = await events_manager.handle_mai_events(
|
continue_flag, modified_message = await events_manager.handle_mai_events(
|
||||||
EventType.ON_PLAN, None, prompt_info[0], None, self.chat_stream.stream_id
|
EventType.ON_PLAN, None, prompt_info[0], None, self.chat_stream.stream_id
|
||||||
|
|
|
||||||
|
|
@ -149,6 +149,9 @@ class BrainPlanner:
|
||||||
|
|
||||||
self.last_obs_time_mark = 0.0
|
self.last_obs_time_mark = 0.0
|
||||||
|
|
||||||
|
# 计划日志记录
|
||||||
|
self.plan_log: List[Tuple[str, float, List[ActionPlannerInfo]]] = []
|
||||||
|
|
||||||
def find_message_by_id(
|
def find_message_by_id(
|
||||||
self, message_id: str, message_id_list: List[Tuple[str, "DatabaseMessages"]]
|
self, message_id: str, message_id_list: List[Tuple[str, "DatabaseMessages"]]
|
||||||
) -> Optional["DatabaseMessages"]:
|
) -> Optional["DatabaseMessages"]:
|
||||||
|
|
@ -179,6 +182,7 @@ class BrainPlanner:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
action = action_json.get("action", "complete_talk")
|
action = action_json.get("action", "complete_talk")
|
||||||
|
logger.debug(f"{self.log_prefix}解析动作JSON: action={action}, json={action_json}")
|
||||||
reasoning = action_json.get("reason", "未提供原因")
|
reasoning = action_json.get("reason", "未提供原因")
|
||||||
action_data = {key: value for key, value in action_json.items() if key not in ["action", "reason"]}
|
action_data = {key: value for key, value in action_json.items() if key not in ["action", "reason"]}
|
||||||
# 非complete_talk动作需要target_message_id
|
# 非complete_talk动作需要target_message_id
|
||||||
|
|
@ -201,6 +205,8 @@ class BrainPlanner:
|
||||||
# 注意:listening 已合并到 wait 中,如果遇到 listening 则转换为 wait
|
# 注意:listening 已合并到 wait 中,如果遇到 listening 则转换为 wait
|
||||||
internal_action_names = ["complete_talk", "reply", "wait_time", "wait", "listening"]
|
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(向后兼容)
|
# 将 listening 转换为 wait(向后兼容)
|
||||||
if action == "listening":
|
if action == "listening":
|
||||||
logger.debug(f"{self.log_prefix}检测到 listening 动作,已合并到 wait,自动转换")
|
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:
|
if action not in internal_action_names and action not in available_action_names:
|
||||||
logger.warning(
|
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 = (
|
reasoning = (
|
||||||
f"LLM 返回了当前不可用的动作 '{action}' (可用: {available_action_names})。原始理由: {reasoning}"
|
f"LLM 返回了当前不可用的动作 '{action}' (可用: {available_action_names})。原始理由: {reasoning}"
|
||||||
)
|
)
|
||||||
action = "complete_talk"
|
action = "complete_talk"
|
||||||
|
logger.warning(f"{self.log_prefix}动作已转换为 complete_talk")
|
||||||
|
|
||||||
# 创建ActionPlannerInfo对象
|
# 创建ActionPlannerInfo对象
|
||||||
# 将列表转换为字典格式
|
# 将列表转换为字典格式
|
||||||
|
|
@ -294,18 +301,16 @@ class BrainPlanner:
|
||||||
prompt_key = "brain_planner_prompt_react"
|
prompt_key = "brain_planner_prompt_react"
|
||||||
# 这里不记录日志,避免重复打印,由调用方按需控制 log_prompt
|
# 这里不记录日志,避免重复打印,由调用方按需控制 log_prompt
|
||||||
prompt, message_id_list = await self.build_planner_prompt(
|
prompt, message_id_list = await self.build_planner_prompt(
|
||||||
is_group_chat=is_group_chat,
|
|
||||||
chat_target_info=chat_target_info,
|
chat_target_info=chat_target_info,
|
||||||
current_available_actions=filtered_actions,
|
current_available_actions=filtered_actions,
|
||||||
chat_content_block=chat_content_block,
|
chat_content_block=chat_content_block,
|
||||||
message_id_list=message_id_list,
|
message_id_list=message_id_list,
|
||||||
interest=global_config.personality.interest,
|
interest=global_config.personality.interest,
|
||||||
prompt_key=prompt_key,
|
prompt_key=prompt_key,
|
||||||
log_prompt=False,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# 调用LLM获取决策
|
# 调用LLM获取决策
|
||||||
actions = await self._execute_main_planner(
|
reasoning, actions = await self._execute_main_planner(
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
message_id_list=message_id_list,
|
message_id_list=message_id_list,
|
||||||
filtered_actions=filtered_actions,
|
filtered_actions=filtered_actions,
|
||||||
|
|
@ -313,18 +318,22 @@ class BrainPlanner:
|
||||||
loop_start_time=loop_start_time,
|
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
|
return actions
|
||||||
|
|
||||||
async def build_planner_prompt(
|
async def build_planner_prompt(
|
||||||
self,
|
self,
|
||||||
is_group_chat: bool,
|
|
||||||
chat_target_info: Optional["TargetPersonInfo"],
|
chat_target_info: Optional["TargetPersonInfo"],
|
||||||
current_available_actions: Dict[str, ActionInfo],
|
current_available_actions: Dict[str, ActionInfo],
|
||||||
message_id_list: List[Tuple[str, "DatabaseMessages"]],
|
message_id_list: List[Tuple[str, "DatabaseMessages"]],
|
||||||
chat_content_block: str = "",
|
chat_content_block: str = "",
|
||||||
interest: str = "",
|
interest: str = "",
|
||||||
prompt_key: str = "brain_planner_prompt_react",
|
prompt_key: str = "brain_planner_prompt_react",
|
||||||
log_prompt: bool = False,
|
|
||||||
) -> tuple[str, List[Tuple[str, "DatabaseMessages"]]]:
|
) -> tuple[str, List[Tuple[str, "DatabaseMessages"]]]:
|
||||||
"""构建 Planner LLM 的提示词 (获取模板并填充数据)"""
|
"""构建 Planner LLM 的提示词 (获取模板并填充数据)"""
|
||||||
try:
|
try:
|
||||||
|
|
@ -373,10 +382,6 @@ class BrainPlanner:
|
||||||
plan_style=global_config.personality.private_plan_style,
|
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
|
return prompt, message_id_list
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"构建 Planner 提示词时出错: {e}")
|
logger.error(f"构建 Planner 提示词时出错: {e}")
|
||||||
|
|
@ -474,17 +479,18 @@ class BrainPlanner:
|
||||||
filtered_actions: Dict[str, ActionInfo],
|
filtered_actions: Dict[str, ActionInfo],
|
||||||
available_actions: Dict[str, ActionInfo],
|
available_actions: Dict[str, ActionInfo],
|
||||||
loop_start_time: float,
|
loop_start_time: float,
|
||||||
) -> List[ActionPlannerInfo]:
|
) -> Tuple[str, List[ActionPlannerInfo]]:
|
||||||
"""执行主规划器"""
|
"""执行主规划器"""
|
||||||
llm_content = None
|
llm_content = None
|
||||||
actions: List[ActionPlannerInfo] = []
|
actions: List[ActionPlannerInfo] = []
|
||||||
|
extracted_reasoning = ""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 调用LLM
|
# 调用LLM
|
||||||
llm_content, (reasoning_content, _, _) = await self.planner_llm.generate_response_async(prompt=prompt)
|
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}规划器原始提示词: {prompt}")
|
||||||
# logger.info(f"{self.log_prefix}规划器原始响应: {llm_content}")
|
logger.info(f"{self.log_prefix}规划器原始响应: {llm_content}")
|
||||||
|
|
||||||
if global_config.debug.show_planner_prompt:
|
if global_config.debug.show_planner_prompt:
|
||||||
logger.info(f"{self.log_prefix}规划器原始提示词: {prompt}")
|
logger.info(f"{self.log_prefix}规划器原始提示词: {prompt}")
|
||||||
|
|
@ -499,10 +505,11 @@ class BrainPlanner:
|
||||||
|
|
||||||
except Exception as req_e:
|
except Exception as req_e:
|
||||||
logger.error(f"{self.log_prefix}LLM 请求执行失败: {req_e}")
|
logger.error(f"{self.log_prefix}LLM 请求执行失败: {req_e}")
|
||||||
return [
|
extracted_reasoning = f"LLM 请求失败,模型出现问题: {req_e}"
|
||||||
|
return extracted_reasoning, [
|
||||||
ActionPlannerInfo(
|
ActionPlannerInfo(
|
||||||
action_type="complete_talk",
|
action_type="complete_talk",
|
||||||
reasoning=f"LLM 请求失败,模型出现问题: {req_e}",
|
reasoning=extracted_reasoning,
|
||||||
action_data={},
|
action_data={},
|
||||||
action_message=None,
|
action_message=None,
|
||||||
available_actions=available_actions,
|
available_actions=available_actions,
|
||||||
|
|
@ -512,22 +519,30 @@ class BrainPlanner:
|
||||||
# 解析LLM响应
|
# 解析LLM响应
|
||||||
if llm_content:
|
if llm_content:
|
||||||
try:
|
try:
|
||||||
if json_objects := self._extract_json_from_markdown(llm_content):
|
json_objects, extracted_reasoning = self._extract_json_from_markdown(llm_content)
|
||||||
logger.debug(f"{self.log_prefix}从响应中提取到{len(json_objects)}个JSON对象")
|
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())
|
filtered_actions_list = list(filtered_actions.items())
|
||||||
for json_obj in json_objects:
|
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:
|
else:
|
||||||
# 尝试解析为直接的JSON
|
# 尝试解析为直接的JSON
|
||||||
logger.warning(f"{self.log_prefix}LLM没有返回可用动作: {llm_content}")
|
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:
|
except Exception as json_e:
|
||||||
logger.warning(f"{self.log_prefix}解析LLM响应JSON失败 {json_e}. LLM原始输出: '{llm_content}'")
|
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()
|
traceback.print_exc()
|
||||||
else:
|
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:
|
for action in actions:
|
||||||
|
|
@ -538,7 +553,7 @@ class BrainPlanner:
|
||||||
f"{self.log_prefix}规划器决定执行{len(actions)}个动作: {' '.join([a.action_type for a in actions])}"
|
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]:
|
def _create_complete_talk(self, reasoning: str, available_actions: Dict[str, ActionInfo]) -> List[ActionPlannerInfo]:
|
||||||
"""创建complete_talk"""
|
"""创建complete_talk"""
|
||||||
|
|
@ -552,33 +567,122 @@ class BrainPlanner:
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
def _extract_json_from_markdown(self, content: str) -> List[dict]:
|
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) -> Tuple[List[dict], str]:
|
||||||
# sourcery skip: for-append-to-extend
|
# sourcery skip: for-append-to-extend
|
||||||
"""从Markdown格式的内容中提取JSON对象"""
|
"""从Markdown格式的内容中提取JSON对象和推理内容"""
|
||||||
json_objects = []
|
json_objects = []
|
||||||
|
reasoning_content = ""
|
||||||
|
|
||||||
# 使用正则表达式查找```json包裹的JSON内容
|
# 使用正则表达式查找```json包裹的JSON内容
|
||||||
json_pattern = r"```json\s*(.*?)\s*```"
|
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:
|
try:
|
||||||
# 清理可能的注释和格式问题
|
# 清理可能的注释和格式问题
|
||||||
json_str = re.sub(r"//.*?\n", "\n", match) # 移除单行注释
|
json_str = re.sub(r"//.*?\n", "\n", match) # 移除单行注释
|
||||||
json_str = re.sub(r"/\*.*?\*/", "", json_str, flags=re.DOTALL) # 移除多行注释
|
json_str = re.sub(r"/\*.*?\*/", "", json_str, flags=re.DOTALL) # 移除多行注释
|
||||||
if json_str := json_str.strip():
|
if json_str := json_str.strip():
|
||||||
json_obj = json.loads(repair_json(json_str))
|
# 先尝试将整个块作为一个JSON对象或数组(适用于多行JSON)
|
||||||
if isinstance(json_obj, dict):
|
try:
|
||||||
json_objects.append(json_obj)
|
json_obj = json.loads(repair_json(json_str))
|
||||||
elif isinstance(json_obj, list):
|
if isinstance(json_obj, dict):
|
||||||
for item in json_obj:
|
json_objects.append(json_obj)
|
||||||
if isinstance(item, dict):
|
elif isinstance(json_obj, list):
|
||||||
json_objects.append(item)
|
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:
|
except Exception as e:
|
||||||
logger.warning(f"解析JSON块失败: {e}, 块内容: {match[:100]}...")
|
logger.warning(f"{self.log_prefix}解析JSON块失败: {e}, 块内容: {match[:100]}...")
|
||||||
continue
|
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()
|
init_prompt()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue