feat:继续重构插件api
This commit is contained in:
752
docs/plugin_detailed_guide.md
Normal file
752
docs/plugin_detailed_guide.md
Normal file
@@ -0,0 +1,752 @@
|
||||
# MaiBot 插件详细解析指南
|
||||
|
||||
## 📋 目录
|
||||
|
||||
1. [插件基类详解](#插件基类详解)
|
||||
2. [Action组件深入](#action组件深入)
|
||||
3. [Command组件深入](#command组件深入)
|
||||
4. [API系统详解](#api系统详解)
|
||||
5. [配置系统](#配置系统)
|
||||
6. [注册中心机制](#注册中心机制)
|
||||
7. [高级功能](#高级功能)
|
||||
8. [最佳实践](#最佳实践)
|
||||
|
||||
---
|
||||
|
||||
## 插件基类详解
|
||||
|
||||
### BasePlugin 核心功能
|
||||
|
||||
`BasePlugin` 是所有插件的基类,提供插件的生命周期管理和基础功能。
|
||||
|
||||
```python
|
||||
@register_plugin
|
||||
class MyPlugin(BasePlugin):
|
||||
# 必需的基本信息
|
||||
plugin_name = "my_plugin" # 插件唯一标识
|
||||
plugin_description = "插件功能描述" # 简短描述
|
||||
plugin_version = "1.0.0" # 版本号
|
||||
plugin_author = "作者名称" # 作者信息
|
||||
enable_plugin = True # 是否启用
|
||||
|
||||
# 可选配置
|
||||
dependencies = ["other_plugin"] # 依赖的其他插件
|
||||
config_file_name = "config.toml" # 配置文件名
|
||||
|
||||
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
|
||||
"""返回插件包含的组件列表(必须实现)"""
|
||||
return [
|
||||
(MyAction.get_action_info(), MyAction),
|
||||
(MyCommand.get_command_info(), MyCommand)
|
||||
]
|
||||
```
|
||||
|
||||
### 插件生命周期
|
||||
|
||||
1. **加载阶段** - 插件管理器扫描插件目录
|
||||
2. **实例化阶段** - 创建插件实例,传入 `plugin_dir`
|
||||
3. **配置加载** - 自动加载配置文件(如果指定)
|
||||
4. **依赖检查** - 验证依赖的插件是否存在
|
||||
5. **组件注册** - 注册所有组件到注册中心
|
||||
6. **运行阶段** - 组件响应用户交互
|
||||
|
||||
### 配置访问
|
||||
|
||||
```python
|
||||
class MyPlugin(BasePlugin):
|
||||
config_file_name = "config.toml"
|
||||
|
||||
def some_method(self):
|
||||
# 获取配置值
|
||||
max_retry = self.get_config("network.max_retry", 3)
|
||||
api_key = self.get_config("api.key", "")
|
||||
|
||||
# 配置支持嵌套结构
|
||||
db_config = self.get_config("database", {})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Action组件深入
|
||||
|
||||
### Action激活机制
|
||||
|
||||
Action组件支持多种激活方式,可以组合使用:
|
||||
|
||||
#### 1. 关键词激活
|
||||
|
||||
```python
|
||||
class KeywordAction(BaseAction):
|
||||
focus_activation_type = ActionActivationType.KEYWORD
|
||||
normal_activation_type = ActionActivationType.KEYWORD
|
||||
activation_keywords = ["天气", "weather", "温度"]
|
||||
keyword_case_sensitive = False # 是否区分大小写
|
||||
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
# 获取触发的关键词
|
||||
triggered_keyword = self.action_data.get("triggered_keyword")
|
||||
return True, f"检测到关键词: {triggered_keyword}"
|
||||
```
|
||||
|
||||
#### 2. LLM智能判断
|
||||
|
||||
```python
|
||||
class SmartAction(BaseAction):
|
||||
focus_activation_type = ActionActivationType.LLM_JUDGE
|
||||
llm_judge_prompt = """
|
||||
判断用户消息是否表达了情感支持的需求。
|
||||
如果用户显得沮丧、焦虑或需要安慰,返回True,否则返回False。
|
||||
"""
|
||||
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
# LLM判断为需要情感支持
|
||||
user_emotion = self.action_data.get("emotion", "neutral")
|
||||
return True, "我理解你现在的感受,有什么可以帮助你的吗? 🤗"
|
||||
```
|
||||
|
||||
#### 3. 随机激活
|
||||
|
||||
```python
|
||||
class RandomAction(BaseAction):
|
||||
focus_activation_type = ActionActivationType.RANDOM
|
||||
random_activation_probability = 0.1 # 10%概率触发
|
||||
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
import random
|
||||
responses = ["今天天气不错呢!", "你知道吗,刚才想到一个有趣的事...", "随便聊聊吧!"]
|
||||
return True, random.choice(responses)
|
||||
```
|
||||
|
||||
#### 4. 始终激活
|
||||
|
||||
```python
|
||||
class AlwaysAction(BaseAction):
|
||||
focus_activation_type = ActionActivationType.ALWAYS
|
||||
parallel_action = True # 允许与其他Action并行
|
||||
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
# 记录所有消息到数据库
|
||||
await self.api.store_user_data("last_message", self.action_data.get("message"))
|
||||
return True, "" # 静默执行,不发送回复
|
||||
```
|
||||
|
||||
### Action数据访问
|
||||
|
||||
```python
|
||||
class DataAction(BaseAction):
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
# 访问消息数据
|
||||
message = self.action_data.get("message", "")
|
||||
username = self.action_data.get("username", "用户")
|
||||
user_id = self.action_data.get("user_id", "")
|
||||
platform = self.action_data.get("platform", "")
|
||||
|
||||
# 访问系统数据
|
||||
thinking_id = self.thinking_id
|
||||
reasoning = self.reasoning # 执行该动作的理由
|
||||
|
||||
# 访问计时器信息
|
||||
timers = self.cycle_timers
|
||||
|
||||
return True, f"处理来自 {platform} 的用户 {username} 的消息"
|
||||
```
|
||||
|
||||
### 聊天模式支持
|
||||
|
||||
```python
|
||||
class ModeAwareAction(BaseAction):
|
||||
mode_enable = ChatMode.PRIVATE # 只在私聊中启用
|
||||
# mode_enable = ChatMode.GROUP # 只在群聊中启用
|
||||
# mode_enable = ChatMode.ALL # 在所有模式中启用
|
||||
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
current_mode = self.action_data.get("chat_mode", ChatMode.PRIVATE)
|
||||
return True, f"当前聊天模式: {current_mode.name}"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Command组件深入
|
||||
|
||||
### 高级正则表达式模式
|
||||
|
||||
Command使用正则表达式进行精确匹配,支持复杂的参数提取:
|
||||
|
||||
#### 1. 基础命令
|
||||
|
||||
```python
|
||||
class BasicCommand(BaseCommand):
|
||||
command_pattern = r"^/hello$"
|
||||
command_help = "简单的问候命令"
|
||||
|
||||
async def execute(self) -> Tuple[bool, Optional[str]]:
|
||||
await self.send_reply("Hello!")
|
||||
return True, "Hello!"
|
||||
```
|
||||
|
||||
#### 2. 带参数命令
|
||||
|
||||
```python
|
||||
class ParameterCommand(BaseCommand):
|
||||
command_pattern = r"^/user\s+(?P<action>add|remove|list)\s+(?P<name>\w+)?$"
|
||||
command_help = "用户管理命令,用法:/user <add|remove|list> [用户名]"
|
||||
command_examples = ["/user add alice", "/user remove bob", "/user list"]
|
||||
|
||||
async def execute(self) -> Tuple[bool, Optional[str]]:
|
||||
action = self.matched_groups.get("action")
|
||||
name = self.matched_groups.get("name")
|
||||
|
||||
if action == "add" and name:
|
||||
# 添加用户逻辑
|
||||
await self.api.store_user_data(f"user_{name}", {"name": name, "created": self.api.get_current_time()})
|
||||
response = f"用户 {name} 已添加"
|
||||
elif action == "remove" and name:
|
||||
# 删除用户逻辑
|
||||
await self.api.delete_user_data(f"user_{name}")
|
||||
response = f"用户 {name} 已删除"
|
||||
elif action == "list":
|
||||
# 列出用户逻辑
|
||||
users = await self.api.get_user_data_pattern("user_*")
|
||||
response = f"用户列表: {', '.join(users.keys())}"
|
||||
else:
|
||||
response = "参数错误,请查看帮助信息"
|
||||
|
||||
await self.send_reply(response)
|
||||
return True, response
|
||||
```
|
||||
|
||||
#### 3. 复杂参数解析
|
||||
|
||||
```python
|
||||
class AdvancedCommand(BaseCommand):
|
||||
command_pattern = r"^/remind\s+(?P<time>\d{1,2}:\d{2})\s+(?P<date>\d{4}-\d{2}-\d{2})?\s+(?P<message>.+)$"
|
||||
command_help = "设置提醒,用法:/remind <时间> [日期] <消息>"
|
||||
command_examples = [
|
||||
"/remind 14:30 买牛奶",
|
||||
"/remind 09:00 2024-12-25 圣诞节快乐"
|
||||
]
|
||||
|
||||
async def execute(self) -> Tuple[bool, Optional[str]]:
|
||||
time_str = self.matched_groups.get("time")
|
||||
date_str = self.matched_groups.get("date")
|
||||
message = self.matched_groups.get("message")
|
||||
|
||||
# 解析时间
|
||||
from datetime import datetime, date
|
||||
try:
|
||||
hour, minute = map(int, time_str.split(":"))
|
||||
if date_str:
|
||||
reminder_date = datetime.strptime(date_str, "%Y-%m-%d").date()
|
||||
else:
|
||||
reminder_date = date.today()
|
||||
|
||||
# 创建提醒
|
||||
reminder_time = datetime.combine(reminder_date, datetime.min.time().replace(hour=hour, minute=minute))
|
||||
|
||||
await self.api.store_user_data("reminder", {
|
||||
"time": reminder_time.isoformat(),
|
||||
"message": message,
|
||||
"user_id": self.api.get_current_user_id()
|
||||
})
|
||||
|
||||
response = f"已设置提醒:{reminder_time.strftime('%Y-%m-%d %H:%M')} - {message}"
|
||||
|
||||
except ValueError as e:
|
||||
response = f"时间格式错误: {e}"
|
||||
|
||||
await self.send_reply(response)
|
||||
return True, response
|
||||
```
|
||||
|
||||
### 命令权限控制
|
||||
|
||||
```python
|
||||
class AdminCommand(BaseCommand):
|
||||
command_pattern = r"^/admin\s+(?P<operation>\w+)"
|
||||
command_help = "管理员命令(需要权限)"
|
||||
|
||||
async def execute(self) -> Tuple[bool, Optional[str]]:
|
||||
# 检查用户权限
|
||||
user_id = self.api.get_current_user_id()
|
||||
user_role = await self.api.get_user_info(user_id, "role", "user")
|
||||
|
||||
if user_role != "admin":
|
||||
await self.send_reply("❌ 权限不足,需要管理员权限")
|
||||
return False, "权限不足"
|
||||
|
||||
operation = self.matched_groups.get("operation")
|
||||
# 执行管理员操作...
|
||||
|
||||
return True, f"管理员操作 {operation} 已执行"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API系统详解
|
||||
|
||||
### MessageAPI - 消息发送
|
||||
|
||||
```python
|
||||
class MessageExampleAction(BaseAction):
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
# 发送文本消息
|
||||
await self.api.send_message("text", "这是一条文本消息")
|
||||
|
||||
# 发送带格式的消息
|
||||
await self.api.send_message("text", "**粗体文本** *斜体文本*")
|
||||
|
||||
# 发送图片(如果支持)
|
||||
await self.api.send_message("image", "/path/to/image.jpg")
|
||||
|
||||
# 发送文件(如果支持)
|
||||
await self.api.send_message("file", "/path/to/document.pdf")
|
||||
|
||||
# 获取消息发送状态
|
||||
success = await self.api.send_message("text", "测试消息")
|
||||
if success:
|
||||
logger.info("消息发送成功")
|
||||
|
||||
return True, "消息发送演示完成"
|
||||
```
|
||||
|
||||
### LLMAPI - 大模型调用
|
||||
|
||||
```python
|
||||
class LLMExampleAction(BaseAction):
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
# 获取可用模型
|
||||
models = self.api.get_available_models()
|
||||
|
||||
if not models:
|
||||
return False, "没有可用的模型"
|
||||
|
||||
# 选择第一个可用模型
|
||||
model_name, model_config = next(iter(models.items()))
|
||||
|
||||
# 生成文本
|
||||
prompt = "写一首关于春天的诗"
|
||||
success, response, usage, model_used = await self.api.generate_with_model(
|
||||
prompt=prompt,
|
||||
model_config=model_config,
|
||||
max_tokens=200,
|
||||
temperature=0.7
|
||||
)
|
||||
|
||||
if success:
|
||||
logger.info(f"使用模型 {model_used} 生成了 {usage.get('total_tokens', 0)} 个token")
|
||||
return True, f"生成的诗歌:\n{response}"
|
||||
else:
|
||||
return False, f"生成失败:{response}"
|
||||
```
|
||||
|
||||
### DatabaseAPI - 数据库操作
|
||||
|
||||
```python
|
||||
class DatabaseExampleAction(BaseAction):
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
user_id = self.api.get_current_user_id()
|
||||
|
||||
# 存储用户数据
|
||||
await self.api.store_user_data("user_score", 100)
|
||||
await self.api.store_user_data("user_level", "beginner")
|
||||
|
||||
# 存储复杂数据结构
|
||||
user_profile = {
|
||||
"name": "Alice",
|
||||
"age": 25,
|
||||
"interests": ["music", "reading", "coding"],
|
||||
"settings": {
|
||||
"theme": "dark",
|
||||
"language": "zh-CN"
|
||||
}
|
||||
}
|
||||
await self.api.store_user_data("profile", user_profile)
|
||||
|
||||
# 读取数据
|
||||
score = await self.api.get_user_data("user_score", 0)
|
||||
profile = await self.api.get_user_data("profile", {})
|
||||
|
||||
# 删除数据
|
||||
await self.api.delete_user_data("old_key")
|
||||
|
||||
# 批量查询
|
||||
all_user_data = await self.api.get_user_data_pattern("user_*")
|
||||
|
||||
# 存储Action执行记录
|
||||
await self.api.store_action_info(
|
||||
action_build_into_prompt=True,
|
||||
action_prompt_display="用户查询了个人信息",
|
||||
action_done=True
|
||||
)
|
||||
|
||||
return True, f"用户数据操作完成,当前积分:{score}"
|
||||
```
|
||||
|
||||
### ConfigAPI - 配置访问
|
||||
|
||||
```python
|
||||
class ConfigExampleAction(BaseAction):
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
# 读取全局配置
|
||||
bot_name = self.api.get_global_config("bot.name", "MaiBot")
|
||||
debug_mode = self.api.get_global_config("system.debug", False)
|
||||
|
||||
# 获取用户信息
|
||||
current_user = self.api.get_current_user_id()
|
||||
platform, user_id = await self.api.get_user_id_by_person_name("Alice")
|
||||
|
||||
# 获取特定用户信息
|
||||
user_nickname = await self.api.get_person_info(current_user, "nickname", "未知用户")
|
||||
user_language = await self.api.get_person_info(current_user, "language", "zh-CN")
|
||||
|
||||
return True, f"配置信息获取完成,机器人名称:{bot_name}"
|
||||
```
|
||||
|
||||
### UtilsAPI - 工具函数
|
||||
|
||||
```python
|
||||
class UtilsExampleAction(BaseAction):
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
# 时间相关
|
||||
current_time = self.api.get_current_time()
|
||||
formatted_time = self.api.format_time(current_time, "%Y-%m-%d %H:%M:%S")
|
||||
|
||||
# ID生成
|
||||
unique_id = self.api.generate_unique_id()
|
||||
random_string = self.api.generate_random_string(length=8)
|
||||
|
||||
# 文件操作
|
||||
if self.api.file_exists("/path/to/file.txt"):
|
||||
content = self.api.read_file("/path/to/file.txt")
|
||||
self.api.write_file("/path/to/backup.txt", content)
|
||||
|
||||
# JSON处理
|
||||
data = {"key": "value", "number": 42}
|
||||
json_str = self.api.to_json(data)
|
||||
parsed_data = self.api.from_json(json_str)
|
||||
|
||||
# 安全字符串处理
|
||||
safe_filename = self.api.sanitize_filename("用户文件 (1).txt")
|
||||
|
||||
return True, f"工具函数演示完成,时间:{formatted_time}"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 配置系统
|
||||
|
||||
### TOML配置文件
|
||||
|
||||
```toml
|
||||
# config.toml
|
||||
|
||||
[plugin]
|
||||
name = "my_plugin"
|
||||
description = "插件描述"
|
||||
enabled = true
|
||||
debug = false
|
||||
|
||||
[features]
|
||||
enable_ai = true
|
||||
enable_voice = false
|
||||
max_users = 100
|
||||
|
||||
[api]
|
||||
timeout = 30
|
||||
retry_count = 3
|
||||
base_url = "https://api.example.com"
|
||||
|
||||
[database]
|
||||
cache_size = 1000
|
||||
auto_cleanup = true
|
||||
|
||||
[messages]
|
||||
welcome = "欢迎使用插件!"
|
||||
error = "操作失败,请重试"
|
||||
success = "操作成功完成"
|
||||
|
||||
[advanced]
|
||||
custom_settings = { theme = "dark", language = "zh-CN" }
|
||||
feature_flags = ["beta_feature", "experimental_ui"]
|
||||
```
|
||||
|
||||
### 配置使用示例
|
||||
|
||||
```python
|
||||
class ConfigurablePlugin(BasePlugin):
|
||||
config_file_name = "config.toml"
|
||||
|
||||
def get_plugin_components(self):
|
||||
# 根据配置决定加载哪些组件
|
||||
components = []
|
||||
|
||||
if self.get_config("features.enable_ai", False):
|
||||
components.append((AIAction.get_action_info(), AIAction))
|
||||
|
||||
if self.get_config("features.enable_voice", False):
|
||||
components.append((VoiceCommand.get_command_info(), VoiceCommand))
|
||||
|
||||
return components
|
||||
|
||||
class ConfigurableAction(BaseAction):
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
# 注意:这里不能直接创建插件实例获取配置
|
||||
# 应该通过其他方式访问配置,比如从API或全局配置中获取
|
||||
|
||||
# 使用默认值或硬编码配置
|
||||
welcome_message = "欢迎使用插件!" # 应该从配置获取
|
||||
timeout = 30 # 应该从配置获取
|
||||
|
||||
return True, welcome_message
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注册中心机制
|
||||
|
||||
### 组件查询
|
||||
|
||||
```python
|
||||
from src.plugin_system.core.component_registry import component_registry
|
||||
|
||||
# 获取所有注册的Action
|
||||
actions = component_registry.get_components_by_type(ComponentType.ACTION)
|
||||
|
||||
# 获取所有注册的Command
|
||||
commands = component_registry.get_components_by_type(ComponentType.COMMAND)
|
||||
|
||||
# 查找特定命令
|
||||
command_info = component_registry.find_command_by_text("/help")
|
||||
|
||||
# 获取插件信息
|
||||
plugin_info = component_registry.get_plugin_info("simple_plugin")
|
||||
|
||||
# 获取插件的所有组件
|
||||
plugin_components = component_registry.get_plugin_components("simple_plugin")
|
||||
```
|
||||
|
||||
### 动态组件操作
|
||||
|
||||
```python
|
||||
# 注册新组件
|
||||
component_info = ActionInfo(name="dynamic_action", ...)
|
||||
component_registry.register_component(component_info, DynamicAction)
|
||||
|
||||
# 注销组件
|
||||
component_registry.unregister_component("dynamic_action")
|
||||
|
||||
# 检查组件是否存在
|
||||
exists = component_registry.component_exists("my_action")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 高级功能
|
||||
|
||||
### 组件依赖管理
|
||||
|
||||
```python
|
||||
class DependentPlugin(BasePlugin):
|
||||
plugin_name = "dependent_plugin"
|
||||
dependencies = ["simple_plugin", "core_plugin"] # 依赖其他插件
|
||||
|
||||
def get_plugin_components(self):
|
||||
# 只有在依赖满足时才会被调用
|
||||
return [(MyAction.get_action_info(), MyAction)]
|
||||
```
|
||||
|
||||
### 动态组件创建
|
||||
|
||||
```python
|
||||
def create_dynamic_action(keyword: str, response: str):
|
||||
"""动态创建Action组件"""
|
||||
|
||||
class DynamicAction(BaseAction):
|
||||
focus_activation_type = ActionActivationType.KEYWORD
|
||||
activation_keywords = [keyword]
|
||||
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
return True, response
|
||||
|
||||
return DynamicAction
|
||||
|
||||
# 使用
|
||||
WeatherAction = create_dynamic_action("天气", "今天天气很好!")
|
||||
```
|
||||
|
||||
### 组件生命周期钩子
|
||||
|
||||
```python
|
||||
class LifecycleAction(BaseAction):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.on_initialize()
|
||||
|
||||
def on_initialize(self):
|
||||
"""组件初始化时调用"""
|
||||
logger.info("Action组件初始化")
|
||||
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
result = await self.on_execute()
|
||||
self.on_complete()
|
||||
return result
|
||||
|
||||
async def on_execute(self) -> Tuple[bool, str]:
|
||||
"""实际执行逻辑"""
|
||||
return True, "执行完成"
|
||||
|
||||
def on_complete(self):
|
||||
"""执行完成后调用"""
|
||||
logger.info("Action执行完成")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 错误处理
|
||||
|
||||
```python
|
||||
class RobustAction(BaseAction):
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
try:
|
||||
# 主要逻辑
|
||||
result = await self.process_main_logic()
|
||||
return True, result
|
||||
|
||||
except ValueError as e:
|
||||
# 参数错误
|
||||
logger.warning(f"参数错误: {e}")
|
||||
return False, "参数格式不正确"
|
||||
|
||||
except ConnectionError as e:
|
||||
# 网络错误
|
||||
logger.error(f"网络连接失败: {e}")
|
||||
return False, "网络连接异常,请稍后重试"
|
||||
|
||||
except Exception as e:
|
||||
# 未知错误
|
||||
logger.error(f"未知错误: {e}", exc_info=True)
|
||||
return False, "处理失败,请联系管理员"
|
||||
|
||||
async def process_main_logic(self):
|
||||
# 具体业务逻辑
|
||||
pass
|
||||
```
|
||||
|
||||
### 2. 性能优化
|
||||
|
||||
```python
|
||||
class OptimizedAction(BaseAction):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._cache = {} # 本地缓存
|
||||
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
cache_key = self.generate_cache_key()
|
||||
|
||||
# 检查缓存
|
||||
if cache_key in self._cache:
|
||||
logger.debug("使用缓存结果")
|
||||
return True, self._cache[cache_key]
|
||||
|
||||
# 计算结果
|
||||
result = await self.compute_result()
|
||||
|
||||
# 存储到缓存
|
||||
self._cache[cache_key] = result
|
||||
|
||||
return True, result
|
||||
|
||||
def generate_cache_key(self) -> str:
|
||||
# 根据输入生成缓存键
|
||||
message = self.action_data.get("message", "")
|
||||
return f"result_{hash(message)}"
|
||||
```
|
||||
|
||||
### 3. 资源管理
|
||||
|
||||
```python
|
||||
class ResourceAction(BaseAction):
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
# 使用上下文管理器确保资源正确释放
|
||||
async with self.api.get_resource_manager() as resources:
|
||||
# 获取资源
|
||||
db_connection = await resources.get_database()
|
||||
file_handle = await resources.get_file("data.txt")
|
||||
|
||||
# 使用资源进行处理
|
||||
result = await self.process_with_resources(db_connection, file_handle)
|
||||
|
||||
return True, result
|
||||
# 资源会自动释放
|
||||
```
|
||||
|
||||
### 4. 测试友好设计
|
||||
|
||||
```python
|
||||
class TestableAction(BaseAction):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.dependencies = self.create_dependencies()
|
||||
|
||||
def create_dependencies(self):
|
||||
"""创建依赖对象,便于测试时注入mock"""
|
||||
return {
|
||||
'weather_service': WeatherService(),
|
||||
'user_service': UserService()
|
||||
}
|
||||
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
weather = await self.dependencies['weather_service'].get_weather()
|
||||
user = await self.dependencies['user_service'].get_current_user()
|
||||
|
||||
return True, f"今天{weather},{user}!"
|
||||
```
|
||||
|
||||
### 5. 日志记录
|
||||
|
||||
```python
|
||||
class LoggedAction(BaseAction):
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
start_time = self.api.get_current_time()
|
||||
|
||||
logger.info(f"{self.log_prefix} 开始执行,用户: {self.action_data.get('username')}")
|
||||
|
||||
try:
|
||||
result = await self.process()
|
||||
|
||||
duration = self.api.get_current_time() - start_time
|
||||
logger.info(f"{self.log_prefix} 执行成功,耗时: {duration}ms")
|
||||
|
||||
return True, result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"{self.log_prefix} 执行失败: {e}", exc_info=True)
|
||||
raise
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
通过本详细指南,你已经深入了解了MaiBot插件系统的各个方面:
|
||||
|
||||
- **插件基类** - 生命周期管理和配置系统
|
||||
- **Action组件** - 多种激活机制和智能交互
|
||||
- **Command组件** - 强大的正则表达式匹配和参数处理
|
||||
- **API系统** - 7大模块提供完整功能支持
|
||||
- **高级功能** - 依赖管理、动态创建、生命周期钩子
|
||||
- **最佳实践** - 错误处理、性能优化、资源管理
|
||||
|
||||
现在你已经具备了开发复杂插件的所有知识!
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [系统总览](plugin_guide_overview.md) - 了解整体架构
|
||||
- [快速开始](plugin_quick_start.md) - 5分钟创建第一个插件
|
||||
- [示例插件](../src/plugins/examples/simple_plugin/) - 完整功能参考
|
||||
|
||||
---
|
||||
|
||||
> 💡 **持续学习**: 插件开发是一个实践的过程,建议边学边做,逐步掌握各种高级特性!
|
||||
155
docs/plugin_guide_overview.md
Normal file
155
docs/plugin_guide_overview.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# MaiBot 插件编写指南 - 总览
|
||||
|
||||
## 📋 目录结构
|
||||
|
||||
本指南分为三个部分:
|
||||
|
||||
- **[总览](plugin_guide_overview.md)** - 插件系统架构和设计理念(当前文档)
|
||||
- **[快速开始](plugin_quick_start.md)** - 5分钟创建你的第一个插件
|
||||
- **[详细解析](plugin_detailed_guide.md)** - 深入理解各个组件和API
|
||||
|
||||
## 🎯 插件系统概述
|
||||
|
||||
MaiBot 采用组件化的插件系统,让开发者可以轻松扩展机器人功能。系统支持两种主要组件类型:
|
||||
|
||||
- **Action组件** - 智能动作,基于关键词、LLM判断等条件自动触发
|
||||
- **Command组件** - 命令处理,基于正则表达式匹配用户输入的命令
|
||||
|
||||
## 🏗️ 系统架构
|
||||
|
||||
```
|
||||
src/
|
||||
├── plugin_system/ # 🔧 系统核心(框架代码)
|
||||
│ ├── core/ # 插件管理和注册中心
|
||||
│ ├── apis/ # 统一API接口(7大模块)
|
||||
│ ├── base/ # 插件和组件基类
|
||||
│ └── registry/ # 组件注册和查询
|
||||
└── plugins/ # 🔌 插件内容(用户代码)
|
||||
├── built_in/ # 内置插件
|
||||
└── examples/ # 示例插件
|
||||
```
|
||||
|
||||
### 核心设计理念
|
||||
|
||||
1. **分离关注点** - 系统框架与插件内容完全分离
|
||||
2. **组件化设计** - 一个插件可包含多个Action和Command组件
|
||||
3. **统一API访问** - 通过PluginAPI统一访问所有系统功能
|
||||
4. **声明式配置** - 通过类属性声明组件行为,简化开发
|
||||
5. **类型安全** - 完整的类型定义,IDE友好
|
||||
|
||||
## 🧩 组件类型详解
|
||||
|
||||
### Action组件 - 智能动作
|
||||
|
||||
Action用于实现智能交互逻辑,支持多种激活方式:
|
||||
|
||||
- **关键词激活** - 消息包含特定关键词时触发
|
||||
- **LLM判断激活** - 使用大模型智能判断是否需要触发
|
||||
- **随机激活** - 按概率随机触发
|
||||
- **始终激活** - 每条消息都触发(谨慎使用)
|
||||
|
||||
**适用场景:**
|
||||
- 智能问候、闲聊互动
|
||||
- 情感分析和回应
|
||||
- 内容审核和提醒
|
||||
- 数据统计和分析
|
||||
|
||||
### Command组件 - 命令处理
|
||||
|
||||
Command用于处理结构化的用户命令,基于正则表达式匹配:
|
||||
|
||||
- **精确匹配** - 支持参数提取和验证
|
||||
- **灵活模式** - 正则表达式的完整威力
|
||||
- **帮助系统** - 自动生成命令帮助信息
|
||||
|
||||
**适用场景:**
|
||||
- 功能性操作(查询、设置、管理)
|
||||
- 工具类命令(计算、转换、搜索)
|
||||
- 系统管理命令
|
||||
- 游戏和娱乐功能
|
||||
|
||||
## 🔌 API系统概览
|
||||
|
||||
系统提供7大API模块,涵盖插件开发的所有需求:
|
||||
|
||||
| API模块 | 功能描述 | 主要用途 |
|
||||
|---------|----------|----------|
|
||||
| **MessageAPI** | 消息发送和交互 | 发送文本、图片、语音等消息 |
|
||||
| **LLMAPI** | 大模型调用 | 文本生成、智能判断、创意创作 |
|
||||
| **DatabaseAPI** | 数据库操作 | 存储用户数据、配置、历史记录 |
|
||||
| **ConfigAPI** | 配置访问 | 读取全局配置和用户信息 |
|
||||
| **UtilsAPI** | 工具函数 | 文件操作、时间处理、ID生成 |
|
||||
| **StreamAPI** | 流管理 | 聊天流控制和状态管理 |
|
||||
| **HearflowAPI** | 心流系统 | 与消息处理流程集成 |
|
||||
|
||||
## 🎨 开发体验
|
||||
|
||||
### 简化的导入接口
|
||||
|
||||
```python
|
||||
from src.plugin_system import (
|
||||
BasePlugin, register_plugin, BaseAction, BaseCommand,
|
||||
ComponentInfo, ActionInfo, CommandInfo, ActionActivationType, ChatMode
|
||||
)
|
||||
```
|
||||
|
||||
### 声明式组件定义
|
||||
|
||||
```python
|
||||
class HelloAction(BaseAction):
|
||||
# 🎯 直接通过类属性定义行为
|
||||
focus_activation_type = ActionActivationType.KEYWORD
|
||||
activation_keywords = ["你好", "hello", "hi"]
|
||||
mode_enable = ChatMode.ALL
|
||||
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
return True, "你好!我是MaiBot 😊"
|
||||
```
|
||||
|
||||
### 统一的API访问
|
||||
|
||||
```python
|
||||
class MyCommand(BaseCommand):
|
||||
async def execute(self) -> Tuple[bool, Optional[str]]:
|
||||
# 💡 通过self.api访问所有系统功能
|
||||
await self.api.send_message("text", "处理中...")
|
||||
models = self.api.get_available_models()
|
||||
await self.api.store_user_data("key", "value")
|
||||
return True, "完成!"
|
||||
```
|
||||
|
||||
## 🚀 开发流程
|
||||
|
||||
1. **创建插件目录** - 在 `src/plugins/` 下创建插件文件夹
|
||||
2. **定义插件类** - 继承 `BasePlugin`,设置基本信息
|
||||
3. **创建组件类** - 继承 `BaseAction` 或 `BaseCommand`
|
||||
4. **注册组件** - 在插件的 `get_plugin_components()` 中返回组件列表
|
||||
5. **测试验证** - 启动系统测试插件功能
|
||||
|
||||
## 📚 学习路径建议
|
||||
|
||||
1. **初学者** - 从[快速开始](plugin_quick_start.md)开始,5分钟体验插件开发
|
||||
2. **进阶开发** - 阅读[详细解析](plugin_detailed_guide.md),深入理解各个组件
|
||||
3. **实战练习** - 参考 `simple_plugin` 示例,尝试开发自己的插件
|
||||
4. **API探索** - 逐步尝试各个API模块的功能
|
||||
|
||||
## 💡 设计亮点
|
||||
|
||||
- **零配置启动** - 插件放入目录即可自动加载
|
||||
- **热重载支持** - 开发过程中可动态重载插件(规划中)
|
||||
- **依赖管理** - 支持插件间依赖关系声明
|
||||
- **配置系统** - 支持TOML配置文件,灵活可定制
|
||||
- **完整API** - 覆盖机器人开发的各个方面
|
||||
- **类型安全** - 完整的类型注解,IDE智能提示
|
||||
|
||||
## 🎯 下一步
|
||||
|
||||
选择适合你的起点:
|
||||
|
||||
- 🚀 [立即开始 →](plugin_quick_start.md)
|
||||
- 📖 [深入学习 →](plugin_detailed_guide.md)
|
||||
- 🔍 [查看示例 →](../src/plugins/examples/simple_plugin/)
|
||||
|
||||
---
|
||||
|
||||
> 💡 **提示**: 插件系统仍在持续改进中,欢迎提出建议和反馈!
|
||||
270
docs/plugin_quick_start.md
Normal file
270
docs/plugin_quick_start.md
Normal file
@@ -0,0 +1,270 @@
|
||||
# MaiBot 插件快速开始指南
|
||||
|
||||
## 🚀 5分钟创建你的第一个插件
|
||||
|
||||
本指南将带你快速创建一个功能完整的插件,体验MaiBot插件开发的简单和强大。
|
||||
|
||||
## 📋 前置要求
|
||||
|
||||
- 已克隆MaiBot项目到本地
|
||||
- Python 3.8+ 环境
|
||||
- 基本的Python编程知识
|
||||
|
||||
## 🎯 我们要做什么
|
||||
|
||||
我们将创建一个名为 `my_first_plugin` 的插件,包含:
|
||||
- 一个Action组件:自动回应"Hello"
|
||||
- 一个Command组件:计算器功能
|
||||
|
||||
## 📁 第一步:创建插件目录
|
||||
|
||||
在 `src/plugins/examples/` 下创建你的插件目录:
|
||||
|
||||
```bash
|
||||
mkdir src/plugins/examples/my_first_plugin
|
||||
```
|
||||
|
||||
## 📝 第二步:创建插件文件
|
||||
|
||||
在插件目录中创建 `plugin.py` 文件:
|
||||
|
||||
```python
|
||||
# src/plugins/examples/my_first_plugin/plugin.py
|
||||
|
||||
from typing import List, Tuple, Type, Optional
|
||||
import re
|
||||
|
||||
# 导入插件系统核心
|
||||
from src.plugin_system import (
|
||||
BasePlugin, register_plugin, BaseAction, BaseCommand,
|
||||
ComponentInfo, ActionInfo, CommandInfo, ActionActivationType, ChatMode
|
||||
)
|
||||
from src.common.logger_manager import get_logger
|
||||
|
||||
logger = get_logger("my_first_plugin")
|
||||
|
||||
|
||||
class HelloAction(BaseAction):
|
||||
"""自动问候Action - 当用户说Hello时自动回应"""
|
||||
|
||||
# 🎯 声明式配置:只需设置类属性
|
||||
focus_activation_type = ActionActivationType.KEYWORD
|
||||
normal_activation_type = ActionActivationType.KEYWORD
|
||||
activation_keywords = ["hello", "Hello", "HELLO"]
|
||||
keyword_case_sensitive = False
|
||||
mode_enable = ChatMode.ALL
|
||||
parallel_action = False
|
||||
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
"""执行问候动作"""
|
||||
username = self.action_data.get("username", "朋友")
|
||||
response = f"Hello, {username}! 很高兴见到你! 🎉"
|
||||
|
||||
logger.info(f"向 {username} 发送问候")
|
||||
return True, response
|
||||
|
||||
|
||||
class CalculatorCommand(BaseCommand):
|
||||
"""计算器命令 - 执行简单数学运算"""
|
||||
|
||||
# 🎯 声明式配置:定义命令模式
|
||||
command_pattern = r"^/calc\s+(?P<expression>[\d\+\-\*/\(\)\s\.]+)$"
|
||||
command_help = "计算器,用法:/calc <数学表达式>"
|
||||
command_examples = ["/calc 1+1", "/calc 2*3+4", "/calc (10-5)*2"]
|
||||
|
||||
async def execute(self) -> Tuple[bool, Optional[str]]:
|
||||
"""执行计算命令"""
|
||||
# 获取匹配的表达式
|
||||
expression = self.matched_groups.get("expression", "").strip()
|
||||
|
||||
if not expression:
|
||||
await self.send_reply("❌ 请提供数学表达式!")
|
||||
return False, None
|
||||
|
||||
try:
|
||||
# 安全计算(只允许基本数学运算)
|
||||
allowed_chars = set("0123456789+-*/.() ")
|
||||
if not all(c in allowed_chars for c in expression):
|
||||
await self.send_reply("❌ 表达式包含不允许的字符!")
|
||||
return False, None
|
||||
|
||||
# 执行计算
|
||||
result = eval(expression) # 在实际项目中应使用更安全的计算方法
|
||||
|
||||
response = f"🧮 计算结果:\n`{expression} = {result}`"
|
||||
await self.send_reply(response)
|
||||
|
||||
logger.info(f"计算: {expression} = {result}")
|
||||
return True, response
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"❌ 计算错误: {str(e)}"
|
||||
await self.send_reply(error_msg)
|
||||
logger.error(f"计算失败: {expression}, 错误: {e}")
|
||||
return False, error_msg
|
||||
|
||||
|
||||
@register_plugin
|
||||
class MyFirstPlugin(BasePlugin):
|
||||
"""我的第一个插件 - 展示基本功能"""
|
||||
|
||||
# 🏷️ 插件基本信息
|
||||
plugin_name = "my_first_plugin"
|
||||
plugin_description = "我的第一个MaiBot插件,包含问候和计算功能"
|
||||
plugin_version = "1.0.0"
|
||||
plugin_author = "你的名字"
|
||||
enable_plugin = True
|
||||
|
||||
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
|
||||
"""返回插件包含的组件"""
|
||||
|
||||
return [
|
||||
# Action组件:自动问候
|
||||
(HelloAction.get_action_info(
|
||||
name="hello_action",
|
||||
description="自动回应Hello问候"
|
||||
), HelloAction),
|
||||
|
||||
# Command组件:计算器
|
||||
(CalculatorCommand.get_command_info(
|
||||
name="calculator_command",
|
||||
description="简单计算器,支持基础数学运算"
|
||||
), CalculatorCommand)
|
||||
]
|
||||
```
|
||||
|
||||
## 📊 第三步:创建配置文件(可选)
|
||||
|
||||
创建 `config.toml` 文件来配置插件:
|
||||
|
||||
```toml
|
||||
# src/plugins/examples/my_first_plugin/config.toml
|
||||
|
||||
[plugin]
|
||||
name = "my_first_plugin"
|
||||
description = "我的第一个插件"
|
||||
enabled = true
|
||||
|
||||
[hello_action]
|
||||
enable_emoji = true
|
||||
greeting_message = "Hello, {username}! 很高兴见到你!"
|
||||
|
||||
[calculator]
|
||||
max_expression_length = 100
|
||||
allow_complex_math = false
|
||||
```
|
||||
|
||||
如果你创建了配置文件,需要在插件类中指定:
|
||||
|
||||
```python
|
||||
@register_plugin
|
||||
class MyFirstPlugin(BasePlugin):
|
||||
# ... 其他属性 ...
|
||||
config_file_name = "config.toml" # 添加这一行
|
||||
```
|
||||
|
||||
## 🔄 第四步:测试插件
|
||||
|
||||
1. **启动MaiBot**:
|
||||
```bash
|
||||
cd /path/to/MaiBot-Core
|
||||
python -m src.main
|
||||
```
|
||||
|
||||
2. **测试Action组件**:
|
||||
- 在聊天中发送 "Hello" 或 "hello"
|
||||
- 应该收到自动回复:"Hello, [用户名]! 很高兴见到你! 🎉"
|
||||
|
||||
3. **测试Command组件**:
|
||||
- 发送 `/calc 1+1`
|
||||
- 应该收到回复:"🧮 计算结果:\n`1+1 = 2`"
|
||||
|
||||
## 🎉 恭喜!
|
||||
|
||||
你已经成功创建了第一个MaiBot插件!插件包含:
|
||||
|
||||
✅ **一个Action组件** - 智能响应用户问候
|
||||
✅ **一个Command组件** - 提供计算器功能
|
||||
✅ **完整的配置** - 支持自定义行为
|
||||
✅ **错误处理** - 优雅处理异常情况
|
||||
|
||||
## 🔍 代码解析
|
||||
|
||||
### Action组件关键点
|
||||
|
||||
```python
|
||||
# 声明激活条件
|
||||
focus_activation_type = ActionActivationType.KEYWORD
|
||||
activation_keywords = ["hello", "Hello", "HELLO"]
|
||||
|
||||
# 执行逻辑
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
# 处理逻辑
|
||||
return True, response # (成功状态, 回复内容)
|
||||
```
|
||||
|
||||
### Command组件关键点
|
||||
|
||||
```python
|
||||
# 声明命令模式(正则表达式)
|
||||
command_pattern = r"^/calc\s+(?P<expression>[\d\+\-\*/\(\)\s\.]+)$"
|
||||
|
||||
# 执行逻辑
|
||||
async def execute(self) -> Tuple[bool, Optional[str]]:
|
||||
expression = self.matched_groups.get("expression") # 获取匹配参数
|
||||
await self.send_reply(response) # 发送回复
|
||||
return True, response
|
||||
```
|
||||
|
||||
### 插件注册
|
||||
|
||||
```python
|
||||
@register_plugin # 装饰器注册插件
|
||||
class MyFirstPlugin(BasePlugin):
|
||||
# 基本信息
|
||||
plugin_name = "my_first_plugin"
|
||||
plugin_description = "插件描述"
|
||||
|
||||
# 返回组件列表
|
||||
def get_plugin_components(self):
|
||||
return [(组件信息, 组件类), ...]
|
||||
```
|
||||
|
||||
## 🎯 下一步学习
|
||||
|
||||
现在你已经掌握了基础,可以继续学习:
|
||||
|
||||
1. **深入API** - 探索[详细解析](plugin_detailed_guide.md)了解更多API功能
|
||||
2. **参考示例** - 查看 `simple_plugin` 了解更复杂的功能
|
||||
3. **自定义扩展** - 尝试添加更多组件和功能
|
||||
|
||||
## 🛠️ 常见问题
|
||||
|
||||
### Q: 插件没有被加载?
|
||||
A: 检查:
|
||||
- 插件目录是否在 `src/plugins/` 下
|
||||
- 文件名是否为 `plugin.py`
|
||||
- 类是否有 `@register_plugin` 装饰器
|
||||
- 是否有语法错误
|
||||
|
||||
### Q: Action组件没有触发?
|
||||
A: 检查:
|
||||
- `activation_keywords` 是否正确设置
|
||||
- `focus_activation_type` 和 `normal_activation_type` 是否设置
|
||||
- 消息内容是否包含关键词
|
||||
|
||||
### Q: Command组件不响应?
|
||||
A: 检查:
|
||||
- `command_pattern` 正则表达式是否正确
|
||||
- 用户输入是否完全匹配模式
|
||||
- 是否有语法错误
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [系统总览](plugin_guide_overview.md) - 了解整体架构
|
||||
- [详细解析](plugin_detailed_guide.md) - 深入学习各个组件
|
||||
- [示例插件](../src/plugins/examples/simple_plugin/) - 完整功能示例
|
||||
|
||||
---
|
||||
|
||||
> 🎉 **恭喜完成快速开始!** 现在你已经是MaiBot插件开发者了!
|
||||
@@ -1,7 +0,0 @@
|
||||
# 导入所有动作模块以确保装饰器被执行
|
||||
from . import reply_action # noqa
|
||||
from . import no_reply_action # noqa
|
||||
from . import exit_focus_chat_action # noqa
|
||||
from . import emoji_action # noqa
|
||||
|
||||
# 在此处添加更多动作模块导入
|
||||
@@ -1,147 +0,0 @@
|
||||
from src.common.logger_manager import get_logger
|
||||
from src.chat.actions.base_action import BaseAction, register_action, ActionActivationType, ChatMode
|
||||
from typing import Tuple, List
|
||||
from src.chat.heart_flow.observation.observation import Observation
|
||||
from src.chat.focus_chat.replyer.default_replyer import DefaultReplyer
|
||||
from src.chat.message_receive.chat_stream import ChatStream
|
||||
from src.chat.focus_chat.hfc_utils import create_empty_anchor_message
|
||||
from src.config.config import global_config
|
||||
|
||||
logger = get_logger("action_taken")
|
||||
|
||||
|
||||
@register_action
|
||||
class EmojiAction(BaseAction):
|
||||
"""表情动作处理类
|
||||
|
||||
处理构建和发送消息表情的动作。
|
||||
"""
|
||||
|
||||
action_name: str = "emoji"
|
||||
action_description: str = "当你想单独发送一个表情包辅助你的回复表达"
|
||||
action_parameters: dict[str:str] = {
|
||||
"description": "文字描述你想要发送的表情包内容",
|
||||
}
|
||||
action_require: list[str] = ["表达情绪时可以选择使用", "重点:不要连续发,如果你已经发过[表情包],就不要选择此动作"]
|
||||
|
||||
associated_types: list[str] = ["emoji"]
|
||||
|
||||
enable_plugin = True
|
||||
|
||||
focus_activation_type = ActionActivationType.LLM_JUDGE
|
||||
normal_activation_type = ActionActivationType.RANDOM
|
||||
|
||||
random_activation_probability = global_config.normal_chat.emoji_chance
|
||||
|
||||
parallel_action = True
|
||||
|
||||
llm_judge_prompt = """
|
||||
判定是否需要使用表情动作的条件:
|
||||
1. 用户明确要求使用表情包
|
||||
2. 这是一个适合表达强烈情绪的场合
|
||||
3. 不要发送太多表情包,如果你已经发送过多个表情包
|
||||
"""
|
||||
|
||||
# 模式启用设置 - 表情动作只在Focus模式下使用
|
||||
mode_enable = ChatMode.ALL
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
action_data: dict,
|
||||
reasoning: str,
|
||||
cycle_timers: dict,
|
||||
thinking_id: str,
|
||||
observations: List[Observation],
|
||||
chat_stream: ChatStream,
|
||||
log_prefix: str,
|
||||
replyer: DefaultReplyer,
|
||||
**kwargs,
|
||||
):
|
||||
"""初始化回复动作处理器
|
||||
|
||||
Args:
|
||||
action_name: 动作名称
|
||||
action_data: 动作数据,包含 message, emojis, target 等
|
||||
reasoning: 执行该动作的理由
|
||||
cycle_timers: 计时器字典
|
||||
thinking_id: 思考ID
|
||||
observations: 观察列表
|
||||
replyer: 回复器
|
||||
chat_stream: 聊天流
|
||||
log_prefix: 日志前缀
|
||||
"""
|
||||
super().__init__(action_data, reasoning, cycle_timers, thinking_id)
|
||||
self.observations = observations
|
||||
self.replyer = replyer
|
||||
self.chat_stream = chat_stream
|
||||
self.log_prefix = log_prefix
|
||||
|
||||
async def handle_action(self) -> Tuple[bool, str]:
|
||||
"""
|
||||
处理回复动作
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (是否执行成功, 回复文本)
|
||||
"""
|
||||
# 注意: 此处可能会使用不同的expressor实现根据任务类型切换不同的回复策略
|
||||
return await self._handle_reply(
|
||||
reasoning=self.reasoning,
|
||||
reply_data=self.action_data,
|
||||
cycle_timers=self.cycle_timers,
|
||||
thinking_id=self.thinking_id,
|
||||
)
|
||||
|
||||
async def _handle_reply(
|
||||
self, reasoning: str, reply_data: dict, cycle_timers: dict, thinking_id: str
|
||||
) -> tuple[bool, str]:
|
||||
"""
|
||||
处理统一的回复动作 - 可包含文本和表情,顺序任意
|
||||
|
||||
reply_data格式:
|
||||
{
|
||||
"description": "描述你想要发送的表情"
|
||||
}
|
||||
"""
|
||||
logger.info(f"{self.log_prefix} 决定发送表情")
|
||||
# 从聊天观察获取锚定消息
|
||||
# chatting_observation: ChattingObservation = next(
|
||||
# obs for obs in self.observations if isinstance(obs, ChattingObservation)
|
||||
# )
|
||||
# if reply_data.get("target"):
|
||||
# anchor_message = chatting_observation.search_message_by_text(reply_data["target"])
|
||||
# else:
|
||||
# anchor_message = None
|
||||
|
||||
# 如果没有找到锚点消息,创建一个占位符
|
||||
# if not anchor_message:
|
||||
# logger.info(f"{self.log_prefix} 未找到锚点消息,创建占位符")
|
||||
# anchor_message = await create_empty_anchor_message(
|
||||
# self.chat_stream.platform, self.chat_stream.group_info, self.chat_stream
|
||||
# )
|
||||
# else:
|
||||
# anchor_message.update_chat_stream(self.chat_stream)
|
||||
|
||||
logger.info(f"{self.log_prefix} 为了表情包创建占位符")
|
||||
anchor_message = await create_empty_anchor_message(
|
||||
self.chat_stream.platform, self.chat_stream.group_info, self.chat_stream
|
||||
)
|
||||
|
||||
success, reply_set = await self.replyer.deal_emoji(
|
||||
cycle_timers=cycle_timers,
|
||||
action_data=reply_data,
|
||||
anchor_message=anchor_message,
|
||||
# reasoning=reasoning,
|
||||
thinking_id=thinking_id,
|
||||
)
|
||||
|
||||
reply_text = ""
|
||||
if reply_set:
|
||||
for reply in reply_set:
|
||||
type = reply[0]
|
||||
data = reply[1]
|
||||
if type == "text":
|
||||
reply_text += data
|
||||
elif type == "emoji":
|
||||
reply_text += data
|
||||
|
||||
return success, reply_text
|
||||
@@ -1,88 +0,0 @@
|
||||
import asyncio
|
||||
import traceback
|
||||
from src.common.logger_manager import get_logger
|
||||
from src.chat.actions.base_action import BaseAction, register_action, ChatMode
|
||||
from typing import Tuple, List
|
||||
from src.chat.heart_flow.observation.observation import Observation
|
||||
from src.chat.message_receive.chat_stream import ChatStream
|
||||
|
||||
logger = get_logger("action_taken")
|
||||
|
||||
|
||||
@register_action
|
||||
class ExitFocusChatAction(BaseAction):
|
||||
"""退出专注聊天动作处理类
|
||||
|
||||
处理决定退出专注聊天的动作。
|
||||
执行后会将所属的sub heartflow转变为normal_chat状态。
|
||||
"""
|
||||
|
||||
action_name = "exit_focus_chat"
|
||||
action_description = "退出专注聊天,转为普通聊天模式"
|
||||
action_parameters = {}
|
||||
action_require = [
|
||||
"很长时间没有回复,你决定退出专注聊天",
|
||||
"当前内容不需要持续专注关注,你决定退出专注聊天",
|
||||
"聊天内容已经完成,你决定退出专注聊天",
|
||||
]
|
||||
# 退出专注聊天是系统核心功能,不是插件,但默认不启用(需要特定条件触发)
|
||||
enable_plugin = False
|
||||
|
||||
# 模式启用设置 - 退出专注聊天动作只在Focus模式下使用
|
||||
mode_enable = ChatMode.FOCUS
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
action_data: dict,
|
||||
reasoning: str,
|
||||
cycle_timers: dict,
|
||||
thinking_id: str,
|
||||
observations: List[Observation],
|
||||
log_prefix: str,
|
||||
chat_stream: ChatStream,
|
||||
shutting_down: bool = False,
|
||||
**kwargs,
|
||||
):
|
||||
"""初始化退出专注聊天动作处理器
|
||||
|
||||
Args:
|
||||
action_data: 动作数据
|
||||
reasoning: 执行该动作的理由
|
||||
cycle_timers: 计时器字典
|
||||
thinking_id: 思考ID
|
||||
observations: 观察列表
|
||||
log_prefix: 日志前缀
|
||||
shutting_down: 是否正在关闭
|
||||
"""
|
||||
super().__init__(action_data, reasoning, cycle_timers, thinking_id)
|
||||
self.observations = observations
|
||||
self.log_prefix = log_prefix
|
||||
self._shutting_down = shutting_down
|
||||
|
||||
async def handle_action(self) -> Tuple[bool, str]:
|
||||
"""
|
||||
处理退出专注聊天的情况
|
||||
|
||||
工作流程:
|
||||
1. 将sub heartflow转换为normal_chat状态
|
||||
2. 等待新消息、超时或关闭信号
|
||||
3. 根据等待结果更新连续不回复计数
|
||||
4. 如果达到阈值,触发回调
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (是否执行成功, 状态转换消息)
|
||||
"""
|
||||
try:
|
||||
# 转换状态
|
||||
status_message = ""
|
||||
command = "stop_focus_chat"
|
||||
return True, status_message, command
|
||||
|
||||
except asyncio.CancelledError:
|
||||
logger.info(f"{self.log_prefix} 处理 'exit_focus_chat' 时等待被中断 (CancelledError)")
|
||||
raise
|
||||
except Exception as e:
|
||||
error_msg = f"处理 'exit_focus_chat' 时发生错误: {str(e)}"
|
||||
logger.error(f"{self.log_prefix} {error_msg}")
|
||||
logger.error(traceback.format_exc())
|
||||
return False, "", ""
|
||||
@@ -1,139 +0,0 @@
|
||||
import asyncio
|
||||
import traceback
|
||||
from src.common.logger_manager import get_logger
|
||||
from src.chat.utils.timer_calculator import Timer
|
||||
from src.chat.actions.base_action import BaseAction, register_action, ActionActivationType, ChatMode
|
||||
from typing import Tuple, List
|
||||
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 parse_thinking_id_to_timestamp
|
||||
|
||||
logger = get_logger("action_taken")
|
||||
|
||||
# 常量定义
|
||||
WAITING_TIME_THRESHOLD = 1200 # 等待新消息时间阈值,单位秒
|
||||
|
||||
|
||||
@register_action
|
||||
class NoReplyAction(BaseAction):
|
||||
"""不回复动作处理类
|
||||
|
||||
处理决定不回复的动作。
|
||||
"""
|
||||
|
||||
action_name = "no_reply"
|
||||
action_description = "暂时不回复消息"
|
||||
action_parameters = {}
|
||||
action_require = [
|
||||
"你连续发送了太多消息,且无人回复",
|
||||
"想要休息一下",
|
||||
]
|
||||
enable_plugin = True
|
||||
|
||||
# 激活类型设置
|
||||
focus_activation_type = ActionActivationType.ALWAYS
|
||||
|
||||
# 模式启用设置 - no_reply动作只在Focus模式下使用
|
||||
mode_enable = ChatMode.FOCUS
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
action_data: dict,
|
||||
reasoning: str,
|
||||
cycle_timers: dict,
|
||||
thinking_id: str,
|
||||
observations: List[Observation],
|
||||
log_prefix: str,
|
||||
shutting_down: bool = False,
|
||||
**kwargs,
|
||||
):
|
||||
"""初始化不回复动作处理器
|
||||
|
||||
Args:
|
||||
action_name: 动作名称
|
||||
action_data: 动作数据
|
||||
reasoning: 执行该动作的理由
|
||||
cycle_timers: 计时器字典
|
||||
thinking_id: 思考ID
|
||||
observations: 观察列表
|
||||
log_prefix: 日志前缀
|
||||
shutting_down: 是否正在关闭
|
||||
"""
|
||||
super().__init__(action_data, reasoning, cycle_timers, thinking_id)
|
||||
self.observations = observations
|
||||
self.log_prefix = log_prefix
|
||||
self._shutting_down = shutting_down
|
||||
|
||||
async def handle_action(self) -> Tuple[bool, str]:
|
||||
"""
|
||||
处理不回复的情况
|
||||
|
||||
工作流程:
|
||||
1. 等待新消息、超时或关闭信号
|
||||
2. 根据等待结果更新连续不回复计数
|
||||
3. 如果达到阈值,触发回调
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (是否执行成功, 空字符串)
|
||||
"""
|
||||
logger.info(f"{self.log_prefix} 决定不回复: {self.reasoning}")
|
||||
|
||||
observation = self.observations[0] if self.observations else None
|
||||
|
||||
try:
|
||||
with Timer("等待新消息", self.cycle_timers):
|
||||
# 等待新消息、超时或关闭信号,并获取结果
|
||||
await self._wait_for_new_message(observation, self.thinking_id, self.log_prefix)
|
||||
|
||||
return True, "" # 不回复动作没有回复文本
|
||||
|
||||
except asyncio.CancelledError:
|
||||
logger.info(f"{self.log_prefix} 处理 'no_reply' 时等待被中断 (CancelledError)")
|
||||
raise
|
||||
except Exception as e: # 捕获调用管理器或其他地方可能发生的错误
|
||||
logger.error(f"{self.log_prefix} 处理 'no_reply' 时发生错误: {e}")
|
||||
logger.error(traceback.format_exc())
|
||||
return False, ""
|
||||
|
||||
async def _wait_for_new_message(self, observation: ChattingObservation, thinking_id: str, log_prefix: str) -> bool:
|
||||
"""
|
||||
等待新消息 或 检测到关闭信号
|
||||
|
||||
参数:
|
||||
observation: 观察实例
|
||||
thinking_id: 思考ID
|
||||
log_prefix: 日志前缀
|
||||
|
||||
返回:
|
||||
bool: 是否检测到新消息 (如果因关闭信号退出则返回 False)
|
||||
"""
|
||||
wait_start_time = asyncio.get_event_loop().time()
|
||||
while True:
|
||||
# --- 在每次循环开始时检查关闭标志 ---
|
||||
if self._shutting_down:
|
||||
logger.info(f"{log_prefix} 等待新消息时检测到关闭信号,中断等待。")
|
||||
return False # 表示因为关闭而退出
|
||||
# -----------------------------------
|
||||
|
||||
thinking_id_timestamp = parse_thinking_id_to_timestamp(thinking_id)
|
||||
|
||||
# 检查新消息
|
||||
if await observation.has_new_messages_since(thinking_id_timestamp):
|
||||
logger.info(f"{log_prefix} 检测到新消息")
|
||||
return True
|
||||
|
||||
# 检查超时 (放在检查新消息和关闭之后)
|
||||
if asyncio.get_event_loop().time() - wait_start_time > WAITING_TIME_THRESHOLD:
|
||||
logger.warning(f"{log_prefix} 等待新消息超时({WAITING_TIME_THRESHOLD}秒)")
|
||||
return False
|
||||
|
||||
try:
|
||||
# 短暂休眠,让其他任务有机会运行,并能更快响应取消或关闭
|
||||
await asyncio.sleep(0.5) # 缩短休眠时间
|
||||
except asyncio.CancelledError:
|
||||
# 如果在休眠时被取消,再次检查关闭标志
|
||||
# 如果是正常关闭,则不需要警告
|
||||
if not self._shutting_down:
|
||||
logger.warning(f"{log_prefix} _wait_for_new_message 的休眠被意外取消")
|
||||
# 无论如何,重新抛出异常,让上层处理
|
||||
raise
|
||||
@@ -1,193 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
from src.common.logger_manager import get_logger
|
||||
from src.chat.actions.base_action import BaseAction, register_action, ActionActivationType, ChatMode
|
||||
from typing import Tuple, List
|
||||
from src.chat.heart_flow.observation.observation import Observation
|
||||
from src.chat.focus_chat.replyer.default_replyer import DefaultReplyer
|
||||
from src.chat.message_receive.chat_stream import ChatStream
|
||||
from src.chat.heart_flow.observation.chatting_observation import ChattingObservation
|
||||
from src.chat.focus_chat.hfc_utils import create_empty_anchor_message
|
||||
import time
|
||||
import traceback
|
||||
from src.common.database.database_model import ActionRecords
|
||||
import re
|
||||
|
||||
logger = get_logger("action_taken")
|
||||
|
||||
|
||||
@register_action
|
||||
class ReplyAction(BaseAction):
|
||||
"""回复动作处理类
|
||||
|
||||
处理构建和发送消息回复的动作。
|
||||
"""
|
||||
|
||||
action_name: str = "reply"
|
||||
action_description: str = "当你想要参与回复或者聊天"
|
||||
action_parameters: dict[str:str] = {
|
||||
"reply_to": "如果是明确回复某个人的发言,请在reply_to参数中指定,格式:(用户名:发言内容),如果不是,reply_to的值设为none"
|
||||
}
|
||||
action_require: list[str] = [
|
||||
"你想要闲聊或者随便附和",
|
||||
"有人提到你",
|
||||
"如果你刚刚进行了回复,不要对同一个话题重复回应",
|
||||
]
|
||||
|
||||
associated_types: list[str] = ["text"]
|
||||
|
||||
enable_plugin = True
|
||||
|
||||
# 激活类型设置
|
||||
focus_activation_type = ActionActivationType.ALWAYS
|
||||
|
||||
# 模式启用设置 - 回复动作只在Focus模式下使用
|
||||
mode_enable = ChatMode.FOCUS
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
action_data: dict,
|
||||
reasoning: str,
|
||||
cycle_timers: dict,
|
||||
thinking_id: str,
|
||||
observations: List[Observation],
|
||||
chat_stream: ChatStream,
|
||||
log_prefix: str,
|
||||
replyer: DefaultReplyer,
|
||||
**kwargs,
|
||||
):
|
||||
"""初始化回复动作处理器
|
||||
|
||||
Args:
|
||||
action_name: 动作名称
|
||||
action_data: 动作数据,包含 message, emojis, target 等
|
||||
reasoning: 执行该动作的理由
|
||||
cycle_timers: 计时器字典
|
||||
thinking_id: 思考ID
|
||||
observations: 观察列表
|
||||
replyer: 回复器
|
||||
chat_stream: 聊天流
|
||||
log_prefix: 日志前缀
|
||||
"""
|
||||
super().__init__(action_data, reasoning, cycle_timers, thinking_id)
|
||||
self.observations = observations
|
||||
self.replyer = replyer
|
||||
self.chat_stream = chat_stream
|
||||
self.log_prefix = log_prefix
|
||||
|
||||
async def handle_action(self) -> Tuple[bool, str]:
|
||||
"""
|
||||
处理回复动作
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (是否执行成功, 回复文本)
|
||||
"""
|
||||
# 注意: 此处可能会使用不同的expressor实现根据任务类型切换不同的回复策略
|
||||
success, reply_text = await self._handle_reply(
|
||||
reasoning=self.reasoning,
|
||||
reply_data=self.action_data,
|
||||
cycle_timers=self.cycle_timers,
|
||||
thinking_id=self.thinking_id,
|
||||
)
|
||||
|
||||
await self.store_action_info(
|
||||
action_build_into_prompt=False,
|
||||
action_prompt_display=f"{reply_text}",
|
||||
)
|
||||
|
||||
return success, reply_text
|
||||
|
||||
async def _handle_reply(
|
||||
self, reasoning: str, reply_data: dict, cycle_timers: dict, thinking_id: str
|
||||
) -> tuple[bool, str]:
|
||||
"""
|
||||
处理统一的回复动作 - 可包含文本和表情,顺序任意
|
||||
|
||||
reply_data格式:
|
||||
{
|
||||
"text": "你好啊" # 文本内容列表(可选)
|
||||
"target": "锚定消息", # 锚定消息的文本内容
|
||||
}
|
||||
"""
|
||||
logger.info(f"{self.log_prefix} 决定回复: {self.reasoning}")
|
||||
|
||||
# 从聊天观察获取锚定消息
|
||||
chatting_observation: ChattingObservation = next(
|
||||
obs for obs in self.observations if isinstance(obs, ChattingObservation)
|
||||
)
|
||||
|
||||
reply_to = reply_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)
|
||||
else:
|
||||
anchor_message = None
|
||||
|
||||
if anchor_message:
|
||||
anchor_message.update_chat_stream(self.chat_stream)
|
||||
else:
|
||||
logger.info(f"{self.log_prefix} 未找到锚点消息,创建占位符")
|
||||
anchor_message = await create_empty_anchor_message(
|
||||
self.chat_stream.platform, self.chat_stream.group_info, self.chat_stream
|
||||
)
|
||||
|
||||
success, reply_set = await self.replyer.deal_reply(
|
||||
cycle_timers=cycle_timers,
|
||||
action_data=reply_data,
|
||||
anchor_message=anchor_message,
|
||||
reasoning=reasoning,
|
||||
thinking_id=thinking_id,
|
||||
)
|
||||
|
||||
reply_text = ""
|
||||
for reply in reply_set:
|
||||
type = reply[0]
|
||||
data = reply[1]
|
||||
if type == "text":
|
||||
reply_text += data
|
||||
elif type == "emoji":
|
||||
reply_text += data
|
||||
|
||||
return success, reply_text
|
||||
|
||||
async def store_action_info(
|
||||
self, action_build_into_prompt: bool = False, action_prompt_display: str = "", action_done: bool = True
|
||||
) -> None:
|
||||
"""存储action执行信息到数据库
|
||||
|
||||
Args:
|
||||
action_build_into_prompt: 是否构建到提示中
|
||||
action_prompt_display: 动作显示内容
|
||||
"""
|
||||
try:
|
||||
chat_stream = self.chat_stream
|
||||
if not chat_stream:
|
||||
logger.error(f"{self.log_prefix} 无法存储action信息:缺少chat_stream服务")
|
||||
return
|
||||
|
||||
action_time = time.time()
|
||||
action_id = f"{action_time}_{self.thinking_id}"
|
||||
|
||||
ActionRecords.create(
|
||||
action_id=action_id,
|
||||
time=action_time,
|
||||
action_name=self.__class__.__name__,
|
||||
action_data=str(self.action_data),
|
||||
action_done=action_done,
|
||||
action_build_into_prompt=action_build_into_prompt,
|
||||
action_prompt_display=action_prompt_display,
|
||||
chat_id=chat_stream.stream_id,
|
||||
chat_info_stream_id=chat_stream.stream_id,
|
||||
chat_info_platform=chat_stream.platform,
|
||||
user_id=chat_stream.user_info.user_id if chat_stream.user_info else "",
|
||||
user_nickname=chat_stream.user_info.user_nickname if chat_stream.user_info else "",
|
||||
user_cardname=chat_stream.user_info.user_cardname if chat_stream.user_info else "",
|
||||
)
|
||||
logger.debug(f"{self.log_prefix} 已存储action信息: {action_prompt_display}")
|
||||
except Exception as e:
|
||||
logger.error(f"{self.log_prefix} 存储action信息时出错: {e}")
|
||||
traceback.print_exc()
|
||||
@@ -15,6 +15,77 @@ logger = get_logger("action_manager")
|
||||
ActionInfo = Dict[str, Any]
|
||||
|
||||
|
||||
class PluginActionWrapper(BaseAction):
|
||||
"""
|
||||
新插件系统Action组件的兼容性包装器
|
||||
|
||||
将新插件系统的Action组件包装为旧系统兼容的BaseAction接口
|
||||
"""
|
||||
|
||||
def __init__(self, plugin_action, action_name: str, action_data: dict, reasoning: str, cycle_timers: dict, thinking_id: str):
|
||||
"""初始化包装器"""
|
||||
# 调用旧系统BaseAction初始化,只传递它能接受的参数
|
||||
super().__init__(
|
||||
action_data=action_data,
|
||||
reasoning=reasoning,
|
||||
cycle_timers=cycle_timers,
|
||||
thinking_id=thinking_id
|
||||
)
|
||||
|
||||
# 存储插件Action实例(它已经包含了所有必要的服务对象)
|
||||
self.plugin_action = plugin_action
|
||||
self.action_name = action_name
|
||||
|
||||
# 从插件Action实例复制属性到包装器
|
||||
self._sync_attributes_from_plugin_action()
|
||||
|
||||
def _sync_attributes_from_plugin_action(self):
|
||||
"""从插件Action实例同步属性到包装器"""
|
||||
# 基本属性
|
||||
self.action_name = getattr(self.plugin_action, 'action_name', self.action_name)
|
||||
|
||||
# 设置兼容的默认值
|
||||
self.action_description = f"插件Action: {self.action_name}"
|
||||
self.action_parameters = {}
|
||||
self.action_require = []
|
||||
|
||||
# 激活类型属性(从新插件系统转换)
|
||||
plugin_focus_type = getattr(self.plugin_action, 'focus_activation_type', None)
|
||||
plugin_normal_type = getattr(self.plugin_action, 'normal_activation_type', None)
|
||||
|
||||
if plugin_focus_type:
|
||||
self.focus_activation_type = plugin_focus_type.value if hasattr(plugin_focus_type, 'value') else str(plugin_focus_type)
|
||||
if plugin_normal_type:
|
||||
self.normal_activation_type = plugin_normal_type.value if hasattr(plugin_normal_type, 'value') else str(plugin_normal_type)
|
||||
|
||||
# 其他属性
|
||||
self.random_activation_probability = getattr(self.plugin_action, 'random_activation_probability', 0.0)
|
||||
self.llm_judge_prompt = getattr(self.plugin_action, 'llm_judge_prompt', "")
|
||||
self.activation_keywords = getattr(self.plugin_action, 'activation_keywords', [])
|
||||
self.keyword_case_sensitive = getattr(self.plugin_action, 'keyword_case_sensitive', False)
|
||||
|
||||
# 模式和并行设置
|
||||
plugin_mode = getattr(self.plugin_action, 'mode_enable', None)
|
||||
if plugin_mode:
|
||||
self.mode_enable = plugin_mode.value if hasattr(plugin_mode, 'value') else str(plugin_mode)
|
||||
|
||||
self.parallel_action = getattr(self.plugin_action, 'parallel_action', True)
|
||||
self.enable_plugin = True
|
||||
|
||||
async def handle_action(self) -> tuple[bool, str]:
|
||||
"""兼容旧系统的动作处理接口,委托给插件Action的execute方法"""
|
||||
try:
|
||||
# 调用插件Action的execute方法
|
||||
success, response = await self.plugin_action.execute()
|
||||
|
||||
logger.debug(f"插件Action {self.action_name} 执行{'成功' if success else '失败'}: {response}")
|
||||
return success, response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"插件Action {self.action_name} 执行异常: {e}")
|
||||
return False, f"插件Action执行失败: {str(e)}"
|
||||
|
||||
|
||||
class ActionManager:
|
||||
"""
|
||||
动作管理器,用于管理各种类型的动作
|
||||
@@ -113,14 +184,74 @@ class ActionManager:
|
||||
加载所有插件目录中的动作
|
||||
|
||||
注意:插件动作的实际导入已经在main.py中完成,这里只需要从_ACTION_REGISTRY获取
|
||||
同时也从新插件系统的component_registry获取Action组件
|
||||
"""
|
||||
try:
|
||||
# 插件动作已在main.py中加载,这里只需要从_ACTION_REGISTRY获取
|
||||
# 从旧的_ACTION_REGISTRY获取插件动作
|
||||
self._load_registered_actions()
|
||||
logger.info("从注册表加载插件动作成功")
|
||||
logger.debug("从旧注册表加载插件动作成功")
|
||||
|
||||
# 从新插件系统获取Action组件
|
||||
self._load_plugin_system_actions()
|
||||
logger.debug("从新插件系统加载Action组件成功")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"加载插件动作失败: {e}")
|
||||
|
||||
def _load_plugin_system_actions(self) -> None:
|
||||
"""从新插件系统的component_registry加载Action组件"""
|
||||
try:
|
||||
from src.plugin_system.core.component_registry import component_registry
|
||||
from src.plugin_system.base.component_types import ComponentType
|
||||
|
||||
# 获取所有Action组件
|
||||
action_components = component_registry.get_components_by_type(ComponentType.ACTION)
|
||||
|
||||
for action_name, action_info in action_components.items():
|
||||
if action_name in self._registered_actions:
|
||||
logger.debug(f"Action组件 {action_name} 已存在,跳过")
|
||||
continue
|
||||
|
||||
# 将新插件系统的ActionInfo转换为旧系统格式
|
||||
converted_action_info = {
|
||||
"description": action_info.description,
|
||||
"parameters": getattr(action_info, 'action_parameters', {}),
|
||||
"require": getattr(action_info, 'action_require', []),
|
||||
"associated_types": getattr(action_info, 'associated_types', []),
|
||||
"enable_plugin": action_info.enabled,
|
||||
|
||||
# 激活类型相关
|
||||
"focus_activation_type": action_info.focus_activation_type.value,
|
||||
"normal_activation_type": action_info.normal_activation_type.value,
|
||||
"random_activation_probability": action_info.random_activation_probability,
|
||||
"llm_judge_prompt": action_info.llm_judge_prompt,
|
||||
"activation_keywords": action_info.activation_keywords,
|
||||
"keyword_case_sensitive": action_info.keyword_case_sensitive,
|
||||
|
||||
# 模式和并行设置
|
||||
"mode_enable": action_info.mode_enable.value,
|
||||
"parallel_action": action_info.parallel_action,
|
||||
|
||||
# 标记这是来自新插件系统的组件
|
||||
"_plugin_system_component": True,
|
||||
"_plugin_name": getattr(action_info, 'plugin_name', ''),
|
||||
}
|
||||
|
||||
self._registered_actions[action_name] = converted_action_info
|
||||
|
||||
# 如果启用,也添加到默认动作集
|
||||
if action_info.enabled:
|
||||
self._default_actions[action_name] = converted_action_info
|
||||
|
||||
logger.debug(f"从插件系统加载Action组件: {action_name} (插件: {getattr(action_info, 'plugin_name', 'unknown')})")
|
||||
|
||||
logger.info(f"从新插件系统加载了 {len(action_components)} 个Action组件")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"从插件系统加载Action组件失败: {e}")
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
def create_action(
|
||||
self,
|
||||
action_name: str,
|
||||
@@ -159,6 +290,15 @@ class ActionManager:
|
||||
# logger.warning(f"当前不可用的动作类型: {action_name}")
|
||||
# return None
|
||||
|
||||
# 检查是否是新插件系统的Action组件
|
||||
action_info = self._registered_actions.get(action_name)
|
||||
if action_info and action_info.get("_plugin_system_component", False):
|
||||
return self._create_plugin_system_action(
|
||||
action_name, action_data, reasoning, cycle_timers, thinking_id,
|
||||
observations, chat_stream, log_prefix, shutting_down, expressor, replyer
|
||||
)
|
||||
|
||||
# 旧系统的动作创建逻辑
|
||||
handler_class = _ACTION_REGISTRY.get(action_name)
|
||||
if not handler_class:
|
||||
logger.warning(f"未注册的动作类型: {action_name}")
|
||||
@@ -185,6 +325,67 @@ class ActionManager:
|
||||
logger.error(f"创建动作处理器实例失败: {e}")
|
||||
return None
|
||||
|
||||
def _create_plugin_system_action(
|
||||
self,
|
||||
action_name: str,
|
||||
action_data: dict,
|
||||
reasoning: str,
|
||||
cycle_timers: dict,
|
||||
thinking_id: str,
|
||||
observations: List[Observation],
|
||||
chat_stream: ChatStream,
|
||||
log_prefix: str,
|
||||
shutting_down: bool = False,
|
||||
expressor: DefaultExpressor = None,
|
||||
replyer: DefaultReplyer = None,
|
||||
) -> Optional['PluginActionWrapper']:
|
||||
"""
|
||||
创建新插件系统的Action组件实例,并包装为兼容旧系统的接口
|
||||
|
||||
Returns:
|
||||
Optional[PluginActionWrapper]: 包装后的Action实例
|
||||
"""
|
||||
try:
|
||||
from src.plugin_system.core.component_registry import component_registry
|
||||
|
||||
# 获取组件类
|
||||
component_class = component_registry.get_component_class(action_name)
|
||||
if not component_class:
|
||||
logger.error(f"未找到插件Action组件类: {action_name}")
|
||||
return None
|
||||
|
||||
# 创建插件Action实例
|
||||
plugin_action_instance = component_class(
|
||||
action_data=action_data,
|
||||
reasoning=reasoning,
|
||||
cycle_timers=cycle_timers,
|
||||
thinking_id=thinking_id,
|
||||
chat_stream=chat_stream,
|
||||
expressor=expressor,
|
||||
replyer=replyer,
|
||||
observations=observations,
|
||||
log_prefix=log_prefix
|
||||
)
|
||||
|
||||
# 创建兼容性包装器
|
||||
wrapper = PluginActionWrapper(
|
||||
plugin_action=plugin_action_instance,
|
||||
action_name=action_name,
|
||||
action_data=action_data,
|
||||
reasoning=reasoning,
|
||||
cycle_timers=cycle_timers,
|
||||
thinking_id=thinking_id
|
||||
)
|
||||
|
||||
logger.debug(f"创建插件Action实例成功: {action_name}")
|
||||
return wrapper
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"创建插件Action实例失败 {action_name}: {e}")
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
return None
|
||||
|
||||
def get_registered_actions(self) -> Dict[str, ActionInfo]:
|
||||
"""获取所有已注册的动作集"""
|
||||
return self._registered_actions.copy()
|
||||
|
||||
@@ -16,29 +16,36 @@ class DatabaseAPI:
|
||||
"""
|
||||
|
||||
async def store_action_info(
|
||||
self, action_build_into_prompt: bool = False, action_prompt_display: str = "", action_done: bool = True
|
||||
self,
|
||||
action_build_into_prompt: bool = False,
|
||||
action_prompt_display: str = "",
|
||||
action_done: bool = True,
|
||||
thinking_id: str = "",
|
||||
action_data: dict = None
|
||||
) -> None:
|
||||
"""存储action执行信息到数据库
|
||||
"""存储action信息到数据库
|
||||
|
||||
Args:
|
||||
action_build_into_prompt: 是否构建到提示中
|
||||
action_prompt_display: 动作显示内容
|
||||
action_done: 动作是否已完成
|
||||
action_prompt_display: 显示的action提示信息
|
||||
action_done: action是否完成
|
||||
thinking_id: 思考ID
|
||||
action_data: action数据,如果不提供则使用空字典
|
||||
"""
|
||||
try:
|
||||
chat_stream = self._services.get("chat_stream")
|
||||
chat_stream = self.get_service("chat_stream")
|
||||
if not chat_stream:
|
||||
logger.error(f"{self.log_prefix} 无法存储action信息:缺少chat_stream服务")
|
||||
return
|
||||
|
||||
action_time = time.time()
|
||||
action_id = f"{action_time}_{self.thinking_id}"
|
||||
action_id = f"{action_time}_{thinking_id}"
|
||||
|
||||
ActionRecords.create(
|
||||
action_id=action_id,
|
||||
time=action_time,
|
||||
action_name=self.__class__.__name__,
|
||||
action_data=str(self.action_data),
|
||||
action_data=str(action_data or {}),
|
||||
action_done=action_done,
|
||||
action_build_into_prompt=action_build_into_prompt,
|
||||
action_prompt_display=action_prompt_display,
|
||||
|
||||
@@ -55,6 +55,9 @@ class PluginAPI(MessageAPI, LLMAPI, DatabaseAPI, ConfigAPI, UtilsAPI, StreamAPI,
|
||||
|
||||
self.log_prefix = log_prefix
|
||||
|
||||
# 存储action上下文信息
|
||||
self._action_context = {}
|
||||
|
||||
# 调用所有父类的初始化
|
||||
super().__init__()
|
||||
|
||||
@@ -88,6 +91,17 @@ class PluginAPI(MessageAPI, LLMAPI, DatabaseAPI, ConfigAPI, UtilsAPI, StreamAPI,
|
||||
"""检查是否有指定的服务对象"""
|
||||
return service_name in self._services and self._services[service_name] is not None
|
||||
|
||||
def set_action_context(self, thinking_id: str = None, shutting_down: bool = False, **kwargs):
|
||||
"""设置action上下文信息"""
|
||||
if thinking_id:
|
||||
self._action_context["thinking_id"] = thinking_id
|
||||
self._action_context["shutting_down"] = shutting_down
|
||||
self._action_context.update(kwargs)
|
||||
|
||||
def get_action_context(self, key: str, default=None):
|
||||
"""获取action上下文信息"""
|
||||
return self._action_context.get(key, default)
|
||||
|
||||
|
||||
# 便捷的工厂函数
|
||||
def create_plugin_api(
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from typing import Optional, List, Dict, Any
|
||||
from typing import Optional, List, Dict, Any, Tuple
|
||||
from src.common.logger_manager import get_logger
|
||||
from src.chat.message_receive.chat_stream import ChatManager, ChatStream
|
||||
from src.chat.focus_chat.hfc_utils import parse_thinking_id_to_timestamp
|
||||
import asyncio
|
||||
|
||||
logger = get_logger("stream_api")
|
||||
|
||||
@@ -157,3 +159,62 @@ class StreamAPI:
|
||||
except Exception as e:
|
||||
logger.error(f"{self.log_prefix} 异步通过群ID获取聊天流时出错: {e}")
|
||||
return None
|
||||
|
||||
async def wait_for_new_message(self, timeout: int = 1200) -> Tuple[bool, str]:
|
||||
"""等待新消息或超时
|
||||
|
||||
Args:
|
||||
timeout: 超时时间(秒),默认1200秒
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (是否收到新消息, 空字符串)
|
||||
"""
|
||||
try:
|
||||
# 获取必要的服务对象
|
||||
observations = self.get_service("observations")
|
||||
if not observations:
|
||||
logger.warning(f"{self.log_prefix} 无法获取observations服务,无法等待新消息")
|
||||
return False, ""
|
||||
|
||||
# 获取第一个观察对象(通常是ChattingObservation)
|
||||
observation = observations[0] if observations else None
|
||||
if not observation:
|
||||
logger.warning(f"{self.log_prefix} 无观察对象,无法等待新消息")
|
||||
return False, ""
|
||||
|
||||
# 从action上下文获取thinking_id
|
||||
thinking_id = self.get_action_context("thinking_id")
|
||||
if not thinking_id:
|
||||
logger.warning(f"{self.log_prefix} 无thinking_id,无法等待新消息")
|
||||
return False, ""
|
||||
|
||||
logger.info(f"{self.log_prefix} 开始等待新消息... (超时: {timeout}秒)")
|
||||
|
||||
wait_start_time = asyncio.get_event_loop().time()
|
||||
while True:
|
||||
# 检查关闭标志
|
||||
shutting_down = self.get_action_context("shutting_down", False)
|
||||
if shutting_down:
|
||||
logger.info(f"{self.log_prefix} 等待新消息时检测到关闭信号,中断等待")
|
||||
return False, ""
|
||||
|
||||
# 检查新消息
|
||||
thinking_id_timestamp = parse_thinking_id_to_timestamp(thinking_id)
|
||||
if await observation.has_new_messages_since(thinking_id_timestamp):
|
||||
logger.info(f"{self.log_prefix} 检测到新消息")
|
||||
return True, ""
|
||||
|
||||
# 检查超时
|
||||
if asyncio.get_event_loop().time() - wait_start_time > timeout:
|
||||
logger.warning(f"{self.log_prefix} 等待新消息超时({timeout}秒)")
|
||||
return False, ""
|
||||
|
||||
# 短暂休眠
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
except asyncio.CancelledError:
|
||||
logger.info(f"{self.log_prefix} 等待新消息被中断 (CancelledError)")
|
||||
return False, ""
|
||||
except Exception as e:
|
||||
logger.error(f"{self.log_prefix} 等待新消息时发生错误: {e}")
|
||||
return False, f"等待新消息失败: {str(e)}"
|
||||
|
||||
@@ -12,7 +12,7 @@ class BaseAction(ABC):
|
||||
|
||||
Action是插件的一种组件类型,用于处理聊天中的动作逻辑
|
||||
|
||||
子类可以通过类属性定义激活条件:
|
||||
子类可以通过类属性定义激活条件,这些会在实例化时转换为实例属性:
|
||||
- focus_activation_type: 专注模式激活类型
|
||||
- normal_activation_type: 普通模式激活类型
|
||||
- activation_keywords: 激活关键词列表
|
||||
@@ -23,17 +23,18 @@ class BaseAction(ABC):
|
||||
- llm_judge_prompt: LLM判断提示词
|
||||
"""
|
||||
|
||||
# 默认激活设置(子类可以覆盖)
|
||||
focus_activation_type: ActionActivationType = ActionActivationType.NEVER
|
||||
normal_activation_type: ActionActivationType = ActionActivationType.NEVER
|
||||
activation_keywords: list = []
|
||||
keyword_case_sensitive: bool = False
|
||||
mode_enable: ChatMode = ChatMode.ALL
|
||||
parallel_action: bool = True
|
||||
random_activation_probability: float = 0.0
|
||||
llm_judge_prompt: str = ""
|
||||
|
||||
def __init__(self, action_data: dict, reasoning: str, cycle_timers: dict, thinking_id: str, **kwargs):
|
||||
def __init__(self,
|
||||
action_data: dict,
|
||||
reasoning: str,
|
||||
cycle_timers: dict,
|
||||
thinking_id: str,
|
||||
observations: list = None,
|
||||
expressor = None,
|
||||
replyer = None,
|
||||
chat_stream = None,
|
||||
log_prefix: str = "",
|
||||
shutting_down: bool = False,
|
||||
**kwargs):
|
||||
"""初始化Action组件
|
||||
|
||||
Args:
|
||||
@@ -41,26 +42,74 @@ class BaseAction(ABC):
|
||||
reasoning: 执行该动作的理由
|
||||
cycle_timers: 计时器字典
|
||||
thinking_id: 思考ID
|
||||
**kwargs: 其他参数(包含服务对象)
|
||||
observations: 观察列表
|
||||
expressor: 表达器对象
|
||||
replyer: 回复器对象
|
||||
chat_stream: 聊天流对象
|
||||
log_prefix: 日志前缀
|
||||
shutting_down: 是否正在关闭
|
||||
**kwargs: 其他参数
|
||||
"""
|
||||
self.action_data = action_data
|
||||
self.reasoning = reasoning
|
||||
self.cycle_timers = cycle_timers
|
||||
self.thinking_id = thinking_id
|
||||
self.log_prefix = log_prefix
|
||||
self.shutting_down = shutting_down
|
||||
|
||||
# 创建API实例
|
||||
# 设置动作基本信息实例属性(兼容旧系统)
|
||||
self.action_name: str = getattr(self, 'action_name', self.__class__.__name__.lower().replace('action', ''))
|
||||
self.action_description: str = getattr(self, 'action_description', self.__doc__ or "Action组件")
|
||||
self.action_parameters: dict = getattr(self.__class__, 'action_parameters', {}).copy()
|
||||
self.action_require: list[str] = getattr(self.__class__, 'action_require', []).copy()
|
||||
|
||||
# 设置激活类型实例属性(从类属性复制,提供默认值)
|
||||
self.focus_activation_type: str = self._get_activation_type_value('focus_activation_type', 'never')
|
||||
self.normal_activation_type: str = self._get_activation_type_value('normal_activation_type', 'never')
|
||||
self.random_activation_probability: float = getattr(self.__class__, 'random_activation_probability', 0.0)
|
||||
self.llm_judge_prompt: str = getattr(self.__class__, 'llm_judge_prompt', "")
|
||||
self.activation_keywords: list[str] = getattr(self.__class__, 'activation_keywords', []).copy()
|
||||
self.keyword_case_sensitive: bool = getattr(self.__class__, 'keyword_case_sensitive', False)
|
||||
self.mode_enable: str = self._get_mode_value('mode_enable', 'all')
|
||||
self.parallel_action: bool = getattr(self.__class__, 'parallel_action', True)
|
||||
self.associated_types: list[str] = getattr(self.__class__, 'associated_types', []).copy()
|
||||
self.enable_plugin: bool = True # 默认启用
|
||||
|
||||
# 创建API实例,传递所有服务对象
|
||||
self.api = PluginAPI(
|
||||
chat_stream=kwargs.get("chat_stream"),
|
||||
expressor=kwargs.get("expressor"),
|
||||
replyer=kwargs.get("replyer"),
|
||||
observations=kwargs.get("observations"),
|
||||
log_prefix=kwargs.get("log_prefix", ""),
|
||||
chat_stream=chat_stream or kwargs.get("chat_stream"),
|
||||
expressor=expressor or kwargs.get("expressor"),
|
||||
replyer=replyer or kwargs.get("replyer"),
|
||||
observations=observations or kwargs.get("observations", []),
|
||||
log_prefix=log_prefix
|
||||
)
|
||||
|
||||
self.log_prefix = kwargs.get("log_prefix", "")
|
||||
# 设置API的action上下文
|
||||
self.api.set_action_context(
|
||||
thinking_id=thinking_id,
|
||||
shutting_down=shutting_down
|
||||
)
|
||||
|
||||
logger.debug(f"{self.log_prefix} Action组件初始化完成")
|
||||
|
||||
def _get_activation_type_value(self, attr_name: str, default: str) -> str:
|
||||
"""获取激活类型的字符串值"""
|
||||
attr = getattr(self.__class__, attr_name, None)
|
||||
if attr is None:
|
||||
return default
|
||||
if hasattr(attr, 'value'):
|
||||
return attr.value
|
||||
return str(attr)
|
||||
|
||||
def _get_mode_value(self, attr_name: str, default: str) -> str:
|
||||
"""获取模式的字符串值"""
|
||||
attr = getattr(self.__class__, attr_name, None)
|
||||
if attr is None:
|
||||
return default
|
||||
if hasattr(attr, 'value'):
|
||||
return attr.value
|
||||
return str(attr)
|
||||
|
||||
async def send_reply(self, content: str) -> bool:
|
||||
"""发送回复消息
|
||||
|
||||
@@ -89,20 +138,38 @@ class BaseAction(ABC):
|
||||
name = cls.__name__.lower().replace("action", "")
|
||||
if description is None:
|
||||
description = cls.__doc__ or f"{cls.__name__} Action组件"
|
||||
description = description.strip().split("\n")[0] # 取第一行作为描述
|
||||
description = description.strip().split('\n')[0] # 取第一行作为描述
|
||||
|
||||
# 安全获取激活类型值
|
||||
def get_enum_value(attr_name, default):
|
||||
attr = getattr(cls, attr_name, None)
|
||||
if attr is None:
|
||||
# 如果没有定义,返回默认的枚举值
|
||||
return getattr(ActionActivationType, default.upper(), ActionActivationType.NEVER)
|
||||
return attr
|
||||
|
||||
def get_mode_value(attr_name, default):
|
||||
attr = getattr(cls, attr_name, None)
|
||||
if attr is None:
|
||||
return getattr(ChatMode, default.upper(), ChatMode.ALL)
|
||||
return attr
|
||||
|
||||
return ActionInfo(
|
||||
name=name,
|
||||
component_type=ComponentType.ACTION,
|
||||
description=description,
|
||||
focus_activation_type=cls.focus_activation_type,
|
||||
normal_activation_type=cls.normal_activation_type,
|
||||
activation_keywords=cls.activation_keywords.copy() if cls.activation_keywords else [],
|
||||
keyword_case_sensitive=cls.keyword_case_sensitive,
|
||||
mode_enable=cls.mode_enable,
|
||||
parallel_action=cls.parallel_action,
|
||||
random_activation_probability=cls.random_activation_probability,
|
||||
llm_judge_prompt=cls.llm_judge_prompt,
|
||||
focus_activation_type=get_enum_value('focus_activation_type', 'never'),
|
||||
normal_activation_type=get_enum_value('normal_activation_type', 'never'),
|
||||
activation_keywords=getattr(cls, 'activation_keywords', []).copy(),
|
||||
keyword_case_sensitive=getattr(cls, 'keyword_case_sensitive', False),
|
||||
mode_enable=get_mode_value('mode_enable', 'all'),
|
||||
parallel_action=getattr(cls, 'parallel_action', True),
|
||||
random_activation_probability=getattr(cls, 'random_activation_probability', 0.0),
|
||||
llm_judge_prompt=getattr(cls, 'llm_judge_prompt', ""),
|
||||
# 使用正确的字段名
|
||||
action_parameters=getattr(cls, 'action_parameters', {}).copy(),
|
||||
action_require=getattr(cls, 'action_require', []).copy(),
|
||||
associated_types=getattr(cls, 'associated_types', []).copy()
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
@@ -113,3 +180,14 @@ class BaseAction(ABC):
|
||||
Tuple[bool, str]: (是否执行成功, 回复文本)
|
||||
"""
|
||||
pass
|
||||
|
||||
async def handle_action(self) -> Tuple[bool, str]:
|
||||
"""兼容旧系统的handle_action接口,委托给execute方法
|
||||
|
||||
为了保持向后兼容性,旧系统的代码可能会调用handle_action方法。
|
||||
此方法将调用委托给新的execute方法。
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (是否执行成功, 回复文本)
|
||||
"""
|
||||
return await self.execute()
|
||||
@@ -105,7 +105,7 @@ class BasePlugin(ABC):
|
||||
if file_ext == ".toml":
|
||||
with open(config_file_path, "r", encoding="utf-8") as f:
|
||||
self.config = toml.load(f) or {}
|
||||
logger.info(f"{self.log_prefix} 配置已从 {config_file_path} 加载")
|
||||
logger.debug(f"{self.log_prefix} 配置已从 {config_file_path} 加载")
|
||||
else:
|
||||
logger.warning(f"{self.log_prefix} 不支持的配置文件格式: {file_ext},仅支持 .toml")
|
||||
self.config = {}
|
||||
@@ -148,7 +148,7 @@ class BasePlugin(ABC):
|
||||
|
||||
# 注册插件
|
||||
if component_registry.register_plugin(self.plugin_info):
|
||||
logger.info(f"{self.log_prefix} 插件注册成功,包含 {len(registered_components)} 个组件")
|
||||
logger.debug(f"{self.log_prefix} 插件注册成功,包含 {len(registered_components)} 个组件")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"{self.log_prefix} 插件注册失败")
|
||||
|
||||
@@ -70,7 +70,7 @@ class ComponentRegistry:
|
||||
elif component_type == ComponentType.COMMAND:
|
||||
self._register_command_component(component_info, component_class)
|
||||
|
||||
logger.info(f"已注册{component_type.value}组件: {component_name} ({component_class.__name__})")
|
||||
logger.debug(f"已注册{component_type.value}组件: {component_name} ({component_class.__name__})")
|
||||
return True
|
||||
|
||||
def _register_action_component(self, action_info: ActionInfo, action_class: Type):
|
||||
@@ -185,7 +185,7 @@ class ComponentRegistry:
|
||||
return False
|
||||
|
||||
self._plugins[plugin_name] = plugin_info
|
||||
logger.info(f"已注册插件: {plugin_name} (组件数量: {len(plugin_info.components)})")
|
||||
logger.debug(f"已注册插件: {plugin_name} (组件数量: {len(plugin_info.components)})")
|
||||
return True
|
||||
|
||||
def get_plugin_info(self, plugin_name: str) -> Optional[PluginInfo]:
|
||||
@@ -215,7 +215,7 @@ class ComponentRegistry:
|
||||
component_info = self._components[component_name]
|
||||
if isinstance(component_info, ActionInfo):
|
||||
self._default_actions[component_name] = component_info.description
|
||||
logger.info(f"已启用组件: {component_name}")
|
||||
logger.debug(f"已启用组件: {component_name}")
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -226,7 +226,7 @@ class ComponentRegistry:
|
||||
# 如果是Action,从默认动作集中移除
|
||||
if component_name in self._default_actions:
|
||||
del self._default_actions[component_name]
|
||||
logger.info(f"已禁用组件: {component_name}")
|
||||
logger.debug(f"已禁用组件: {component_name}")
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
from typing import Dict, List, Optional, Any
|
||||
from typing import Dict, List, Optional, Any, TYPE_CHECKING
|
||||
import os
|
||||
import importlib
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from src.plugin_system.base.base_plugin import BasePlugin
|
||||
|
||||
from src.common.logger_manager import get_logger
|
||||
from src.plugin_system.core.component_registry import component_registry
|
||||
from src.plugin_system.base.component_types import PluginInfo, ComponentType
|
||||
from src.plugin_system.base.component_types import ComponentType, PluginInfo
|
||||
|
||||
logger = get_logger("plugin_manager")
|
||||
|
||||
@@ -18,8 +22,9 @@ class PluginManager:
|
||||
|
||||
def __init__(self):
|
||||
self.plugin_directories: List[str] = []
|
||||
self.loaded_plugins: Dict[str, Any] = {}
|
||||
self.loaded_plugins: Dict[str, 'BasePlugin'] = {}
|
||||
self.failed_plugins: Dict[str, str] = {}
|
||||
self.plugin_paths: Dict[str, str] = {} # 记录插件名到目录路径的映射
|
||||
|
||||
logger.info("插件管理器初始化完成")
|
||||
|
||||
@@ -27,7 +32,7 @@ class PluginManager:
|
||||
"""添加插件目录"""
|
||||
if os.path.exists(directory):
|
||||
self.plugin_directories.append(directory)
|
||||
logger.info(f"已添加插件目录: {directory}")
|
||||
logger.debug(f"已添加插件目录: {directory}")
|
||||
else:
|
||||
logger.warning(f"插件目录不存在: {directory}")
|
||||
|
||||
@@ -37,7 +42,7 @@ class PluginManager:
|
||||
Returns:
|
||||
tuple[int, int]: (插件数量, 组件数量)
|
||||
"""
|
||||
logger.info("开始加载所有插件...")
|
||||
logger.debug("开始加载所有插件...")
|
||||
|
||||
# 第一阶段:加载所有插件模块(注册插件类)
|
||||
total_loaded_modules = 0
|
||||
@@ -48,7 +53,7 @@ class PluginManager:
|
||||
total_loaded_modules += loaded
|
||||
total_failed_modules += failed
|
||||
|
||||
logger.info(f"插件模块加载完成 - 成功: {total_loaded_modules}, 失败: {total_failed_modules}")
|
||||
logger.debug(f"插件模块加载完成 - 成功: {total_loaded_modules}, 失败: {total_failed_modules}")
|
||||
|
||||
# 第二阶段:实例化所有已注册的插件类
|
||||
from src.plugin_system.base.base_plugin import get_registered_plugin_classes, instantiate_and_register_plugin
|
||||
@@ -58,24 +63,109 @@ class PluginManager:
|
||||
total_failed_registration = 0
|
||||
|
||||
for plugin_name, plugin_class in plugin_classes.items():
|
||||
# 尝试找到插件对应的目录
|
||||
# 使用记录的插件目录路径
|
||||
plugin_dir = self.plugin_paths.get(plugin_name)
|
||||
|
||||
# 如果没有记录,则尝试查找(fallback)
|
||||
if not plugin_dir:
|
||||
plugin_dir = self._find_plugin_directory(plugin_class)
|
||||
if plugin_dir:
|
||||
self.plugin_paths[plugin_name] = plugin_dir
|
||||
|
||||
if instantiate_and_register_plugin(plugin_class, plugin_dir):
|
||||
total_registered += 1
|
||||
self.loaded_plugins[plugin_name] = plugin_class
|
||||
|
||||
# 📊 显示插件详细信息
|
||||
plugin_info = component_registry.get_plugin_info(plugin_name)
|
||||
if plugin_info:
|
||||
component_count = len(plugin_info.components)
|
||||
component_types = {}
|
||||
for comp in plugin_info.components:
|
||||
comp_type = comp.component_type.name
|
||||
component_types[comp_type] = component_types.get(comp_type, 0) + 1
|
||||
|
||||
components_str = ", ".join([f"{count}个{ctype}" for ctype, count in component_types.items()])
|
||||
logger.info(f"✅ 插件加载成功: {plugin_name} v{plugin_info.version} ({components_str}) - {plugin_info.description}")
|
||||
else:
|
||||
logger.info(f"✅ 插件加载成功: {plugin_name}")
|
||||
else:
|
||||
total_failed_registration += 1
|
||||
self.failed_plugins[plugin_name] = "插件注册失败"
|
||||
|
||||
logger.info(f"插件注册完成 - 成功: {total_registered}, 失败: {total_failed_registration}")
|
||||
logger.error(f"❌ 插件加载失败: {plugin_name}")
|
||||
|
||||
# 获取组件统计信息
|
||||
stats = component_registry.get_registry_stats()
|
||||
logger.info(f"组件注册统计: {stats}")
|
||||
|
||||
# 📋 显示插件加载总览
|
||||
if total_registered > 0:
|
||||
action_count = stats.get('action_components', 0)
|
||||
command_count = stats.get('command_components', 0)
|
||||
total_components = stats.get('total_components', 0)
|
||||
|
||||
logger.info("🎉 插件系统加载完成!")
|
||||
logger.info(f"📊 总览: {total_registered}个插件, {total_components}个组件 (Action: {action_count}, Command: {command_count})")
|
||||
|
||||
# 显示详细的插件列表
|
||||
logger.info("📋 已加载插件详情:")
|
||||
for plugin_name, plugin_class in self.loaded_plugins.items():
|
||||
plugin_info = component_registry.get_plugin_info(plugin_name)
|
||||
if plugin_info:
|
||||
# 插件基本信息
|
||||
version_info = f"v{plugin_info.version}" if plugin_info.version else ""
|
||||
author_info = f"by {plugin_info.author}" if plugin_info.author else ""
|
||||
info_parts = [part for part in [version_info, author_info] if part]
|
||||
extra_info = f" ({', '.join(info_parts)})" if info_parts else ""
|
||||
|
||||
logger.info(f" 📦 {plugin_name}{extra_info}")
|
||||
|
||||
# 组件列表
|
||||
if plugin_info.components:
|
||||
action_components = [c for c in plugin_info.components if c.component_type.name == 'ACTION']
|
||||
command_components = [c for c in plugin_info.components if c.component_type.name == 'COMMAND']
|
||||
|
||||
if action_components:
|
||||
action_names = [c.name for c in action_components]
|
||||
logger.info(f" 🎯 Action组件: {', '.join(action_names)}")
|
||||
|
||||
if command_components:
|
||||
command_names = [c.name for c in command_components]
|
||||
logger.info(f" ⚡ Command组件: {', '.join(command_names)}")
|
||||
|
||||
# 依赖信息
|
||||
if plugin_info.dependencies:
|
||||
logger.info(f" 🔗 依赖: {', '.join(plugin_info.dependencies)}")
|
||||
|
||||
# 配置文件信息
|
||||
if plugin_info.config_file:
|
||||
config_status = "✅" if self.plugin_paths.get(plugin_name) else "❌"
|
||||
logger.info(f" ⚙️ 配置: {plugin_info.config_file} {config_status}")
|
||||
|
||||
# 显示目录统计
|
||||
logger.info("📂 加载目录统计:")
|
||||
for directory in self.plugin_directories:
|
||||
if os.path.exists(directory):
|
||||
plugins_in_dir = []
|
||||
for plugin_name in self.loaded_plugins.keys():
|
||||
plugin_path = self.plugin_paths.get(plugin_name, "")
|
||||
if plugin_path.startswith(directory):
|
||||
plugins_in_dir.append(plugin_name)
|
||||
|
||||
if plugins_in_dir:
|
||||
logger.info(f" 📁 {directory}: {len(plugins_in_dir)}个插件 ({', '.join(plugins_in_dir)})")
|
||||
else:
|
||||
logger.info(f" 📁 {directory}: 0个插件")
|
||||
|
||||
# 失败信息
|
||||
if total_failed_registration > 0:
|
||||
logger.info(f"⚠️ 失败统计: {total_failed_registration}个插件加载失败")
|
||||
for failed_plugin, error in self.failed_plugins.items():
|
||||
logger.info(f" ❌ {failed_plugin}: {error}")
|
||||
else:
|
||||
logger.warning("😕 没有成功加载任何插件")
|
||||
|
||||
# 返回插件数量和组件数量
|
||||
return total_registered, stats.get("total_components", 0)
|
||||
return total_registered, total_components
|
||||
|
||||
def _find_plugin_directory(self, plugin_class) -> Optional[str]:
|
||||
"""查找插件类对应的目录路径"""
|
||||
@@ -85,8 +175,8 @@ class PluginManager:
|
||||
module = inspect.getmodule(plugin_class)
|
||||
if module and hasattr(module, "__file__") and module.__file__:
|
||||
return os.path.dirname(module.__file__)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.debug(f"通过inspect获取插件目录失败: {e}")
|
||||
return None
|
||||
|
||||
def _load_plugin_modules_from_directory(self, directory: str) -> tuple[int, int]:
|
||||
@@ -98,7 +188,7 @@ class PluginManager:
|
||||
logger.warning(f"插件目录不存在: {directory}")
|
||||
return loaded_count, failed_count
|
||||
|
||||
logger.info(f"正在扫描插件目录: {directory}")
|
||||
logger.debug(f"正在扫描插件目录: {directory}")
|
||||
|
||||
# 遍历目录中的所有Python文件和包
|
||||
for item in os.listdir(directory):
|
||||
@@ -106,7 +196,8 @@ class PluginManager:
|
||||
|
||||
if os.path.isfile(item_path) and item.endswith(".py") and item != "__init__.py":
|
||||
# 单文件插件
|
||||
if self._load_plugin_module_file(item_path):
|
||||
plugin_name = Path(item_path).stem
|
||||
if self._load_plugin_module_file(item_path, plugin_name, directory):
|
||||
loaded_count += 1
|
||||
else:
|
||||
failed_count += 1
|
||||
@@ -115,17 +206,22 @@ class PluginManager:
|
||||
# 插件包
|
||||
plugin_file = os.path.join(item_path, "plugin.py")
|
||||
if os.path.exists(plugin_file):
|
||||
if self._load_plugin_module_file(plugin_file):
|
||||
plugin_name = item # 使用目录名作为插件名
|
||||
if self._load_plugin_module_file(plugin_file, plugin_name, item_path):
|
||||
loaded_count += 1
|
||||
else:
|
||||
failed_count += 1
|
||||
|
||||
return loaded_count, failed_count
|
||||
|
||||
def _load_plugin_module_file(self, plugin_file: str) -> bool:
|
||||
"""加载单个插件模块文件"""
|
||||
plugin_name = None
|
||||
def _load_plugin_module_file(self, plugin_file: str, plugin_name: str, plugin_dir: str) -> bool:
|
||||
"""加载单个插件模块文件
|
||||
|
||||
Args:
|
||||
plugin_file: 插件文件路径
|
||||
plugin_name: 插件名称
|
||||
plugin_dir: 插件目录路径
|
||||
"""
|
||||
# 生成模块名
|
||||
plugin_path = Path(plugin_file)
|
||||
if plugin_path.parent.name != "plugins":
|
||||
@@ -145,8 +241,8 @@ class PluginManager:
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
|
||||
# 模块加载成功,插件类会自动通过装饰器注册
|
||||
plugin_name = plugin_path.parent.name if plugin_path.parent.name != "plugins" else plugin_path.stem
|
||||
# 记录插件名和目录路径的映射
|
||||
self.plugin_paths[plugin_name] = plugin_dir
|
||||
|
||||
logger.debug(f"插件模块加载成功: {plugin_file}")
|
||||
return True
|
||||
@@ -154,7 +250,6 @@ class PluginManager:
|
||||
except Exception as e:
|
||||
error_msg = f"加载插件模块 {plugin_file} 失败: {e}"
|
||||
logger.error(error_msg)
|
||||
if plugin_name:
|
||||
self.failed_plugins[plugin_name] = error_msg
|
||||
return False
|
||||
|
||||
@@ -174,7 +269,7 @@ class PluginManager:
|
||||
# 启用插件的所有组件
|
||||
for component in plugin_info.components:
|
||||
component_registry.enable_component(component.name)
|
||||
logger.info(f"已启用插件: {plugin_name}")
|
||||
logger.debug(f"已启用插件: {plugin_name}")
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -186,7 +281,7 @@ class PluginManager:
|
||||
# 禁用插件的所有组件
|
||||
for component in plugin_info.components:
|
||||
component_registry.disable_component(component.name)
|
||||
logger.info(f"已禁用插件: {plugin_name}")
|
||||
logger.debug(f"已禁用插件: {plugin_name}")
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
27
src/plugins/built_in/core_actions/config.toml
Normal file
27
src/plugins/built_in/core_actions/config.toml
Normal 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 = ""
|
||||
395
src/plugins/built_in/core_actions/plugin.py
Normal file
395
src/plugins/built_in/core_actions/plugin.py
Normal 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)
|
||||
]
|
||||
@@ -41,11 +41,10 @@ class HelloAction(BaseAction):
|
||||
"""执行问候动作"""
|
||||
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:
|
||||
@@ -118,10 +117,9 @@ 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)}"
|
||||
|
||||
Reference in New Issue
Block a user