This commit is contained in:
SengokuCola
2025-07-25 15:45:27 +08:00
10 changed files with 94 additions and 713 deletions

View File

@@ -268,7 +268,6 @@ action_message为一个字典包含的键值对如下省略了不必要的
## Action 内置方法说明 ## Action 内置方法说明
```python ```python
class BaseAction: class BaseAction:
# 配置相关
def get_config(self, key: str, default=None): def get_config(self, key: str, default=None):
"""获取插件配置值,使用嵌套键访问""" """获取插件配置值,使用嵌套键访问"""

View File

@@ -2,7 +2,9 @@
## 📖 什么是Command ## 📖 什么是Command
Command是直接响应用户明确指令的组件与Action不同Command是**被动触发**的,当用户输入特定格式的命令时立即执行。Command通过正则表达式匹配用户输入提供确定性的功能服务。 Command是直接响应用户明确指令的组件与Action不同Command是**被动触发**的,当用户输入特定格式的命令时立即执行。
Command通过正则表达式匹配用户输入提供确定性的功能服务。
### 🎯 Command的特点 ### 🎯 Command的特点
@@ -12,501 +14,76 @@ Command是直接响应用户明确指令的组件与Action不同Command是
- 🛑 **拦截控制**:可以控制是否阻止消息继续处理 - 🛑 **拦截控制**:可以控制是否阻止消息继续处理
- 📝 **参数解析**:支持从用户输入中提取参数 - 📝 **参数解析**:支持从用户输入中提取参数
## 🆚 Action vs Command 核心区别 ---
| 特征 | Action | Command | ## 🛠️ Command组件的基本结构
| ------------------ | --------------------- | ---------------- |
| **触发方式** | 麦麦主动决策使用 | 用户主动触发 |
| **决策机制** | 两层决策(激活+使用) | 直接匹配执行 |
| **随机性** | 有随机性和智能性 | 确定性执行 |
| **用途** | 增强麦麦行为拟人化 | 提供具体功能服务 |
| **性能影响** | 需要LLM决策 | 正则匹配,性能好 |
## 🏗️ Command基本结构 首先Command组件需要继承自`BaseCommand`类,并实现必要的方法。
### 必须属性
```python ```python
from src.plugin_system import BaseCommand class ExampleCommand(BaseCommand):
command_name = "example" # 命令名称,作为唯一标识符
command_description = "这是一个示例命令" # 命令描述
command_pattern = r"" # 命令匹配的正则表达式
class MyCommand(BaseCommand): async def execute(self) -> Tuple[bool, Optional[str], bool]:
# 正则表达式匹配模式 """
command_pattern = r"^/help\s+(?P<topic>\w+)$" 执行Command的主要逻辑
# 命令帮助说明 Returns:
command_help = "显示指定主题的帮助信息" Tuple[bool, str, bool]:
- 第一个bool表示是否成功执行
# 使用示例 - 第二个str是执行结果消息
command_examples = ["/help action", "/help command"] - 第三个bool表示是否需要阻止消息继续处理
"""
# 是否拦截后续处理 # ---- 执行命令的逻辑 ----
intercept_message = True return True, "执行成功", False
async def execute(self) -> Tuple[bool, Optional[str]]:
"""执行命令逻辑"""
# 命令执行逻辑
return True, "执行成功"
``` ```
**`command_pattern`**: 该Command匹配的正则表达式用于精确匹配用户输入。
### 属性说明 请注意:如果希望能获取到命令中的参数,请在正则表达式中使用有命名的捕获组,例如`(?P<param_name>pattern)`
| 属性 | 类型 | 说明 | 这样在匹配时,内部实现可以使用`re.match.groupdict()`方法获取到所有捕获组的参数,并以字典的形式存储在`self.matched_groups`中。
| --------------------- | --------- | -------------------- |
| `command_pattern` | str | 正则表达式匹配模式 |
| `command_help` | str | 命令帮助说明 |
| `command_examples` | List[str] | 使用示例列表 |
| `intercept_message` | bool | 是否拦截消息继续处理 |
## 🔍 正则表达式匹配 ### 匹配样例
假设我们有一个命令`/example param1=value1 param2=value2`,对应的正则表达式可以是:
### 基础匹配
```python ```python
class SimpleCommand(BaseCommand): class ExampleCommand(BaseCommand):
# 匹配 /ping command_name = "example"
command_pattern = r"^/ping$" command_description = "这是一个示例命令"
command_pattern = r"/example (?P<param1>\w+) (?P<param2>\w+)"
async def execute(self) -> Tuple[bool, Optional[str]]:
await self.send_text("Pong!") async def execute(self) -> Tuple[bool, Optional[str], bool]:
return True, "发送了Pong回复" # 获取匹配的参数
``` param1 = self.matched_groups.get("param1")
param2 = self.matched_groups.get("param2")
### 参数捕获
使用命名组 `(?P<n>pattern)` 捕获参数:
```python
class UserCommand(BaseCommand):
# 匹配 /user add 张三 或 /user del 李四
command_pattern = r"^/user\s+(?P<action>add|del|info)\s+(?P<username>\w+)$"
async def execute(self) -> Tuple[bool, Optional[str]]:
# 通过 self.matched_groups 获取捕获的参数
action = self.matched_groups.get("action")
username = self.matched_groups.get("username")
if action == "add":
await self.send_text(f"添加用户:{username}")
elif action == "del":
await self.send_text(f"删除用户:{username}")
elif action == "info":
await self.send_text(f"用户信息:{username}")
return True, f"执行了{action}操作"
```
### 可选参数
```python
class HelpCommand(BaseCommand):
# 匹配 /help 或 /help topic
command_pattern = r"^/help(?:\s+(?P<topic>\w+))?$"
async def execute(self) -> Tuple[bool, Optional[str]]:
topic = self.matched_groups.get("topic")
if topic:
await self.send_text(f"显示{topic}的帮助")
else:
await self.send_text("显示总体帮助")
return True, "显示了帮助信息"
```
## 🛑 拦截控制详解
### 拦截消息 (intercept_message = True)
```python
class AdminCommand(BaseCommand):
command_pattern = r"^/admin\s+.+"
command_help = "管理员命令"
intercept_message = True # 拦截,不继续处理
async def execute(self) -> Tuple[bool, Optional[str]]:
# 执行管理操作
await self.send_text("执行管理命令")
# 消息不会继续传递给其他组件
return True, "管理命令执行完成"
```
### 不拦截消息 (intercept_message = False)
```python
class LogCommand(BaseCommand):
command_pattern = r"^/log\s+.+"
command_help = "记录日志"
intercept_message = False # 不拦截,继续处理
async def execute(self) -> Tuple[bool, Optional[str]]:
# 记录日志但不阻止后续处理
await self.send_text("已记录到日志")
# 消息会继续传递可能触发Action等其他组件
return True, "日志记录完成"
```
### 拦截控制的用途
| 场景 | intercept_message | 说明 |
| -------- | ----------------- | -------------------------- |
| 系统命令 | True | 防止命令被当作普通消息处理 |
| 查询命令 | True | 直接返回结果,无需后续处理 |
| 日志命令 | False | 记录但允许消息继续流转 |
| 监控命令 | False | 监控但不影响正常聊天 |
## 🎨 完整Command示例
### 用户管理Command
```python
from src.plugin_system import BaseCommand
from typing import Tuple, Optional
class UserManagementCommand(BaseCommand):
"""用户管理Command - 展示复杂参数处理"""
command_pattern = r"^/user\s+(?P<action>add|del|list|info)\s*(?P<username>\w+)?(?:\s+--(?P<options>.+))?$"
command_help = "用户管理命令,支持添加、删除、列表、信息查询"
command_examples = [
"/user add 张三",
"/user del 李四",
"/user list",
"/user info 王五",
"/user add 赵六 --role=admin"
]
intercept_message = True
async def execute(self) -> Tuple[bool, Optional[str]]:
"""执行用户管理命令"""
try:
action = self.matched_groups.get("action")
username = self.matched_groups.get("username")
options = self.matched_groups.get("options")
# 解析选项
parsed_options = self._parse_options(options) if options else {}
if action == "add":
return await self._add_user(username, parsed_options)
elif action == "del":
return await self._delete_user(username)
elif action == "list":
return await self._list_users()
elif action == "info":
return await self._show_user_info(username)
else:
await self.send_text("❌ 不支持的操作")
return False, f"不支持的操作: {action}"
except Exception as e:
await self.send_text(f"❌ 命令执行失败: {str(e)}")
return False, f"执行失败: {e}"
def _parse_options(self, options_str: str) -> dict:
"""解析命令选项"""
options = {}
if options_str:
for opt in options_str.split():
if "=" in opt:
key, value = opt.split("=", 1)
options[key] = value
return options
async def _add_user(self, username: str, options: dict) -> Tuple[bool, str]:
"""添加用户"""
if not username:
await self.send_text("❌ 请指定用户名")
return False, "缺少用户名参数"
# 检查用户是否已存在
existing_users = await self._get_user_list()
if username in existing_users:
await self.send_text(f"❌ 用户 {username} 已存在")
return False, f"用户已存在: {username}"
# 添加用户逻辑
role = options.get("role", "user")
await self.send_text(f"✅ 成功添加用户 {username},角色: {role}")
return True, f"添加用户成功: {username}"
async def _delete_user(self, username: str) -> Tuple[bool, str]:
"""删除用户"""
if not username:
await self.send_text("❌ 请指定用户名")
return False, "缺少用户名参数"
await self.send_text(f"✅ 用户 {username} 已删除")
return True, f"删除用户成功: {username}"
async def _list_users(self) -> Tuple[bool, str]:
"""列出所有用户"""
users = await self._get_user_list()
if users:
user_list = "\n".join([f"• {user}" for user in users])
await self.send_text(f"📋 用户列表:\n{user_list}")
else:
await self.send_text("📋 暂无用户")
return True, "显示用户列表"
async def _show_user_info(self, username: str) -> Tuple[bool, str]:
"""显示用户信息"""
if not username:
await self.send_text("❌ 请指定用户名")
return False, "缺少用户名参数"
# 模拟用户信息
user_info = f"""
👤 用户信息: {username}
📧 邮箱: {username}@example.com
🕒 注册时间: 2024-01-01
🎯 角色: 普通用户
""".strip()
await self.send_text(user_info)
return True, f"显示用户信息: {username}"
async def _get_user_list(self) -> list:
"""获取用户列表(示例)"""
return ["张三", "李四", "王五"]
```
### 系统信息Command
```python
class SystemInfoCommand(BaseCommand):
"""系统信息Command - 展示系统查询功能"""
command_pattern = r"^/(?:status|info)(?:\s+(?P<type>system|memory|plugins|all))?$"
command_help = "查询系统状态信息"
command_examples = [
"/status",
"/info system",
"/status memory",
"/info plugins"
]
intercept_message = True
async def execute(self) -> Tuple[bool, Optional[str]]:
"""执行系统信息查询"""
info_type = self.matched_groups.get("type", "all")
try:
if info_type in ["system", "all"]:
await self._show_system_info()
if info_type in ["memory", "all"]:
await self._show_memory_info()
if info_type in ["plugins", "all"]:
await self._show_plugin_info()
return True, f"显示了{info_type}类型的系统信息"
except Exception as e:
await self.send_text(f"❌ 获取系统信息失败: {str(e)}")
return False, f"查询失败: {e}"
async def _show_system_info(self):
"""显示系统信息"""
import platform
import datetime
system_info = f"""
🖥️ **系统信息**
📱 平台: {platform.system()} {platform.release()}
🐍 Python: {platform.python_version()}
⏰ 运行时间: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
""".strip()
await self.send_text(system_info)
async def _show_memory_info(self):
"""显示内存信息"""
import psutil
memory = psutil.virtual_memory()
memory_info = f"""
💾 **内存信息**
📊 总内存: {memory.total // (1024**3)} GB
🟢 可用内存: {memory.available // (1024**3)} GB
📈 使用率: {memory.percent}%
""".strip()
await self.send_text(memory_info)
async def _show_plugin_info(self):
"""显示插件信息"""
# 通过配置获取插件信息
plugins = await self._get_loaded_plugins()
plugin_info = f"""
🔌 **插件信息**
📦 已加载插件: {len(plugins)}
🔧 活跃插件: {len([p for p in plugins if p.get('active', False)])}
""".strip()
await self.send_text(plugin_info)
async def _get_loaded_plugins(self) -> list:
"""获取已加载的插件列表"""
# 这里可以通过配置或API获取实际的插件信息
return [
{"name": "core_actions", "active": True},
{"name": "example_plugin", "active": True},
]
```
### 自定义前缀Command
```python
class CustomPrefixCommand(BaseCommand):
"""自定义前缀Command - 展示非/前缀的命令"""
# 使用!前缀而不是/前缀
command_pattern = r"^[!](?P<command>roll|dice)\s*(?P<count>\d+)?$"
command_help = "骰子命令,使用!前缀"
command_examples = ["!roll", "!dice 6", "roll 20"]
intercept_message = True
async def execute(self) -> Tuple[bool, Optional[str]]:
"""执行骰子命令"""
import random
command = self.matched_groups.get("command")
count = int(self.matched_groups.get("count", "6"))
# 限制骰子面数
if count > 100:
await self.send_text("❌ 骰子面数不能超过100")
return False, "骰子面数超限"
result = random.randint(1, count)
await self.send_text(f"🎲 投掷{count}面骰子,结果: {result}")
return True, f"投掷了{count}面骰子,结果{result}"
```
## 📊 性能优化建议
### 1. 正则表达式优化
```python
# ✅ 好的做法 - 简单直接
command_pattern = r"^/ping$"
# ❌ 避免 - 过于复杂
command_pattern = r"^/(?:ping|pong|test|check|status|info|help|...)"
# ✅ 好的做法 - 分离复杂逻辑
```
### 2. 参数验证
```python
# ✅ 好的做法 - 早期验证
async def execute(self) -> Tuple[bool, Optional[str]]:
username = self.matched_groups.get("username")
if not username:
await self.send_text("❌ 请提供用户名")
return False, "缺少参数"
# 继续处理...
```
### 3. 错误处理
```python
# ✅ 好的做法 - 完整错误处理
async def execute(self) -> Tuple[bool, Optional[str]]:
try:
# 主要逻辑
result = await self._process_command()
return True, "执行成功"
except ValueError as e:
await self.send_text(f"❌ 参数错误: {e}")
return False, f"参数错误: {e}"
except Exception as e:
await self.send_text(f"❌ 执行失败: {e}")
return False, f"执行失败: {e}"
```
## 🎯 最佳实践
### 1. 命令设计原则
```python
# ✅ 好的命令设计
"/user add 张三" # 动作 + 对象 + 参数
"/config set key=value" # 动作 + 子动作 + 参数
"/help command" # 动作 + 可选参数
# ❌ 避免的设计
"/add_user_with_name_张三" # 过于冗长
"/u a 张三" # 过于简写
```
### 2. 帮助信息
```python
class WellDocumentedCommand(BaseCommand):
command_pattern = r"^/example\s+(?P<param>\w+)$"
command_help = "示例命令:处理指定参数并返回结果"
command_examples = [
"/example test",
"/example debug",
"/example production"
]
```
### 3. 错误处理
```python
async def execute(self) -> Tuple[bool, Optional[str]]:
param = self.matched_groups.get("param")
# 参数验证
if param not in ["test", "debug", "production"]:
await self.send_text("❌ 无效的参数,支持: test, debug, production")
return False, "无效参数"
# 执行逻辑 # 执行逻辑
try: return True, f"参数1: {param1}, 参数2: {param2}", False
result = await self._process_param(param)
await self.send_text(f"✅ 处理完成: {result}")
return True, f"处理{param}成功"
except Exception as e:
await self.send_text("❌ 处理失败,请稍后重试")
return False, f"处理失败: {e}"
``` ```
### 4. 配置集成 ---
## Command 内置方法说明
```python ```python
async def execute(self) -> Tuple[bool, Optional[str]]: class BaseCommand:
# 从配置读取设置 def get_config(self, key: str, default=None):
max_items = self.get_config("command.max_items", 10) """获取插件配置值,使用嵌套键访问"""
timeout = self.get_config("command.timeout", 30)
# 使用配置进行处理 async def send_text(self, content: str, reply_to: str = "") -> bool:
... """发送回复消息"""
async def send_type(self, message_type: str, content: str, display_message: str = "", typing: bool = False, reply_to: str = "") -> bool:
"""发送指定类型的回复消息到当前聊天环境"""
async def send_command(self, command_name: str, args: Optional[dict] = None, display_message: str = "", storage_message: bool = True) -> bool:
"""发送命令消息"""
async def send_emoji(self, emoji_base64: str) -> bool:
"""发送表情包"""
async def send_image(self, image_base64: str) -> bool:
"""发送图片"""
``` ```
具体参数与用法参见`BaseCommand`基类的定义。
## 📝 Command vs Action 选择指南
### 使用Command的场景
- ✅ 用户需要明确调用特定功能
- ✅ 需要精确的参数控制
- ✅ 管理和配置操作
- ✅ 查询和信息显示
- ✅ 系统维护命令
### 使用Action的场景
- ✅ 增强麦麦的智能行为
- ✅ 根据上下文自动触发
- ✅ 情绪和表情表达
- ✅ 智能建议和帮助
- ✅ 随机化的互动

View File

@@ -147,7 +147,7 @@ python scripts/manifest_tool.py validate src/plugins/my_plugin
## 📋 字段说明 ## 📋 字段说明
### 基本信息 ### 基本信息
- `manifest_version`: manifest格式版本当前为3 - `manifest_version`: manifest格式版本当前为1
- `name`: 插件显示名称(必需) - `name`: 插件显示名称(必需)
- `version`: 插件版本号(必需) - `version`: 插件版本号(必需)
- `description`: 插件功能描述(必需) - `description`: 插件功能描述(必需)
@@ -165,10 +165,12 @@ python scripts/manifest_tool.py validate src/plugins/my_plugin
- `categories`: 分类数组(可选,建议填写) - `categories`: 分类数组(可选,建议填写)
### 兼容性 ### 兼容性
- `host_application`: 主机应用兼容性(可选) - `host_application`: 主机应用兼容性(可选,建议填写
- `min_version`: 最低兼容版本 - `min_version`: 最低兼容版本
- `max_version`: 最高兼容版本 - `max_version`: 最高兼容版本
⚠️ 在不填写的情况下,插件将默认支持所有版本。**(由于我们在不同版本对插件系统进行了大量的重构,这种情况几乎不可能。)**
### 国际化 ### 国际化
- `default_locale`: 默认语言(可选) - `default_locale`: 默认语言(可选)
- `locales_path`: 语言文件目录(可选) - `locales_path`: 语言文件目录(可选)
@@ -185,24 +187,13 @@ python scripts/manifest_tool.py validate src/plugins/my_plugin
2. **编码格式**manifest文件必须使用UTF-8编码 2. **编码格式**manifest文件必须使用UTF-8编码
3. **JSON格式**文件必须是有效的JSON格式 3. **JSON格式**文件必须是有效的JSON格式
4. **必需字段**`manifest_version`、`name`、`version`、`description`、`author.name`是必需的 4. **必需字段**`manifest_version`、`name`、`version`、`description`、`author.name`是必需的
5. **版本兼容**当前只支持manifest_version = 3 5. **版本兼容**:当前只支持`manifest_version = 1`
## 🔍 常见问题 ## 🔍 常见问题
### Q: 为什么要强制要求manifest文件
A: Manifest文件提供了插件的标准化元数据使得插件管理、依赖检查、版本兼容性验证等功能成为可能。
### Q: 可以不填写可选字段吗? ### Q: 可以不填写可选字段吗?
A: 可以。所有标记为"可选"的字段都可以不填写,但建议至少填写`license`和`keywords`。 A: 可以。所有标记为"可选"的字段都可以不填写,但建议至少填写`license`和`keywords`。
### Q: 如何快速为所有插件创建manifest
A: 可以编写脚本批量处理:
```bash
# 扫描并为每个缺少manifest的插件创建最小化manifest
python scripts/manifest_tool.py scan src/plugins
# 然后手动为每个插件运行create-minimal命令
```
### Q: manifest验证失败怎么办 ### Q: manifest验证失败怎么办
A: 根据验证器的错误提示修复相应问题。错误会导致插件加载失败,警告不会。 A: 根据验证器的错误提示修复相应问题。错误会导致插件加载失败,警告不会。
@@ -210,5 +201,5 @@ A: 根据验证器的错误提示修复相应问题。错误会导致插件加
查看内置插件的manifest文件作为参考 查看内置插件的manifest文件作为参考
- `src/plugins/built_in/core_actions/_manifest.json` - `src/plugins/built_in/core_actions/_manifest.json`
- `src/plugins/built_in/doubao_pic_plugin/_manifest.json`
- `src/plugins/built_in/tts_plugin/_manifest.json` - `src/plugins/built_in/tts_plugin/_manifest.json`
- `src/plugins/hello_world_plugin/_manifest.json`

View File

@@ -2,12 +2,11 @@
## 📖 什么是工具系统 ## 📖 什么是工具系统
工具系统是MaiBot的信息获取能力扩展组件**专门用于在Focus模式下扩宽麦麦能够获得的信息量**。如果说Action组件功能五花八门可以拓展麦麦能做的事情那么Tool就是在某个过程中拓宽了麦麦能够获得的信息量。 工具系统是MaiBot的信息获取能力扩展组件。如果说Action组件功能五花八门可以拓展麦麦能做的事情那么Tool就是在某个过程中拓宽了麦麦能够获得的信息量。
### 🎯 工具系统的特点 ### 🎯 工具系统的特点
- 🔍 **信息获取增强**:扩展麦麦获取外部信息的能力 - 🔍 **信息获取增强**:扩展麦麦获取外部信息的能力
- 🎯 **Focus模式专用**:仅在专注聊天模式下工作,必须开启工具处理器
- 📊 **数据丰富**:帮助麦麦获得更多背景信息和实时数据 - 📊 **数据丰富**:帮助麦麦获得更多背景信息和实时数据
- 🔌 **插件式架构**:支持独立开发和注册新工具 - 🔌 **插件式架构**:支持独立开发和注册新工具
-**自动发现**:工具会被系统自动识别和注册 -**自动发现**:工具会被系统自动识别和注册
@@ -17,7 +16,6 @@
| 特征 | Action | Command | Tool | | 特征 | Action | Command | Tool |
|-----|-------|---------|------| |-----|-------|---------|------|
| **主要用途** | 扩展麦麦行为能力 | 响应用户指令 | 扩展麦麦信息获取 | | **主要用途** | 扩展麦麦行为能力 | 响应用户指令 | 扩展麦麦信息获取 |
| **适用模式** | 所有模式 | 所有模式 | 仅Focus模式 |
| **触发方式** | 麦麦智能决策 | 用户主动触发 | LLM根据需要调用 | | **触发方式** | 麦麦智能决策 | 用户主动触发 | LLM根据需要调用 |
| **目标** | 让麦麦做更多事情 | 提供具体功能 | 让麦麦知道更多信息 | | **目标** | 让麦麦做更多事情 | 提供具体功能 | 让麦麦知道更多信息 |
| **使用场景** | 增强交互体验 | 功能服务 | 信息查询和分析 | | **使用场景** | 增强交互体验 | 功能服务 | 信息查询和分析 |
@@ -54,7 +52,7 @@ class MyTool(BaseTool):
"required": ["query"] "required": ["query"]
} }
async def execute(self, function_args, message_txt=""): async def execute(self, function_args: Dict[str, Any]):
"""执行工具逻辑""" """执行工具逻辑"""
# 实现工具功能 # 实现工具功能
result = f"查询结果: {function_args.get('query')}" result = f"查询结果: {function_args.get('query')}"
@@ -63,9 +61,6 @@ class MyTool(BaseTool):
"name": self.name, "name": self.name,
"content": result "content": result
} }
# 注册工具
register_tool(MyTool)
``` ```
### 属性说明 ### 属性说明
@@ -80,7 +75,7 @@ register_tool(MyTool)
| 方法 | 参数 | 返回值 | 说明 | | 方法 | 参数 | 返回值 | 说明 |
|-----|------|--------|------| |-----|------|--------|------|
| `execute` | `function_args`, `message_txt` | `dict` | 执行工具核心逻辑 | | `execute` | `function_args` | `dict` | 执行工具核心逻辑 |
## 🔄 自动注册机制 ## 🔄 自动注册机制
@@ -88,28 +83,14 @@ register_tool(MyTool)
1. **文件扫描**:系统自动遍历 `tool_can_use` 目录中的所有Python文件 1. **文件扫描**:系统自动遍历 `tool_can_use` 目录中的所有Python文件
2. **类识别**:寻找继承自 `BaseTool` 的工具类 2. **类识别**:寻找继承自 `BaseTool` 的工具类
3. **自动注册**调用 `register_tool()` 的工具会被注册到系统中 3. **自动注册**只需要实现对应的类并把文件放在正确文件夹中就可自动注册
4. **即用即加载**:工具在需要时被实例化和调用 4. **即用即加载**:工具在需要时被实例化和调用
### 注册流程 ---
```python
# 1. 创建工具类
class WeatherTool(BaseTool):
name = "weather_query"
description = "查询指定城市的天气信息"
# ...
# 2. 注册工具(在文件末尾)
register_tool(WeatherTool)
# 3. 系统自动发现(无需手动操作)
# discover_tools() 函数会自动完成注册
```
## 🎨 完整工具示例 ## 🎨 完整工具示例
### 天气查询工具 完成一个天气查询工具
```python ```python
from src.tools.tool_can_use.base_tool import BaseTool, register_tool from src.tools.tool_can_use.base_tool import BaseTool, register_tool
@@ -192,102 +173,9 @@ class WeatherTool(BaseTool):
💧 湿度: {humidity}% 💧 湿度: {humidity}%
━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━
""".strip() """.strip()
# 注册工具
register_tool(WeatherTool)
``` ```
### 知识查询工具 ---
```python
from src.tools.tool_can_use.base_tool import BaseTool, register_tool
class KnowledgeSearchTool(BaseTool):
"""知识搜索工具 - 查询百科知识和专业信息"""
name = "knowledge_search"
description = "搜索百科知识、专业术语解释、历史事件等信息"
parameters = {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "要搜索的知识关键词或问题"
},
"category": {
"type": "string",
"description": "知识分类science(科学)、history(历史)、technology(技术)、general(通用)等",
"enum": ["science", "history", "technology", "general"]
},
"language": {
"type": "string",
"description": "结果语言zh(中文)、en(英文)",
"enum": ["zh", "en"]
}
},
"required": ["query"]
}
async def execute(self, function_args, message_txt=""):
"""执行知识搜索"""
try:
query = function_args.get("query")
category = function_args.get("category", "general")
language = function_args.get("language", "zh")
# 执行搜索逻辑
search_results = await self._search_knowledge(query, category, language)
# 格式化结果
result = self._format_search_results(query, search_results)
return {
"name": self.name,
"content": result
}
except Exception as e:
return {
"name": self.name,
"content": f"知识搜索失败: {str(e)}"
}
async def _search_knowledge(self, query: str, category: str, language: str) -> list:
"""执行知识搜索"""
# 这里实现实际的搜索逻辑
# 可以对接维基百科API、百度百科API等
# 示例返回数据
return [
{
"title": f"{query}的定义",
"summary": f"关于{query}的详细解释...",
"source": "Wikipedia"
}
]
def _format_search_results(self, query: str, results: list) -> str:
"""格式化搜索结果"""
if not results:
return f"未找到关于 '{query}' 的相关信息"
formatted_text = f"📚 关于 '{query}' 的搜索结果:\n\n"
for i, result in enumerate(results[:3], 1): # 限制显示前3条
title = result.get("title", "无标题")
summary = result.get("summary", "无摘要")
source = result.get("source", "未知来源")
formatted_text += f"{i}. **{title}**\n"
formatted_text += f" {summary}\n"
formatted_text += f" 📖 来源: {source}\n\n"
return formatted_text.strip()
# 注册工具
register_tool(KnowledgeSearchTool)
```
## 📊 工具开发步骤 ## 📊 工具开发步骤
@@ -323,86 +211,21 @@ class MyNewTool(BaseTool):
"name": self.name, "name": self.name,
"content": "执行结果" "content": "执行结果"
} }
register_tool(MyNewTool)
``` ```
### 3. 测试工具 ### 3. 系统集成
创建测试文件验证工具功能:
```python
import asyncio
from my_new_tool import MyNewTool
async def test_tool():
tool = MyNewTool()
result = await tool.execute({"param": "value"})
print(result)
asyncio.run(test_tool())
```
### 4. 系统集成
工具创建完成后,系统会自动发现和注册,无需额外配置。 工具创建完成后,系统会自动发现和注册,无需额外配置。
## ⚙️ 工具处理器配置 ---
### 启用工具处理器
工具系统仅在Focus模式下工作需要确保工具处理器已启用
```python
# 在Focus模式配置中
focus_config = {
"enable_tool_processor": True, # 必须启用
"tool_timeout": 30, # 工具执行超时时间(秒)
"max_tools_per_message": 3 # 单次消息最大工具调用数
}
```
### 工具使用流程
1. **用户发送消息**在Focus模式下发送需要信息查询的消息
2. **LLM判断需求**:麦麦分析消息,判断是否需要使用工具获取信息
3. **选择工具**:根据需求选择合适的工具
4. **调用工具**:执行工具获取信息
5. **整合回复**:将工具获取的信息整合到回复中
### 使用示例
```python
# 用户消息示例
"今天北京的天气怎么样?"
# 系统处理流程:
# 1. 麦麦识别这是天气查询需求
# 2. 调用 weather_query 工具
# 3. 获取北京天气信息
# 4. 整合信息生成回复
# 最终回复:
"根据最新天气数据北京今天晴天温度22°C湿度45%,适合外出活动。"
```
## 🚨 注意事项和限制 ## 🚨 注意事项和限制
### 当前限制 ### 当前限制
1. **模式限制**仅在Focus模式下可用 1. **独立开发**:需要单独编写,暂未完全融入插件系统
2. **独立开发**:需要单独编写,暂未完全融入插件系统 2. **适用范围**:主要适用于信息获取场景
3. **适用范围**:主要适用于信息获取场景 3. **配置要求**:必须开启工具处理器
4. **配置要求**:必须开启工具处理器
### 未来改进
工具系统在之后可能会面临以下修改:
1. **插件系统融合**:更好地集成到插件系统中
2. **模式扩展**:可能扩展到其他聊天模式
3. **配置简化**:简化配置和部署流程
4. **性能优化**:提升工具调用效率
### 开发建议 ### 开发建议

View File

@@ -60,10 +60,10 @@ class BaseCommand(ABC):
pass pass
def get_config(self, key: str, default=None): def get_config(self, key: str, default=None):
"""获取插件配置值,支持嵌套键访问 """获取插件配置值,使用嵌套键访问
Args: Args:
key: 配置键名,支持嵌套访问如 "section.subsection.key" key: 配置键名,使用嵌套访问如 "section.subsection.key"
default: 默认值 default: 默认值
Returns: Returns:

View File

@@ -163,12 +163,11 @@ class VersionComparator:
version_normalized, max_normalized version_normalized, max_normalized
) )
if is_compatible: if not is_compatible:
logger.info(f"版本兼容性检查:{compat_msg}")
return True, compat_msg
else:
return False, f"版本 {version_normalized} 高于最大支持版本 {max_normalized},且无兼容性映射" return False, f"版本 {version_normalized} 高于最大支持版本 {max_normalized},且无兼容性映射"
logger.info(f"版本兼容性检查:{compat_msg}")
return True, compat_msg
return True, "" return True, ""
@staticmethod @staticmethod
@@ -358,14 +357,10 @@ class ManifestValidator:
if self.validation_errors: if self.validation_errors:
report.append("❌ 验证错误:") report.append("❌ 验证错误:")
for error in self.validation_errors: report.extend(f" - {error}" for error in self.validation_errors)
report.append(f" - {error}")
if self.validation_warnings: if self.validation_warnings:
report.append("⚠️ 验证警告:") report.append("⚠️ 验证警告:")
for warning in self.validation_warnings: report.extend(f" - {warning}" for warning in self.validation_warnings)
report.append(f" - {warning}")
if not self.validation_errors and not self.validation_warnings: if not self.validation_errors and not self.validation_warnings:
report.append("✅ Manifest文件验证通过") report.append("✅ Manifest文件验证通过")

View File

@@ -39,11 +39,7 @@ class CompareNumbersTool(BaseTool):
else: else:
result = f"{num1} 等于 {num2}" result = f"{num1} 等于 {num2}"
return {"type": "comparison_result", "id": f"{num1}_vs_{num2}", "content": result} return {"name": self.name, "content": result}
except Exception as e: except Exception as e:
logger.error(f"比较数字失败: {str(e)}") logger.error(f"比较数字失败: {str(e)}")
return {"type": "info", "id": f"{num1}_vs_{num2}", "content": f"比较数字失败,炸了: {str(e)}"} return {"name": self.name, "content": f"比较数字失败,炸了: {str(e)}"}
# 注册工具
# register_tool(CompareNumbersTool)

View File

@@ -1,7 +1,6 @@
from src.tools.tool_can_use.base_tool import BaseTool from src.tools.tool_can_use.base_tool import BaseTool
from src.person_info.person_info import get_person_info_manager from src.person_info.person_info import get_person_info_manager
from src.common.logger import get_logger from src.common.logger import get_logger
import time
logger = get_logger("rename_person_tool") logger = get_logger("rename_person_tool")
@@ -24,7 +23,7 @@ class RenamePersonTool(BaseTool):
"required": ["person_name"], "required": ["person_name"],
} }
async def execute(self, function_args: dict, message_txt=""): async def execute(self, function_args: dict):
""" """
执行取名工具逻辑 执行取名工具逻辑
@@ -82,7 +81,7 @@ class RenamePersonTool(BaseTool):
content = f"已成功将用户 {person_name_to_find} 的备注名更新为 {new_name}" content = f"已成功将用户 {person_name_to_find} 的备注名更新为 {new_name}"
logger.info(content) logger.info(content)
return {"type": "info", "id": f"rename_success_{time.time()}", "content": content} return {"name": self.name, "content": content}
else: else:
logger.warning(f"为用户 {person_id} 调用 qv_person_name 后未能成功获取新昵称。") logger.warning(f"为用户 {person_id} 调用 qv_person_name 后未能成功获取新昵称。")
# 尝试从内存中获取可能已经更新的名字 # 尝试从内存中获取可能已经更新的名字
@@ -101,4 +100,4 @@ class RenamePersonTool(BaseTool):
except Exception as e: except Exception as e:
error_msg = f"重命名失败: {str(e)}" error_msg = f"重命名失败: {str(e)}"
logger.error(error_msg, exc_info=True) logger.error(error_msg, exc_info=True)
return {"type": "info_error", "id": f"rename_error_{time.time()}", "content": error_msg} return {"name": self.name, "content": error_msg}

View File

@@ -172,7 +172,7 @@ class ToolExecutor:
logger.debug(f"{self.log_prefix}执行工具: {tool_name}") logger.debug(f"{self.log_prefix}执行工具: {tool_name}")
# 执行工具 # 执行工具
result = await self.tool_instance._execute_tool_call(tool_call) result = await self.tool_instance.execute_tool_call(tool_call)
if result: if result:
tool_info = { tool_info = {
@@ -299,7 +299,7 @@ class ToolExecutor:
logger.info(f"{self.log_prefix}直接执行工具: {tool_name}") logger.info(f"{self.log_prefix}直接执行工具: {tool_name}")
result = await self.tool_instance._execute_tool_call(tool_call) result = await self.tool_instance.execute_tool_call(tool_call)
if result: if result:
tool_info = { tool_info = {

View File

@@ -16,7 +16,8 @@ class ToolUser:
return get_all_tool_definitions() return get_all_tool_definitions()
@staticmethod @staticmethod
async def _execute_tool_call(tool_call): async def execute_tool_call(tool_call):
# sourcery skip: use-assigned-variable
"""执行特定的工具调用 """执行特定的工具调用
Args: Args: