Files
Mofox-Core/docs/plugins/action-components.md
2025-12-17 09:44:51 +08:00

437 lines
16 KiB
Markdown
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.

# ⚡ Action组件详解
> **🎉 新功能:更灵活的激活机制!**
> MoFox-Bot 现在支持通过 `go_activate()` 方法自定义 Action 激活逻辑!
> 详见:[Action 激活机制重构指南](./action-activation-guide.md)
## 📖 什么是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]: 两个元素的元组
- bool: 是否执行成功 (True=成功, False=失败)
- str: 执行结果的简短描述(用于日志记录)
注意:
- 使用 self.send_text() 等方法发送消息给用户
- 返回值中的描述仅用于内部日志,不会发送给用户
"""
# 发送消息给用户
await self.send_text("这是发给用户的消息")
# 返回执行结果(用于日志)
return True, "执行成功"
```
#### execute() 返回值 vs Command 返回值
⚠️ **重要Action 和 Command 的返回值不同!**
| 组件类型 | 返回值 | 说明 |
|----------|----------|------|
| **Action** | `Tuple[bool, str]` | 2个元素成功标志、日志描述 |
| **Command** | `Tuple[bool, Optional[str], bool]` | 3个元素成功标志、日志描述、拦截标志 |
```python
# Action 返回值
async def execute(self) -> Tuple[bool, str]:
await self.send_text("给用户的消息")
return True, "日志执行了XX动作" # 2个元素
# Command 返回值
async def execute(self, args: CommandArgs) -> Tuple[bool, Optional[str], bool]:
await self.send_text("给用户的消息")
return True, "日志执行了XX命令", True # 3个元素
```
---
#### associated_types: 该Action会发送的消息类型例如文本、表情等。
这部分由Adapter传递给处理器。
以 MoFox-Bot-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哈气了返回了错误的内容**。
---
## 组件信息注册说明
### 自动生成 ComponentInfo推荐
大多数情况下,你不需要手动创建 `ActionInfo` 对象。系统提供了 `get_action_info()` 方法来自动生成:
```python
# 推荐的方式 - 自动生成
class HelloAction(BaseAction):
action_name = "hello"
action_description = "问候动作"
# ... 其他配置 ...
# 在插件中注册
def get_plugin_components(self):
return [
(HelloAction.get_action_info(), HelloAction), # 自动生成 ActionInfo
]
```
### 手动创建 ActionInfo高级用法
⚠️ **重要:如果手动创建 ActionInfo必须指定 `component_type` 参数!**
当你需要自定义 `ActionInfo` 时(例如动态生成组件),必须手动指定 `component_type`
```python
from src.plugin_system import ActionInfo, ComponentType
# ❌ 错误 - 缺少 component_type
action_info = ActionInfo(
name="hello",
description="问候动作"
# 错误:会报错 "missing required argument: 'component_type'"
)
# ✅ 正确 - 必须指定 component_type
action_info = ActionInfo(
name="hello",
description="问候动作",
component_type=ComponentType.ACTION # 必须指定!
)
```
**为什么需要手动指定?**
- `get_action_info()` 方法会自动设置 `component_type`
- 但手动创建时,系统无法自动推断类型,必须明确指定
**什么时候需要手动创建?**
- 动态生成组件
- 自定义 `get_handler_info()` 方法
- 需要特殊的 ComponentInfo 配置
大多数情况下,直接使用 `get_action_info()` 即可,无需手动创建。
---
## 🎯 Action 调用的决策机制
Action采用**两层决策机制**来优化性能和决策质量:
> 设计目的在加载许多插件的时候降低LLM决策压力避免让MoFox-Bot在过多的选项中纠结。
**第一层激活控制Activation Control**
激活决定MoFox-Bot是否 **"知道"** 这个Action的存在即这个Action是否进入决策候选池。不被激活的ActionMoFox-Bot永远不会选择。
**第二层使用决策Usage Decision**
在Action被激活后使用条件决定MoFox-Bot什么时候会 **"选择"** 使用这个Action。
---
## 🆕 新的激活机制(推荐)
从现在开始,推荐使用 **`go_activate()` 方法** 来自定义 Action 的激活逻辑。这种方式更灵活、更强大!
### 快速示例
```python
class MyAction(BaseAction):
action_name = "my_action"
action_description = "我的自定义 Action"
async def go_activate(self, llm_judge_model=None) -> bool:
"""判断是否激活此 Action
注意:聊天内容会自动从实例属性中获取,无需手动传入
"""
# 关键词激活
if await self._keyword_match(["你好", "hello"]):
return True
# 或者随机 10% 概率激活
return await self._random_activation(0.1)
async def execute(self) -> tuple[bool, str]:
await self.send_text("Hello!")
return True, "发送成功"
```
**提供的工具函数:**
- `_random_activation(probability)` - 随机激活
- `_keyword_match(keywords)` - 关键词匹配(自动获取聊天内容)
- `_llm_judge_activation(judge_prompt, llm_judge_model)` - LLM 智能判断(自动获取聊天内容)
**📚 完整指南:** 查看 [Action 激活机制重构指南](./action-activation-guide.md) 了解详情和更多示例。
---
## 📜 旧的激活机制(已废弃但仍然兼容)
> ⚠️ **注意:** 以下激活类型配置方式已废弃,但仍然兼容。
> 推荐使用新的 `go_activate()` 方法来实现更灵活的激活逻辑。
### 决策参数详解 🔧
#### 第一层ActivationType 激活类型说明
| 激活类型 | 说明 | 使用场景 |
| ----------- | ---------------------------------------- | ---------------------- |
| [`NEVER`](#never-激活) | 从不激活Action对MoFox-Bot不可见 | 临时禁用某个Action |
| [`ALWAYS`](#always-激活) | 永远激活Action总是在MoFox-Bot的候选池中 | 核心功能,如回复、不回复 |
| [`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被激活后使用条件决定MoFox-Bot什么时候会"选择"使用这个Action**
这一层由以下因素综合决定:
- `action_require`使用场景描述帮助LLM判断何时选择
- `action_parameters`所需参数影响Action的可执行性
- 当前聊天上下文和MoFox-Bot的决策逻辑
---
### 决策流程示例
```python
class EmojiAction(BaseAction):
# 第一层:激活控制
activation_type = ActionActivationType.RANDOM # 随机激活
random_activation_probability = 0.1 # 10%概率激活
# 第二层:使用决策
action_require = [
"表达情绪时可以选择使用",
"增加聊天趣味性",
"不要连续发送多个表情"
]
```
**决策流程**
1. **第一层激活判断**
- 使用随机数进行决策,当`random.random() < self.random_activation_probability`MoFox-Bot才"知道"可以使用这个Action
2. **第二层使用决策**
- 即使Action被激活MoFox-Bot还会根据 `action_require` 中的条件判断是否真正选择使用
- 例如:如果刚刚已经发过表情,根据"不要连续发送多个表情"的要求MoFox-Bot可能不会选择这个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", # 消息idstr
"time": 1627545600.0, # 时间戳float
"chat_id": "abcdef123456", # 聊天IDstr
"reply_to": None, # 回复消息idstr或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_configdict或None
"is_emoji": False, # 是否为表情bool
"is_picid": False, # 是否为图片IDbool
"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`基类的定义。