pull/1217/head
SengokuCola 2025-08-23 12:17:52 +08:00
commit d64215c21e
4 changed files with 169 additions and 147 deletions

View File

@ -22,7 +22,7 @@ if TYPE_CHECKING:
logger = get_logger("chat")
async def _calculate_interest(message: MessageRecv) -> Tuple[float, bool, list[str]]:
async def _calculate_interest(message: MessageRecv) -> Tuple[float, list[str]]:
"""计算消息的兴趣度
Args:

View File

@ -1,6 +1,7 @@
import json
import time
import traceback
import asyncio
from typing import Dict, Optional, Tuple, List, Any
from rich.traceback import install
from datetime import datetime
@ -119,7 +120,6 @@ def init_prompt():
"action_prompt",
)
Prompt(
"""
{name_block}
@ -145,7 +145,7 @@ no_action不选择任何动作
请选择并说明触发action的消息id和选择该action的原因消息id格式:m+数字
请根据动作示例以严格的 JSON 格式输出且仅包含 JSON 内容
""",
"sub_planner_prompt",
"sub_planner_prompt",
)
@ -216,31 +216,37 @@ class ActionPlanner:
logger.warning(
f"{self.log_prefix}LLM 返回了当前不可用或无效的动作: '{action}' (可用: {available_action_names}),将强制使用 'no_action'"
)
reasoning = f"LLM 返回了当前不可用的动作 '{action}' (可用: {available_action_names})。原始理由: {reasoning}"
reasoning = (
f"LLM 返回了当前不可用的动作 '{action}' (可用: {available_action_names})。原始理由: {reasoning}"
)
action = "no_action"
# 创建ActionPlannerInfo对象
# 将列表转换为字典格式
available_actions_dict = dict(current_available_actions)
action_planner_infos.append(ActionPlannerInfo(
action_type=action,
reasoning=reasoning,
action_data=action_data,
action_message=target_message,
available_actions=available_actions_dict,
))
action_planner_infos.append(
ActionPlannerInfo(
action_type=action,
reasoning=reasoning,
action_data=action_data,
action_message=target_message,
available_actions=available_actions_dict,
)
)
except Exception as e:
logger.error(f"{self.log_prefix}解析单个action时出错: {e}")
# 将列表转换为字典格式
available_actions_dict = dict(current_available_actions)
action_planner_infos.append(ActionPlannerInfo(
action_type="no_action",
reasoning=f"解析单个action时出错: {e}",
action_data={},
action_message=None,
available_actions=available_actions_dict,
))
action_planner_infos.append(
ActionPlannerInfo(
action_type="no_action",
reasoning=f"解析单个action时出错: {e}",
action_data={},
action_message=None,
available_actions=available_actions_dict,
)
)
return action_planner_infos
@ -339,13 +345,15 @@ class ActionPlanner:
logger.error(f"构建 Planner 提示词时出错: {e}")
logger.error(traceback.format_exc())
# 返回一个默认的no_action而不是字符串
return [ActionPlannerInfo(
action_type="no_action",
reasoning=f"构建 Planner Prompt 时出错: {e}",
action_data={},
action_message=None,
available_actions=action_list,
)]
return [
ActionPlannerInfo(
action_type="no_action",
reasoning=f"构建 Planner Prompt 时出错: {e}",
action_data={},
action_message=None,
available_actions=action_list,
)
]
# --- 调用 LLM (普通文本生成) ---
llm_content = None
@ -368,13 +376,15 @@ class ActionPlanner:
except Exception as req_e:
logger.error(f"{self.log_prefix}副规划器LLM 请求执行失败: {req_e}")
# 返回一个默认的no_action
action_planner_infos.append(ActionPlannerInfo(
action_type="no_action",
reasoning=f"副规划器LLM 请求失败,模型出现问题: {req_e}",
action_data={},
action_message=None,
available_actions=action_list,
))
action_planner_infos.append(
ActionPlannerInfo(
action_type="no_action",
reasoning=f"副规划器LLM 请求失败,模型出现问题: {req_e}",
action_data={},
action_message=None,
available_actions=action_list,
)
)
return action_planner_infos
if llm_content:
@ -388,74 +398,81 @@ class ActionPlanner:
logger.info(f"{self.log_prefix}LLM返回了{len(parsed_json)}个action")
for action_item in parsed_json:
if isinstance(action_item, dict):
action_planner_infos.extend(self._parse_single_action(
action_item, message_id_list, action_list
))
action_planner_infos.extend(
self._parse_single_action(action_item, message_id_list, action_list)
)
else:
logger.warning(f"{self.log_prefix}列表中的action项不是字典类型: {type(action_item)}")
else:
logger.warning(f"{self.log_prefix}LLM返回了空列表")
action_planner_infos.append(ActionPlannerInfo(
action_planner_infos.append(
ActionPlannerInfo(
action_type="no_action",
reasoning="LLM返回了空列表选择no_action",
action_data={},
action_message=None,
available_actions=action_list,
)
)
elif isinstance(parsed_json, dict):
# 如果是单个字典处理单个action
action_planner_infos.extend(self._parse_single_action(parsed_json, message_id_list, action_list))
else:
logger.error(f"{self.log_prefix}解析后的JSON不是字典或列表类型: {type(parsed_json)}")
action_planner_infos.append(
ActionPlannerInfo(
action_type="no_action",
reasoning="LLM返回了空列表选择no_action",
reasoning=f"解析后的JSON类型错误: {type(parsed_json)}",
action_data={},
action_message=None,
available_actions=action_list,
))
elif isinstance(parsed_json, dict):
# 如果是单个字典处理单个action
action_planner_infos.extend(self._parse_single_action(
parsed_json, message_id_list, action_list
))
else:
logger.error(f"{self.log_prefix}解析后的JSON不是字典或列表类型: {type(parsed_json)}")
action_planner_infos.append(ActionPlannerInfo(
action_type="no_action",
reasoning=f"解析后的JSON类型错误: {type(parsed_json)}",
action_data={},
action_message=None,
available_actions=action_list,
))
)
)
except Exception as json_e:
logger.warning(f"{self.log_prefix}解析LLM响应JSON失败 {json_e}. LLM原始输出: '{llm_content}'")
traceback.print_exc()
action_planner_infos.append(ActionPlannerInfo(
action_planner_infos.append(
ActionPlannerInfo(
action_type="no_action",
reasoning=f"解析LLM响应JSON失败: {json_e}. 将使用默认动作 'no_action'.",
action_data={},
action_message=None,
available_actions=action_list,
)
)
else:
# 如果没有LLM内容返回默认的no_action
action_planner_infos.append(
ActionPlannerInfo(
action_type="no_action",
reasoning=f"解析LLM响应JSON失败: {json_e}. 将使用默认动作 'no_action'.",
reasoning="副规划器没有获得LLM响应",
action_data={},
action_message=None,
available_actions=action_list,
))
else:
# 如果没有LLM内容返回默认的no_action
action_planner_infos.append(ActionPlannerInfo(
action_type="no_action",
reasoning="副规划器没有获得LLM响应",
action_data={},
action_message=None,
available_actions=action_list,
))
)
)
# 如果没有解析到任何action返回默认的no_action
if not action_planner_infos:
action_planner_infos.append(ActionPlannerInfo(
action_type="no_action",
reasoning="副规划器没有解析到任何有效action",
action_data={},
action_message=None,
available_actions=action_list,
))
action_planner_infos.append(
ActionPlannerInfo(
action_type="no_action",
reasoning="副规划器没有解析到任何有效action",
action_data={},
action_message=None,
available_actions=action_list,
)
)
logger.info(f"{self.log_prefix}副规划器返回了{len(action_planner_infos)}个action")
return action_planner_infos
async def plan(
self,
available_actions: Dict[str, ActionInfo],
mode: ChatMode = ChatMode.FOCUS,
loop_start_time: float = 0.0,
available_actions: Optional[Dict[str, ActionInfo]] = None,
) -> Tuple[List[ActionPlannerInfo], Optional[DatabaseMessages]]:
"""
规划器 (Planner): 使用LLM根据上下文决定做出什么动作
@ -496,10 +513,10 @@ class ActionPlanner:
try:
logger.info(f"{self.log_prefix}开始构建副Planner")
sub_planner_actions = {}
sub_planner_actions: Dict[str, ActionInfo] = {}
for action_name, action_info in available_actions.items():
if action_info.activation_type == ActionActivationType.LLM_JUDGE or action_info.activation_type == ActionActivationType.ALWAYS:
if action_info.activation_type in [ActionActivationType.LLM_JUDGE, ActionActivationType.ALWAYS]:
sub_planner_actions[action_name] = action_info
elif action_info.activation_type == ActionActivationType.RANDOM:
if random.random() < action_info.random_activation_probability:
@ -543,25 +560,23 @@ class ActionPlanner:
sub_planner_lists[i].append((action_name, action_info))
else:
# 随机选择一个列表添加action但不超过最大大小限制
available_lists = [j for j, lst in enumerate(sub_planner_lists)
if len(lst) < sub_planner_size]
available_lists = [j for j, lst in enumerate(sub_planner_lists) if len(lst) < sub_planner_size]
if available_lists:
target_list = random.choice(available_lists)
sub_planner_lists[target_list].append((action_name, action_info))
logger.info(f"{self.log_prefix}成功将{len(sub_planner_actions)}个actions分配到{sub_planner_num}个子列表中")
logger.info(
f"{self.log_prefix}成功将{len(sub_planner_actions)}个actions分配到{sub_planner_num}个子列表中"
)
for i, lst in enumerate(sub_planner_lists):
logger.debug(f"{self.log_prefix}子列表{i+1}: {len(lst)}个actions")
logger.debug(f"{self.log_prefix}子列表{i + 1}: {len(lst)}个actions")
else:
logger.info(f"{self.log_prefix}没有可用的actions需要分配")
# 先获取必要信息
is_group_chat, chat_target_info, current_available_actions = self.get_necessary_info()
# 并行执行所有副规划器
import asyncio
async def execute_sub_plan(action_list):
return await self.sub_plan(
action_list=action_list,
@ -701,13 +716,15 @@ class ActionPlanner:
# 添加主规划器的action如果不是no_action
if action != "no_action":
main_actions.append(ActionPlannerInfo(
action_type=action,
reasoning=reasoning,
action_data=action_data,
action_message=target_message,
available_actions=available_actions,
))
main_actions.append(
ActionPlannerInfo(
action_type=action,
reasoning=reasoning,
action_data=action_data,
action_message=target_message,
available_actions=available_actions,
)
)
# 先合并主副规划器的结果
all_actions = main_actions + all_sub_planner_results
@ -717,28 +734,34 @@ class ActionPlanner:
# 如果所有结果都是no_action返回一个no_action
if not actions:
actions = [ActionPlannerInfo(
action_type="no_action",
reasoning="所有规划器都选择不执行动作",
action_data={},
action_message=None,
available_actions=available_actions,
)]
actions = [
ActionPlannerInfo(
action_type="no_action",
reasoning="所有规划器都选择不执行动作",
action_data={},
action_message=None,
available_actions=available_actions,
)
]
logger.info(f"{self.log_prefix}并行模式:返回主规划器{len(main_actions)}个action + 副规划器{len(all_sub_planner_results)}个action过滤后总计{len(actions)}个action")
logger.info(
f"{self.log_prefix}并行模式:返回主规划器{len(main_actions)}个action + 副规划器{len(all_sub_planner_results)}个action过滤后总计{len(actions)}个action"
)
else:
# 如果为假,只返回副规划器的结果
actions = filter_no_actions(all_sub_planner_results)
# 如果所有结果都是no_action返回一个no_action
if not actions:
actions = [ActionPlannerInfo(
action_type="no_action",
reasoning="副规划器都选择不执行动作",
action_data={},
action_message=None,
available_actions=available_actions,
)]
actions = [
ActionPlannerInfo(
action_type="no_action",
reasoning="副规划器都选择不执行动作",
action_data={},
action_message=None,
available_actions=available_actions,
)
]
logger.info(f"{self.log_prefix}非并行模式:返回副规划器的{len(actions)}个action已过滤no_action")

View File

@ -330,7 +330,7 @@ def _build_readable_messages_internal(
pic_id_mapping: Optional[Dict[str, str]] = None,
pic_counter: int = 1,
show_pic: bool = True,
message_id_list: Optional[List[DatabaseMessages]] = None,
message_id_list: Optional[List[Tuple[str, DatabaseMessages]]] = None,
) -> Tuple[str, List[Tuple[float, str, str]], Dict[str, str], int]:
# sourcery skip: use-getitem-for-re-match-groups
"""
@ -638,7 +638,7 @@ def build_readable_messages(
truncate: bool = False,
show_actions: bool = False,
show_pic: bool = True,
message_id_list: Optional[List[DatabaseMessages]] = None,
message_id_list: Optional[List[Tuple[str, DatabaseMessages]]] = None,
) -> str: # sourcery skip: extract-method
"""
将消息列表转换为可读的文本格式

View File

@ -1,6 +1,5 @@
import random
import re
import string
import time
import jieba
import json
@ -8,7 +7,7 @@ import ast
import numpy as np
from collections import Counter
from typing import Optional, Tuple, Dict, List, Any
from typing import Optional, Tuple, Dict, List
from src.common.logger import get_logger
from src.common.data_models.database_data_model import DatabaseMessages
@ -675,7 +674,7 @@ def get_chat_type_and_target_info(chat_id: str) -> Tuple[bool, Optional[Dict]]:
return is_group_chat, chat_target_info
def assign_message_ids(messages: List[DatabaseMessages]) -> List[DatabaseMessages]:
def assign_message_ids(messages: List[DatabaseMessages]) -> List[Tuple[str, DatabaseMessages]]:
"""
为消息列表中的每个消息分配唯一的简短随机ID