Files
mai-bot/src/chat/normal_chat/normal_chat_planner.py

309 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import json
from typing import Dict, Any
from rich.traceback import install
from src.llm_models.utils_model import LLMRequest
from src.config.config import global_config
from src.common.logger import get_logger
from src.chat.utils.prompt_builder import Prompt, global_prompt_manager
from src.individuality.individuality import get_individuality
from src.chat.focus_chat.planners.action_manager import ActionManager
from src.chat.message_receive.message import MessageThinking
from json_repair import repair_json
from src.chat.utils.chat_message_builder import build_readable_messages, get_raw_msg_before_timestamp_with_chat
import time
import traceback
logger = get_logger("normal_chat_planner")
install(extra_lines=3)
def init_prompt():
Prompt(
"""
你的自我认知是:
{self_info_block}
请记住你的性格,身份和特点。
你是群内的一员,你现在正在参与群内的闲聊,以下是群内的聊天内容:
{chat_context}
基于以上聊天上下文和用户的最新消息选择最合适的action。
注意除了下面动作选项之外你在聊天中不能做其他任何事情这是你能力的边界现在请你选择合适的action:
{action_options_text}
重要说明:
- "no_action" 表示只进行普通聊天回复,不执行任何额外动作
- 其他action表示在普通回复的基础上执行相应的额外动作
你必须从上面列出的可用action中选择一个并说明原因。
{moderation_prompt}
请以动作的输出要求,以严格的 JSON 格式输出,且仅包含 JSON 内容。不要有任何其他文字或解释:
""",
"normal_chat_planner_prompt",
)
Prompt(
"""
动作:{action_name}
该动作的描述:{action_description}
使用该动作的场景:
{action_require}
输出要求:
{{
"action": "{action_name}",{action_parameters}
}}
""",
"normal_chat_action_prompt",
)
class NormalChatPlanner:
def __init__(self, log_prefix: str, action_manager: ActionManager):
self.log_prefix = log_prefix
# LLM规划器配置
self.planner_llm = LLMRequest(
model=global_config.model.planner,
request_type="normal.planner", # 用于normal_chat动作规划
)
self.action_manager = action_manager
async def plan(self, message: MessageThinking, sender_name: str = "某人") -> Dict[str, Any]:
"""
Normal Chat 规划器: 使用LLM根据上下文决定做出什么动作。
参数:
message: 思考消息对象
sender_name: 发送者名称
"""
action = "no_action" # 默认动作改为no_action
reasoning = "规划器初始化默认"
action_data = {}
try:
# 设置默认值
nickname_str = ""
for nicknames in global_config.bot.alias_names:
nickname_str += f"{nicknames},"
name_block = f"你的名字是{global_config.bot.nickname},你的昵称有{nickname_str},有人也会用这些昵称称呼你。"
personality_block = get_individuality().get_personality_prompt(x_person=2, level=2)
identity_block = get_individuality().get_identity_prompt(x_person=2, level=2)
self_info = name_block + personality_block + identity_block
# 获取当前可用的动作使用Normal模式过滤
current_available_actions = self.action_manager.get_using_actions_for_mode("normal")
# 注意:动作的激活判定现在在 normal_chat_action_modifier 中完成
# 这里直接使用经过 action_modifier 处理后的最终动作集
# 符合职责分离原则ActionModifier负责动作管理Planner专注于决策
# 如果没有可用动作直接返回no_action
if not current_available_actions:
logger.debug(f"{self.log_prefix}规划器: 没有可用动作返回no_action")
return {
"action_result": {
"action_type": action,
"action_data": action_data,
"reasoning": reasoning,
"is_parallel": True,
},
"chat_context": "",
"action_prompt": "",
}
# 构建normal_chat的上下文 (使用与normal_chat相同的prompt构建方法)
message_list_before_now = get_raw_msg_before_timestamp_with_chat(
chat_id=message.chat_stream.stream_id,
timestamp=time.time(),
limit=global_config.focus_chat.observation_context_size,
)
chat_context = build_readable_messages(
message_list_before_now,
replace_bot_name=True,
merge_messages=False,
timestamp_mode="relative",
read_mark=0.0,
show_actions=True,
)
# 构建planner的prompt
prompt = await self.build_planner_prompt(
self_info_block=self_info,
chat_context=chat_context,
current_available_actions=current_available_actions,
)
if not prompt:
logger.warning(f"{self.log_prefix}规划器: 构建提示词失败")
return {
"action_result": {
"action_type": action,
"action_data": action_data,
"reasoning": reasoning,
"is_parallel": False,
},
"chat_context": chat_context,
"action_prompt": "",
}
# 使用LLM生成动作决策
try:
content, (reasoning_content, model_name) = await self.planner_llm.generate_response_async(prompt)
logger.info(f"{self.log_prefix}规划器原始提示词: {prompt}")
logger.info(f"{self.log_prefix}规划器原始响应: {content}")
logger.info(f"{self.log_prefix}规划器推理: {reasoning_content}")
logger.info(f"{self.log_prefix}规划器模型: {model_name}")
# 解析JSON响应
try:
# 尝试修复JSON
fixed_json = repair_json(content)
action_result = json.loads(fixed_json)
action = action_result.get("action", "no_action")
reasoning = action_result.get("reasoning", "未提供原因")
# 提取其他参数作为action_data
action_data = {k: v for k, v in action_result.items() if k not in ["action", "reasoning"]}
# 验证动作是否在可用动作列表中,或者是特殊动作
if action not in current_available_actions:
logger.warning(f"{self.log_prefix}规划器选择了不可用的动作: {action}, 回退到no_action")
action = "no_action"
reasoning = f"选择的动作{action}不在可用列表中回退到no_action"
action_data = {}
except json.JSONDecodeError as e:
logger.warning(f"{self.log_prefix}规划器JSON解析失败: {e}, 内容: {content}")
action = "no_action"
reasoning = "JSON解析失败使用默认动作"
action_data = {}
except Exception as e:
logger.error(f"{self.log_prefix}规划器LLM调用失败: {e}")
action = "no_action"
reasoning = "LLM调用失败使用默认动作"
action_data = {}
except Exception as outer_e:
logger.error(f"{self.log_prefix}规划器异常: {outer_e}")
# 设置异常时的默认值
current_available_actions = {}
chat_context = "无法获取聊天上下文"
prompt = ""
action = "no_action"
reasoning = "规划器出现异常,使用默认动作"
action_data = {}
# 检查动作是否支持并行执行
is_parallel = False
if action in current_available_actions:
action_info = current_available_actions[action]
is_parallel = action_info.get("parallel_action", False)
logger.debug(
f"{self.log_prefix}规划器决策动作:{action}, 动作信息: '{action_data}', 理由: {reasoning}, 并行执行: {is_parallel}"
)
# 恢复到默认动作集
self.action_manager.restore_actions()
logger.debug(
f"{self.log_prefix}规划后恢复到默认动作集, 当前可用: {list(self.action_manager.get_using_actions().keys())}"
)
# 构建 action 记录
action_record = {
"action_type": action,
"action_data": action_data,
"reasoning": reasoning,
"timestamp": time.time(),
"model_name": model_name if "model_name" in locals() else None,
}
action_result = {
"action_type": action,
"action_data": action_data,
"reasoning": reasoning,
"is_parallel": is_parallel,
"action_record": json.dumps(action_record, ensure_ascii=False),
}
plan_result = {
"action_result": action_result,
"chat_context": chat_context,
"action_prompt": prompt,
}
return plan_result
async def build_planner_prompt(
self,
self_info_block: str,
chat_context: str,
current_available_actions: Dict[str, Any],
) -> str:
"""构建 Normal Chat Planner LLM 的提示词"""
try:
# 构建动作选项文本
action_options_text = ""
for action_name, action_info in current_available_actions.items():
action_description = action_info.get("description", "")
action_parameters = action_info.get("parameters", {})
action_require = action_info.get("require", [])
if action_parameters:
param_text = "\n"
# print(action_parameters)
for param_name, param_description in action_parameters.items():
param_text += f' "{param_name}":"{param_description}"\n'
param_text = param_text.rstrip("\n")
else:
param_text = ""
require_text = ""
for require_item in action_require:
require_text += f"- {require_item}\n"
require_text = require_text.rstrip("\n")
# 构建单个动作的提示
action_prompt = await global_prompt_manager.format_prompt(
"normal_chat_action_prompt",
action_name=action_name,
action_description=action_description,
action_parameters=param_text,
action_require=require_text,
)
action_options_text += action_prompt + "\n\n"
# 审核提示
moderation_prompt = "请确保你的回复符合平台规则,避免不当内容。"
# 使用模板构建最终提示词
prompt = await global_prompt_manager.format_prompt(
"normal_chat_planner_prompt",
self_info_block=self_info_block,
action_options_text=action_options_text,
moderation_prompt=moderation_prompt,
chat_context=chat_context,
)
return prompt
except Exception as e:
logger.error(f"{self.log_prefix}构建Planner提示词失败: {e}")
traceback.print_exc()
return ""
init_prompt()