diff --git a/README.MD b/README.MD index d303dd7..7e472fc 100644 --- a/README.MD +++ b/README.MD @@ -33,31 +33,16 @@ Bubbles 是一个功能丰富的微信机器人框架,基于 [wcferry](https://github.com/lich0821/wcferry) 和 [WechatRobot](https://github.com/lich0821/wechatrobot) 开发,支持接入多种LLM,提供丰富的交互功能和定时任务。该项目旨在将微信客户端转变为一个智能的个人助手,可以执行多种实用功能,带来便捷的用户体验。 -和一般机器人框架不同的是,Bubbles 设计了两套灵活的路由系统: +和一般机器人框架不同的是,Bubbles 构建了一套基于 AI 的智能路由系统:AI 会读取所有已注册的功能描述,自动判断用户想要执行的操作并调用对应的处理函数。添加新功能时,只需要编写处理逻辑并通过装饰器注册到 AI 路由,无需在消息处理流程里额外写分支逻辑。 -1. **命令路由系统** - 基于正则表达式的精确命令匹配,适合有明确触发词的功能 -2. **AI智能路由系统** - 基于AI的自然语言理解,自动识别用户意图并调用相应功能 - -通过这两套路由系统,同一个功能函数,可以让 ai 有两种方式进行调用。这不但使得添加新功能变得极其简单,且不需要改动原有代码。相当于给一个主线 Hub 添加插件,让海量的、不同种类的工具都能集成到 AI 里。 - -路由系统是本项目的核心,通过它,理论上可以实现任何操作。 - -已实现的操作详见 **如何添加新功能** 章节。 +这套路由机制是项目的核心,使得机器人可以在保持主线稳定的同时,快速接入数量众多、类型各异的工具。已实现的功能示例见 **如何添加新功能** 章节。 ## 案例演示 -#### 案例演示其一:使用自然语言设置提醒(命令路由) +#### 案例演示:使用自然语言设置提醒(AI 智能路由) 结构: -用户输入 -> 击中命令 -> 调用命令函数 -> agent分析 -> agent格式化输出 -> 选择函数 -> 格式解析 -> 函数循环调用 -> 数据库持久化 -> 结果回调 - - - -#### 案例演示其二:使用自然语言设置提醒(AI 智能路由) - -结构: - -用户输入 -> 未击中命令 -> agent路由选择 -> 满足功能要求 -> agent格式化输出 -> 格式解析 -> 函数调用 -> 接口访问 -> 查询数据 -> 数据库持久化 -> 结果回调 +用户输入 -> AI 路由决策 -> 满足功能要求 -> agent格式化输出 -> 格式解析 -> 函数调用 -> 接口访问 -> 查询数据 -> 数据库持久化 -> 结果回调 @@ -69,47 +54,37 @@ Bubbles 是一个功能丰富的微信机器人框架,基于 [wcferry](https:/ - DeepSeek - Gemini -#### 🛠️ 双重路由系统 -- **命令路由系统**:基于正则表达式的精确匹配,高效处理特定命令 -- **AI智能路由**:自然语言理解,无需记住特定命令格式 -- 支持自定义命令及参数 -- 预设 [多种实用和娱乐命令](#可用命令) +#### 🛠️ 智能路由系统 +- 基于 AI 的意图识别,无需记住特定命令格式 +- 支持为功能提供描述、示例和参数说明,帮助模型精准调用 +- 通过装饰器注册新功能即可扩展机器人能力 #### 路由系统架构图 ```mermaid flowchart TD A[User Message] --> B[Message Preprocessing] - B --> C{At Bot or Private Chat?} - - C -->|Yes| D[Command Router] - C -->|No| E[Ignore Message] - - D --> F{Regex Match?} - F -->|Yes| G[Execute Command Handler] - F -->|No| H[AI Router] - - H --> I[AI Analyze Intent] - I --> J{Function Match?} - J -->|Yes| K[Call Function] - J -->|No| L[Chat Mode] - - G --> M[Return Result] - K --> M - L --> N[AI Conversation] - N --> M - + B --> C{私聊或@机器人?} + C -->|No| D[忽略消息] + C -->|Yes| E[AI Router] + E --> F[AI Analyze Intent] + F --> G{Function Decision?} + G -->|Function| H[Call Registered Handler] + G -->|Chat| I[AI Conversation] + H --> J[Return Result] + I --> J + style A fill:#f9f,stroke:#333,stroke-width:2px - style D fill:#bbf,stroke:#333,stroke-width:2px - style H fill:#bfb,stroke:#333,stroke-width:2px - style M fill:#fbb,stroke:#333,stroke-width:2px + style E fill:#bfb,stroke:#333,stroke-width:2px + style H fill:#bbf,stroke:#333,stroke-width:2px + style J fill:#fbb,stroke:#333,stroke-width:2px ``` 消息处理流程说明: -1. **消息预处理**:系统接收用户消息,判断是否需要响应 -2. **命令路由优先**:首先尝试使用正则表达式匹配已注册的命令 -3. **AI路由兜底**:如果没有匹配到命令,则使用AI分析用户意图 -4. **智能分发**:AI可以理解自然语言并调用相应功能,或进入聊天模式 +1. **消息预处理**:系统接收用户消息,提取纯文本、发送者等上下文 +2. **权限判断**:仅在私聊或群聊中被 @ 时才进入 AI 路由,避免打扰其他群成员 +3. **AI 决策**:大模型根据功能描述判断是否调用某个功能或直接聊天 +4. **结果输出**:调用成功的功能会返回处理结果,否则使用默认闲聊响应 ### ⏰ 定时任务与提醒功能 - 每日天气预报推送 @@ -225,30 +200,23 @@ GROUP_MODELS: python main.py ``` -#### 可用命令(命令路由系统) +#### 可用功能示例(AI 路由) -机器人支持多种命令,按功能分类如下: +以下是已经注册到 AI 路由的典型意图,机器人会尽力理解类似的自然语言表述: ##### 提醒功能 -- `..提醒我..` - 用自然语言设置一个或多个提醒 -- `查看提醒`、`我的提醒`、`提醒列表` - 查看您设置的所有提醒 -- `..删..提醒..` - 用自然语言删除指定的(或所有)提醒 +- “提醒我明天下午三点开会” —— 设置一次性提醒 +- “每天早上 8 点提醒我跑步” —— 支持循环提醒 +- “查看我的提醒” / “我有哪些提醒” —— 查看当前所有提醒 +- “把明天的开会提醒删掉” —— 删除指定提醒 -##### 基础系统命令 -- `info`、`帮助`、`指令` - 显示机器人的帮助信息 -- `骂一下 @用户名` - 让机器人骂指定用户(仅群聊) +##### 搜索与资讯 +- “帮我查查最近的 AI 新闻” —— 使用 Perplexity 进行深度检索 +- “看看今天的新闻” —— 推送最新要闻 -##### Perplexity AI 命令 -- `ask 问题内容` - 使用 Perplexity AI 进行深度查询(需@机器人) - -##### 消息管理命令 -- `summary`、`/总结` - 总结群聊最近的消息(仅群聊) -- `clearmessages`、`/清除历史` - 从数据库中清除群聊的历史消息记录(仅群聊) - -##### 天气和新闻工具 -- `天气预报 城市名`、`预报 城市名` - 查询指定城市未来几天的天气预报 -- `天气 城市名`、`温度 城市名` - 查询指定城市的当前天气 -- `新闻` - 获取最新新闻 +##### 天气工具 +- “北京天气怎么样” +- “查一下上海未来几天的天气” ## 📋 项目结构 @@ -257,9 +225,8 @@ Bubbles-WechatAI/ ├── ai_providers/ # AI 模块 │ ├── ai_name.py # AI 模型接口实现 │ └── ... -├── commands/ # 命令系统 -│ ├── registry.py # 正则命令注册 -│ ├── handlers.py # 命令处理函数 +├── commands/ # 消息上下文与 AI 路由 +│ ├── handlers.py # 功能处理函数 │ ├── ai_router.py # AI智能路由器 │ ├── ai_functions.py # AI路由功能注册 │ └── ... @@ -274,74 +241,18 @@ Bubbles-WechatAI/ ### ✨ 如何添加新功能 -本项目提供两种方式添加新功能: +目前所有新功能均通过 AI 智能路由接入,开发流程如下: -```mermaid -graph LR - A[新功能] --> B{选择路由方式} - B --> C[命令路由] - B --> D[AI路由] - - C --> E[精确匹配] - C --> F[固定格式命令] - C --> G[例如:天气北京] - - D --> H[自然语言理解] - D --> I[灵活表达] - D --> J[例如:北京天气怎么样] - - style A fill:#f9f,stroke:#333,stroke-width:2px - style C fill:#bbf,stroke:#333,stroke-width:2px - style D fill:#bfb,stroke:#333,stroke-width:2px -``` +1. **实现功能逻辑** + * 在 `function/` 目录下创建或复用功能模块,封装核心业务逻辑,便于测试和复用。 -#### 方式一:使用命令路由系统(适合有明确触发词的功能) - -1. **定义功能逻辑 (可选但推荐)**: - * 如果你的功能逻辑比较复杂,建议在 `function/` 目录下创建一个新的 Python 文件 (例如 `func_your_feature.py`)。 - * 在这个文件中实现你的核心功能代码,例如定义类或函数。这有助于保持代码结构清晰。 - -2. **创建命令处理器**: - * 打开 `commands/handlers.py` 文件。 - * 添加一个新的处理函数,例如 `handle_your_feature(ctx: 'MessageContext', match: Optional[Match]) -> bool:`。 - * 这个函数接收 `MessageContext` (包含消息上下文信息) 和 `match` (正则表达式匹配结果) 作为参数。 - * 在函数内部,你可以: - * 调用你在 `function/` 目录下创建的功能模块。 - * 使用 `ctx.send_text()` 发送回复消息。 - * 根据需要处理 `match` 对象提取用户输入的参数。 - * 函数应返回 `True` 表示命令已被处理,`False` 则表示未处理 (会继续尝试匹配后续命令或进行闲聊)。 - * 确保从 `function` 目录导入必要的模块。 - -3. **注册命令**: - * 打开 `commands/registry.py` 文件。 - * 在 `COMMANDS` 列表中,按照优先级顺序添加一个新的 `Command` 对象。 - * 配置 `Command` 参数: - * `name`: 命令的唯一标识名 (小写下划线)。 - * `pattern`: 用于匹配用户输入的正则表达式 (`re.compile`)。注意捕获用户参数。 - * `scope`: 命令适用范围 (`"group"`, `"private"`, `"both"`)。 - * `need_at`: 在群聊中是否需要 `@` 机器人才能触发 (`True`/`False`)。 - * `priority`: 命令的优先级 (数字越小越优先匹配)。 - * `handler`: 指向你在 `handlers.py` 中创建的处理函数 (例如 `handle_your_feature`)。 - * `description`: 命令的简短描述,用于帮助信息。 - * 确保从 `handlers.py` 导入你的新处理函数。 - -4. **更新帮助信息 (可选)**: - * 如果希望用户能在 `帮助` 命令中看到你的新功能,可以更新 `commands/handlers.py` 中的 `handle_help` 函数,将新命令的用法添加到帮助文本中。 - -#### 方式二:使用AI智能路由(适合自然语言交互的功能) - -AI路由系统让用户可以用自然语言触发功能,无需记住特定命令格式: - -1. **实现功能逻辑**: - * 在 `function/` 目录下创建功能模块(如已有则跳过) - -2. **注册AI路由功能**: +2. **注册 AI 路由功能** * 打开 `commands/ai_functions.py` - * 使用装饰器注册你的功能: + * 使用装饰器注册功能,提供给模型判定意图所需的描述信息: ```python @ai_router.register( name="your_function_name", - description="功能描述(AI会根据这个判断用户意图)", + description="功能描述(AI 会根据这个判断用户意图)", examples=[ "示例用法1", "示例用法2", @@ -350,23 +261,23 @@ AI路由系统让用户可以用自然语言触发功能,无需记住特定命 params_description="参数说明" ) def ai_handle_your_function(ctx: MessageContext, params: str) -> bool: - # params 是AI从用户输入中提取的参数 + # params 是 AI 从用户输入中提取的参数 # 调用你的功能逻辑 # 使用 ctx.send_text() 发送回复 return True ``` -3. **工作原理**: - * 用户发送消息时,如果正则路由未匹配,AI会分析用户意图 - * AI根据功能描述和示例,判断应该调用哪个功能 - * AI会自动提取参数并传递给功能处理函数 +3. **理解路由机制** + * 用户消息会在私聊或被 @ 时进入 AI 路由 + * AI 根据 `description`、`examples` 和 `params_description` 选择最合适的功能 + * `params` 字段由模型提取,可根据需要进一步解析或校验 例如,注册了天气查询功能后,用户可以说: - "北京天气怎么样" - "查一下上海的天气" - "明天深圳会下雨吗" -AI都能理解并调用天气查询功能。 +AI 都能理解并调用天气查询功能。 完成以上步骤后,重启机器人即可测试你的新功能! diff --git a/commands/__init__.py b/commands/__init__.py index 85d8b34..657dcd0 100644 --- a/commands/__init__.py +++ b/commands/__init__.py @@ -1,11 +1,10 @@ # commands package """ -命令路由系统包 +消息处理组件包 -此包包含了命令路由系统的所有组件: +当前模块提供基于 AI 的功能派发能力: - context: 消息上下文类 -- models: 命令数据模型 -- router: 命令路由器 -- registry: 命令注册表 -- handlers: 命令处理函数 -""" \ No newline at end of file +- handlers: 功能处理函数 +- ai_router: AI 智能路由核心 +- ai_functions: 面向 AI 路由的功能注册 +""" diff --git a/commands/handlers.py b/commands/handlers.py index 7131c0f..6fa40b7 100644 --- a/commands/handlers.py +++ b/commands/handlers.py @@ -755,20 +755,11 @@ def handle_list_reminders(ctx: 'MessageContext', match: Optional[Match]) -> bool return True def handle_delete_reminder(ctx: 'MessageContext', match: Optional[Match]) -> bool: - """ - 处理删除提醒命令(支持群聊和私聊)。 - 检查消息是否包含"提醒"和"删"相关字眼,然后使用 AI 理解具体意图。 - """ + """处理删除提醒命令(支持群聊和私聊),通过 AI 理解用户意图并执行操作。""" # 1. 获取用户输入的完整内容 raw_text = ctx.msg.content.strip() - # 2. 检查是否包含删除提醒的两个核心要素:"提醒"和"删/删除/取消" - # Regex 已经保证了后者,这里只需检查前者 - if "提醒" not in raw_text: - # 如果消息匹配了 "删" 但没有 "提醒",说明不是删除提醒的意图,不处理 - return False # 返回 False,让命令路由器可以尝试匹配其他命令 - - # 3. 检查 ReminderManager 是否存在 + # 2. 检查 ReminderManager 是否存在 if not hasattr(ctx.robot, 'reminder_manager'): # 这个检查需要保留,是内部依赖 ctx.send_text("❌ 内部错误:提醒管理器未初始化。", ctx.msg.sender if ctx.is_group else "") @@ -779,7 +770,7 @@ def handle_delete_reminder(ctx: 'MessageContext', match: Optional[Match]) -> boo # --- 核心流程:直接使用 AI 分析 --- - # 4. 获取用户的所有提醒作为 AI 的上下文 + # 3. 获取用户的所有提醒作为 AI 的上下文 reminders = ctx.robot.reminder_manager.list_reminders(ctx.msg.sender) if not reminders: # 如果用户没有任何提醒,直接告知 @@ -794,7 +785,7 @@ def handle_delete_reminder(ctx: 'MessageContext', match: Optional[Match]) -> boo if ctx.logger: ctx.logger.error(f"序列化提醒列表失败: {e}", exc_info=True) return True - # 5. 构造 AI Prompt (与之前相同,AI 需要能处理所有情况) + # 4. 构造 AI Prompt (与之前相同,AI 需要能处理所有情况) # 注意:确保 prompt 中的 {{ 和 }} 转义正确 sys_prompt = """ 你是提醒删除助手。用户会提出删除提醒的请求。我会提供用户的**完整请求原文**,以及一个包含该用户所有当前提醒的 JSON 列表。 @@ -870,7 +861,7 @@ def handle_delete_reminder(ctx: 'MessageContext', match: Optional[Match]) -> boo return True - # 6. 调用 AI (使用完整的用户原始输入) + # 5. 调用 AI (使用完整的用户原始输入) q_for_ai = f"请根据以下用户完整请求,分析需要删除哪个提醒:\n{raw_text}" # 使用 raw_text try: if not hasattr(ctx, 'chat') or not ctx.chat: @@ -903,7 +894,7 @@ def handle_delete_reminder(ctx: 'MessageContext', match: Optional[Match]) -> boo # 获取AI回答 ai_response = ctx.chat.get_answer(q_for_ai, ctx.get_receiver(), system_prompt_override=formatted_prompt) - # 7. 解析 AI 的 JSON 回复 + # 6. 解析 AI 的 JSON 回复 json_str = None json_match_obj = re.search(r'\{.*\}', ai_response, re.DOTALL) if json_match_obj: @@ -932,7 +923,7 @@ def handle_delete_reminder(ctx: 'MessageContext', match: Optional[Match]) -> boo return True # 否则继续下一次循环重试 - # 8. 根据 AI 指令执行操作 (与之前相同) + # 7. 根据 AI 指令执行操作 (与之前相同) action = parsed_ai_response.get("action") if action == "delete_specific": diff --git a/commands/models.py b/commands/models.py deleted file mode 100644 index 4034e3f..0000000 --- a/commands/models.py +++ /dev/null @@ -1,38 +0,0 @@ -import re -from dataclasses import dataclass -from typing import Pattern, Callable, Literal, Optional, Any, Union, Match - -# 导入 MessageContext,使用前向引用避免循环导入 -from typing import TYPE_CHECKING -if TYPE_CHECKING: - from .context import MessageContext - - -@dataclass -class Command: - """ - 命令定义类,封装命令的匹配条件和处理函数 - """ - name: str # 命令名称,用于日志和调试 - pattern: Union[Pattern, Callable[['MessageContext'], Optional[Match]]] # 匹配规则:正则表达式或自定义匹配函数 - scope: Literal["group", "private", "both"] # 生效范围: "group"-仅群聊, "private"-仅私聊, "both"-两者都可 - handler: Callable[['MessageContext', Optional[Match]], bool] # 处理函数 - need_at: bool = False # 在群聊中是否必须@机器人才能触发 - priority: int = 100 # 优先级,数字越小越先匹配 - description: str = "" # 命令的描述,用于生成帮助信息 - - def __post_init__(self): - """验证命令配置的有效性""" - if self.scope not in ["group", "private", "both"]: - raise ValueError(f"无效的作用域: {self.scope},必须是 'group', 'private' 或 'both'") - - # 检查pattern是否为正则表达式或可调用对象 - if not isinstance(self.pattern, (Pattern, Callable)): - # 如果是字符串,尝试转换为正则表达式 - if isinstance(self.pattern, str): - try: - self.pattern = re.compile(self.pattern) - except re.error: - raise ValueError(f"无效的正则表达式: {self.pattern}") - else: - raise TypeError(f"pattern 必须是正则表达式或可调用对象,而不是 {type(self.pattern)}") \ No newline at end of file diff --git a/commands/registry.py b/commands/registry.py deleted file mode 100644 index a2fee50..0000000 --- a/commands/registry.py +++ /dev/null @@ -1,134 +0,0 @@ -import re -from .models import Command -from .handlers import ( - handle_help, - handle_summary, handle_clear_messages, handle_news_request, - handle_chitchat, handle_insult, - handle_perplexity_ask, handle_reminder, handle_list_reminders, handle_delete_reminder, - handle_weather_forecast -) - -# 命令列表,按优先级排序 -# 优先级越小越先匹配 -COMMANDS = [ - # ======== 基础系统命令 ======== - Command( - name="help", - pattern=re.compile(r"^(info|帮助|指令)$", re.IGNORECASE), - scope="both", # 群聊和私聊都支持 - need_at=False, # 不需要@机器人 - priority=10, # 优先级较高 - handler=handle_help, - description="显示机器人的帮助信息" - ), - - # ======== Perplexity AI 命令 ======== - Command( - name="perplexity_ask", - pattern=re.compile(r"^ask\s*(.+)", re.IGNORECASE | re.DOTALL), - scope="both", # 群聊和私聊都支持 - need_at=True, # 需要@机器人 - priority=25, # 较高优先级,确保在闲聊之前处理 - handler=handle_perplexity_ask, - description="使用 Perplexity AI 进行深度查询" - ), - - # ======== 消息管理命令 ======== - Command( - name="summary", - pattern=re.compile(r"^(summary|总结)$", re.IGNORECASE), - scope="group", # 仅群聊支持 - need_at=True, # 需要@机器人 - priority=30, # 优先级一般 - handler=handle_summary, - description="总结群聊最近的消息" - ), - - Command( - name="clear_messages", - pattern=re.compile(r"^(clearmessages|清除历史)$", re.IGNORECASE), - scope="group", # 仅群聊支持 - need_at=True, # 需要@机器人 - priority=31, # 优先级一般 - handler=handle_clear_messages, - description="从数据库中清除群聊的历史消息记录" - ), - - # ======== 提醒功能 ======== - Command( - name="reminder", - pattern=re.compile(r"提醒我", re.IGNORECASE), - scope="both", # 支持群聊和私聊 - need_at=True, # 在群聊中需要@机器人 - priority=35, # 优先级适中,在基础命令后,复杂功能或闲聊前 - handler=handle_reminder, - description="设置一个提醒 (包含 '提醒我' 关键字即可, 例如:提醒我明天下午3点开会)" - ), - - Command( - name="list_reminders", - pattern=re.compile(r"^(查看提醒|我的提醒|提醒列表)$", re.IGNORECASE), - scope="both", # 支持群聊和私聊 - need_at=True, # 在群聊中需要@机器人 - priority=36, # 优先级略低于设置提醒 - handler=handle_list_reminders, - description="查看您设置的所有提醒" - ), - - Command( - name="delete_reminder", - # 修改为只匹配包含"删"、"删除"或"取消"的消息,不再要求特定格式 - pattern=re.compile(r"(?:删|删除|取消)", re.IGNORECASE), - scope="both", # 支持群聊和私聊 - need_at=True, # 在群聊中需要@机器人 - priority=37, - handler=handle_delete_reminder, - description="删除提醒 (包含'删'和'提醒'关键字即可,如: 把开会的提醒删了)" - ), - - # ======== 新闻和实用工具 ======== - Command( - name="weather_forecast", - pattern=re.compile(r"^(?:天气预报|天气)\s+(.+)$"), # 匹配 天气预报/预报 城市名 - scope="both", # 群聊和私聊都支持 - need_at=True, # 需要@机器人 - priority=38, # 优先级比天气高一点 - handler=handle_weather_forecast, - description="查询指定城市未来几天的天气预报 (例如:天气预报 北京)" - ), - - Command( - name="news", - pattern=re.compile(r"^新闻$"), - scope="both", # 群聊和私聊都支持 - need_at=True, # 需要@机器人 - priority=40, # 优先级一般 - handler=handle_news_request, - description="获取最新新闻" - ), - - # ======== 骂人命令 ======== - Command( - name="insult", - pattern=re.compile(r"骂一下\s*@([^\s@]+)"), - scope="group", # 仅群聊支持 - need_at=True, # 需要@机器人 - priority=100, # 优先级较高 - handler=handle_insult, - description="骂指定用户" - ), - -] - -# 可以添加一个函数,获取命令列表的简单描述 -def get_commands_info(): - """获取所有命令的简要信息,用于调试""" - info = [] - for i, cmd in enumerate(COMMANDS): - scope_str = {"group": "仅群聊", "private": "仅私聊", "both": "群聊私聊"}[cmd.scope] - at_str = "需要@" if cmd.need_at else "不需@" - info.append(f"{i+1}. [{cmd.priority}] {cmd.name} ({scope_str},{at_str}) - {cmd.description or '无描述'}") - return "\n".join(info) - -# 导出所有命令 -__all__ = ["COMMANDS", "get_commands_info"] diff --git a/commands/router.py b/commands/router.py deleted file mode 100644 index 8fd769d..0000000 --- a/commands/router.py +++ /dev/null @@ -1,117 +0,0 @@ -import re -import logging -from typing import List, Optional, Any, Dict, Match -import traceback - -from .models import Command -from .context import MessageContext - -# 获取模块级 logger -logger = logging.getLogger(__name__) - - -class CommandRouter: - """ - 命令路由器,负责将消息路由到对应的命令处理函数 - """ - def __init__(self, commands: List[Command], robot_instance: Optional[Any] = None): - # 按优先级排序命令列表,数字越小优先级越高 - self.commands = sorted(commands, key=lambda cmd: cmd.priority) - self.robot_instance = robot_instance - - # 分析并输出命令注册信息,便于调试 - scope_count = {"group": 0, "private": 0, "both": 0} - for cmd in commands: - scope_count[cmd.scope] += 1 - - logger.info(f"命令路由器初始化成功,共加载 {len(commands)} 个命令") - logger.info(f"命令作用域分布: 仅群聊 {scope_count['group']},仅私聊 {scope_count['private']},两者均可 {scope_count['both']}") - - # 按优先级输出命令信息 - for i, cmd in enumerate(self.commands[:10]): # 只输出前10个 - logger.info(f"{i+1}. [{cmd.priority}] {cmd.name} - {cmd.description or '无描述'}") - if len(self.commands) > 10: - logger.info(f"... 共 {len(self.commands)} 个命令") - - def dispatch(self, ctx: MessageContext) -> bool: - """ - 根据消息上下文分发命令 - :param ctx: 消息上下文对象 - :return: 是否有命令成功处理 - """ - # 确保context可以访问到robot实例 - if self.robot_instance and not ctx.robot: - ctx.robot = self.robot_instance - # 如果robot有logger属性且ctx没有logger,则使用robot的logger - if hasattr(self.robot_instance, 'LOG') and not ctx.logger: - ctx.logger = self.robot_instance.LOG - - # 记录日志,便于调试 - if ctx.logger: - ctx.logger.debug(f"开始路由消息: '{ctx.text}', 来自: {ctx.sender_name}, 群聊: {ctx.is_group}, @机器人: {ctx.is_at_bot}") - - # 遍历命令列表,按优先级顺序匹配 - for cmd in self.commands: - # 1. 检查作用域 (scope) - if cmd.scope != "both": - if (cmd.scope == "group" and not ctx.is_group) or \ - (cmd.scope == "private" and ctx.is_group): - continue # 作用域不匹配,跳过 - - # 2. 检查是否需要 @ (need_at) - 仅在群聊中有效 - if ctx.is_group and cmd.need_at and not ctx.is_at_bot: - continue # 需要@机器人但未被@,跳过 - - # 3. 执行匹配逻辑 - match_result = None - try: - # 根据pattern类型执行匹配 - if callable(cmd.pattern): - # 自定义匹配函数 - match_result = cmd.pattern(ctx) - else: - # 正则表达式匹配 - match_obj = cmd.pattern.search(ctx.text) - match_result = match_obj - - # 匹配失败,尝试下一个命令 - if match_result is None: - continue - - # 匹配成功,记录日志 - if ctx.logger: - ctx.logger.info(f"命令 '{cmd.name}' 匹配成功,准备处理") - - # 4. 执行命令处理函数 - try: - result = cmd.handler(ctx, match_result) - if result: - if ctx.logger: - ctx.logger.info(f"命令 '{cmd.name}' 处理成功") - return True - else: - if ctx.logger: - ctx.logger.warning(f"命令 '{cmd.name}' 处理返回False,尝试下一个命令") - except Exception as e: - if ctx.logger: - ctx.logger.error(f"执行命令 '{cmd.name}' 处理函数时出错: {e}") - ctx.logger.error(traceback.format_exc()) - else: - logger.error(f"执行命令 '{cmd.name}' 处理函数时出错: {e}", exc_info=True) - # 出错后继续尝试下一个命令 - except Exception as e: - # 匹配过程出错,记录并继续 - if ctx.logger: - ctx.logger.error(f"匹配命令 '{cmd.name}' 时出错: {e}") - else: - logger.error(f"匹配命令 '{cmd.name}' 时出错: {e}", exc_info=True) - continue - - # 所有命令都未匹配或处理失败 - if ctx.logger: - ctx.logger.debug("所有命令匹配失败或处理失败") - return False - - def get_command_descriptions(self) -> Dict[str, str]: - """获取所有命令的描述,用于生成帮助信息""" - return {cmd.name: cmd.description for cmd in self.commands if cmd.description} \ No newline at end of file diff --git a/function/func_summary.py b/function/func_summary.py index 13932d0..40c6da1 100644 --- a/function/func_summary.py +++ b/function/func_summary.py @@ -8,7 +8,6 @@ from collections import deque import sqlite3 # 添加sqlite3模块 import os # 用于处理文件路径 from function.func_xml_process import XmlProcessor # 导入XmlProcessor -# from commands.registry import COMMANDS # 不再需要导入命令列表 class MessageSummary: """消息总结功能类 (使用SQLite持久化) diff --git a/robot.py b/robot.py index c6ef434..655630c 100644 --- a/robot.py +++ b/robot.py @@ -27,10 +27,8 @@ from constants import ChatType from job_mgmt import Job from function.func_xml_process import XmlProcessor -# 导入命令路由系统 +# 导入上下文及常用处理函数 from commands.context import MessageContext -from commands.router import CommandRouter -from commands.registry import COMMANDS, get_commands_info from commands.handlers import handle_chitchat # 导入闲聊处理函数 # 导入AI路由系统 @@ -163,10 +161,6 @@ class Robot(Job): # 初始化图像生成管理器 self.image_manager = ImageGenerationManager(self.config, self.wcf, self.LOG, self.sendTextMsg) - # 初始化命令路由器 - self.command_router = CommandRouter(COMMANDS, robot_instance=self) - self.LOG.info(f"命令路由系统初始化完成,共加载 {len(COMMANDS)} 条命令") - # 初始化AI路由器 self.LOG.info(f"AI路由系统初始化完成,共加载 {len(ai_router.functions)} 个AI功能") @@ -179,9 +173,6 @@ class Robot(Job): except Exception as e: self.LOG.error(f"初始化提醒管理器失败: {e}", exc_info=True) - # 输出命令列表信息,便于调试 - # self.LOG.debug(get_commands_info()) # 如果需要在日志中输出所有命令信息,取消本行注释 - @staticmethod def value_check(args: dict) -> bool: if args: @@ -210,24 +201,21 @@ class Robot(Job): setattr(ctx, 'chat', self.chat) setattr(ctx, 'specific_max_history', specific_limit) - # 5. 使用命令路由器分发处理消息 - handled = self.command_router.dispatch(ctx) - - # 6. 如果正则路由器没有处理,尝试AI路由器 - if not handled: - # 只在被@或私聊时才使用AI路由 - if (msg.from_group() and msg.is_at(self.wxid)) or not msg.from_group(): - print(f"[AI路由调试] 准备调用AI路由器处理消息: {msg.content}") - ai_handled = ai_router.dispatch(ctx) - print(f"[AI路由调试] AI路由器处理结果: {ai_handled}") - if ai_handled: - self.LOG.info("消息已由AI路由器处理") - print("[AI路由调试] 消息已成功由AI路由器处理") - return - else: - print("[AI路由调试] AI路由器未处理该消息") - - # 7. 如果没有命令处理器处理,则进行特殊逻辑处理 + handled = False + + # 5. 优先尝试使用AI路由器处理消息(仅限私聊或@机器人) + if (msg.from_group() and msg.is_at(self.wxid)) or not msg.from_group(): + print(f"[AI路由调试] 准备调用AI路由器处理消息: {msg.content}") + handled = ai_router.dispatch(ctx) + print(f"[AI路由调试] AI路由器处理结果: {handled}") + if handled: + self.LOG.info("消息已由AI路由器处理") + print("[AI路由调试] 消息已成功由AI路由器处理") + return + else: + print("[AI路由调试] AI路由器未处理该消息") + + # 6. 如果AI路由器未处理,则进行特殊逻辑处理 if not handled: # 7.1 好友请求自动处理 if msg.type == 37: # 好友请求 @@ -254,7 +242,7 @@ class Robot(Job): # 7.3 群聊消息,且配置了响应该群 if msg.from_group() and msg.roomid in self.config.GROUPS: - # 如果在群里被@了,但命令路由器没有处理,则进行闲聊 + # 如果在群里被@了,但AI路由器未处理,则进行闲聊 if msg.is_at(self.wxid): # 调用handle_chitchat函数处理闲聊,传递完整的上下文 handle_chitchat(ctx, None)