# ⚡ Action组件详解 ## 📖 什么是Action Action是给MoFox_Bot在回复之外提供额外功能的智能组件,**由MoFox_Bot的决策系统自主选择是否使用**,具有随机性和拟人化的调用特点。Action不是直接响应用户命令,而是让MoFox_Bot根据聊天情境智能地选择合适的动作,使其行为更加自然和真实。 ### Action的特点 - 🧠 **智能激活**:MoFox_Bot根据多种条件智能判断是否使用 - 🎲 **可随机性**:可以使用随机数激活,增加行为的不可预测性,更接近真人交流 - 🤖 **拟人化**:让MoFox_Bot的回应更自然、更有个性 - 🔄 **情境感知**:基于聊天上下文做出合适的反应 --- ## 🎯 Action组件的基本结构 首先,所有的Action都应该继承`BaseAction`类。 其次,每个Action组件都应该实现以下基本信息: ```python class ExampleAction(BaseAction): action_name = "example_action" # 动作的唯一标识符 action_description = "这是一个示例动作" # 动作描述 activation_type = ActionActivationType.ALWAYS # 这里以 ALWAYS 为例 mode_enable = ChatMode.ALL # 一般取ALL,表示在所有聊天模式下都可用 associated_types = ["text", "emoji", ...] # 关联类型 parallel_action = False # 是否允许与其他Action并行执行 action_parameters = {"param1": "参数1的说明", "param2": "参数2的说明", ...} # Action使用场景描述 - 帮助LLM判断何时"选择"使用 action_require = ["使用场景描述1", "使用场景描述2", ...] async def execute(self) -> Tuple[bool, str]: """ 执行Action的主要逻辑 Returns: Tuple[bool, str]: (是否成功, 执行结果描述) """ # ---- 执行动作的逻辑 ---- return True, "执行成功" ``` #### associated_types: 该Action会发送的消息类型,例如文本、表情等。 这部分由Adapter传递给处理器。 以 MaiBot-Napcat-Adapter 为例,可选项目如下: | 类型 | 说明 | 格式 | | --- | --- | --- | | text | 文本消息 | str | | emoji | 表情消息 | str: 表情包的无头base64| | image | 图片消息 | str: 图片的无头base64 | | reply | 回复消息 | str: 回复的消息ID | | voice | 语音消息 | str: wav格式语音的无头base64 | | command | 命令消息 | 参见Adapter文档 | | voiceurl | 语音URL消息 | str: wav格式语音的URL | | music | 音乐消息 | str: 这首歌在网易云音乐的音乐id | | videourl | 视频URL消息 | str: 视频的URL | | file | 文件消息 | str: 文件的路径 | **请知悉,对于不同的处理器,其支持的消息类型可能会有所不同。在开发时请注意。** #### action_parameters: 该Action的参数说明。 这是一个字典,键为参数名,值为参数说明。这个字段可以帮助LLM理解如何使用这个Action,并由LLM返回对应的参数,最后传递到 Action 的 **`action_data`** 属性中。其格式与你定义的格式完全相同 **(除非LLM哈气了,返回了错误的内容)**。 --- ## 🎯 Action 调用的决策机制 Action采用**两层决策机制**来优化性能和决策质量: > 设计目的:在加载许多插件的时候降低LLM决策压力,避免让麦麦在过多的选项中纠结。 **第一层:激活控制(Activation Control)** 激活决定麦麦是否 **“知道”** 这个Action的存在,即这个Action是否进入决策候选池。不被激活的Action麦麦永远不会选择。 **第二层:使用决策(Usage Decision)** 在Action被激活后,使用条件决定麦麦什么时候会 **“选择”** 使用这个Action。 ### 决策参数详解 🔧 #### 第一层:ActivationType 激活类型说明 | 激活类型 | 说明 | 使用场景 | | ----------- | ---------------------------------------- | ---------------------- | | [`NEVER`](#never-激活) | 从不激活,Action对麦麦不可见 | 临时禁用某个Action | | [`ALWAYS`](#always-激活) | 永远激活,Action总是在麦麦的候选池中 | 核心功能,如回复、不回复 | | [`LLM_JUDGE`](#llm_judge-激活) | 通过LLM智能判断当前情境是否需要激活此Action | 需要智能判断的复杂场景 | | `RANDOM` | 基于随机概率决定是否激活 | 增加行为随机性的功能 | | `KEYWORD` | 当检测到特定关键词时激活 | 明确触发条件的功能 | #### `NEVER` 激活 `ActionActivationType.NEVER` 会使得 Action 永远不会被激活 ```python class DisabledAction(BaseAction): activation_type = ActionActivationType.NEVER # 永远不激活 async def execute(self) -> Tuple[bool, str]: # 这个Action永远不会被执行 return False, "这个Action被禁用" ``` #### `ALWAYS` 激活 `ActionActivationType.ALWAYS` 会使得 Action 永远会被激活,即一直在 Action 候选池中 这种激活方式常用于核心功能,如回复或不回复。 ```python class AlwaysActivatedAction(BaseAction): activation_type = ActionActivationType.ALWAYS # 永远激活 async def execute(self) -> Tuple[bool, str]: # 执行核心功能 return True, "执行了核心功能" ``` #### `LLM_JUDGE` 激活 `ActionActivationType.LLM_JUDGE`会使得这个 Action 根据 LLM 的判断来决定是否加入候选池。 而 LLM 的判断是基于代码中预设的`llm_judge_prompt`和自动提供的聊天上下文进行的。 因此使用此种方法需要实现`llm_judge_prompt`属性。 ```python class LLMJudgedAction(BaseAction): activation_type = ActionActivationType.LLM_JUDGE # 通过LLM判断激活 # LLM判断提示词 llm_judge_prompt = ( "判定是否需要使用这个动作的条件:\n" "1. 用户希望调用XXX这个动作\n" "...\n" "请回答\"是\"或\"否\"。\n" ) async def execute(self) -> Tuple[bool, str]: # 根据LLM判断是否执行 return True, "执行了LLM判断功能" ``` #### `RANDOM` 激活 `ActionActivationType.RANDOM`会使得这个 Action 根据随机概率决定是否加入候选池。 概率则由代码中的`random_activation_probability`控制。在内部实现中我们使用了`random.random()`来生成一个0到1之间的随机数,并与这个概率进行比较。 因此使用这个方法需要实现`random_activation_probability`属性。 ```python class SurpriseAction(BaseAction): activation_type = ActionActivationType.RANDOM # 基于随机概率激活 # 随机激活概率 random_activation_probability = 0.1 # 10%概率激活 async def execute(self) -> Tuple[bool, str]: # 执行惊喜动作 return True, "发送了惊喜内容" ``` #### `KEYWORD` 激活 `ActionActivationType.KEYWORD`会使得这个 Action 在检测到特定关键词时激活。 关键词由代码中的`activation_keywords`定义,而`keyword_case_sensitive`则控制关键词匹配时是否区分大小写。在内部实现中,我们使用了`in`操作符来检查消息内容是否包含这些关键词。 因此,使用此种方法需要实现`activation_keywords`和`keyword_case_sensitive`属性。 ```python class GreetingAction(BaseAction): activation_type = ActionActivationType.KEYWORD # 关键词激活 activation_keywords = ["你好", "hello", "hi", "嗨"] # 关键词配置 keyword_case_sensitive = False # 不区分大小写 async def execute(self) -> Tuple[bool, str]: # 执行问候逻辑 return True, "发送了问候" ``` 一个完整的使用`ActionActivationType.KEYWORD`的例子请参考`plugins/hello_world_plugin`中的`ByeAction`。 #### 第二层:使用决策 **在Action被激活后,使用条件决定麦麦什么时候会"选择"使用这个Action**。 这一层由以下因素综合决定: - `action_require`:使用场景描述,帮助LLM判断何时选择 - `action_parameters`:所需参数,影响Action的可执行性 - 当前聊天上下文和麦麦的决策逻辑 --- ### 决策流程示例 ```python class EmojiAction(BaseAction): # 第一层:激活控制 activation_type = ActionActivationType.RANDOM # 随机激活 random_activation_probability = 0.1 # 10%概率激活 # 第二层:使用决策 action_require = [ "表达情绪时可以选择使用", "增加聊天趣味性", "不要连续发送多个表情" ] ``` **决策流程**: 1. **第一层激活判断**: - 使用随机数进行决策,当`random.random() < self.random_activation_probability`时,麦麦才"知道"可以使用这个Action 2. **第二层使用决策**: - 即使Action被激活,麦麦还会根据 `action_require` 中的条件判断是否真正选择使用 - 例如:如果刚刚已经发过表情,根据"不要连续发送多个表情"的要求,麦麦可能不会选择这个Action --- ## Action 内置属性说明 ```python class BaseAction: def __init__(self): # 消息相关属性 self.log_prefix: str # 日志前缀 self.group_id: str # 群组ID self.group_name: str # 群组名称 self.user_id: str # 用户ID self.user_nickname: str # 用户昵称 self.platform: str # 平台类型 (qq, telegram等) self.chat_id: str # 聊天ID self.chat_stream: ChatStream # 聊天流对象 self.is_group: bool # 是否群聊 # 消息体 self.action_message: dict # 消息数据 # Action相关属性 self.action_data: dict # Action执行时的数据 self.thinking_id: str # 思考ID ``` action_message为一个字典,包含的键值对如下(省略了不必要的键值对) ```python { "message_id": "1234567890", # 消息id,str "time": 1627545600.0, # 时间戳,float "chat_id": "abcdef123456", # 聊天ID,str "reply_to": None, # 回复消息id,str或None "interest_value": 0.85, # 兴趣值,float "is_mentioned": True, # 是否被提及,bool "chat_info_last_active_time": 1627548600.0, # 最后活跃时间,float "processed_plain_text": None, # 处理后的文本,str或None "additional_config": None, # Adapter传来的additional_config,dict或None "is_emoji": False, # 是否为表情,bool "is_picid": False, # 是否为图片ID,bool "is_command": False # 是否为命令,bool } ``` 部分值的格式请自行查询数据库。 --- ## Action 内置方法说明 ```python class BaseAction: def get_config(self, key: str, default=None): """获取插件配置值,使用嵌套键访问""" async def wait_for_new_message(self, timeout: int = 1200) -> Tuple[bool, str]: """等待新消息或超时""" async def send_text(self, content: str, reply_to: str = "", reply_to_platform_id: str = "", typing: bool = False) -> bool: """发送文本消息""" async def send_emoji(self, emoji_base64: str) -> bool: """发送表情包""" async def send_image(self, image_base64: str) -> bool: """发送图片""" async def send_custom(self, message_type: str, content: str, typing: bool = False, reply_to: str = "") -> bool: """发送自定义类型消息""" async def store_action_info(self, action_build_into_prompt: bool = False, action_prompt_display: str = "", action_done: bool = True) -> None: """存储动作信息到数据库""" async def send_command(self, command_name: str, args: Optional[dict] = None, display_message: str = "", storage_message: bool = True) -> bool: """发送命令消息""" ``` 具体参数与用法参见`BaseAction`基类的定义。