feat:继续重构插件api

This commit is contained in:
SengokuCola
2025-06-10 19:16:58 +08:00
parent e5007cc8cd
commit de0bdd3766
19 changed files with 2161 additions and 682 deletions

View File

@@ -0,0 +1,27 @@
# 核心动作插件配置文件
[plugin]
name = "core_actions"
description = "系统核心动作插件"
version = "0.2"
author = "built-in"
enabled = true
[no_reply]
# 等待新消息的超时时间(秒)
waiting_timeout = 1200
[emoji]
# 表情动作配置
enabled = true
# 在Normal模式下的随机激活概率
random_probability = 0.1
# 是否启用智能表情选择
smart_selection = true
# LLM判断相关配置
[emoji.llm_judge]
# 是否启用LLM智能判断
enabled = true
# 自定义判断提示词(可选)
custom_prompt = ""

View File

@@ -0,0 +1,395 @@
"""
核心动作插件
将系统核心动作reply、no_reply、emoji转换为新插件系统格式
这是系统的内置插件,提供基础的聊天交互功能
"""
import asyncio
import re
import time
import traceback
from typing import List, Tuple, Type, Optional
# 导入新插件系统
from src.plugin_system import (
BasePlugin, register_plugin, BaseAction,
ComponentInfo, ActionInfo, ActionActivationType, ChatMode
)
# 导入依赖的系统组件
from src.common.logger_manager import get_logger
from src.chat.heart_flow.observation.observation import Observation
from src.chat.heart_flow.observation.chatting_observation import ChattingObservation
from src.chat.focus_chat.hfc_utils import create_empty_anchor_message, parse_thinking_id_to_timestamp
from src.chat.utils.timer_calculator import Timer
from src.common.database.database_model import ActionRecords
from src.config.config import global_config
logger = get_logger("core_actions")
# 常量定义
WAITING_TIME_THRESHOLD = 1200 # 等待新消息时间阈值,单位秒
class ReplyAction(BaseAction):
"""回复动作 - 参与聊天回复"""
# 激活设置
focus_activation_type = ActionActivationType.ALWAYS
normal_activation_type = ActionActivationType.NEVER
mode_enable = ChatMode.FOCUS
parallel_action = False
# 动作参数定义(旧系统格式)
action_parameters = {
"reply_to": "如果是明确回复某个人的发言请在reply_to参数中指定格式用户名:发言内容如果不是reply_to的值设为none"
}
# 动作使用场景(旧系统字段名)
action_require = [
"你想要闲聊或者随便附和",
"有人提到你",
"如果你刚刚进行了回复,不要对同一个话题重复回应"
]
# 关联类型
associated_types = ["text"]
async def execute(self) -> Tuple[bool, str]:
"""执行回复动作"""
logger.info(f"{self.log_prefix} 决定回复: {self.reasoning}")
try:
# 获取聊天观察
chatting_observation = self._get_chatting_observation()
if not chatting_observation:
return False, "未找到聊天观察"
# 处理回复目标
anchor_message = await self._resolve_reply_target(chatting_observation)
# 获取回复器服务
replyer = self.api.get_service("replyer")
if not replyer:
logger.error(f"{self.log_prefix} 未找到回复器服务")
return False, "回复器服务不可用"
# 执行回复
success, reply_set = await replyer.deal_reply(
cycle_timers=self.cycle_timers,
action_data=self.action_data,
anchor_message=anchor_message,
reasoning=self.reasoning,
thinking_id=self.thinking_id,
)
# 构建回复文本
reply_text = self._build_reply_text(reply_set)
# 存储动作记录
await self.api.store_action_info(
action_build_into_prompt=False,
action_prompt_display=reply_text,
action_done=True,
thinking_id=self.thinking_id,
action_data=self.action_data
)
return success, reply_text
except Exception as e:
logger.error(f"{self.log_prefix} 回复动作执行失败: {e}")
return False, f"回复失败: {str(e)}"
def _get_chatting_observation(self) -> Optional[ChattingObservation]:
"""获取聊天观察对象"""
observations = self.api.get_service("observations") or []
for obs in observations:
if isinstance(obs, ChattingObservation):
return obs
return None
async def _resolve_reply_target(self, chatting_observation: ChattingObservation):
"""解析回复目标消息"""
reply_to = self.action_data.get("reply_to", "none")
if ":" in reply_to or "" in reply_to:
# 解析回复目标格式:用户名:消息内容
parts = re.split(pattern=r"[:]", string=reply_to, maxsplit=1)
if len(parts) == 2:
target = parts[1].strip()
anchor_message = chatting_observation.search_message_by_text(target)
if anchor_message:
chat_stream = self.api.get_service("chat_stream")
if chat_stream:
anchor_message.update_chat_stream(chat_stream)
return anchor_message
# 创建空锚点消息
logger.info(f"{self.log_prefix} 未找到锚点消息,创建占位符")
chat_stream = self.api.get_service("chat_stream")
if chat_stream:
return await create_empty_anchor_message(
chat_stream.platform,
chat_stream.group_info,
chat_stream
)
return None
def _build_reply_text(self, reply_set) -> str:
"""构建回复文本"""
reply_text = ""
if reply_set:
for reply in reply_set:
reply_type = reply[0]
data = reply[1]
if reply_type in ["text", "emoji"]:
reply_text += data
return reply_text
class NoReplyAction(BaseAction):
"""不回复动作,继承时会等待新消息或超时"""
focus_activation_type = ActionActivationType.ALWAYS
normal_activation_type = ActionActivationType.NEVER
mode_enable = ChatMode.FOCUS
parallel_action = False
# 默认超时时间,将由插件在注册时设置
waiting_timeout = 1200
# 动作参数定义
action_parameters = {}
# 动作使用场景
action_require = [
"你连续发送了太多消息,且无人回复",
"想要暂时不回复"
]
# 关联类型
associated_types = []
async def execute(self) -> Tuple[bool, str]:
"""执行不回复动作,等待新消息或超时"""
try:
# 使用类属性中的超时时间
timeout = self.waiting_timeout
logger.info(f"{self.log_prefix} 选择不回复,等待新消息中... (超时: {timeout}秒)")
# 等待新消息或达到时间上限
return await self.api.wait_for_new_message(timeout)
except Exception as e:
logger.error(f"{self.log_prefix} 不回复动作执行失败: {e}")
return False, f"不回复动作执行失败: {e}"
class EmojiAction(BaseAction):
"""表情动作 - 发送表情包"""
# 激活设置
focus_activation_type = ActionActivationType.LLM_JUDGE
normal_activation_type = ActionActivationType.RANDOM
mode_enable = ChatMode.ALL
parallel_action = True
random_activation_probability = 0.1 # 默认值,可通过配置覆盖
# LLM判断提示词
llm_judge_prompt = """
判定是否需要使用表情动作的条件:
1. 用户明确要求使用表情包
2. 这是一个适合表达强烈情绪的场合
3. 不要发送太多表情包,如果你已经发送过多个表情包则回答""
请回答""""
"""
# 动作参数定义
action_parameters = {
"description": "文字描述你想要发送的表情包内容"
}
# 动作使用场景
action_require = [
"表达情绪时可以选择使用",
"重点:不要连续发,如果你已经发过[表情包],就不要选择此动作"
]
# 关联类型
associated_types = ["emoji"]
async def execute(self) -> Tuple[bool, str]:
"""执行表情动作"""
logger.info(f"{self.log_prefix} 决定发送表情")
try:
# 创建空锚点消息
anchor_message = await self._create_anchor_message()
if not anchor_message:
return False, "无法创建锚点消息"
# 获取回复器服务
replyer = self.api.get_service("replyer")
if not replyer:
logger.error(f"{self.log_prefix} 未找到回复器服务")
return False, "回复器服务不可用"
# 执行表情处理
success, reply_set = await replyer.deal_emoji(
cycle_timers=self.cycle_timers,
action_data=self.action_data,
anchor_message=anchor_message,
thinking_id=self.thinking_id,
)
# 构建回复文本
reply_text = self._build_reply_text(reply_set)
return success, reply_text
except Exception as e:
logger.error(f"{self.log_prefix} 表情动作执行失败: {e}")
return False, f"表情发送失败: {str(e)}"
async def _create_anchor_message(self):
"""创建锚点消息"""
chat_stream = self.api.get_service("chat_stream")
if chat_stream:
logger.info(f"{self.log_prefix} 为表情包创建占位符")
return await create_empty_anchor_message(
chat_stream.platform,
chat_stream.group_info,
chat_stream
)
return None
def _build_reply_text(self, reply_set) -> str:
"""构建回复文本"""
reply_text = ""
if reply_set:
for reply in reply_set:
reply_type = reply[0]
data = reply[1]
if reply_type in ["text", "emoji"]:
reply_text += data
return reply_text
class ExitFocusChatAction(BaseAction):
"""退出专注聊天动作 - 从专注模式切换到普通模式"""
# 激活设置
focus_activation_type = ActionActivationType.LLM_JUDGE
normal_activation_type = ActionActivationType.NEVER
mode_enable = ChatMode.FOCUS
parallel_action = False
# LLM判断提示词
llm_judge_prompt = """
判定是否需要退出专注聊天的条件:
1. 很长时间没有回复,应该退出专注聊天
2. 当前内容不需要持续专注关注
3. 聊天内容已经完成,话题结束
请回答""""
"""
# 动作参数定义
action_parameters = {}
# 动作使用场景
action_require = [
"很长时间没有回复,你决定退出专注聊天",
"当前内容不需要持续专注关注,你决定退出专注聊天",
"聊天内容已经完成,你决定退出专注聊天"
]
# 关联类型
associated_types = []
async def execute(self) -> Tuple[bool, str]:
"""执行退出专注聊天动作"""
logger.info(f"{self.log_prefix} 决定退出专注聊天: {self.reasoning}")
try:
# 转换状态 - 这里返回特殊的命令标识
status_message = ""
# 通过返回值中的特殊标识来通知系统执行状态切换
# 系统会识别这个返回值并执行相应的状态切换逻辑
self._mark_state_change()
return True, status_message
except Exception as e:
logger.error(f"{self.log_prefix} 退出专注聊天动作执行失败: {e}")
return False, f"退出专注聊天失败: {str(e)}"
def _mark_state_change(self):
"""标记状态切换请求"""
# 通过action_data传递状态切换命令
self.action_data["_system_command"] = "stop_focus_chat"
logger.debug(f"{self.log_prefix} 已标记状态切换命令: stop_focus_chat")
@register_plugin
class CoreActionsPlugin(BasePlugin):
"""核心动作插件
系统内置插件,提供基础的聊天交互功能:
- Reply: 回复动作
- NoReply: 不回复动作
- Emoji: 表情动作
"""
# 插件基本信息
plugin_name = "core_actions"
plugin_description = "系统核心动作插件,提供基础聊天交互功能"
plugin_version = "1.0.0"
plugin_author = "MaiBot团队"
enable_plugin = True
config_file_name = "config.toml"
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
"""返回插件包含的组件列表"""
# 从配置获取表情动作的随机概率
emoji_chance = self.get_config("emoji.random_probability", 0.1)
# 动态设置EmojiAction的随机概率
EmojiAction.random_activation_probability = emoji_chance
# 从配置获取不回复动作的超时时间
no_reply_timeout = self.get_config("no_reply.waiting_timeout", 1200)
# 动态设置NoReplyAction的超时时间
NoReplyAction.waiting_timeout = no_reply_timeout
return [
# 回复动作
(ReplyAction.get_action_info(
name="reply",
description="参与聊天回复,处理文本和表情的发送"
), ReplyAction),
# 不回复动作
(NoReplyAction.get_action_info(
name="no_reply",
description="暂时不回复消息,等待新消息或超时"
), NoReplyAction),
# 表情动作
(EmojiAction.get_action_info(
name="emoji",
description="发送表情包辅助表达情绪"
), EmojiAction),
# 退出专注聊天动作
(ExitFocusChatAction.get_action_info(
name="exit_focus_chat",
description="退出专注聊天,从专注模式切换到普通模式"
), ExitFocusChatAction)
]

