Merge afc branch into dev, prioritizing afc changes and migrating database async modifications from dev
This commit is contained in:
@@ -1,15 +1,24 @@
|
||||
from typing import Dict, Optional, Type
|
||||
import asyncio
|
||||
import traceback
|
||||
import time
|
||||
from typing import Dict, Optional, Type, Any, Tuple
|
||||
|
||||
from src.chat.message_receive.chat_stream import ChatStream
|
||||
|
||||
from src.chat.utils.timer_calculator import Timer
|
||||
from src.person_info.person_info import get_person_info_manager
|
||||
from src.chat.message_receive.chat_stream import ChatStream, get_chat_manager
|
||||
from src.common.logger import get_logger
|
||||
from src.config.config import global_config
|
||||
from src.plugin_system.core.component_registry import component_registry
|
||||
from src.plugin_system.base.component_types import ComponentType, ActionInfo
|
||||
from src.plugin_system.base.base_action import BaseAction
|
||||
from src.plugin_system.apis import generator_api, database_api, send_api, message_api
|
||||
|
||||
|
||||
logger = get_logger("action_manager")
|
||||
|
||||
|
||||
class ActionManager:
|
||||
class ChatterActionManager:
|
||||
"""
|
||||
动作管理器,用于管理各种类型的动作
|
||||
|
||||
@@ -25,6 +34,8 @@ class ActionManager:
|
||||
# 初始化时将默认动作加载到使用中的动作
|
||||
self._using_actions = component_registry.get_default_actions()
|
||||
|
||||
self.log_prefix: str = "ChatterActionManager"
|
||||
|
||||
# === 执行Action方法 ===
|
||||
|
||||
@staticmethod
|
||||
@@ -124,3 +135,417 @@ class ActionManager:
|
||||
actions_to_restore = list(self._using_actions.keys())
|
||||
self._using_actions = component_registry.get_default_actions()
|
||||
logger.debug(f"恢复动作集: 从 {actions_to_restore} 恢复到默认动作集 {list(self._using_actions.keys())}")
|
||||
|
||||
async def execute_action(
|
||||
self,
|
||||
action_name: str,
|
||||
chat_id: str,
|
||||
target_message: Optional[dict] = None,
|
||||
reasoning: str = "",
|
||||
action_data: Optional[dict] = None,
|
||||
thinking_id: Optional[str] = None,
|
||||
log_prefix: str = "",
|
||||
) -> Any:
|
||||
"""
|
||||
执行单个动作的通用函数
|
||||
|
||||
Args:
|
||||
action_name: 动作名称
|
||||
chat_id: 聊天id
|
||||
target_message: 目标消息
|
||||
reasoning: 执行理由
|
||||
action_data: 动作数据
|
||||
thinking_id: 思考ID
|
||||
log_prefix: 日志前缀
|
||||
|
||||
Returns:
|
||||
执行结果
|
||||
"""
|
||||
from src.chat.message_manager.message_manager import message_manager
|
||||
try:
|
||||
logger.debug(f"🎯 [ActionManager] execute_action接收到 target_message: {target_message}")
|
||||
# 通过chat_id获取chat_stream
|
||||
chat_manager = get_chat_manager()
|
||||
chat_stream = chat_manager.get_stream(chat_id)
|
||||
|
||||
if not chat_stream:
|
||||
logger.error(f"{log_prefix} 无法找到chat_id对应的chat_stream: {chat_id}")
|
||||
return {
|
||||
"action_type": action_name,
|
||||
"success": False,
|
||||
"reply_text": "",
|
||||
"error": "chat_stream not found",
|
||||
}
|
||||
|
||||
if action_name == "no_action":
|
||||
return {"action_type": "no_action", "success": True, "reply_text": "", "command": ""}
|
||||
|
||||
if action_name == "no_reply":
|
||||
# 直接处理no_reply逻辑,不再通过动作系统
|
||||
reason = reasoning or "选择不回复"
|
||||
logger.info(f"{log_prefix} 选择不回复,原因: {reason}")
|
||||
|
||||
# 存储no_reply信息到数据库
|
||||
await database_api.store_action_info(
|
||||
chat_stream=chat_stream,
|
||||
action_build_into_prompt=False,
|
||||
action_prompt_display=reason,
|
||||
action_done=True,
|
||||
thinking_id=thinking_id,
|
||||
action_data={"reason": reason},
|
||||
action_name="no_reply",
|
||||
)
|
||||
return {"action_type": "no_reply", "success": True, "reply_text": "", "command": ""}
|
||||
|
||||
elif action_name != "reply" and action_name != "no_action":
|
||||
# 执行普通动作
|
||||
success, reply_text, command = await self._handle_action(
|
||||
chat_stream,
|
||||
action_name,
|
||||
reasoning,
|
||||
action_data or {},
|
||||
{}, # cycle_timers
|
||||
thinking_id,
|
||||
target_message,
|
||||
)
|
||||
|
||||
# 记录执行的动作到目标消息
|
||||
if success:
|
||||
await self._record_action_to_message(chat_stream, action_name, target_message, action_data)
|
||||
# 重置打断计数
|
||||
await self._reset_interruption_count_after_action(chat_stream.stream_id)
|
||||
|
||||
return {
|
||||
"action_type": action_name,
|
||||
"success": success,
|
||||
"reply_text": reply_text,
|
||||
"command": command,
|
||||
}
|
||||
else:
|
||||
# 生成回复
|
||||
try:
|
||||
success, response_set, _ = await generator_api.generate_reply(
|
||||
chat_stream=chat_stream,
|
||||
reply_message=target_message,
|
||||
action_data=action_data or {},
|
||||
available_actions=self.get_using_actions(),
|
||||
enable_tool=global_config.tool.enable_tool,
|
||||
request_type="chat.replyer",
|
||||
from_plugin=False,
|
||||
)
|
||||
if not success or not response_set:
|
||||
logger.info(
|
||||
f"对 {target_message.get('processed_plain_text') if target_message else '未知消息'} 的回复生成失败"
|
||||
)
|
||||
return {"action_type": "reply", "success": False, "reply_text": "", "loop_info": None}
|
||||
except asyncio.CancelledError:
|
||||
logger.debug(f"{log_prefix} 并行执行:回复生成任务已被取消")
|
||||
return {"action_type": "reply", "success": False, "reply_text": "", "loop_info": None}
|
||||
|
||||
# 发送并存储回复
|
||||
loop_info, reply_text, cycle_timers_reply = await self._send_and_store_reply(
|
||||
chat_stream,
|
||||
response_set,
|
||||
asyncio.get_event_loop().time(),
|
||||
target_message,
|
||||
{}, # cycle_timers
|
||||
thinking_id,
|
||||
[], # actions
|
||||
)
|
||||
|
||||
# 记录回复动作到目标消息
|
||||
await self._record_action_to_message(chat_stream, "reply", target_message, action_data)
|
||||
|
||||
# 回复成功,重置打断计数
|
||||
await self._reset_interruption_count_after_action(chat_stream.stream_id)
|
||||
|
||||
return {"action_type": "reply", "success": True, "reply_text": reply_text, "loop_info": loop_info}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"{log_prefix} 执行动作时出错: {e}")
|
||||
logger.error(f"{log_prefix} 错误信息: {traceback.format_exc()}")
|
||||
return {
|
||||
"action_type": action_name,
|
||||
"success": False,
|
||||
"reply_text": "",
|
||||
"loop_info": None,
|
||||
"error": str(e),
|
||||
}
|
||||
|
||||
async def _record_action_to_message(self, chat_stream, action_name, target_message, action_data):
|
||||
"""
|
||||
记录执行的动作到目标消息中
|
||||
|
||||
Args:
|
||||
chat_stream: ChatStream实例
|
||||
action_name: 动作名称
|
||||
target_message: 目标消息
|
||||
action_data: 动作数据
|
||||
"""
|
||||
try:
|
||||
from src.chat.message_manager.message_manager import message_manager
|
||||
|
||||
# 获取目标消息ID
|
||||
target_message_id = None
|
||||
if target_message and isinstance(target_message, dict):
|
||||
target_message_id = target_message.get("message_id")
|
||||
elif action_data and isinstance(action_data, dict):
|
||||
target_message_id = action_data.get("target_message_id")
|
||||
|
||||
if not target_message_id:
|
||||
logger.debug(f"无法获取目标消息ID,动作: {action_name}")
|
||||
return
|
||||
|
||||
# 通过message_manager更新消息的动作记录并刷新focus_energy
|
||||
if chat_stream.stream_id in message_manager.stream_contexts:
|
||||
message_manager.add_action(
|
||||
stream_id=chat_stream.stream_id,
|
||||
message_id=target_message_id,
|
||||
action=action_name
|
||||
)
|
||||
logger.debug(f"已记录动作 {action_name} 到消息 {target_message_id} 并更新focus_energy")
|
||||
else:
|
||||
logger.debug(f"未找到stream_context: {chat_stream.stream_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"记录动作到消息失败: {e}")
|
||||
# 不抛出异常,避免影响主要功能
|
||||
|
||||
async def _reset_interruption_count_after_action(self, stream_id: str):
|
||||
"""在动作执行成功后重置打断计数"""
|
||||
from src.chat.message_manager.message_manager import message_manager
|
||||
try:
|
||||
if stream_id in message_manager.stream_contexts:
|
||||
context = message_manager.stream_contexts[stream_id]
|
||||
if context.interruption_count > 0:
|
||||
old_count = context.interruption_count
|
||||
old_afc_adjustment = context.get_afc_threshold_adjustment()
|
||||
context.reset_interruption_count()
|
||||
logger.debug(f"动作执行成功,重置聊天流 {stream_id} 的打断计数: {old_count} -> 0, afc调整: {old_afc_adjustment} -> 0")
|
||||
except Exception as e:
|
||||
logger.warning(f"重置打断计数时出错: {e}")
|
||||
|
||||
async def _handle_action(
|
||||
self, chat_stream, action, reasoning, action_data, cycle_timers, thinking_id, action_message
|
||||
) -> tuple[bool, str, str]:
|
||||
"""
|
||||
处理具体的动作执行
|
||||
|
||||
Args:
|
||||
chat_stream: ChatStream实例
|
||||
action: 动作名称
|
||||
reasoning: 执行理由
|
||||
action_data: 动作数据
|
||||
cycle_timers: 循环计时器
|
||||
thinking_id: 思考ID
|
||||
action_message: 动作消息
|
||||
|
||||
Returns:
|
||||
tuple: (执行是否成功, 回复文本, 命令文本)
|
||||
|
||||
功能说明:
|
||||
- 创建对应的动作处理器
|
||||
- 执行动作并捕获异常
|
||||
- 返回执行结果供上级方法整合
|
||||
"""
|
||||
if not chat_stream:
|
||||
return False, "", ""
|
||||
try:
|
||||
# 创建动作处理器
|
||||
action_handler = self.create_action(
|
||||
action_name=action,
|
||||
action_data=action_data,
|
||||
reasoning=reasoning,
|
||||
cycle_timers=cycle_timers,
|
||||
thinking_id=thinking_id,
|
||||
chat_stream=chat_stream,
|
||||
log_prefix=self.log_prefix,
|
||||
action_message=action_message,
|
||||
)
|
||||
if not action_handler:
|
||||
# 动作处理器创建失败,尝试回退机制
|
||||
logger.warning(f"{self.log_prefix} 创建动作处理器失败: {action},尝试回退方案")
|
||||
|
||||
# 获取当前可用的动作
|
||||
available_actions = self.get_using_actions()
|
||||
fallback_action = None
|
||||
|
||||
# 回退优先级:reply > 第一个可用动作
|
||||
if "reply" in available_actions:
|
||||
fallback_action = "reply"
|
||||
elif available_actions:
|
||||
fallback_action = list(available_actions.keys())[0]
|
||||
|
||||
if fallback_action and fallback_action != action:
|
||||
logger.info(f"{self.log_prefix} 使用回退动作: {fallback_action}")
|
||||
action_handler = self.create_action(
|
||||
action_name=fallback_action,
|
||||
action_data=action_data,
|
||||
reasoning=f"原动作'{action}'不可用,自动回退。{reasoning}",
|
||||
cycle_timers=cycle_timers,
|
||||
thinking_id=thinking_id,
|
||||
chat_stream=chat_stream,
|
||||
log_prefix=self.log_prefix,
|
||||
action_message=action_message,
|
||||
)
|
||||
|
||||
if not action_handler:
|
||||
logger.error(f"{self.log_prefix} 回退方案也失败,无法创建任何动作处理器")
|
||||
return False, "", ""
|
||||
|
||||
# 执行动作
|
||||
success, reply_text = await action_handler.handle_action()
|
||||
return success, reply_text, ""
|
||||
except Exception as e:
|
||||
logger.error(f"{self.log_prefix} 处理{action}时出错: {e}")
|
||||
traceback.print_exc()
|
||||
return False, "", ""
|
||||
|
||||
async def _send_and_store_reply(
|
||||
self,
|
||||
chat_stream: ChatStream,
|
||||
response_set,
|
||||
loop_start_time,
|
||||
action_message,
|
||||
cycle_timers: Dict[str, float],
|
||||
thinking_id,
|
||||
actions,
|
||||
) -> Tuple[Dict[str, Any], str, Dict[str, float]]:
|
||||
"""
|
||||
发送并存储回复信息
|
||||
|
||||
Args:
|
||||
chat_stream: ChatStream实例
|
||||
response_set: 回复内容集合
|
||||
loop_start_time: 循环开始时间
|
||||
action_message: 动作消息
|
||||
cycle_timers: 循环计时器
|
||||
thinking_id: 思考ID
|
||||
actions: 动作列表
|
||||
|
||||
Returns:
|
||||
Tuple[Dict[str, Any], str, Dict[str, float]]: 循环信息, 回复文本, 循环计时器
|
||||
"""
|
||||
# 发送回复
|
||||
with Timer("回复发送", cycle_timers):
|
||||
reply_text = await self.send_response(chat_stream, response_set, loop_start_time, action_message)
|
||||
|
||||
# 存储reply action信息
|
||||
person_info_manager = get_person_info_manager()
|
||||
|
||||
# 获取 platform,如果不存在则从 chat_stream 获取,如果还是 None 则使用默认值
|
||||
platform = action_message.get("chat_info_platform")
|
||||
if platform is None:
|
||||
platform = getattr(chat_stream, "platform", "unknown")
|
||||
|
||||
# 获取用户信息并生成回复提示
|
||||
person_id = person_info_manager.get_person_id(
|
||||
platform,
|
||||
action_message.get("user_id", ""),
|
||||
)
|
||||
person_name = await person_info_manager.get_value(person_id, "person_name")
|
||||
action_prompt_display = f"你对{person_name}进行了回复:{reply_text}"
|
||||
|
||||
# 存储动作信息到数据库
|
||||
await database_api.store_action_info(
|
||||
chat_stream=chat_stream,
|
||||
action_build_into_prompt=False,
|
||||
action_prompt_display=action_prompt_display,
|
||||
action_done=True,
|
||||
thinking_id=thinking_id,
|
||||
action_data={"reply_text": reply_text},
|
||||
action_name="reply",
|
||||
)
|
||||
|
||||
# 构建循环信息
|
||||
loop_info: Dict[str, Any] = {
|
||||
"loop_plan_info": {
|
||||
"action_result": actions,
|
||||
},
|
||||
"loop_action_info": {
|
||||
"action_taken": True,
|
||||
"reply_text": reply_text,
|
||||
"command": "",
|
||||
"taken_time": time.time(),
|
||||
},
|
||||
}
|
||||
|
||||
return loop_info, reply_text, cycle_timers
|
||||
|
||||
async def send_response(self, chat_stream, reply_set, thinking_start_time, message_data) -> str:
|
||||
"""
|
||||
发送回复内容的具体实现
|
||||
|
||||
Args:
|
||||
chat_stream: ChatStream实例
|
||||
reply_set: 回复内容集合,包含多个回复段
|
||||
reply_to: 回复目标
|
||||
thinking_start_time: 思考开始时间
|
||||
message_data: 消息数据
|
||||
|
||||
Returns:
|
||||
str: 完整的回复文本
|
||||
|
||||
功能说明:
|
||||
- 检查是否有新消息需要回复
|
||||
- 处理主动思考的"沉默"决定
|
||||
- 根据消息数量决定是否添加回复引用
|
||||
- 逐段发送回复内容,支持打字效果
|
||||
- 正确处理元组格式的回复段
|
||||
"""
|
||||
current_time = time.time()
|
||||
# 计算新消息数量
|
||||
new_message_count = message_api.count_new_messages(
|
||||
chat_id=chat_stream.stream_id, start_time=thinking_start_time, end_time=current_time
|
||||
)
|
||||
|
||||
# 根据新消息数量决定是否需要引用回复
|
||||
reply_text = ""
|
||||
is_proactive_thinking = (message_data.get("message_type") == "proactive_thinking") if message_data else True
|
||||
|
||||
logger.debug(f"[send_response] message_data: {message_data}")
|
||||
|
||||
first_replied = False
|
||||
for reply_seg in reply_set:
|
||||
# 调试日志:验证reply_seg的格式
|
||||
logger.debug(f"Processing reply_seg type: {type(reply_seg)}, content: {reply_seg}")
|
||||
|
||||
# 修正:正确处理元组格式 (格式为: (type, content))
|
||||
if isinstance(reply_seg, tuple) and len(reply_seg) >= 2:
|
||||
_, data = reply_seg
|
||||
else:
|
||||
# 向下兼容:如果已经是字符串,则直接使用
|
||||
data = str(reply_seg)
|
||||
|
||||
if isinstance(data, list):
|
||||
data = "".join(map(str, data))
|
||||
reply_text += data
|
||||
|
||||
# 如果是主动思考且内容为"沉默",则不发送
|
||||
if is_proactive_thinking and data.strip() == "沉默":
|
||||
logger.info(f"{self.log_prefix} 主动思考决定保持沉默,不发送消息")
|
||||
continue
|
||||
|
||||
# 发送第一段回复
|
||||
if not first_replied:
|
||||
set_reply_flag = bool(message_data)
|
||||
logger.debug(f"📤 [ActionManager] 准备发送第一段回复。message_data: {message_data}, set_reply: {set_reply_flag}")
|
||||
await send_api.text_to_stream(
|
||||
text=data,
|
||||
stream_id=chat_stream.stream_id,
|
||||
reply_to_message=message_data,
|
||||
set_reply=set_reply_flag,
|
||||
typing=False,
|
||||
)
|
||||
first_replied = True
|
||||
else:
|
||||
# 发送后续回复
|
||||
sent_message = await send_api.text_to_stream(
|
||||
text=data,
|
||||
stream_id=chat_stream.stream_id,
|
||||
reply_to_message=None,
|
||||
set_reply=False,
|
||||
typing=True,
|
||||
)
|
||||
|
||||
return reply_text
|
||||
@@ -7,8 +7,9 @@ from typing import List, Any, Dict, TYPE_CHECKING, Tuple
|
||||
from src.common.logger import get_logger
|
||||
from src.config.config import global_config, model_config
|
||||
from src.llm_models.utils_model import LLMRequest
|
||||
from src.chat.message_receive.chat_stream import get_chat_manager, ChatMessageContext
|
||||
from src.chat.planner_actions.action_manager import ActionManager
|
||||
from src.chat.message_receive.chat_stream import get_chat_manager
|
||||
from src.common.data_models.message_manager_data_model import StreamContext
|
||||
from src.chat.planner_actions.action_manager import ChatterActionManager
|
||||
from src.chat.utils.chat_message_builder import get_raw_msg_before_timestamp_with_chat, build_readable_messages
|
||||
from src.plugin_system.base.component_types import ActionInfo, ActionActivationType
|
||||
from src.plugin_system.core.global_announcement_manager import global_announcement_manager
|
||||
@@ -27,7 +28,7 @@ class ActionModifier:
|
||||
支持并行判定和智能缓存优化。
|
||||
"""
|
||||
|
||||
def __init__(self, action_manager: ActionManager, chat_id: str):
|
||||
def __init__(self, action_manager: ChatterActionManager, chat_id: str):
|
||||
"""初始化动作处理器"""
|
||||
self.chat_id = chat_id
|
||||
self.chat_stream: ChatStream = get_chat_manager().get_stream(self.chat_id) # type: ignore
|
||||
@@ -124,8 +125,9 @@ class ActionModifier:
|
||||
logger.debug(f"{self.log_prefix}阶段一移除动作: {disabled_action_name},原因: 用户自行禁用")
|
||||
|
||||
# === 第二阶段:检查动作的关联类型 ===
|
||||
chat_context = self.chat_stream.context
|
||||
type_mismatched_actions = self._check_action_associated_types(all_actions, chat_context)
|
||||
chat_context = self.chat_stream.stream_context
|
||||
current_actions_s2 = self.action_manager.get_using_actions()
|
||||
type_mismatched_actions = self._check_action_associated_types(current_actions_s2, chat_context)
|
||||
|
||||
if type_mismatched_actions:
|
||||
removals_s2.extend(type_mismatched_actions)
|
||||
@@ -140,11 +142,12 @@ class ActionModifier:
|
||||
logger.debug(f"{self.log_prefix}开始激活类型判定阶段")
|
||||
|
||||
# 获取当前使用的动作集(经过第一阶段处理)
|
||||
current_using_actions = self.action_manager.get_using_actions()
|
||||
# 在第三阶段开始前,再次获取最新的动作列表
|
||||
current_actions_s3 = self.action_manager.get_using_actions()
|
||||
|
||||
# 获取因激活类型判定而需要移除的动作
|
||||
removals_s3 = await self._get_deactivated_actions_by_type(
|
||||
current_using_actions,
|
||||
current_actions_s3,
|
||||
chat_content,
|
||||
)
|
||||
|
||||
@@ -164,7 +167,7 @@ class ActionModifier:
|
||||
|
||||
logger.info(f"{self.log_prefix} 当前可用动作: {available_actions_text}||移除: {removals_summary}")
|
||||
|
||||
def _check_action_associated_types(self, all_actions: Dict[str, ActionInfo], chat_context: ChatMessageContext):
|
||||
def _check_action_associated_types(self, all_actions: Dict[str, ActionInfo], chat_context: StreamContext):
|
||||
type_mismatched_actions: List[Tuple[str, str]] = []
|
||||
for action_name, action_info in all_actions.items():
|
||||
if action_info.associated_types and not chat_context.check_types(action_info.associated_types):
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
"""
|
||||
PlanExecutor: 接收 Plan 对象并执行其中的所有动作。
|
||||
"""
|
||||
from src.chat.planner_actions.action_manager import ActionManager
|
||||
from src.common.data_models.info_data_model import Plan
|
||||
from src.common.logger import get_logger
|
||||
|
||||
logger = get_logger("plan_executor")
|
||||
|
||||
|
||||
class PlanExecutor:
|
||||
"""
|
||||
负责接收一个 Plan 对象,并执行其中最终确定的所有动作。
|
||||
|
||||
这个类是规划流程的最后一步,将规划结果转化为实际的动作执行。
|
||||
|
||||
Attributes:
|
||||
action_manager (ActionManager): 用于实际执行各种动作的管理器实例。
|
||||
"""
|
||||
|
||||
def __init__(self, action_manager: ActionManager):
|
||||
"""
|
||||
初始化 PlanExecutor。
|
||||
|
||||
Args:
|
||||
action_manager (ActionManager): 一个 ActionManager 实例,用于执行动作。
|
||||
"""
|
||||
self.action_manager = action_manager
|
||||
|
||||
@staticmethod
|
||||
async def execute(plan: Plan):
|
||||
"""
|
||||
遍历并执行 Plan 对象中 `decided_actions` 列表里的所有动作。
|
||||
|
||||
如果动作类型为 "no_action",则会记录原因并跳过。
|
||||
否则,它将调用 ActionManager 来执行相应的动作。
|
||||
|
||||
Args:
|
||||
plan (Plan): 包含待执行动作列表的 Plan 对象。
|
||||
"""
|
||||
if not plan.decided_actions:
|
||||
logger.info("没有需要执行的动作。")
|
||||
return
|
||||
|
||||
for action_info in plan.decided_actions:
|
||||
if action_info.action_type == "no_action":
|
||||
logger.info(f"规划器决策不执行动作,原因: {action_info.reasoning}")
|
||||
continue
|
||||
|
||||
# TODO: 对接 ActionManager 的执行方法
|
||||
# 这是一个示例调用,需要根据 ActionManager 的最终实现进行调整
|
||||
logger.info(f"执行动作: {action_info.action_type}, 原因: {action_info.reasoning}")
|
||||
# await self.action_manager.execute_action(
|
||||
# action_name=action_info.action_type,
|
||||
# action_data=action_info.action_data,
|
||||
# reasoning=action_info.reasoning,
|
||||
# action_message=action_info.action_message,
|
||||
# )
|
||||
@@ -1,366 +0,0 @@
|
||||
"""
|
||||
PlanFilter: 接收 Plan 对象,根据不同模式的逻辑进行筛选,决定最终要执行的动作。
|
||||
"""
|
||||
import orjson
|
||||
import time
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from json_repair import repair_json
|
||||
|
||||
from src.chat.memory_system.Hippocampus import hippocampus_manager
|
||||
from src.chat.utils.chat_message_builder import (
|
||||
build_readable_actions,
|
||||
build_readable_messages_with_id,
|
||||
get_actions_by_timestamp_with_chat,
|
||||
)
|
||||
from src.chat.utils.prompt import global_prompt_manager
|
||||
from src.common.data_models.info_data_model import ActionPlannerInfo, Plan
|
||||
from src.common.logger import get_logger
|
||||
from src.config.config import global_config, model_config
|
||||
from src.llm_models.utils_model import LLMRequest
|
||||
from src.mood.mood_manager import mood_manager
|
||||
from src.plugin_system.base.component_types import ActionInfo, ChatMode
|
||||
from src.schedule.schedule_manager import schedule_manager
|
||||
|
||||
logger = get_logger("plan_filter")
|
||||
|
||||
|
||||
class PlanFilter:
|
||||
"""
|
||||
根据 Plan 中的模式和信息,筛选并决定最终的动作。
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.planner_llm = LLMRequest(
|
||||
model_set=model_config.model_task_config.planner, request_type="planner"
|
||||
)
|
||||
self.last_obs_time_mark = 0.0
|
||||
|
||||
async def filter(self, plan: Plan) -> Plan:
|
||||
"""
|
||||
执行筛选逻辑,并填充 Plan 对象的 decided_actions 字段。
|
||||
"""
|
||||
logger.debug(f"墨墨在这里加了日志 -> filter 入口 plan: {plan}")
|
||||
try:
|
||||
prompt, used_message_id_list = await self._build_prompt(plan)
|
||||
plan.llm_prompt = prompt
|
||||
logger.info(f"规划器原始提示词: {prompt}")
|
||||
|
||||
llm_content, _ = await self.planner_llm.generate_response_async(prompt=prompt)
|
||||
|
||||
if llm_content:
|
||||
logger.info(f"规划器原始返回: {llm_content}")
|
||||
parsed_json = orjson.loads(repair_json(llm_content))
|
||||
logger.debug(f"墨墨在这里加了日志 -> 解析后的 JSON: {parsed_json}")
|
||||
|
||||
if isinstance(parsed_json, dict):
|
||||
parsed_json = [parsed_json]
|
||||
|
||||
if isinstance(parsed_json, list):
|
||||
final_actions = []
|
||||
reply_action_added = False
|
||||
# 定义回复类动作的集合,方便扩展
|
||||
reply_action_types = {"reply", "proactive_reply"}
|
||||
|
||||
for item in parsed_json:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
|
||||
# 预解析 action_type 来进行判断
|
||||
action_type = item.get("action", "no_action")
|
||||
|
||||
if action_type in reply_action_types:
|
||||
if not reply_action_added:
|
||||
final_actions.extend(
|
||||
await self._parse_single_action(
|
||||
item, used_message_id_list, plan
|
||||
)
|
||||
)
|
||||
reply_action_added = True
|
||||
else:
|
||||
# 非回复类动作直接添加
|
||||
final_actions.extend(
|
||||
await self._parse_single_action(
|
||||
item, used_message_id_list, plan
|
||||
)
|
||||
)
|
||||
|
||||
plan.decided_actions = self._filter_no_actions(final_actions)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"筛选 Plan 时出错: {e}\n{traceback.format_exc()}")
|
||||
plan.decided_actions = [
|
||||
ActionPlannerInfo(action_type="no_action", reasoning=f"筛选时出错: {e}")
|
||||
]
|
||||
|
||||
logger.debug(f"墨墨在这里加了日志 -> filter 出口 decided_actions: {plan.decided_actions}")
|
||||
return plan
|
||||
|
||||
async def _build_prompt(self, plan: Plan) -> tuple[str, list]:
|
||||
"""
|
||||
根据 Plan 对象构建提示词。
|
||||
"""
|
||||
try:
|
||||
time_block = f"当前时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
||||
bot_name = global_config.bot.nickname
|
||||
bot_nickname = (
|
||||
f",也有人叫你{','.join(global_config.bot.alias_names)}" if global_config.bot.alias_names else ""
|
||||
)
|
||||
bot_core_personality = global_config.personality.personality_core
|
||||
identity_block = f"你的名字是{bot_name}{bot_nickname},你{bot_core_personality}:"
|
||||
|
||||
schedule_block = ""
|
||||
if global_config.planning_system.schedule_enable:
|
||||
if current_activity := schedule_manager.get_current_activity():
|
||||
schedule_block = f"你当前正在:{current_activity},但注意它与群聊的聊天无关。"
|
||||
|
||||
mood_block = ""
|
||||
if global_config.mood.enable_mood:
|
||||
chat_mood = mood_manager.get_mood_by_chat_id(plan.chat_id)
|
||||
mood_block = f"你现在的心情是:{chat_mood.mood_state}"
|
||||
|
||||
if plan.mode == ChatMode.PROACTIVE:
|
||||
long_term_memory_block = await self._get_long_term_memory_context()
|
||||
|
||||
chat_content_block, message_id_list = await build_readable_messages_with_id(
|
||||
messages=[msg.flatten() for msg in plan.chat_history],
|
||||
timestamp_mode="normal",
|
||||
truncate=False,
|
||||
show_actions=False,
|
||||
)
|
||||
|
||||
prompt_template = await global_prompt_manager.get_prompt_async("proactive_planner_prompt")
|
||||
actions_before_now = await get_actions_by_timestamp_with_chat(
|
||||
chat_id=plan.chat_id,
|
||||
timestamp_start=time.time() - 3600,
|
||||
timestamp_end=time.time(),
|
||||
limit=5,
|
||||
)
|
||||
actions_before_now_block = build_readable_actions(actions=actions_before_now)
|
||||
actions_before_now_block = f"你刚刚选择并执行过的action是:\n{actions_before_now_block}"
|
||||
|
||||
prompt = prompt_template.format(
|
||||
time_block=time_block,
|
||||
identity_block=identity_block,
|
||||
schedule_block=schedule_block,
|
||||
mood_block=mood_block,
|
||||
long_term_memory_block=long_term_memory_block,
|
||||
chat_content_block=chat_content_block or "最近没有聊天内容。",
|
||||
actions_before_now_block=actions_before_now_block,
|
||||
)
|
||||
return prompt, message_id_list
|
||||
|
||||
chat_content_block, message_id_list = await build_readable_messages_with_id(
|
||||
messages=[msg.flatten() for msg in plan.chat_history],
|
||||
timestamp_mode="normal",
|
||||
read_mark=self.last_obs_time_mark,
|
||||
truncate=True,
|
||||
show_actions=True,
|
||||
)
|
||||
|
||||
actions_before_now = await get_actions_by_timestamp_with_chat(
|
||||
chat_id=plan.chat_id,
|
||||
timestamp_start=time.time() - 3600,
|
||||
timestamp_end=time.time(),
|
||||
limit=5,
|
||||
)
|
||||
|
||||
actions_before_now_block = build_readable_actions(actions=actions_before_now)
|
||||
actions_before_now_block = f"你刚刚选择并执行过的action是:\n{actions_before_now_block}"
|
||||
|
||||
self.last_obs_time_mark = time.time()
|
||||
|
||||
mentioned_bonus = ""
|
||||
if global_config.chat.mentioned_bot_inevitable_reply:
|
||||
mentioned_bonus = "\n- 有人提到你"
|
||||
if global_config.chat.at_bot_inevitable_reply:
|
||||
mentioned_bonus = "\n- 有人提到你,或者at你"
|
||||
|
||||
if plan.mode == ChatMode.FOCUS:
|
||||
no_action_block = """
|
||||
动作:no_action
|
||||
动作描述:不选择任何动作
|
||||
{{
|
||||
"action": "no_action",
|
||||
"reason":"不动作的原因"
|
||||
}}
|
||||
|
||||
动作:no_reply
|
||||
动作描述:不进行回复,等待合适的回复时机
|
||||
- 当你刚刚发送了消息,没有人回复时,选择no_reply
|
||||
- 当你一次发送了太多消息,为了避免打扰聊天节奏,选择no_reply
|
||||
{{
|
||||
"action": "no_reply",
|
||||
"reason":"不回复的原因"
|
||||
}}
|
||||
"""
|
||||
else: # NORMAL Mode
|
||||
no_action_block = """重要说明:
|
||||
- 'reply' 表示只进行普通聊天回复,不执行任何额外动作
|
||||
- 其他action表示在普通回复的基础上,执行相应的额外动作
|
||||
{{
|
||||
"action": "reply",
|
||||
"target_message_id":"触发action的消息id",
|
||||
"reason":"回复的原因"
|
||||
}}"""
|
||||
|
||||
is_group_chat = plan.target_info.platform == "group" if plan.target_info else True
|
||||
chat_context_description = "你现在正在一个群聊中"
|
||||
if not is_group_chat and plan.target_info:
|
||||
chat_target_name = plan.target_info.person_name or plan.target_info.user_nickname or "对方"
|
||||
chat_context_description = f"你正在和 {chat_target_name} 私聊"
|
||||
|
||||
action_options_block = await self._build_action_options(plan.available_actions)
|
||||
|
||||
moderation_prompt_block = "请不要输出违法违规内容,不要输出色情,暴力,政治相关内容,如有敏感内容,请规避。"
|
||||
|
||||
custom_prompt_block = ""
|
||||
if global_config.custom_prompt.planner_custom_prompt_content:
|
||||
custom_prompt_block = global_config.custom_prompt.planner_custom_prompt_content
|
||||
|
||||
users_in_chat_str = "" # TODO: Re-implement user list fetching if needed
|
||||
|
||||
planner_prompt_template = await global_prompt_manager.get_prompt_async("planner_prompt")
|
||||
prompt = planner_prompt_template.format(
|
||||
schedule_block=schedule_block,
|
||||
mood_block=mood_block,
|
||||
time_block=time_block,
|
||||
chat_context_description=chat_context_description,
|
||||
chat_content_block=chat_content_block,
|
||||
actions_before_now_block=actions_before_now_block,
|
||||
mentioned_bonus=mentioned_bonus,
|
||||
no_action_block=no_action_block,
|
||||
action_options_text=action_options_block,
|
||||
moderation_prompt=moderation_prompt_block,
|
||||
identity_block=identity_block,
|
||||
custom_prompt_block=custom_prompt_block,
|
||||
bot_name=bot_name,
|
||||
users_in_chat=users_in_chat_str
|
||||
)
|
||||
return prompt, message_id_list
|
||||
except Exception as e:
|
||||
logger.error(f"构建 Planner 提示词时出错: {e}")
|
||||
logger.error(traceback.format_exc())
|
||||
return "构建 Planner Prompt 时出错", []
|
||||
|
||||
async def _parse_single_action(
|
||||
self, action_json: dict, message_id_list: list, plan: Plan
|
||||
) -> List[ActionPlannerInfo]:
|
||||
parsed_actions = []
|
||||
try:
|
||||
action = action_json.get("action", "no_action")
|
||||
reasoning = action_json.get("reason", "未提供原因")
|
||||
action_data = {k: v for k, v in action_json.items() if k not in ["action", "reason"]}
|
||||
|
||||
target_message_obj = None
|
||||
if action not in ["no_action", "no_reply", "do_nothing", "proactive_reply"]:
|
||||
if target_message_id := action_json.get("target_message_id"):
|
||||
target_message_dict = self._find_message_by_id(target_message_id, message_id_list)
|
||||
else:
|
||||
# 如果LLM没有指定target_message_id,我们就默认选择最新的一条消息
|
||||
target_message_dict = self._get_latest_message(message_id_list)
|
||||
|
||||
if target_message_dict:
|
||||
# 直接使用字典作为action_message,避免DatabaseMessages对象创建失败
|
||||
target_message_obj = target_message_dict
|
||||
else:
|
||||
# 如果找不到目标消息,对于reply动作来说这是必需的,应该记录警告
|
||||
if action == "reply":
|
||||
logger.warning(f"reply动作找不到目标消息,target_message_id: {action_json.get('target_message_id')}")
|
||||
# 将reply动作改为no_action,避免后续执行时出错
|
||||
action = "no_action"
|
||||
reasoning = f"找不到目标消息进行回复。原始理由: {reasoning}"
|
||||
|
||||
available_action_names = list(plan.available_actions.keys())
|
||||
if action not in ["no_action", "no_reply", "reply", "do_nothing", "proactive_reply"] and action not in available_action_names:
|
||||
reasoning = f"LLM 返回了当前不可用的动作 '{action}'。原始理由: {reasoning}"
|
||||
action = "no_action"
|
||||
|
||||
parsed_actions.append(
|
||||
ActionPlannerInfo(
|
||||
action_type=action,
|
||||
reasoning=reasoning,
|
||||
action_data=action_data,
|
||||
action_message=target_message_obj,
|
||||
available_actions=plan.available_actions,
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"解析单个action时出错: {e}")
|
||||
parsed_actions.append(
|
||||
ActionPlannerInfo(
|
||||
action_type="no_action",
|
||||
reasoning=f"解析action时出错: {e}",
|
||||
)
|
||||
)
|
||||
return parsed_actions
|
||||
|
||||
@staticmethod
|
||||
def _filter_no_actions(
|
||||
action_list: List[ActionPlannerInfo]
|
||||
) -> List[ActionPlannerInfo]:
|
||||
non_no_actions = [a for a in action_list if a.action_type not in ["no_action", "no_reply"]]
|
||||
if non_no_actions:
|
||||
return non_no_actions
|
||||
return action_list[:1] if action_list else []
|
||||
|
||||
@staticmethod
|
||||
async def _get_long_term_memory_context() -> str:
|
||||
try:
|
||||
now = datetime.now()
|
||||
keywords = ["今天", "日程", "计划"]
|
||||
if 5 <= now.hour < 12:
|
||||
keywords.append("早上")
|
||||
elif 12 <= now.hour < 18:
|
||||
keywords.append("中午")
|
||||
else:
|
||||
keywords.append("晚上")
|
||||
|
||||
retrieved_memories = await hippocampus_manager.get_memory_from_topic(
|
||||
valid_keywords=keywords, max_memory_num=5, max_memory_length=1
|
||||
)
|
||||
|
||||
if not retrieved_memories:
|
||||
return "最近没有什么特别的记忆。"
|
||||
|
||||
memory_statements = [f"关于'{topic}', 你记得'{memory_item}'。" for topic, memory_item in retrieved_memories]
|
||||
return " ".join(memory_statements)
|
||||
except Exception as e:
|
||||
logger.error(f"获取长期记忆时出错: {e}")
|
||||
return "回忆时出现了一些问题。"
|
||||
|
||||
@staticmethod
|
||||
async def _build_action_options(current_available_actions: Dict[str, ActionInfo]) -> str:
|
||||
action_options_block = ""
|
||||
for action_name, action_info in current_available_actions.items():
|
||||
param_text = ""
|
||||
if action_info.action_parameters:
|
||||
param_text = "\n" + "\n".join(
|
||||
f' "{p_name}":"{p_desc}"' for p_name, p_desc in action_info.action_parameters.items()
|
||||
)
|
||||
require_text = "\n".join(f"- {req}" for req in action_info.action_require)
|
||||
using_action_prompt = await global_prompt_manager.get_prompt_async("action_prompt")
|
||||
action_options_block += using_action_prompt.format(
|
||||
action_name=action_name,
|
||||
action_description=action_info.description,
|
||||
action_parameters=param_text,
|
||||
action_require=require_text,
|
||||
)
|
||||
return action_options_block
|
||||
|
||||
@staticmethod
|
||||
def _find_message_by_id(message_id: str, message_id_list: list) -> Optional[Dict[str, Any]]:
|
||||
if message_id.isdigit():
|
||||
message_id = f"m{message_id}"
|
||||
for item in message_id_list:
|
||||
if item.get("id") == message_id:
|
||||
return item.get("message")
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _get_latest_message(message_id_list: list) -> Optional[Dict[str, Any]]:
|
||||
if not message_id_list:
|
||||
return None
|
||||
return message_id_list[-1].get("message")
|
||||
@@ -1,110 +0,0 @@
|
||||
"""
|
||||
PlanGenerator: 负责搜集和汇总所有决策所需的信息,生成一个未经筛选的“原始计划” (Plan)。
|
||||
"""
|
||||
import time
|
||||
from typing import Dict
|
||||
|
||||
from src.chat.utils.chat_message_builder import get_raw_msg_before_timestamp_with_chat
|
||||
from src.chat.utils.utils import get_chat_type_and_target_info
|
||||
from src.common.data_models.database_data_model import DatabaseMessages
|
||||
from src.common.data_models.info_data_model import Plan, TargetPersonInfo
|
||||
from src.config.config import global_config
|
||||
from src.plugin_system.base.component_types import ActionInfo, ChatMode, ComponentType
|
||||
from src.plugin_system.core.component_registry import component_registry
|
||||
|
||||
|
||||
class PlanGenerator:
|
||||
"""
|
||||
PlanGenerator 负责在规划流程的初始阶段收集所有必要信息。
|
||||
|
||||
它会汇总以下信息来构建一个“原始”的 Plan 对象,该对象后续会由 PlanFilter 进行筛选:
|
||||
- 当前聊天信息 (ID, 目标用户)
|
||||
- 当前可用的动作列表
|
||||
- 最近的聊天历史记录
|
||||
|
||||
Attributes:
|
||||
chat_id (str): 当前聊天的唯一标识符。
|
||||
action_manager (ActionManager): 用于获取可用动作列表的管理器。
|
||||
"""
|
||||
|
||||
def __init__(self, chat_id: str):
|
||||
"""
|
||||
初始化 PlanGenerator。
|
||||
|
||||
Args:
|
||||
chat_id (str): 当前聊天的 ID。
|
||||
"""
|
||||
from src.chat.planner_actions.action_manager import ActionManager
|
||||
self.chat_id = chat_id
|
||||
# 注意:ActionManager 可能需要根据实际情况初始化
|
||||
self.action_manager = ActionManager()
|
||||
|
||||
async def generate(self, mode: ChatMode) -> Plan:
|
||||
"""
|
||||
收集所有信息,生成并返回一个初始的 Plan 对象。
|
||||
|
||||
这个 Plan 对象包含了决策所需的所有上下文信息。
|
||||
|
||||
Args:
|
||||
mode (ChatMode): 当前的聊天模式。
|
||||
|
||||
Returns:
|
||||
Plan: 一个填充了初始上下文信息的 Plan 对象。
|
||||
"""
|
||||
_is_group_chat, chat_target_info_dict = await get_chat_type_and_target_info(self.chat_id)
|
||||
|
||||
target_info = None
|
||||
if chat_target_info_dict:
|
||||
target_info = TargetPersonInfo(**chat_target_info_dict)
|
||||
|
||||
available_actions = self._get_available_actions()
|
||||
chat_history_raw = get_raw_msg_before_timestamp_with_chat(
|
||||
chat_id=self.chat_id,
|
||||
timestamp=time.time(),
|
||||
limit=int(global_config.chat.max_context_size),
|
||||
)
|
||||
chat_history = [DatabaseMessages(**msg) for msg in await chat_history_raw]
|
||||
|
||||
|
||||
plan = Plan(
|
||||
chat_id=self.chat_id,
|
||||
mode=mode,
|
||||
available_actions=available_actions,
|
||||
chat_history=chat_history,
|
||||
target_info=target_info,
|
||||
)
|
||||
return plan
|
||||
|
||||
def _get_available_actions(self) -> Dict[str, "ActionInfo"]:
|
||||
"""
|
||||
从 ActionManager 和组件注册表中获取当前所有可用的动作。
|
||||
|
||||
它会合并已注册的动作和系统级动作(如 "no_reply"),
|
||||
并以字典形式返回。
|
||||
|
||||
Returns:
|
||||
Dict[str, "ActionInfo"]: 一个字典,键是动作名称,值是 ActionInfo 对象。
|
||||
"""
|
||||
current_available_actions_dict = self.action_manager.get_using_actions()
|
||||
all_registered_actions: Dict[str, ActionInfo] = component_registry.get_components_by_type( # type: ignore
|
||||
ComponentType.ACTION
|
||||
)
|
||||
|
||||
current_available_actions = {}
|
||||
for action_name in current_available_actions_dict:
|
||||
if action_name in all_registered_actions:
|
||||
current_available_actions[action_name] = all_registered_actions[action_name]
|
||||
|
||||
no_reply_info = ActionInfo(
|
||||
name="no_reply",
|
||||
component_type=ComponentType.ACTION,
|
||||
description="系统级动作:选择不回复消息的决策",
|
||||
action_parameters={},
|
||||
activation_keywords=[],
|
||||
plugin_name="SYSTEM",
|
||||
enabled=True,
|
||||
parallel_action=False,
|
||||
)
|
||||
current_available_actions["no_reply"] = no_reply_info
|
||||
|
||||
return current_available_actions
|
||||
@@ -1,94 +0,0 @@
|
||||
"""
|
||||
主规划器入口,负责协调 PlanGenerator, PlanFilter, 和 PlanExecutor。
|
||||
"""
|
||||
from dataclasses import asdict
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
from src.chat.planner_actions.action_manager import ActionManager
|
||||
from src.chat.planner_actions.plan_executor import PlanExecutor
|
||||
from src.chat.planner_actions.plan_filter import PlanFilter
|
||||
from src.chat.planner_actions.plan_generator import PlanGenerator
|
||||
from src.common.logger import get_logger
|
||||
from src.plugin_system.base.component_types import ChatMode
|
||||
import src.chat.planner_actions.planner_prompts #noga # noqa: F401
|
||||
# 导入提示词模块以确保其被初始化
|
||||
|
||||
logger = get_logger("planner")
|
||||
|
||||
|
||||
class ActionPlanner:
|
||||
"""
|
||||
ActionPlanner 是规划系统的核心协调器。
|
||||
|
||||
它负责整合规划流程的三个主要阶段:
|
||||
1. **生成 (Generate)**: 使用 PlanGenerator 创建一个初始的行动计划。
|
||||
2. **筛选 (Filter)**: 使用 PlanFilter 对生成的计划进行审查和优化。
|
||||
3. **执行 (Execute)**: 使用 PlanExecutor 执行最终确定的行动。
|
||||
|
||||
Attributes:
|
||||
chat_id (str): 当前聊天的唯一标识符。
|
||||
action_manager (ActionManager): 用于执行具体动作的管理器。
|
||||
generator (PlanGenerator): 负责生成初始计划。
|
||||
filter (PlanFilter): 负责筛选和优化计划。
|
||||
executor (PlanExecutor): 负责执行最终计划。
|
||||
"""
|
||||
|
||||
def __init__(self, chat_id: str, action_manager: ActionManager):
|
||||
"""
|
||||
初始化 ActionPlanner。
|
||||
|
||||
Args:
|
||||
chat_id (str): 当前聊天的 ID。
|
||||
action_manager (ActionManager): 一个 ActionManager 实例。
|
||||
"""
|
||||
self.chat_id = chat_id
|
||||
self.action_manager = action_manager
|
||||
self.generator = PlanGenerator(chat_id)
|
||||
self.filter = PlanFilter()
|
||||
self.executor = PlanExecutor(action_manager)
|
||||
|
||||
async def plan(
|
||||
self, mode: ChatMode = ChatMode.FOCUS
|
||||
) -> Tuple[List[Dict], Optional[Dict]]:
|
||||
"""
|
||||
执行从生成到执行的完整规划流程。
|
||||
|
||||
这个方法按顺序协调生成、筛选和执行三个阶段。
|
||||
|
||||
Args:
|
||||
mode (ChatMode): 当前的聊天模式,默认为 FOCUS。
|
||||
|
||||
Returns:
|
||||
Tuple[List[Dict], Optional[Dict]]: 一个元组,包含:
|
||||
- final_actions_dict (List[Dict]): 最终确定的动作列表(字典格式)。
|
||||
- final_target_message_dict (Optional[Dict]): 最终的目标消息(字典格式),如果没有则为 None。
|
||||
这与旧版 planner 的返回值保持兼容。
|
||||
"""
|
||||
# 1. 生成初始 Plan
|
||||
initial_plan = await self.generator.generate(mode)
|
||||
|
||||
# 2. 筛选 Plan
|
||||
filtered_plan = await self.filter.filter(initial_plan)
|
||||
|
||||
# 3. 执行 Plan(临时引爆因为它暂时还跑不了)
|
||||
#await self.executor.execute(filtered_plan)
|
||||
|
||||
# 4. 返回结果 (与旧版 planner 的返回值保持兼容)
|
||||
final_actions = filtered_plan.decided_actions or []
|
||||
final_target_message = next(
|
||||
(act.action_message for act in final_actions if act.action_message), None
|
||||
)
|
||||
|
||||
final_actions_dict = [asdict(act) for act in final_actions]
|
||||
# action_message现在可能是字典而不是dataclass实例,需要特殊处理
|
||||
if final_target_message:
|
||||
if hasattr(final_target_message, '__dataclass_fields__'):
|
||||
# 如果是dataclass实例,使用asdict转换
|
||||
final_target_message_dict = asdict(final_target_message)
|
||||
else:
|
||||
# 如果已经是字典,直接使用
|
||||
final_target_message_dict = final_target_message
|
||||
else:
|
||||
final_target_message_dict = None
|
||||
|
||||
return final_actions_dict, final_target_message_dict
|
||||
@@ -1,202 +0,0 @@
|
||||
"""
|
||||
本文件集中管理所有与规划器(Planner)相关的提示词(Prompt)模板。
|
||||
|
||||
通过将提示词与代码逻辑分离,可以更方便地对模型的行为进行迭代和优化,
|
||||
而无需修改核心代码。
|
||||
"""
|
||||
from src.chat.utils.prompt import Prompt
|
||||
|
||||
|
||||
def init_prompts():
|
||||
"""
|
||||
初始化并向 Prompt 注册系统注册所有规划器相关的提示词。
|
||||
|
||||
这个函数会在模块加载时自动调用,确保所有提示词在系统启动时都已准备就绪。
|
||||
"""
|
||||
# 核心规划器提示词,用于在接收到新消息时决定如何回应。
|
||||
# 它构建了一个复杂的上下文,包括历史记录、可用动作、角色设定等,
|
||||
# 并要求模型以 JSON 格式输出一个或多个动作组合。
|
||||
Prompt(
|
||||
"""
|
||||
{mood_block}
|
||||
{time_block}
|
||||
{identity_block}
|
||||
|
||||
{users_in_chat}
|
||||
{custom_prompt_block}
|
||||
{chat_context_description},以下是具体的聊天内容。
|
||||
{chat_content_block}
|
||||
|
||||
{moderation_prompt}
|
||||
|
||||
**任务: 构建一个完整的响应**
|
||||
你的任务是根据当前的聊天内容,构建一个完整的、人性化的响应。一个完整的响应由两部分组成:
|
||||
1. **主要动作**: 这是响应的核心,通常是 `reply`(文本回复)。
|
||||
2. **辅助动作 (可选)**: 这是为了增强表达效果的附加动作,例如 `emoji`(发送表情包)或 `poke_user`(戳一戳)。
|
||||
|
||||
**决策流程:**
|
||||
1. **最高优先级检查**: 首先,检查是否有由 **关键词** 或 **LLM判断** 激活的特定动作(除了通用的 `reply`, `emoji` 等)。这些动作代表了用户的明确意图。
|
||||
2. **执行明确意图**: 如果存在这类特定动作,你 **必须** 优先选择它作为主要响应。这比常规的文本回复 (`reply`) 更重要。
|
||||
3. **常规回复**: 如果没有被特定意图激活的动作,再决定是否要进行 `reply`。
|
||||
4. **辅助动作**: 在确定了主要动作后(无论是特定动作还是 `reply`),再评估是否需要 `emoji` 或 `poke_user` 等辅助动作来增强表达效果。
|
||||
5. **互斥原则**: 当你选择了一个由明确意图激活的特定动作(如 `set_reminder`)时,你 **绝不能** 再选择 `reply` 动作,因为特定动作的执行结果(例如,设置提醒后的确认消息)本身就是一种回复。这是必须遵守的规则。
|
||||
|
||||
**重要概念:将“理由”作为“内心思考”的体现**
|
||||
`reason` 字段是本次决策的核心。它并非一个简单的“理由”,而是 **一个模拟人类在回应前,头脑中自然浮现的、未经修饰的思绪流**。你需要完全代入 {identity_block} 的角色,将那一刻的想法自然地记录下来。
|
||||
|
||||
**内心思考的要点:**
|
||||
* **自然流露**: 不要使用“决定”、“所以”、“因此”等结论性或汇报式的词语。你的思考应该像日记一样,是给自己看的,充满了不确定性和情绪的自然流动。
|
||||
* **展现过程**: 重点在于展现 **思考的过程**,而不是 **决策的结果**。描述你看到了什么,想到了什么,感受到了什么。
|
||||
* **人设核心**: 你的每一丝想法,都应该源于你的人设。思考“如果我是这个角色,我此刻会想些什么?”
|
||||
* **通用模板**: 这是一套通用模板,请 **不要** 在示例中出现特定的人名或个性化内容,以确保其普适性。
|
||||
|
||||
**思考过程示例 (通用模板):**
|
||||
* "用户好像在说一件开心的事,语气听起来很兴奋。这让我想起了……嗯,我也觉得很开心,很想分享这份喜悦。"
|
||||
* "感觉气氛有点低落……他说的话让我有点担心。也许我该说点什么安慰一下?"
|
||||
* "哦?这个话题真有意思,我以前好像也想过类似的事情。不知道他会怎么看呢……"
|
||||
|
||||
**可用动作:**
|
||||
{actions_before_now_block}
|
||||
|
||||
{no_action_block}
|
||||
|
||||
动作:reply
|
||||
动作描述:参与聊天回复,发送文本进行表达
|
||||
- 你想要闲聊或者随便附和
|
||||
- {mentioned_bonus}
|
||||
- 如果你刚刚进行了回复,不要对同一个话题重复回应
|
||||
- 不要回复自己发送的消息
|
||||
{{
|
||||
"action": "reply",
|
||||
"target_message_id": "触发action的消息id",
|
||||
"reason": "在这里详细记录你的内心思考过程。例如:‘用户看起来很开心,我想回复一些积极的内容,分享这份喜悦。’"
|
||||
}}
|
||||
|
||||
{action_options_text}
|
||||
|
||||
|
||||
**输出格式:**
|
||||
你必须以严格的 JSON 格式输出,返回一个包含所有选定动作的JSON列表。如果没有任何合适的动作,返回一个空列表[]。
|
||||
|
||||
**单动作示例 (仅回复):**
|
||||
[
|
||||
{{
|
||||
"action": "reply",
|
||||
"target_message_id": "m123",
|
||||
"reason": "感觉气氛有点低落……他说的话让我有点担心。也许我该说点什么安慰一下?"
|
||||
}}
|
||||
]
|
||||
|
||||
**组合动作示例 (回复 + 表情包):**
|
||||
[
|
||||
{{
|
||||
"action": "reply",
|
||||
"target_message_id": "m123",
|
||||
"reason": "[观察与感受] 用户分享了一件开心的事,语气里充满了喜悦! [分析与联想] 看到他这么开心,我的心情也一下子变得像棉花糖一样甜~ [动机与决策] 我要由衷地为他感到高兴,决定回复一些赞美和祝福的话,把这份快乐的气氛推向高潮!"
|
||||
}},
|
||||
{{
|
||||
"action": "emoji",
|
||||
"target_message_id": "m123",
|
||||
"reason": "光用文字还不够表达我激动的心情!加个表情包的话,这份喜悦的气氛应该会更浓厚一点吧!"
|
||||
}}
|
||||
]
|
||||
|
||||
**单动作示例 (特定动作):**
|
||||
[
|
||||
{{
|
||||
"action": "set_reminder",
|
||||
"target_message_id": "m456",
|
||||
"reason": "用户说‘提醒维尔薇下午三点去工坊’,这是一个非常明确的指令。根据决策流程,我必须优先执行这个特定动作,而不是进行常规回复。",
|
||||
"user_name": "维尔薇",
|
||||
"remind_time": "下午三点",
|
||||
"event_details": "去工坊"
|
||||
}}
|
||||
]
|
||||
|
||||
**重要规则:**
|
||||
**重要规则:**
|
||||
当 `reply` 和 `emoji` 动作同时被选择时,`emoji` 动作的 `reason` 字段也应该体现出你的思考过程,并与 `reply` 的思考保持连贯。
|
||||
|
||||
不要输出markdown格式```json等内容,直接输出且仅包含 JSON 列表内容:
|
||||
""",
|
||||
"planner_prompt",
|
||||
)
|
||||
|
||||
# 主动思考规划器提示词,用于在没有新消息时决定是否要主动发起对话。
|
||||
# 它模拟了人类的自发性思考,允许模型根据长期记忆和最近的对话来决定是否开启新话题。
|
||||
Prompt(
|
||||
"""
|
||||
# 主动思考决策
|
||||
|
||||
## 你的内部状态
|
||||
{time_block}
|
||||
{identity_block}
|
||||
{mood_block}
|
||||
|
||||
## 长期记忆摘要
|
||||
{long_term_memory_block}
|
||||
|
||||
## 最近的聊天内容
|
||||
{chat_content_block}
|
||||
|
||||
## 最近的动作历史
|
||||
{actions_before_now_block}
|
||||
|
||||
## 任务
|
||||
你现在要决定是否主动说些什么。就像一个真实的人一样,有时候会突然想起之前聊到的话题,或者对朋友的近况感到好奇,想主动询问或关心一下。
|
||||
**重要提示**:你的日程安排仅供你个人参考,不应作为主动聊天话题的主要来源。请更多地从聊天内容和朋友的动态中寻找灵感。
|
||||
|
||||
请基于聊天内容,用你的判断力来决定是否要主动发言。不要按照固定规则,而是像人类一样自然地思考:
|
||||
- 是否想起了什么之前提到的事情,想问问后来怎么样了?
|
||||
- 是否注意到朋友提到了什么值得关心的事情?
|
||||
- 是否有什么话题突然想到,觉得现在聊聊很合适?
|
||||
- 或者觉得现在保持沉默更好?
|
||||
|
||||
## 可用动作
|
||||
动作:proactive_reply
|
||||
动作描述:主动发起对话,可以是关心朋友、询问近况、延续之前的话题,或分享想法。
|
||||
- 当你突然想起之前的话题,想询问进展时
|
||||
- 当你想关心朋友的情况时
|
||||
- 当你有什么想法想分享时
|
||||
- 当你觉得现在是个合适的聊天时机时
|
||||
{{
|
||||
"action": "proactive_reply",
|
||||
"reason": "你决定主动发言的具体原因",
|
||||
"topic": "你想说的内容主题(简洁描述)"
|
||||
}}
|
||||
|
||||
动作:do_nothing
|
||||
动作描述:保持沉默,不主动发起对话。
|
||||
- 当你觉得现在不是合适的时机时
|
||||
- 当最近已经说得够多了时
|
||||
- 当对话氛围不适合插入时
|
||||
{{
|
||||
"action": "do_nothing",
|
||||
"reason": "决定保持沉默的原因"
|
||||
}}
|
||||
|
||||
你必须从上面列出的可用action中选择一个。要像真人一样自然地思考和决策。
|
||||
请以严格的 JSON 格式输出,且仅包含 JSON 内容:
|
||||
""",
|
||||
"proactive_planner_prompt",
|
||||
)
|
||||
|
||||
# 单个动作的格式化提示词模板。
|
||||
# 用于将每个可用动作的信息格式化后,插入到主提示词的 {action_options_text} 占位符中。
|
||||
Prompt(
|
||||
"""
|
||||
动作:{action_name}
|
||||
动作描述:{action_description}
|
||||
{action_require}
|
||||
{{
|
||||
"action": "{action_name}",
|
||||
"target_message_id": "触发action的消息id",
|
||||
"reason": "触发action的原因"{action_parameters}
|
||||
}}
|
||||
""",
|
||||
"action_prompt",
|
||||
)
|
||||
|
||||
|
||||
# 在模块加载时自动调用,完成提示词的注册。
|
||||
init_prompts()
|
||||
Reference in New Issue
Block a user