From 6a7cf71d1d6de8991a41b7a8c4c0964a7be4fe5c Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Fri, 25 Jul 2025 14:06:41 +0800 Subject: [PATCH 1/4] =?UTF-8?q?command=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/plugins/action-components.md | 1 - docs/plugins/command-components.md | 543 ++++------------------------- 2 files changed, 60 insertions(+), 484 deletions(-) diff --git a/docs/plugins/action-components.md b/docs/plugins/action-components.md index 3953c79c2..4c844df85 100644 --- a/docs/plugins/action-components.md +++ b/docs/plugins/action-components.md @@ -268,7 +268,6 @@ action_message为一个字典,包含的键值对如下(省略了不必要的 ## Action 内置方法说明 ```python class BaseAction: - # 配置相关 def get_config(self, key: str, default=None): """获取插件配置值,使用嵌套键访问""" diff --git a/docs/plugins/command-components.md b/docs/plugins/command-components.md index d3eb20032..77cc8accf 100644 --- a/docs/plugins/command-components.md +++ b/docs/plugins/command-components.md @@ -2,7 +2,9 @@ ## 📖 什么是Command -Command是直接响应用户明确指令的组件,与Action不同,Command是**被动触发**的,当用户输入特定格式的命令时立即执行。Command通过正则表达式匹配用户输入,提供确定性的功能服务。 +Command是直接响应用户明确指令的组件,与Action不同,Command是**被动触发**的,当用户输入特定格式的命令时立即执行。 + +Command通过正则表达式匹配用户输入,提供确定性的功能服务。 ### 🎯 Command的特点 @@ -12,501 +14,76 @@ Command是直接响应用户明确指令的组件,与Action不同,Command是 - 🛑 **拦截控制**:可以控制是否阻止消息继续处理 - 📝 **参数解析**:支持从用户输入中提取参数 -## 🆚 Action vs Command 核心区别 +--- -| 特征 | Action | Command | -| ------------------ | --------------------- | ---------------- | -| **触发方式** | 麦麦主动决策使用 | 用户主动触发 | -| **决策机制** | 两层决策(激活+使用) | 直接匹配执行 | -| **随机性** | 有随机性和智能性 | 确定性执行 | -| **用途** | 增强麦麦行为拟人化 | 提供具体功能服务 | -| **性能影响** | 需要LLM决策 | 正则匹配,性能好 | +## 🛠️ Command组件的基本结构 -## 🏗️ Command基本结构 - -### 必须属性 +首先,Command组件需要继承自`BaseCommand`类,并实现必要的方法。 ```python -from src.plugin_system import BaseCommand +class ExampleCommand(BaseCommand): + command_name = "example" # 命令名称,作为唯一标识符 + command_description = "这是一个示例命令" # 命令描述 + command_pattern = r"" # 命令匹配的正则表达式 -class MyCommand(BaseCommand): - # 正则表达式匹配模式 - command_pattern = r"^/help\s+(?P\w+)$" - - # 命令帮助说明 - command_help = "显示指定主题的帮助信息" - - # 使用示例 - command_examples = ["/help action", "/help command"] - - # 是否拦截后续处理 - intercept_message = True - - async def execute(self) -> Tuple[bool, Optional[str]]: - """执行命令逻辑""" - # 命令执行逻辑 - return True, "执行成功" + async def execute(self) -> Tuple[bool, Optional[str], bool]: + """ + 执行Command的主要逻辑 + + Returns: + Tuple[bool, str, bool]: + - 第一个bool表示是否成功执行 + - 第二个str是执行结果消息 + - 第三个bool表示是否需要阻止消息继续处理 + """ + # ---- 执行命令的逻辑 ---- + return True, "执行成功", False ``` +**`command_pattern`**: 该Command匹配的正则表达式,用于精确匹配用户输入。 -### 属性说明 +请注意:如果希望能获取到命令中的参数,请在正则表达式中使用有命名的捕获组,例如`(?Ppattern)`。 -| 属性 | 类型 | 说明 | -| --------------------- | --------- | -------------------- | -| `command_pattern` | str | 正则表达式匹配模式 | -| `command_help` | str | 命令帮助说明 | -| `command_examples` | List[str] | 使用示例列表 | -| `intercept_message` | bool | 是否拦截消息继续处理 | +这样在匹配时,内部实现可以使用`re.match.groupdict()`方法获取到所有捕获组的参数,并以字典的形式存储在`self.matched_groups`中。 -## 🔍 正则表达式匹配 - -### 基础匹配 +### 匹配样例 +假设我们有一个命令`/example param1=value1 param2=value2`,对应的正则表达式可以是: ```python -class SimpleCommand(BaseCommand): - # 匹配 /ping - command_pattern = r"^/ping$" - - async def execute(self) -> Tuple[bool, Optional[str]]: - await self.send_text("Pong!") - return True, "发送了Pong回复" +class ExampleCommand(BaseCommand): + command_name = "example" + command_description = "这是一个示例命令" + command_pattern = r"/example (?P\w+) (?P\w+)" + + async def execute(self) -> Tuple[bool, Optional[str], bool]: + # 获取匹配的参数 + param1 = self.matched_groups.get("param1") + param2 = self.matched_groups.get("param2") + + # 执行逻辑 + return True, f"参数1: {param1}, 参数2: {param2}", False ``` -### 参数捕获 - -使用命名组 `(?Ppattern)` 捕获参数: +--- +## Command 内置方法说明 ```python -class UserCommand(BaseCommand): - # 匹配 /user add 张三 或 /user del 李四 - command_pattern = r"^/user\s+(?Padd|del|info)\s+(?P\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}操作" +class BaseCommand: + def get_config(self, key: str, default=None): + """获取插件配置值,使用嵌套键访问""" + + 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: + """发送图片""" ``` - -### 可选参数 - -```python -class HelpCommand(BaseCommand): - # 匹配 /help 或 /help topic - command_pattern = r"^/help(?:\s+(?P\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+(?Padd|del|list|info)\s*(?P\w+)?(?:\s+--(?P.+))?$" - 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+(?Psystem|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"^[!!](?Proll|dice)\s*(?P\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\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: - 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. 配置集成 - -```python -async def execute(self) -> Tuple[bool, Optional[str]]: - # 从配置读取设置 - max_items = self.get_config("command.max_items", 10) - timeout = self.get_config("command.timeout", 30) - - # 使用配置进行处理 - ... -``` - -## 📝 Command vs Action 选择指南 - -### 使用Command的场景 - -- ✅ 用户需要明确调用特定功能 -- ✅ 需要精确的参数控制 -- ✅ 管理和配置操作 -- ✅ 查询和信息显示 -- ✅ 系统维护命令 - -### 使用Action的场景 - -- ✅ 增强麦麦的智能行为 -- ✅ 根据上下文自动触发 -- ✅ 情绪和表情表达 -- ✅ 智能建议和帮助 -- ✅ 随机化的互动 - - +具体参数与用法参见`BaseCommand`基类的定义。 \ No newline at end of file From 37bf904c4571b8e20cf5281efe795e50707dc13d Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Fri, 25 Jul 2025 14:33:33 +0800 Subject: [PATCH 2/4] =?UTF-8?q?tools=20=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/plugins/tool-system.md | 201 +++--------------------------------- 1 file changed, 12 insertions(+), 189 deletions(-) diff --git a/docs/plugins/tool-system.md b/docs/plugins/tool-system.md index d9093c89f..baa43528c 100644 --- a/docs/plugins/tool-system.md +++ b/docs/plugins/tool-system.md @@ -2,12 +2,11 @@ ## 📖 什么是工具系统 -工具系统是MaiBot的信息获取能力扩展组件,**专门用于在Focus模式下扩宽麦麦能够获得的信息量**。如果说Action组件功能五花八门,可以拓展麦麦能做的事情,那么Tool就是在某个过程中拓宽了麦麦能够获得的信息量。 +工具系统是MaiBot的信息获取能力扩展组件。如果说Action组件功能五花八门,可以拓展麦麦能做的事情,那么Tool就是在某个过程中拓宽了麦麦能够获得的信息量。 ### 🎯 工具系统的特点 - 🔍 **信息获取增强**:扩展麦麦获取外部信息的能力 -- 🎯 **Focus模式专用**:仅在专注聊天模式下工作,必须开启工具处理器 - 📊 **数据丰富**:帮助麦麦获得更多背景信息和实时数据 - 🔌 **插件式架构**:支持独立开发和注册新工具 - ⚡ **自动发现**:工具会被系统自动识别和注册 @@ -17,7 +16,6 @@ | 特征 | Action | Command | Tool | |-----|-------|---------|------| | **主要用途** | 扩展麦麦行为能力 | 响应用户指令 | 扩展麦麦信息获取 | -| **适用模式** | 所有模式 | 所有模式 | 仅Focus模式 | | **触发方式** | 麦麦智能决策 | 用户主动触发 | LLM根据需要调用 | | **目标** | 让麦麦做更多事情 | 提供具体功能 | 让麦麦知道更多信息 | | **使用场景** | 增强交互体验 | 功能服务 | 信息查询和分析 | @@ -54,7 +52,7 @@ class MyTool(BaseTool): "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')}" @@ -63,9 +61,6 @@ class MyTool(BaseTool): "name": self.name, "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文件 2. **类识别**:寻找继承自 `BaseTool` 的工具类 -3. **自动注册**:调用 `register_tool()` 的工具会被注册到系统中 +3. **自动注册**:只需要实现对应的类并把文件放在正确文件夹中就可自动注册 4. **即用即加载**:工具在需要时被实例化和调用 -### 注册流程 - -```python -# 1. 创建工具类 -class WeatherTool(BaseTool): - name = "weather_query" - description = "查询指定城市的天气信息" - # ... - -# 2. 注册工具(在文件末尾) -register_tool(WeatherTool) - -# 3. 系统自动发现(无需手动操作) -# discover_tools() 函数会自动完成注册 -``` +--- ## 🎨 完整工具示例 -### 天气查询工具 +完成一个天气查询工具 ```python from src.tools.tool_can_use.base_tool import BaseTool, register_tool @@ -192,102 +173,9 @@ class WeatherTool(BaseTool): 💧 湿度: {humidity}% ━━━━━━━━━━━━━━━━━━ """.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, "content": "执行结果" } - -register_tool(MyNewTool) ``` -### 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. 系统集成 +### 3. 系统集成 工具创建完成后,系统会自动发现和注册,无需额外配置。 -## ⚙️ 工具处理器配置 - -### 启用工具处理器 - -工具系统仅在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模式下可用 -2. **独立开发**:需要单独编写,暂未完全融入插件系统 -3. **适用范围**:主要适用于信息获取场景 -4. **配置要求**:必须开启工具处理器 - -### 未来改进 - -工具系统在之后可能会面临以下修改: - -1. **插件系统融合**:更好地集成到插件系统中 -2. **模式扩展**:可能扩展到其他聊天模式 -3. **配置简化**:简化配置和部署流程 -4. **性能优化**:提升工具调用效率 +1. **独立开发**:需要单独编写,暂未完全融入插件系统 +2. **适用范围**:主要适用于信息获取场景 +3. **配置要求**:必须开启工具处理器 ### 开发建议 From 3a4f343d8412e2abe75b83fb3e58615efe2e90ba Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Fri, 25 Jul 2025 14:35:59 +0800 Subject: [PATCH 3/4] =?UTF-8?q?tools=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugin_system/base/base_command.py | 4 ++-- src/tools/tool_can_use/compare_numbers_tool.py | 8 ++------ src/tools/tool_can_use/rename_person_tool.py | 7 +++---- src/tools/tool_executor.py | 4 ++-- src/tools/tool_use.py | 3 ++- 5 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/plugin_system/base/base_command.py b/src/plugin_system/base/base_command.py index 60ee99add..652acb4c4 100644 --- a/src/plugin_system/base/base_command.py +++ b/src/plugin_system/base/base_command.py @@ -60,10 +60,10 @@ class BaseCommand(ABC): pass def get_config(self, key: str, default=None): - """获取插件配置值,支持嵌套键访问 + """获取插件配置值,使用嵌套键访问 Args: - key: 配置键名,支持嵌套访问如 "section.subsection.key" + key: 配置键名,使用嵌套访问如 "section.subsection.key" default: 默认值 Returns: diff --git a/src/tools/tool_can_use/compare_numbers_tool.py b/src/tools/tool_can_use/compare_numbers_tool.py index 2930f8f4b..236a4587d 100644 --- a/src/tools/tool_can_use/compare_numbers_tool.py +++ b/src/tools/tool_can_use/compare_numbers_tool.py @@ -39,11 +39,7 @@ class CompareNumbersTool(BaseTool): else: 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: logger.error(f"比较数字失败: {str(e)}") - return {"type": "info", "id": f"{num1}_vs_{num2}", "content": f"比较数字失败,炸了: {str(e)}"} - - -# 注册工具 -# register_tool(CompareNumbersTool) + return {"name": self.name, "content": f"比较数字失败,炸了: {str(e)}"} diff --git a/src/tools/tool_can_use/rename_person_tool.py b/src/tools/tool_can_use/rename_person_tool.py index 2216b8245..17e624686 100644 --- a/src/tools/tool_can_use/rename_person_tool.py +++ b/src/tools/tool_can_use/rename_person_tool.py @@ -1,7 +1,6 @@ from src.tools.tool_can_use.base_tool import BaseTool from src.person_info.person_info import get_person_info_manager from src.common.logger import get_logger -import time logger = get_logger("rename_person_tool") @@ -24,7 +23,7 @@ class RenamePersonTool(BaseTool): "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}" logger.info(content) - return {"type": "info", "id": f"rename_success_{time.time()}", "content": content} + return {"name": self.name, "content": content} else: logger.warning(f"为用户 {person_id} 调用 qv_person_name 后未能成功获取新昵称。") # 尝试从内存中获取可能已经更新的名字 @@ -101,4 +100,4 @@ class RenamePersonTool(BaseTool): except Exception as e: error_msg = f"重命名失败: {str(e)}" 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} diff --git a/src/tools/tool_executor.py b/src/tools/tool_executor.py index 403ed554f..0f50ca2ab 100644 --- a/src/tools/tool_executor.py +++ b/src/tools/tool_executor.py @@ -172,7 +172,7 @@ class ToolExecutor: 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: tool_info = { @@ -299,7 +299,7 @@ class ToolExecutor: 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: tool_info = { diff --git a/src/tools/tool_use.py b/src/tools/tool_use.py index 738eeed48..6a8cd48a6 100644 --- a/src/tools/tool_use.py +++ b/src/tools/tool_use.py @@ -16,7 +16,8 @@ class ToolUser: return get_all_tool_definitions() @staticmethod - async def _execute_tool_call(tool_call): + async def execute_tool_call(tool_call): + # sourcery skip: use-assigned-variable """执行特定的工具调用 Args: From 5182609ca433d7a6597c0f5f85d64f7b5b484d47 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Fri, 25 Jul 2025 14:47:40 +0800 Subject: [PATCH 4/4] =?UTF-8?q?manifest=E8=AF=B4=E6=98=8E=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/plugins/manifest-guide.md | 21 ++++++--------------- src/plugin_system/utils/manifest_utils.py | 15 +++++---------- 2 files changed, 11 insertions(+), 25 deletions(-) diff --git a/docs/plugins/manifest-guide.md b/docs/plugins/manifest-guide.md index 5c5d7e3fb..d3dd746af 100644 --- a/docs/plugins/manifest-guide.md +++ b/docs/plugins/manifest-guide.md @@ -147,7 +147,7 @@ python scripts/manifest_tool.py validate src/plugins/my_plugin ## 📋 字段说明 ### 基本信息 -- `manifest_version`: manifest格式版本,当前为3 +- `manifest_version`: manifest格式版本,当前为1 - `name`: 插件显示名称(必需) - `version`: 插件版本号(必需) - `description`: 插件功能描述(必需) @@ -165,10 +165,12 @@ python scripts/manifest_tool.py validate src/plugins/my_plugin - `categories`: 分类数组(可选,建议填写) ### 兼容性 -- `host_application`: 主机应用兼容性(可选) +- `host_application`: 主机应用兼容性(可选,建议填写) - `min_version`: 最低兼容版本 - `max_version`: 最高兼容版本 +⚠️ 在不填写的情况下,插件将默认支持所有版本。**(由于我们在不同版本对插件系统进行了大量的重构,这种情况几乎不可能。)** + ### 国际化 - `default_locale`: 默认语言(可选) - `locales_path`: 语言文件目录(可选) @@ -185,24 +187,13 @@ python scripts/manifest_tool.py validate src/plugins/my_plugin 2. **编码格式**:manifest文件必须使用UTF-8编码 3. **JSON格式**:文件必须是有效的JSON格式 4. **必需字段**:`manifest_version`、`name`、`version`、`description`、`author.name`是必需的 -5. **版本兼容**:当前只支持manifest_version = 3 +5. **版本兼容**:当前只支持`manifest_version = 1` ## 🔍 常见问题 -### Q: 为什么要强制要求manifest文件? -A: Manifest文件提供了插件的标准化元数据,使得插件管理、依赖检查、版本兼容性验证等功能成为可能。 - ### Q: 可以不填写可选字段吗? A: 可以。所有标记为"可选"的字段都可以不填写,但建议至少填写`license`和`keywords`。 -### Q: 如何快速为所有插件创建manifest? -A: 可以编写脚本批量处理: -```bash -# 扫描并为每个缺少manifest的插件创建最小化manifest -python scripts/manifest_tool.py scan src/plugins -# 然后手动为每个插件运行create-minimal命令 -``` - ### Q: manifest验证失败怎么办? A: 根据验证器的错误提示修复相应问题。错误会导致插件加载失败,警告不会。 @@ -210,5 +201,5 @@ A: 根据验证器的错误提示修复相应问题。错误会导致插件加 查看内置插件的manifest文件作为参考: - `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/hello_world_plugin/_manifest.json` diff --git a/src/plugin_system/utils/manifest_utils.py b/src/plugin_system/utils/manifest_utils.py index 6a8aa804b..d070b733c 100644 --- a/src/plugin_system/utils/manifest_utils.py +++ b/src/plugin_system/utils/manifest_utils.py @@ -163,12 +163,11 @@ class VersionComparator: version_normalized, max_normalized ) - if is_compatible: - logger.info(f"版本兼容性检查:{compat_msg}") - return True, compat_msg - else: + if not is_compatible: return False, f"版本 {version_normalized} 高于最大支持版本 {max_normalized},且无兼容性映射" + logger.info(f"版本兼容性检查:{compat_msg}") + return True, compat_msg return True, "" @staticmethod @@ -358,14 +357,10 @@ class ManifestValidator: if self.validation_errors: report.append("❌ 验证错误:") - for error in self.validation_errors: - report.append(f" - {error}") - + report.extend(f" - {error}" for error in self.validation_errors) if self.validation_warnings: report.append("⚠️ 验证警告:") - for warning in self.validation_warnings: - report.append(f" - {warning}") - + report.extend(f" - {warning}" for warning in self.validation_warnings) if not self.validation_errors and not self.validation_warnings: report.append("✅ Manifest文件验证通过")