View File

@@ -40,13 +40,12 @@ class HelloAction(BaseAction):
async def execute(self) -> Tuple[bool, str]:
"""执行问候动作"""
username = self.action_data.get("username", "朋友")
# 使用配置文件中的问候消息
plugin_instance = SimplePlugin()
greeting_template = plugin_instance.get_config("hello_action.greeting_message", "你好,{username}")
enable_emoji = plugin_instance.get_config("hello_action.enable_emoji", True)
enable_llm = plugin_instance.get_config("hello_action.enable_llm_greeting", False)
# 使用默认配置值(避免创建新插件实例)
greeting_template = "你好,{username}"
enable_emoji = True
enable_llm = False
# 如果启用LLM生成个性化问候
if enable_llm:
try:
@@ -117,12 +116,11 @@ class StatusCommand(BaseCommand):
"""执行状态查询命令"""
# 获取匹配的参数
query_type = self.matched_groups.get("type", "系统")
# 从配置文件获取设置
plugin_instance = SimplePlugin()
show_detailed = plugin_instance.get_config("status_command.show_detailed_info", True)
allowed_types = plugin_instance.get_config("status_command.allowed_types", ["系统", "插件"])
# 使用默认配置值(避免创建新插件实例)
show_detailed = True
allowed_types = ["系统", "插件"]
if query_type not in allowed_types:
response = f"不支持的查询类型: {query_type}\n支持的类型: {', '.join(allowed_types)}"
elif show_detailed: