From 14c0413d7ce9f429cd55ce7e784c59fdecf701ae Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sun, 27 Jul 2025 00:14:58 +0800 Subject: [PATCH 01/22] =?UTF-8?q?=E9=9D=9E=E7=A9=BA=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugin_system/apis/generator_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugin_system/apis/generator_api.py b/src/plugin_system/apis/generator_api.py index 4ecadd8b8..c2e5700bf 100644 --- a/src/plugin_system/apis/generator_api.py +++ b/src/plugin_system/apis/generator_api.py @@ -108,7 +108,7 @@ async def generate_reply( logger.debug("[GeneratorAPI] 开始生成回复") - if not reply_to: + if not reply_to and action_data: reply_to = action_data.get("reply_to", "") if not extra_info and action_data: extra_info = action_data.get("extra_info", "") From ab71d30437d5fd28e8233b074ac99bbc829fa486 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Sun, 27 Jul 2025 00:46:34 +0800 Subject: [PATCH 02/22] =?UTF-8?q?better=EF=BC=9A=E7=B2=BE=E7=AE=80rewrite?= =?UTF-8?q?=E7=9A=84=E5=8F=82=E6=95=B0=EF=BC=8C=E6=B7=BB=E5=8A=A0=E6=B3=A8?= =?UTF-8?q?=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/replyer/default_generator.py | 132 ++++++++++++++++++------ src/plugin_system/apis/generator_api.py | 21 +++- 2 files changed, 122 insertions(+), 31 deletions(-) diff --git a/src/chat/replyer/default_generator.py b/src/chat/replyer/default_generator.py index 0b411a8eb..cab6a2b41 100644 --- a/src/chat/replyer/default_generator.py +++ b/src/chat/replyer/default_generator.py @@ -158,7 +158,17 @@ class DefaultReplyer: enable_timeout: bool = False, ) -> Tuple[bool, Optional[str], Optional[str]]: """ - 回复器 (Replier): 核心逻辑,负责生成回复文本。 + 回复器 (Replier): 负责生成回复文本的核心逻辑。 + + Args: + reply_to: 回复对象,格式为 "发送者:消息内容" + extra_info: 额外信息,用于补充上下文 + available_actions: 可用的动作信息字典 + enable_tool: 是否启用工具调用 + enable_timeout: 是否启用超时处理 + + Returns: + Tuple[bool, Optional[str], Optional[str]]: (是否成功, 生成的回复内容, 使用的prompt) """ prompt = None if available_actions is None: @@ -219,25 +229,30 @@ class DefaultReplyer: async def rewrite_reply_with_context( self, - reply_data: Dict[str, Any], raw_reply: str = "", reason: str = "", reply_to: str = "", - relation_info: str = "", ) -> Tuple[bool, Optional[str]]: """ - 表达器 (Expressor): 核心逻辑,负责生成回复文本。 + 表达器 (Expressor): 负责重写和优化回复文本。 + + Args: + raw_reply: 原始回复内容 + reason: 回复原因 + reply_to: 回复对象,格式为 "发送者:消息内容" + relation_info: 关系信息 + + Returns: + Tuple[bool, Optional[str]]: (是否成功, 重写后的回复内容) """ try: - if not reply_data: - reply_data = { - "reply_to": reply_to, - "relation_info": relation_info, - } + with Timer("构建Prompt", {}): # 内部计时器,可选保留 prompt = await self.build_prompt_rewrite_context( - reply_data=reply_data, + raw_reply=raw_reply, + reason=reason, + reply_to=reply_to, ) content = None @@ -296,7 +311,16 @@ class DefaultReplyer: return await relationship_fetcher.build_relation_info(person_id, points_num=5) - async def build_expression_habits(self, chat_history, target): + async def build_expression_habits(self, chat_history: str, target: str) -> str: + """构建表达习惯块 + + Args: + chat_history: 聊天历史记录 + target: 目标消息内容 + + Returns: + str: 表达习惯信息字符串 + """ if not global_config.expression.enable_expression: return "" @@ -346,7 +370,16 @@ class DefaultReplyer: return expression_habits_block - async def build_memory_block(self, chat_history, target): + async def build_memory_block(self, chat_history: str, target: str) -> str: + """构建记忆块 + + Args: + chat_history: 聊天历史记录 + target: 目标消息内容 + + Returns: + str: 记忆信息字符串 + """ if not global_config.memory.enable_memory: return "" @@ -374,12 +407,13 @@ class DefaultReplyer: return memory_str - async def build_tool_info(self, chat_history, reply_to: str = "", enable_tool: bool = True): + async def build_tool_info(self, chat_history: str, reply_to: str = "", enable_tool: bool = True) -> str: """构建工具信息块 Args: - reply_data: 回复数据,包含要回复的消息内容 - chat_history: 聊天历史 + chat_history: 聊天历史记录 + reply_to: 回复对象,格式为 "发送者:消息内容" + enable_tool: 是否启用工具调用 Returns: str: 工具信息字符串 @@ -423,7 +457,15 @@ class DefaultReplyer: logger.error(f"工具信息获取失败: {e}") return "" - def _parse_reply_target(self, target_message: str) -> tuple: + def _parse_reply_target(self, target_message: str) -> Tuple[str, str]: + """解析回复目标消息 + + Args: + target_message: 目标消息,格式为 "发送者:消息内容" 或 "发送者:消息内容" + + Returns: + Tuple[str, str]: (发送者名称, 消息内容) + """ sender = "" target = "" # 添加None检查,防止NoneType错误 @@ -437,7 +479,15 @@ class DefaultReplyer: target = parts[1].strip() return sender, target - async def build_keywords_reaction_prompt(self, target): + async def build_keywords_reaction_prompt(self, target: Optional[str]) -> str: + """构建关键词反应提示 + + Args: + target: 目标消息内容 + + Returns: + str: 关键词反应提示字符串 + """ # 关键词检测与反应 keywords_reaction_prompt = "" try: @@ -471,15 +521,23 @@ class DefaultReplyer: return keywords_reaction_prompt - async def _time_and_run_task(self, coroutine, name: str): - """一个简单的帮助函数,用于计时和运行异步任务,返回任务名、结果和耗时""" + async def _time_and_run_task(self, coroutine, name: str) -> Tuple[str, Any, float]: + """计时并运行异步任务的辅助函数 + + Args: + coroutine: 要执行的协程 + name: 任务名称 + + Returns: + Tuple[str, Any, float]: (任务名称, 任务结果, 执行耗时) + """ start_time = time.time() result = await coroutine end_time = time.time() duration = end_time - start_time return name, result, duration - def build_s4u_chat_history_prompts(self, message_list_before_now: list, target_user_id: str) -> tuple[str, str]: + def build_s4u_chat_history_prompts(self, message_list_before_now: List[Dict[str, Any]], target_user_id: str) -> Tuple[str, str]: """ 构建 s4u 风格的分离对话 prompt @@ -488,7 +546,7 @@ class DefaultReplyer: target_user_id: 目标用户ID(当前对话对象) Returns: - tuple: (核心对话prompt, 背景对话prompt) + Tuple[str, str]: (核心对话prompt, 背景对话prompt) """ core_dialogue_list = [] background_dialogue_list = [] @@ -507,7 +565,7 @@ class DefaultReplyer: # 其他用户的对话 background_dialogue_list.append(msg_dict) except Exception as e: - logger.error(f"![1753364551656](image/default_generator/1753364551656.png)记录: {msg_dict}, 错误: {e}") + logger.error(f"处理消息记录时出错: {msg_dict}, 错误: {e}") # 构建背景对话 prompt background_dialogue_prompt = "" @@ -552,8 +610,25 @@ class DefaultReplyer: sender: str, target: str, chat_info: str, - ): - """构建 mai_think 上下文信息""" + ) -> Any: + """构建 mai_think 上下文信息 + + Args: + chat_id: 聊天ID + memory_block: 记忆块内容 + relation_info: 关系信息 + time_block: 时间块内容 + chat_target_1: 聊天目标1 + chat_target_2: 聊天目标2 + mood_prompt: 情绪提示 + identity_block: 身份块内容 + sender: 发送者名称 + target: 目标消息内容 + chat_info: 聊天信息 + + Returns: + Any: mai_think 实例 + """ mai_think = mai_thinking_manager.get_mai_think(chat_id) mai_think.memory_block = memory_block mai_think.relation_info_block = relation_info @@ -799,15 +874,14 @@ class DefaultReplyer: async def build_prompt_rewrite_context( self, - reply_data: Dict[str, Any], + raw_reply: str, + reason: str, + reply_to: str, ) -> str: chat_stream = self.chat_stream chat_id = chat_stream.stream_id is_group_chat = bool(chat_stream.group_info) - reply_to = reply_data.get("reply_to", "none") - raw_reply = reply_data.get("raw_reply", "") - reason = reply_data.get("reason", "") sender, target = self._parse_reply_target(reply_to) # 添加情绪状态获取 @@ -834,7 +908,7 @@ class DefaultReplyer: # 并行执行2个构建任务 expression_habits_block, relation_info = await asyncio.gather( self.build_expression_habits(chat_talking_prompt_half, target), - self.build_relation_info(reply_data), + self.build_relation_info(reply_to), ) keywords_reaction_prompt = await self.build_keywords_reaction_prompt(target) diff --git a/src/plugin_system/apis/generator_api.py b/src/plugin_system/apis/generator_api.py index 4ecadd8b8..bd68b6617 100644 --- a/src/plugin_system/apis/generator_api.py +++ b/src/plugin_system/apis/generator_api.py @@ -151,15 +151,22 @@ async def rewrite_reply( enable_splitter: bool = True, enable_chinese_typo: bool = True, model_configs: Optional[List[Dict[str, Any]]] = None, + raw_reply: str = "", + reason: str = "", + reply_to: str = "", ) -> Tuple[bool, List[Tuple[str, Any]]]: """重写回复 Args: chat_stream: 聊天流对象(优先) - reply_data: 回复数据 + reply_data: 回复数据字典(备用,当其他参数缺失时从此获取) chat_id: 聊天ID(备用) enable_splitter: 是否启用消息分割器 enable_chinese_typo: 是否启用错字生成器 + model_configs: 模型配置列表 + raw_reply: 原始回复内容 + reason: 回复原因 + reply_to: 回复对象 Returns: Tuple[bool, List[Tuple[str, Any]]]: (是否成功, 回复集合) @@ -173,8 +180,18 @@ async def rewrite_reply( logger.info("[GeneratorAPI] 开始重写回复") + # 如果参数缺失,从reply_data中获取 + if reply_data: + raw_reply = raw_reply or reply_data.get("raw_reply", "") + reason = reason or reply_data.get("reason", "") + reply_to = reply_to or reply_data.get("reply_to", "") + # 调用回复器重写回复 - success, content = await replyer.rewrite_reply_with_context(reply_data=reply_data or {}) + success, content = await replyer.rewrite_reply_with_context( + raw_reply=raw_reply, + reason=reason, + reply_to=reply_to, + ) reply_set = [] if content: reply_set = await process_human_text(content, enable_splitter, enable_chinese_typo) From c3d9f53cb28f856d14c14a937ff1ca9ba9a12b14 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sun, 27 Jul 2025 10:23:06 +0800 Subject: [PATCH 03/22] chat_api_doc --- docs/plugins/api/chat-api.md | 169 +++++++++++++---------------- src/plugin_system/apis/chat_api.py | 18 +-- 2 files changed, 83 insertions(+), 104 deletions(-) diff --git a/docs/plugins/api/chat-api.md b/docs/plugins/api/chat-api.md index 496a58623..b9b95e274 100644 --- a/docs/plugins/api/chat-api.md +++ b/docs/plugins/api/chat-api.md @@ -5,147 +5,126 @@ ## 导入方式 ```python -from src.plugin_system.apis import chat_api +from src.plugin_system import chat_api # 或者 -from src.plugin_system.apis.chat_api import ChatManager as chat +from src.plugin_system.apis import chat_api +``` + +一种**Deprecated**方式: +```python +from src.plugin_system.apis.chat_api import ChatManager ``` ## 主要功能 -### 1. 获取聊天流 +### 1. 获取所有的聊天流 -#### `get_all_streams(platform: str = "qq") -> List[ChatStream]` -获取所有聊天流 +```python +def get_all_streams(platform: Optional[str] | SpecialTypes = "qq") -> List[ChatStream]: +``` -**参数:** -- `platform`:平台筛选,默认为"qq" +**Args**: +- `platform`:平台筛选,默认为"qq",可以使用`SpecialTypes`枚举类中的`SpecialTypes.ALL_PLATFORMS`来获取所有平台的聊天流。 -**返回:** +**Returns**: - `List[ChatStream]`:聊天流列表 -**示例:** +### 2. 获取群聊聊天流 + ```python -streams = chat_api.get_all_streams() -for stream in streams: - print(f"聊天流ID: {stream.stream_id}") +def get_group_streams(platform: Optional[str] | SpecialTypes = "qq") -> List[ChatStream]: ``` -#### `get_group_streams(platform: str = "qq") -> List[ChatStream]` -获取所有群聊聊天流 +**Args**: +- `platform`:平台筛选,默认为"qq",可以使用`SpecialTypes`枚举类中的`SpecialTypes.ALL_PLATFORMS`来获取所有平台的群聊流。 -**参数:** -- `platform`:平台筛选,默认为"qq" - -**返回:** +**Returns**: - `List[ChatStream]`:群聊聊天流列表 -#### `get_private_streams(platform: str = "qq") -> List[ChatStream]` -获取所有私聊聊天流 +### 3. 获取私聊聊天流 -**参数:** -- `platform`:平台筛选,默认为"qq" +```python +def get_private_streams(platform: Optional[str] | SpecialTypes = "qq") -> List[ChatStream]: +``` -**返回:** +**Args**: +- `platform`:平台筛选,默认为"qq",可以使用`SpecialTypes`枚举类中的`SpecialTypes.ALL_PLATFORMS`来获取所有平台的私聊流。 + +**Returns**: - `List[ChatStream]`:私聊聊天流列表 -### 2. 查找特定聊天流 +### 4. 根据群ID获取聊天流 -#### `get_stream_by_group_id(group_id: str, platform: str = "qq") -> Optional[ChatStream]` -根据群ID获取聊天流 +```python +def get_stream_by_group_id(group_id: str, platform: Optional[str] | SpecialTypes = "qq") -> Optional[ChatStream]: +``` -**参数:** +**Args**: - `group_id`:群聊ID -- `platform`:平台,默认为"qq" +- `platform`:平台筛选,默认为"qq",可以使用`SpecialTypes`枚举类中的`SpecialTypes.ALL_PLATFORMS`来获取所有平台的群聊流。 -**返回:** +**Returns**: - `Optional[ChatStream]`:聊天流对象,如果未找到返回None -**示例:** +### 5. 根据用户ID获取私聊流 + ```python -chat_stream = chat_api.get_stream_by_group_id("123456789") -if chat_stream: - print(f"找到群聊: {chat_stream.group_info.group_name}") +def get_stream_by_user_id(user_id: str, platform: Optional[str] | SpecialTypes = "qq") -> Optional[ChatStream]: ``` -#### `get_stream_by_user_id(user_id: str, platform: str = "qq") -> Optional[ChatStream]` -根据用户ID获取私聊流 - -**参数:** +**Args**: - `user_id`:用户ID -- `platform`:平台,默认为"qq" +- `platform`:平台筛选,默认为"qq",可以使用`SpecialTypes`枚举类中的`SpecialTypes.ALL_PLATFORMS`来获取所有平台的私聊流。 -**返回:** +**Returns**: - `Optional[ChatStream]`:聊天流对象,如果未找到返回None -### 3. 聊天流信息查询 +### 6. 获取聊天流类型 -#### `get_stream_type(chat_stream: ChatStream) -> str` -获取聊天流类型 +```python +def get_stream_type(chat_stream: ChatStream) -> str: +``` -**参数:** +**Args**: - `chat_stream`:聊天流对象 -**返回:** -- `str`:聊天类型 ("group", "private", "unknown") +**Returns**: +- `str`:聊天流类型,可能的值包括`private`(私聊流),`group`(群聊流)以及`unknown`(未知类型)。 -#### `get_stream_info(chat_stream: ChatStream) -> Dict[str, Any]` -获取聊天流详细信息 +### 7. 获取聊天流信息 -**参数:** +```python +def get_stream_info(chat_stream: ChatStream) -> Dict[str, Any]: +``` + +**Args**: - `chat_stream`:聊天流对象 -**返回:** -- `Dict[str, Any]`:聊天流信息字典,包含stream_id、platform、type等信息 +**Returns**: +- `Dict[str, Any]`:聊天流的详细信息,包括但不限于: + - `stream_id`:聊天流ID + - `platform`:平台名称 + - `type`:聊天流类型 + - `group_id`:群聊ID + - `group_name`:群聊名称 + - `user_id`:用户ID + - `user_name`:用户名称 + +### 8. 获取聊天流统计摘要 -**示例:** ```python -info = chat_api.get_stream_info(chat_stream) -print(f"聊天类型: {info['type']}") -print(f"平台: {info['platform']}") -if info['type'] == 'group': - print(f"群ID: {info['group_id']}") - print(f"群名: {info['group_name']}") +def get_streams_summary() -> Dict[str, int]: ``` -#### `get_streams_summary() -> Dict[str, int]` -获取聊天流统计信息 +**Returns**: +- `Dict[str, int]`:聊天流统计信息摘要,包含以下键: + - `total_streams`:总聊天流数量 + - `group_streams`:群聊流数量 + - `private_streams`:私聊流数量 + - `qq_streams`:QQ平台流数量 -**返回:** -- `Dict[str, int]`:包含各平台群聊和私聊数量的统计字典 - -## 使用示例 - -### 基础用法 -```python -from src.plugin_system.apis import chat_api - -# 获取所有群聊 -group_streams = chat_api.get_group_streams() -print(f"共有 {len(group_streams)} 个群聊") - -# 查找特定群聊 -target_group = chat_api.get_stream_by_group_id("123456789") -if target_group: - group_info = chat_api.get_stream_info(target_group) - print(f"群名: {group_info['group_name']}") -``` - -### 遍历所有聊天流 -```python -# 获取所有聊天流并分类处理 -all_streams = chat_api.get_all_streams() - -for stream in all_streams: - stream_type = chat_api.get_stream_type(stream) - if stream_type == "group": - print(f"群聊: {stream.group_info.group_name}") - elif stream_type == "private": - print(f"私聊: {stream.user_info.user_nickname}") -``` ## 注意事项 -1. 所有函数都有错误处理,失败时会记录日志 -2. 查询函数返回None或空列表时表示未找到结果 -3. `platform`参数通常为"qq",也可能支持其他平台 -4. `ChatStream`对象包含了聊天的完整信息,包括用户信息、群信息等 \ No newline at end of file +1. 大部分函数在参数不合法时候会抛出异常,请确保你的程序进行了捕获。 +2. `ChatStream`对象包含了聊天的完整信息,包括用户信息、群信息等。 \ No newline at end of file diff --git a/src/plugin_system/apis/chat_api.py b/src/plugin_system/apis/chat_api.py index 7a3f0941c..9e995d36f 100644 --- a/src/plugin_system/apis/chat_api.py +++ b/src/plugin_system/apis/chat_api.py @@ -210,7 +210,7 @@ class ChatManager: chat_stream: 聊天流对象 Returns: - Dict[str, Any]: 聊天流信息字典 + Dict ({str: Any}): 聊天流信息字典 Raises: TypeError: 如果 chat_stream 不是 ChatStream 类型 @@ -285,41 +285,41 @@ class ChatManager: # ============================================================================= -def get_all_streams(platform: Optional[str] | SpecialTypes = "qq"): +def get_all_streams(platform: Optional[str] | SpecialTypes = "qq") -> List[ChatStream]: """获取所有聊天流的便捷函数""" return ChatManager.get_all_streams(platform) -def get_group_streams(platform: Optional[str] | SpecialTypes = "qq"): +def get_group_streams(platform: Optional[str] | SpecialTypes = "qq") -> List[ChatStream]: """获取群聊聊天流的便捷函数""" return ChatManager.get_group_streams(platform) -def get_private_streams(platform: Optional[str] | SpecialTypes = "qq"): +def get_private_streams(platform: Optional[str] | SpecialTypes = "qq") -> List[ChatStream]: """获取私聊聊天流的便捷函数""" return ChatManager.get_private_streams(platform) -def get_stream_by_group_id(group_id: str, platform: Optional[str] | SpecialTypes = "qq"): +def get_stream_by_group_id(group_id: str, platform: Optional[str] | SpecialTypes = "qq") -> Optional[ChatStream]: """根据群ID获取聊天流的便捷函数""" return ChatManager.get_group_stream_by_group_id(group_id, platform) -def get_stream_by_user_id(user_id: str, platform: Optional[str] | SpecialTypes = "qq"): +def get_stream_by_user_id(user_id: str, platform: Optional[str] | SpecialTypes = "qq") -> Optional[ChatStream]: """根据用户ID获取私聊流的便捷函数""" return ChatManager.get_private_stream_by_user_id(user_id, platform) -def get_stream_type(chat_stream: ChatStream): +def get_stream_type(chat_stream: ChatStream) -> str: """获取聊天流类型的便捷函数""" return ChatManager.get_stream_type(chat_stream) -def get_stream_info(chat_stream: ChatStream): +def get_stream_info(chat_stream: ChatStream) -> Dict[str, Any]: """获取聊天流信息的便捷函数""" return ChatManager.get_stream_info(chat_stream) -def get_streams_summary(): +def get_streams_summary() -> Dict[str, int]: """获取聊天流统计摘要的便捷函数""" return ChatManager.get_streams_summary() From c4c0983947c48d22f0e3740a458d5657bfe3625d Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sun, 27 Jul 2025 12:43:59 +0800 Subject: [PATCH 04/22] typing and ruff fix --- src/chat/chat_loop/heartFC_chat.py | 236 +++++++++++++++------------- src/chat/planner_actions/planner.py | 10 +- 2 files changed, 134 insertions(+), 112 deletions(-) diff --git a/src/chat/chat_loop/heartFC_chat.py b/src/chat/chat_loop/heartFC_chat.py index 5feae462a..efa8f69b9 100644 --- a/src/chat/chat_loop/heartFC_chat.py +++ b/src/chat/chat_loop/heartFC_chat.py @@ -2,7 +2,7 @@ import asyncio import time import traceback import random -from typing import List, Optional, Dict, Any +from typing import List, Optional, Dict, Any, Tuple from rich.traceback import install from src.config.config import global_config @@ -217,9 +217,11 @@ class HeartFChatting: filter_bot=True, ) if global_config.chat.focus_value != 0: - if len(new_messages_data) > 3 / pow(global_config.chat.focus_value,0.5): + if len(new_messages_data) > 3 / pow(global_config.chat.focus_value, 0.5): self.loop_mode = ChatMode.FOCUS - self.energy_value = 10 + (len(new_messages_data) / (3 / pow(global_config.chat.focus_value,0.5))) * 10 + self.energy_value = ( + 10 + (len(new_messages_data) / (3 / pow(global_config.chat.focus_value, 0.5))) * 10 + ) return True if self.energy_value >= 30: @@ -254,20 +256,21 @@ class HeartFChatting: ) person_name = await person_info_manager.get_value(person_id, "person_name") return f"{person_name}:{message_data.get('processed_plain_text')}" - + async def _send_and_store_reply( self, - response_set, - reply_to_str, - loop_start_time, - action_message, - cycle_timers, + response_set, + reply_to_str, + loop_start_time, + action_message, + cycle_timers: Dict[str, float], thinking_id, - plan_result): + plan_result, + ) -> Tuple[Dict[str, Any], str, Dict[str, float]]: with Timer("回复发送", cycle_timers): reply_text = await self._send_response(response_set, reply_to_str, loop_start_time, action_message) - # 存储reply action信息 + # 存储reply action信息 person_info_manager = get_person_info_manager() person_id = person_info_manager.get_person_id( action_message.get("chat_info_platform", ""), @@ -275,7 +278,7 @@ class HeartFChatting: ) person_name = await person_info_manager.get_value(person_id, "person_name") action_prompt_display = f"你对{person_name}进行了回复:{reply_text}" - + await database_api.store_action_info( chat_stream=self.chat_stream, action_build_into_prompt=False, @@ -285,10 +288,9 @@ class HeartFChatting: action_data={"reply_text": reply_text, "reply_to": reply_to_str}, action_name="reply", ) - - + # 构建循环信息 - loop_info = { + loop_info: Dict[str, Any] = { "loop_plan_info": { "action_result": plan_result.get("action_result", {}), }, @@ -299,8 +301,8 @@ class HeartFChatting: "taken_time": time.time(), }, } - - return loop_info, reply_text,cycle_timers + + return loop_info, reply_text, cycle_timers async def _observe(self, message_data: Optional[Dict[str, Any]] = None): # sourcery skip: hoist-statement-from-if, merge-comparisons, reintroduce-else @@ -310,12 +312,12 @@ class HeartFChatting: reply_text = "" # 初始化reply_text变量,避免UnboundLocalError gen_task = None # 初始化gen_task变量,避免UnboundLocalError reply_to_str = "" # 初始化reply_to_str变量 - + # 创建新的循环信息 cycle_timers, thinking_id = self.start_cycle() logger.info(f"{self.log_prefix} 开始第{self._cycle_counter}次思考[模式:{self.loop_mode}]") - + if ENABLE_S4U: await send_typing() @@ -337,19 +339,20 @@ class HeartFChatting: skip_planner = False if self.loop_mode == ChatMode.NORMAL: # 过滤掉reply相关的动作,检查是否还有其他动作 - non_reply_actions = {k: v for k, v in available_actions.items() - if k not in ['reply', 'no_reply', 'no_action']} - + non_reply_actions = { + k: v for k, v in available_actions.items() if k not in ["reply", "no_reply", "no_action"] + } + if not non_reply_actions: skip_planner = True logger.info(f"{self.log_prefix} Normal模式下没有可用动作,直接回复") - + # 直接设置为reply动作 action_type = "reply" reasoning = "" action_data = {"loop_start_time": loop_start_time} is_parallel = False - + # 构建plan_result用于后续处理 plan_result = { "action_result": { @@ -361,22 +364,25 @@ class HeartFChatting: }, "action_prompt": "", } - target_message = message_data - + target_message = message_data + # 如果normal模式且不跳过规划器,开始一个回复生成进程,先准备好回复(其实是和planer同时进行的) if not skip_planner: reply_to_str = await self.build_reply_to_str(message_data) - gen_task = asyncio.create_task(self._generate_response( - message_data=message_data, - available_actions=available_actions, - reply_to=reply_to_str, - request_type="chat.replyer.normal")) - + gen_task = asyncio.create_task( + self._generate_response( + message_data=message_data, + available_actions=available_actions, + reply_to=reply_to_str, + request_type="chat.replyer.normal", + ) + ) + if not skip_planner: with Timer("规划器", cycle_timers): plan_result, target_message = await self.action_planner.plan(mode=self.loop_mode) - action_result: dict = plan_result.get("action_result", {}) # type: ignore + action_result: Dict[str, Any] = plan_result.get("action_result", {}) # type: ignore action_type, action_data, reasoning, is_parallel = ( action_result.get("action_type", "error"), action_result.get("action_data", {}), @@ -386,29 +392,27 @@ class HeartFChatting: action_data["loop_start_time"] = loop_start_time - if action_type == "reply": logger.info(f"{self.log_prefix}{global_config.bot.nickname} 决定进行回复") elif is_parallel: - logger.info( - f"{self.log_prefix}{global_config.bot.nickname} 决定进行回复, 同时执行{action_type}动作" - ) + logger.info(f"{self.log_prefix}{global_config.bot.nickname} 决定进行回复, 同时执行{action_type}动作") else: # 只有在gen_task存在时才进行相关操作 - if gen_task is not None: + if gen_task: if not gen_task.done(): gen_task.cancel() logger.debug(f"{self.log_prefix} 已取消预生成的回复任务") logger.info( f"{self.log_prefix}{global_config.bot.nickname} 原本想要回复,但选择执行{action_type},不发表回复" ) - else: - content = " ".join([item[1] for item in gen_task.result() if item[0] == "text"]) + elif generation_result := gen_task.result(): + content = " ".join([item[1] for item in generation_result if item[0] == "text"]) logger.debug(f"{self.log_prefix} 预生成的回复任务已完成") logger.info( f"{self.log_prefix}{global_config.bot.nickname} 原本想要回复:{content},但选择执行{action_type},不发表回复" ) - + else: + logger.warning(f"{self.log_prefix} 预生成的回复任务未生成有效内容") action_message: Dict[str, Any] = message_data or target_message # type: ignore if action_type == "reply": @@ -417,11 +421,14 @@ class HeartFChatting: # 只有在gen_task存在时才等待 if not gen_task: reply_to_str = await self.build_reply_to_str(message_data) - gen_task = asyncio.create_task(self._generate_response( - message_data=message_data, - available_actions=available_actions, - reply_to=reply_to_str, - request_type="chat.replyer.normal")) + gen_task = asyncio.create_task( + self._generate_response( + message_data=message_data, + available_actions=available_actions, + reply_to=reply_to_str, + request_type="chat.replyer.normal", + ) + ) gather_timeout = global_config.chat.thinking_timeout try: @@ -436,56 +443,71 @@ class HeartFChatting: return False else: logger.info(f"{self.log_prefix}{global_config.bot.nickname} 决定进行回复 (focus模式)") - + # 构建reply_to字符串 reply_to_str = await self.build_reply_to_str(action_message) - + # 生成回复 with Timer("回复生成", cycle_timers): response_set = await self._generate_response( - message_data=action_message, - available_actions=available_actions, - reply_to=reply_to_str, - request_type="chat.replyer.focus") - + message_data=action_message, + available_actions=available_actions, + reply_to=reply_to_str, + request_type="chat.replyer.focus", + ) + if not response_set: logger.warning(f"{self.log_prefix}模型未生成回复内容") return False - - loop_info, reply_text,cycle_timers = await self._send_and_store_reply(response_set, reply_to_str, loop_start_time, action_message, cycle_timers, thinking_id, plan_result) - + + loop_info, reply_text, cycle_timers = await self._send_and_store_reply( + response_set, reply_to_str, loop_start_time, action_message, cycle_timers, thinking_id, plan_result + ) + return True else: # 并行执行:同时进行回复发送和动作执行 - tasks = [] - + # 先置空防止未定义错误 + background_reply_task = None + background_action_task = None # 如果是并行执行且在normal模式下,需要等待预生成的回复任务完成并发送回复 if self.loop_mode == ChatMode.NORMAL and is_parallel and gen_task: - async def handle_reply_task(): + + async def handle_reply_task() -> Tuple[Optional[Dict[str, Any]], str, Dict[str, float]]: # 等待预生成的回复任务完成 gather_timeout = global_config.chat.thinking_timeout try: response_set = await asyncio.wait_for(gen_task, timeout=gather_timeout) - + except asyncio.TimeoutError: - logger.warning(f"{self.log_prefix} 并行执行:回复生成超时>{global_config.chat.thinking_timeout}s,已跳过") + logger.warning( + f"{self.log_prefix} 并行执行:回复生成超时>{global_config.chat.thinking_timeout}s,已跳过" + ) return None, "", {} except asyncio.CancelledError: logger.debug(f"{self.log_prefix} 并行执行:回复生成任务已被取消") return None, "", {} - + if not response_set: logger.warning(f"{self.log_prefix} 模型超时或生成回复内容为空") return None, "", {} - + reply_to_str = await self.build_reply_to_str(action_message) - loop_info, reply_text, cycle_timers_reply = await self._send_and_store_reply(response_set, reply_to_str, loop_start_time, action_message, cycle_timers, thinking_id, plan_result) + loop_info, reply_text, cycle_timers_reply = await self._send_and_store_reply( + response_set, + reply_to_str, + loop_start_time, + action_message, + cycle_timers, + thinking_id, + plan_result, + ) return loop_info, reply_text, cycle_timers_reply - - # 添加回复任务到并行任务列表 - tasks.append(asyncio.create_task(handle_reply_task())) - + + # 执行回复任务并赋值到变量 + background_reply_task = asyncio.create_task(handle_reply_task()) + # 动作执行任务 async def handle_action_task(): with Timer("动作执行", cycle_timers): @@ -493,52 +515,55 @@ class HeartFChatting: action_type, reasoning, action_data, cycle_timers, thinking_id, action_message ) return success, reply_text, command - - # 添加动作执行任务到并行任务列表 - tasks.append(asyncio.create_task(handle_action_task())) - - # 并行执行所有任务 - results = await asyncio.gather(*tasks, return_exceptions=True) - - # 处理结果 + + # 执行动作任务并赋值到变量 + background_action_task = asyncio.create_task(handle_action_task()) + reply_loop_info = None reply_text_from_reply = "" action_success = False action_reply_text = "" action_command = "" - - if len(tasks) == 2: # 有回复任务和动作任务 + + # 并行执行所有任务 + if background_reply_task: + results = await asyncio.gather( + background_reply_task, background_action_task, return_exceptions=True + ) # 处理回复任务结果 reply_result = results[0] - if isinstance(reply_result, Exception): + if isinstance(reply_result, BaseException): logger.error(f"{self.log_prefix} 回复任务执行异常: {reply_result}") elif reply_result and reply_result[0] is not None: reply_loop_info, reply_text_from_reply, _ = reply_result - + # 处理动作任务结果 - action_result = results[1] - if isinstance(action_result, Exception): - logger.error(f"{self.log_prefix} 动作任务执行异常: {action_result}") + action_task_result = results[1] + if isinstance(action_task_result, BaseException): + logger.error(f"{self.log_prefix} 动作任务执行异常: {action_task_result}") else: - action_success, action_reply_text, action_command = action_result - - else: # 只有动作任务 - action_result = results[0] - if isinstance(action_result, Exception): - logger.error(f"{self.log_prefix} 动作任务执行异常: {action_result}") + action_success, action_reply_text, action_command = action_task_result + else: + results = await asyncio.gather(background_action_task, return_exceptions=True) + # 只有动作任务 + action_task_result = results[0] + if isinstance(action_task_result, BaseException): + logger.error(f"{self.log_prefix} 动作任务执行异常: {action_task_result}") else: - action_success, action_reply_text, action_command = action_result + action_success, action_reply_text, action_command = action_task_result # 构建最终的循环信息 if reply_loop_info: # 如果有回复信息,使用回复的loop_info作为基础 loop_info = reply_loop_info # 更新动作执行信息 - loop_info["loop_action_info"].update({ - "action_taken": action_success, - "command": action_command, - "taken_time": time.time(), - }) + loop_info["loop_action_info"].update( + { + "action_taken": action_success, + "command": action_command, + "taken_time": time.time(), + } + ) reply_text = reply_text_from_reply else: # 没有回复信息,构建纯动作的loop_info @@ -555,11 +580,10 @@ class HeartFChatting: } reply_text = action_reply_text - if ENABLE_S4U: await stop_typing() await mai_thinking_manager.get_mai_think(self.stream_id).do_think_after_response(reply_text) - + self.end_cycle(loop_info, cycle_timers) self.print_cycle_info(cycle_timers) @@ -603,7 +627,7 @@ class HeartFChatting: action: str, reasoning: str, action_data: dict, - cycle_timers: dict, + cycle_timers: Dict[str, float], thinking_id: str, action_message: dict, ) -> tuple[bool, str, str]: @@ -712,7 +736,11 @@ class HeartFChatting: return False async def _generate_response( - self, message_data: dict, available_actions: Optional[Dict[str, ActionInfo]], reply_to: str, request_type: str = "chat.replyer.normal" + self, + message_data: dict, + available_actions: Optional[Dict[str, ActionInfo]], + reply_to: str, + request_type: str = "chat.replyer.normal", ) -> Optional[list]: """生成普通回复""" try: @@ -734,7 +762,7 @@ class HeartFChatting: logger.error(f"{self.log_prefix}回复生成出现错误:{str(e)} {traceback.format_exc()}") return None - async def _send_response(self, reply_set, reply_to, thinking_start_time, message_data): + async def _send_response(self, reply_set, reply_to, thinking_start_time, message_data) -> str: current_time = time.time() new_message_count = message_api.count_new_messages( chat_id=self.chat_stream.stream_id, start_time=thinking_start_time, end_time=current_time @@ -746,13 +774,9 @@ class HeartFChatting: need_reply = new_message_count >= random.randint(2, 4) if need_reply: - logger.info( - f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,使用引用回复" - ) + logger.info(f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,使用引用回复") else: - logger.info( - f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,不使用引用回复" - ) + logger.info(f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,不使用引用回复") reply_text = "" first_replied = False diff --git a/src/chat/planner_actions/planner.py b/src/chat/planner_actions/planner.py index d3ea2fd74..0b26a97d0 100644 --- a/src/chat/planner_actions/planner.py +++ b/src/chat/planner_actions/planner.py @@ -211,9 +211,8 @@ class ActionPlanner: reasoning = f"Planner 内部处理错误: {outer_e}" is_parallel = False - if mode == ChatMode.NORMAL: - if action in current_available_actions: - is_parallel = current_available_actions[action].parallel_action + if mode == ChatMode.NORMAL and action in current_available_actions: + is_parallel = current_available_actions[action].parallel_action action_result = { "action_type": action, @@ -256,7 +255,7 @@ class ActionPlanner: actions_before_now = get_actions_by_timestamp_with_chat( chat_id=self.chat_id, - timestamp_start=time.time()-3600, + timestamp_start=time.time() - 3600, timestamp_end=time.time(), limit=5, ) @@ -264,7 +263,7 @@ class ActionPlanner: actions_before_now_block = build_readable_actions( actions=actions_before_now, ) - + actions_before_now_block = f"你刚刚选择并执行过的action是:\n{actions_before_now_block}" self.last_obs_time_mark = time.time() @@ -276,7 +275,6 @@ class ActionPlanner: if global_config.chat.at_bot_inevitable_reply: mentioned_bonus = "\n- 有人提到你,或者at你" - by_what = "聊天内容" target_prompt = '\n "target_message_id":"触发action的消息id"' no_action_block = f"""重要说明: From dc1b9963214526132f841063c085a592c5faeada Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sun, 27 Jul 2025 12:54:37 +0800 Subject: [PATCH 05/22] config_api_doc --- docs/plugins/api/config-api.md | 185 ++++----------------------- src/plugin_system/apis/config_api.py | 50 +------- 2 files changed, 28 insertions(+), 207 deletions(-) diff --git a/docs/plugins/api/config-api.md b/docs/plugins/api/config-api.md index e61bb6962..2a5691fc4 100644 --- a/docs/plugins/api/config-api.md +++ b/docs/plugins/api/config-api.md @@ -6,178 +6,47 @@ ```python from src.plugin_system.apis import config_api +# 或者 +from src.plugin_system import config_api ``` ## 主要功能 -### 1. 配置访问 +### 1. 访问全局配置 -#### `get_global_config(key: str, default: Any = None) -> Any` -安全地从全局配置中获取一个值 - -**参数:** -- `key`:配置键名,支持嵌套访问如 "section.subsection.key" -- `default`:如果配置不存在时返回的默认值 - -**返回:** -- `Any`:配置值或默认值 - -**示例:** ```python -# 获取机器人昵称 +def get_global_config(key: str, default: Any = None) -> Any: +``` + +**Args**: +- `key`: 命名空间式配置键名,使用嵌套访问,如 "section.subsection.key",大小写敏感 +- `default`: 如果配置不存在时返回的默认值 + +**Returns**: +- `Any`: 配置值或默认值 + +#### 示例: +获取机器人昵称 +```python bot_name = config_api.get_global_config("bot.nickname", "MaiBot") - -# 获取嵌套配置 -llm_model = config_api.get_global_config("model.default.model_name", "gpt-3.5-turbo") - -# 获取不存在的配置 -unknown_config = config_api.get_global_config("unknown.config", "默认值") ``` -#### `get_plugin_config(plugin_config: dict, key: str, default: Any = None) -> Any` -从插件配置中获取值,支持嵌套键访问 +### 2. 获取插件配置 -**参数:** -- `plugin_config`:插件配置字典 -- `key`:配置键名,支持嵌套访问如 "section.subsection.key" -- `default`:如果配置不存在时返回的默认值 - -**返回:** -- `Any`:配置值或默认值 - -**示例:** ```python -# 在插件中使用 -class MyPlugin(BasePlugin): - async def handle_action(self, action_data, chat_stream): - # 获取插件配置 - api_key = config_api.get_plugin_config(self.config, "api.key", "") - timeout = config_api.get_plugin_config(self.config, "timeout", 30) - - if not api_key: - logger.warning("API密钥未配置") - return False +def get_plugin_config(plugin_config: dict, key: str, default: Any = None) -> Any: ``` +**Args**: +- `plugin_config`: 插件配置字典 +- `key`: 配置键名,支持嵌套访问如 "section.subsection.key",大小写敏感 +- `default`: 如果配置不存在时返回的默认值 -### 2. 用户信息API - -#### `get_user_id_by_person_name(person_name: str) -> tuple[str, str]` -根据用户名获取用户ID - -**参数:** -- `person_name`:用户名 - -**返回:** -- `tuple[str, str]`:(平台, 用户ID) - -**示例:** -```python -platform, user_id = await config_api.get_user_id_by_person_name("张三") -if platform and user_id: - print(f"用户张三在{platform}平台的ID是{user_id}") -``` - -#### `get_person_info(person_id: str, key: str, default: Any = None) -> Any` -获取用户信息 - -**参数:** -- `person_id`:用户ID -- `key`:信息键名 -- `default`:默认值 - -**返回:** -- `Any`:用户信息值或默认值 - -**示例:** -```python -# 获取用户昵称 -nickname = await config_api.get_person_info(person_id, "nickname", "未知用户") - -# 获取用户印象 -impression = await config_api.get_person_info(person_id, "impression", "") -``` - -## 使用示例 - -### 配置驱动的插件开发 -```python -from src.plugin_system.apis import config_api -from src.plugin_system.base import BasePlugin - -class WeatherPlugin(BasePlugin): - async def handle_action(self, action_data, chat_stream): - # 从全局配置获取API配置 - api_endpoint = config_api.get_global_config("weather.api_endpoint", "") - default_city = config_api.get_global_config("weather.default_city", "北京") - - # 从插件配置获取特定设置 - api_key = config_api.get_plugin_config(self.config, "api_key", "") - timeout = config_api.get_plugin_config(self.config, "timeout", 10) - - if not api_key: - return {"success": False, "message": "Weather API密钥未配置"} - - # 使用配置进行天气查询... - return {"success": True, "message": f"{default_city}今天天气晴朗"} -``` - -### 用户信息查询 -```python -async def get_user_by_name(user_name: str): - """根据用户名获取完整的用户信息""" - - # 获取用户的平台和ID - platform, user_id = await config_api.get_user_id_by_person_name(user_name) - - if not platform or not user_id: - return None - - # 构建person_id - from src.person_info.person_info import PersonInfoManager - person_id = PersonInfoManager.get_person_id(platform, user_id) - - # 获取用户详细信息 - nickname = await config_api.get_person_info(person_id, "nickname", user_name) - impression = await config_api.get_person_info(person_id, "impression", "") - - return { - "platform": platform, - "user_id": user_id, - "nickname": nickname, - "impression": impression - } -``` - -## 配置键名说明 - -### 常用全局配置键 -- `bot.nickname`:机器人昵称 -- `bot.qq_account`:机器人QQ号 -- `model.default`:默认LLM模型配置 -- `database.path`:数据库路径 - -### 嵌套配置访问 -配置支持点号分隔的嵌套访问: -```python -# config.toml 中的配置: -# [bot] -# nickname = "MaiBot" -# qq_account = "123456" -# -# [model.default] -# model_name = "gpt-3.5-turbo" -# temperature = 0.7 - -# API调用: -bot_name = config_api.get_global_config("bot.nickname") -model_name = config_api.get_global_config("model.default.model_name") -temperature = config_api.get_global_config("model.default.temperature") -``` +**Returns**: +- `Any`: 配置值或默认值 ## 注意事项 1. **只读访问**:配置API只提供读取功能,插件不能修改全局配置 -2. **异步函数**:用户信息相关的函数是异步的,需要使用`await` -3. **错误处理**:所有函数都有错误处理,失败时会记录日志并返回默认值 -4. **安全性**:插件通过此API访问配置是安全和隔离的 -5. **性能**:频繁访问的配置建议在插件初始化时获取并缓存 \ No newline at end of file +2. **错误处理**:所有函数都有错误处理,失败时会记录日志并返回默认值 +3. **安全性**:插件通过此API访问配置是安全和隔离的 +4. **性能**:频繁访问的配置建议在插件初始化时获取并缓存 \ No newline at end of file diff --git a/src/plugin_system/apis/config_api.py b/src/plugin_system/apis/config_api.py index 6ec492caf..05556414e 100644 --- a/src/plugin_system/apis/config_api.py +++ b/src/plugin_system/apis/config_api.py @@ -10,7 +10,6 @@ from typing import Any from src.common.logger import get_logger from src.config.config import global_config -from src.person_info.person_info import get_person_info_manager logger = get_logger("config_api") @@ -26,7 +25,7 @@ def get_global_config(key: str, default: Any = None) -> Any: 插件应使用此方法读取全局配置,以保证只读和隔离性。 Args: - key: 命名空间式配置键名,支持嵌套访问,如 "section.subsection.key",大小写敏感 + key: 命名空间式配置键名,使用嵌套访问,如 "section.subsection.key",大小写敏感 default: 如果配置不存在时返回的默认值 Returns: @@ -76,50 +75,3 @@ def get_plugin_config(plugin_config: dict, key: str, default: Any = None) -> Any except Exception as e: logger.warning(f"[ConfigAPI] 获取插件配置 {key} 失败: {e}") return default - - -# ============================================================================= -# 用户信息API函数 -# ============================================================================= - - -async def get_user_id_by_person_name(person_name: str) -> tuple[str, str]: - """根据内部用户名获取用户ID - - Args: - person_name: 用户名 - - Returns: - tuple[str, str]: (平台, 用户ID) - """ - try: - person_info_manager = get_person_info_manager() - person_id = person_info_manager.get_person_id_by_person_name(person_name) - user_id: str = await person_info_manager.get_value(person_id, "user_id") # type: ignore - platform: str = await person_info_manager.get_value(person_id, "platform") # type: ignore - return platform, user_id - except Exception as e: - logger.error(f"[ConfigAPI] 根据用户名获取用户ID失败: {e}") - return "", "" - - -async def get_person_info(person_id: str, key: str, default: Any = None) -> Any: - """获取用户信息 - - Args: - person_id: 用户ID - key: 信息键名 - default: 默认值 - - Returns: - Any: 用户信息值或默认值 - """ - try: - person_info_manager = get_person_info_manager() - response = await person_info_manager.get_value(person_id, key) - if not response: - raise ValueError(f"[ConfigAPI] 获取用户 {person_id} 的信息 '{key}' 失败,返回默认值") - return response - except Exception as e: - logger.error(f"[ConfigAPI] 获取用户信息失败: {e}") - return default From d872d63feb643df1ac9b73eca9c29be95a0a46ca Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sun, 27 Jul 2025 13:33:16 +0800 Subject: [PATCH 06/22] database_api_doc --- docs/plugins/api/database-api.md | 340 +++++++++++-------------- src/plugin_system/apis/database_api.py | 19 +- 2 files changed, 157 insertions(+), 202 deletions(-) diff --git a/docs/plugins/api/database-api.md b/docs/plugins/api/database-api.md index 174bef158..5b6b4468f 100644 --- a/docs/plugins/api/database-api.md +++ b/docs/plugins/api/database-api.md @@ -6,72 +6,51 @@ ```python from src.plugin_system.apis import database_api +# 或者 +from src.plugin_system import database_api ``` ## 主要功能 -### 1. 通用数据库查询 - -#### `db_query(model_class, query_type="get", filters=None, data=None, limit=None, order_by=None, single_result=False)` -执行数据库查询操作的通用接口 - -**参数:** -- `model_class`:Peewee模型类,如ActionRecords、Messages等 -- `query_type`:查询类型,可选值: "get", "create", "update", "delete", "count" -- `filters`:过滤条件字典,键为字段名,值为要匹配的值 -- `data`:用于创建或更新的数据字典 -- `limit`:限制结果数量 -- `order_by`:排序字段列表,使用字段名,前缀'-'表示降序 -- `single_result`:是否只返回单个结果 - -**返回:** -根据查询类型返回不同的结果: -- "get":返回查询结果列表或单个结果 -- "create":返回创建的记录 -- "update":返回受影响的行数 -- "delete":返回受影响的行数 -- "count":返回记录数量 - -### 2. 便捷查询函数 - -#### `db_save(model_class, data, key_field=None, key_value=None)` -保存数据到数据库(创建或更新) - -**参数:** -- `model_class`:Peewee模型类 -- `data`:要保存的数据字典 -- `key_field`:用于查找现有记录的字段名 -- `key_value`:用于查找现有记录的字段值 - -**返回:** -- `Dict[str, Any]`:保存后的记录数据,失败时返回None - -#### `db_get(model_class, filters=None, order_by=None, limit=None)` -简化的查询函数 - -**参数:** -- `model_class`:Peewee模型类 -- `filters`:过滤条件字典 -- `order_by`:排序字段 -- `limit`:限制结果数量 - -**返回:** -- `Union[List[Dict], Dict, None]`:查询结果 - -### 3. 专用函数 - -#### `store_action_info(...)` -存储动作信息的专用函数 - -## 使用示例 - -### 1. 基本查询操作 +### 1. 通用数据库操作 ```python -from src.plugin_system.apis import database_api -from src.common.database.database_model import Messages, ActionRecords +async def db_query( + model_class: Type[Model], + data: Optional[Dict[str, Any]] = None, + query_type: Optional[str] = "get", + filters: Optional[Dict[str, Any]] = None, + limit: Optional[int] = None, + order_by: Optional[List[str]] = None, + single_result: Optional[bool] = False, +) -> Union[List[Dict[str, Any]], Dict[str, Any], None]: +``` +执行数据库查询操作的通用接口。 -# 查询最近10条消息 +**Args:** +- `model_class`: Peewee模型类。 + - Peewee模型类可以在`src.common.database.database_model`模块中找到,如`ActionRecords`、`Messages`等。 +- `data`: 用于创建或更新的数据 +- `query_type`: 查询类型 + - 可选值: `get`, `create`, `update`, `delete`, `count`。 +- `filters`: 过滤条件字典,键为字段名,值为要匹配的值。 +- `limit`: 限制结果数量。 +- `order_by`: 排序字段列表,使用字段名,前缀'-'表示降序。 + - 排序字段,前缀`-`表示降序,例如`-time`表示按时间字段(即`time`字段)降序 +- `single_result`: 是否只返回单个结果。 + +**Returns:** +- 根据查询类型返回不同的结果: + - `get`: 返回查询结果列表或单个结果。(如果 `single_result=True`) + - `create`: 返回创建的记录。 + - `update`: 返回受影响的行数。 + - `delete`: 返回受影响的行数。 + - `count`: 返回记录数量。 + +#### 示例 + +1. 查询最近10条消息 +```python messages = await database_api.db_query( Messages, query_type="get", @@ -79,180 +58,159 @@ messages = await database_api.db_query( limit=10, order_by=["-time"] ) - -# 查询单条记录 -message = await database_api.db_query( - Messages, - query_type="get", - filters={"message_id": "msg_123"}, - single_result=True -) ``` - -### 2. 创建记录 - +2. 创建一条记录 ```python -# 创建新的动作记录 new_record = await database_api.db_query( ActionRecords, + data={"action_id": "123", "time": time.time(), "action_name": "TestAction"}, query_type="create", - data={ - "action_id": "action_123", - "time": time.time(), - "action_name": "TestAction", - "action_done": True - } ) - -print(f"创建了记录: {new_record['id']}") ``` - -### 3. 更新记录 - +3. 更新记录 ```python -# 更新动作状态 updated_count = await database_api.db_query( ActionRecords, + data={"action_done": True}, query_type="update", - filters={"action_id": "action_123"}, - data={"action_done": True, "completion_time": time.time()} + filters={"action_id": "123"}, ) - -print(f"更新了 {updated_count} 条记录") ``` - -### 4. 删除记录 - +4. 删除记录 ```python -# 删除过期记录 deleted_count = await database_api.db_query( ActionRecords, query_type="delete", - filters={"time__lt": time.time() - 86400} # 删除24小时前的记录 + filters={"action_id": "123"} ) - -print(f"删除了 {deleted_count} 条过期记录") ``` - -### 5. 统计查询 - +5. 计数 ```python -# 统计消息数量 -message_count = await database_api.db_query( +count = await database_api.db_query( Messages, query_type="count", filters={"chat_id": chat_stream.stream_id} ) - -print(f"该聊天有 {message_count} 条消息") ``` -### 6. 使用便捷函数 - +### 2. 数据库保存 +```python +async def db_save( + model_class: Type[Model], data: Dict[str, Any], key_field: Optional[str] = None, key_value: Optional[Any] = None +) -> Optional[Dict[str, Any]]: +``` +保存数据到数据库(创建或更新) + +如果提供了key_field和key_value,会先尝试查找匹配的记录进行更新; + +如果没有找到匹配记录,或未提供key_field和key_value,则创建新记录。 + +**Args:** +- `model_class`: Peewee模型类。 +- `data`: 要保存的数据字典。 +- `key_field`: 用于查找现有记录的字段名,例如"action_id"。 +- `key_value`: 用于查找现有记录的字段值。 + +**Returns:** +- `Optional[Dict[str, Any]]`: 保存后的记录数据,失败时返回None。 + +#### 示例 +创建或更新一条记录 ```python -# 使用db_save进行创建或更新 record = await database_api.db_save( ActionRecords, { - "action_id": "action_123", + "action_id": "123", "time": time.time(), "action_name": "TestAction", "action_done": True }, key_field="action_id", - key_value="action_123" + key_value="123" ) +``` -# 使用db_get进行简单查询 -recent_messages = await database_api.db_get( +### 3. 数据库获取 +```python +async def db_get( + model_class: Type[Model], + filters: Optional[Dict[str, Any]] = None, + limit: Optional[int] = None, + order_by: Optional[str] = None, + single_result: Optional[bool] = False, +) -> Union[List[Dict[str, Any]], Dict[str, Any], None]: +``` + +从数据库获取记录 + +这是db_query方法的简化版本,专注于数据检索操作。 + +**Args:** +- `model_class`: Peewee模型类。 +- `filters`: 过滤条件字典,键为字段名,值为要匹配的值。 +- `limit`: 限制结果数量。 +- `order_by`: 排序字段,使用字段名,前缀'-'表示降序。 +- `single_result`: 是否只返回单个结果,如果为True,则返回单个记录字典或None;否则返回记录字典列表或空列表 + +**Returns:** +- `Union[List[Dict], Dict, None]`: 查询结果列表或单个结果(如果`single_result=True`),失败时返回None。 + +#### 示例 +1. 获取单个记录 +```python +record = await database_api.db_get( + ActionRecords, + filters={"action_id": "123"}, + limit=1 +) +``` +2. 获取最近10条记录 +```python +records = await database_api.db_get( Messages, filters={"chat_id": chat_stream.stream_id}, + limit=10, order_by="-time", - limit=5 ) ``` -## 高级用法 - -### 复杂查询示例 - +### 4. 动作信息存储 ```python -# 查询特定用户在特定时间段的消息 -user_messages = await database_api.db_query( - Messages, - query_type="get", - filters={ - "user_id": "123456", - "time__gte": start_time, # 大于等于开始时间 - "time__lt": end_time # 小于结束时间 - }, - order_by=["-time"], - limit=50 +async def store_action_info( + chat_stream=None, + action_build_into_prompt: bool = False, + action_prompt_display: str = "", + action_done: bool = True, + thinking_id: str = "", + action_data: Optional[dict] = None, + action_name: str = "", +) -> Optional[Dict[str, Any]]: +``` +存储动作信息到数据库,是一种针对 Action 的 `db_save()` 的封装函数。 + +将Action执行的相关信息保存到ActionRecords表中,用于后续的记忆和上下文构建。 + +**Args:** +- `chat_stream`: 聊天流对象,包含聊天ID等信息。 +- `action_build_into_prompt`: 是否将动作信息构建到提示中。 +- `action_prompt_display`: 动作提示的显示文本。 +- `action_done`: 动作是否完成。 +- `thinking_id`: 思考过程的ID。 +- `action_data`: 动作的数据字典。 +- `action_name`: 动作的名称。 + +**Returns:** +- `Optional[Dict[str, Any]]`: 存储后的记录数据,失败时返回None。 + +#### 示例 +```python +record = await database_api.store_action_info( + chat_stream=chat_stream, + action_build_into_prompt=True, + action_prompt_display="执行了回复动作", + action_done=True, + thinking_id="thinking_123", + action_data={"content": "Hello"}, + action_name="reply_action" ) - -# 批量处理 -for message in user_messages: - print(f"消息内容: {message['plain_text']}") - print(f"发送时间: {message['time']}") -``` - -### 插件中的数据持久化 - -```python -from src.plugin_system.base import BasePlugin -from src.plugin_system.apis import database_api - -class DataPlugin(BasePlugin): - async def handle_action(self, action_data, chat_stream): - # 保存插件数据 - plugin_data = { - "plugin_name": self.plugin_name, - "chat_id": chat_stream.stream_id, - "data": json.dumps(action_data), - "created_time": time.time() - } - - # 使用自定义表模型(需要先定义) - record = await database_api.db_save( - PluginData, # 假设的插件数据模型 - plugin_data, - key_field="plugin_name", - key_value=self.plugin_name - ) - - return {"success": True, "record_id": record["id"]} -``` - -## 数据模型 - -### 常用模型类 -系统提供了以下常用的数据模型: - -- `Messages`:消息记录 -- `ActionRecords`:动作记录 -- `UserInfo`:用户信息 -- `GroupInfo`:群组信息 - -### 字段说明 - -#### Messages模型主要字段 -- `message_id`:消息ID -- `chat_id`:聊天ID -- `user_id`:用户ID -- `plain_text`:纯文本内容 -- `time`:时间戳 - -#### ActionRecords模型主要字段 -- `action_id`:动作ID -- `action_name`:动作名称 -- `action_done`:是否完成 -- `time`:创建时间 - -## 注意事项 - -1. **异步操作**:所有数据库API都是异步的,必须使用`await` -2. **错误处理**:函数内置错误处理,失败时返回None或空列表 -3. **数据类型**:返回的都是字典格式的数据,不是模型对象 -4. **性能考虑**:使用`limit`参数避免查询大量数据 -5. **过滤条件**:支持简单的等值过滤,复杂查询需要使用原生Peewee语法 -6. **事务**:如需事务支持,建议直接使用Peewee的事务功能 \ No newline at end of file +``` \ No newline at end of file diff --git a/src/plugin_system/apis/database_api.py b/src/plugin_system/apis/database_api.py index d46bfba39..8b253806b 100644 --- a/src/plugin_system/apis/database_api.py +++ b/src/plugin_system/apis/database_api.py @@ -152,10 +152,7 @@ async def db_query( except DoesNotExist: # 记录不存在 - if query_type == "get" and single_result: - return None - return [] - + return None if query_type == "get" and single_result else [] except Exception as e: logger.error(f"[DatabaseAPI] 数据库操作出错: {e}") traceback.print_exc() @@ -170,7 +167,8 @@ async def db_query( async def db_save( model_class: Type[Model], data: Dict[str, Any], key_field: Optional[str] = None, key_value: Optional[Any] = None -) -> Union[Dict[str, Any], None]: +) -> Optional[Dict[str, Any]]: + # sourcery skip: inline-immediately-returned-variable """保存数据到数据库(创建或更新) 如果提供了key_field和key_value,会先尝试查找匹配的记录进行更新; @@ -203,10 +201,9 @@ async def db_save( try: # 如果提供了key_field和key_value,尝试更新现有记录 if key_field and key_value is not None: - # 查找现有记录 - existing_records = list(model_class.select().where(getattr(model_class, key_field) == key_value).limit(1)) - - if existing_records: + if existing_records := list( + model_class.select().where(getattr(model_class, key_field) == key_value).limit(1) + ): # 更新现有记录 existing_record = existing_records[0] for field, value in data.items(): @@ -244,8 +241,8 @@ async def db_get( Args: model_class: Peewee模型类 filters: 过滤条件,字段名和值的字典 - order_by: 排序字段,前缀'-'表示降序,例如'-time'表示按时间字段(即time字段)降序 limit: 结果数量限制 + order_by: 排序字段,前缀'-'表示降序,例如'-time'表示按时间字段(即time字段)降序 single_result: 是否只返回单个结果,如果为True,则返回单个记录字典或None;否则返回记录字典列表或空列表 Returns: @@ -310,7 +307,7 @@ async def store_action_info( thinking_id: str = "", action_data: Optional[dict] = None, action_name: str = "", -) -> Union[Dict[str, Any], None]: +) -> Optional[Dict[str, Any]]: """存储动作信息到数据库 将Action执行的相关信息保存到ActionRecords表中,用于后续的记忆和上下文构建。 From 5470f68f4a973deef735b0ce614979c39a752880 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sun, 27 Jul 2025 14:42:04 +0800 Subject: [PATCH 07/22] emoji_api_doc --- docs/plugins/api/emoji-api.md | 243 +++++++--------------------- src/plugin_system/apis/emoji_api.py | 30 ++-- 2 files changed, 72 insertions(+), 201 deletions(-) diff --git a/docs/plugins/api/emoji-api.md b/docs/plugins/api/emoji-api.md index 6dd071b9a..ce9dd0c81 100644 --- a/docs/plugins/api/emoji-api.md +++ b/docs/plugins/api/emoji-api.md @@ -6,11 +6,13 @@ ```python from src.plugin_system.apis import emoji_api +# 或者 +from src.plugin_system import emoji_api ``` -## 🆕 **二步走识别优化** +## 二步走识别优化 -从最新版本开始,表情包识别系统采用了**二步走识别 + 智能缓存**的优化方案: +从新版本开始,表情包识别系统采用了**二步走识别 + 智能缓存**的优化方案: ### **收到表情包时的识别流程** 1. **第一步**:VLM视觉分析 - 生成详细描述 @@ -30,217 +32,84 @@ from src.plugin_system.apis import emoji_api ## 主要功能 ### 1. 表情包获取 - -#### `get_by_description(description: str) -> Optional[Tuple[str, str, str]]` +```python +async def get_by_description(description: str) -> Optional[Tuple[str, str, str]]: +``` 根据场景描述选择表情包 -**参数:** -- `description`:场景描述文本,例如"开心的大笑"、"轻微的讽刺"、"表示无奈和沮丧"等 +**Args:** +- `description`:表情包的描述文本,例如"开心"、"难过"、"愤怒"等 -**返回:** -- `Optional[Tuple[str, str, str]]`:(base64编码, 表情包描述, 匹配的场景) 或 None +**Returns:** +- `Optional[Tuple[str, str, str]]`:一个元组: (表情包的base64编码, 描述, 情感标签),如果未找到匹配的表情包则返回None -**示例:** +#### 示例 ```python -emoji_result = await emoji_api.get_by_description("开心的大笑") +emoji_result = await emoji_api.get_by_description("大笑") if emoji_result: emoji_base64, description, matched_scene = emoji_result print(f"获取到表情包: {description}, 场景: {matched_scene}") # 可以将emoji_base64用于发送表情包 ``` -#### `get_random() -> Optional[Tuple[str, str, str]]` -随机获取表情包 - -**返回:** -- `Optional[Tuple[str, str, str]]`:(base64编码, 表情包描述, 随机场景) 或 None - -**示例:** +### 2. 随机获取表情包 ```python -random_emoji = await emoji_api.get_random() -if random_emoji: - emoji_base64, description, scene = random_emoji - print(f"随机表情包: {description}") +async def get_random(count: Optional[int] = 1) -> List[Tuple[str, str, str]]: ``` +随机获取指定数量的表情包 -#### `get_by_emotion(emotion: str) -> Optional[Tuple[str, str, str]]` -根据场景关键词获取表情包 +**Args:** +- `count`:要获取的表情包数量,默认为1 -**参数:** -- `emotion`:场景关键词,如"大笑"、"讽刺"、"无奈"等 +**Returns:** +- `List[Tuple[str, str, str]]`:一个包含多个表情包的列表,每个元素是一个元组: (表情包的base64编码, 描述, 情感标签),如果未找到或出错则返回空列表 -**返回:** -- `Optional[Tuple[str, str, str]]`:(base64编码, 表情包描述, 匹配的场景) 或 None - -**示例:** +### 3. 根据情感获取表情包 ```python -emoji_result = await emoji_api.get_by_emotion("讽刺") -if emoji_result: - emoji_base64, description, scene = emoji_result - # 发送讽刺表情包 +async def get_by_emotion(emotion: str) -> Optional[Tuple[str, str, str]]: ``` +根据情感标签获取表情包 -### 2. 表情包信息查询 +**Args:** +- `emotion`:情感标签,例如"开心"、"悲伤"、"愤怒"等 -#### `get_count() -> int` -获取表情包数量 +**Returns:** +- `Optional[Tuple[str, str, str]]`:一个元组: (表情包的base64编码, 描述, 情感标签),如果未找到则返回None -**返回:** -- `int`:当前可用的表情包数量 +### 4. 获取表情包数量 +```python +def get_count() -> int: +``` +获取当前可用表情包的数量 -#### `get_info() -> dict` -获取表情包系统信息 +### 5. 获取表情包系统信息 +```python +def get_info() -> Dict[str, Any]: +``` +获取表情包系统的基本信息 -**返回:** -- `dict`:包含表情包数量、最大数量等信息 +**Returns:** +- `Dict[str, Any]`:包含表情包数量、描述等信息的字典,包含以下键: + - `current_count`:当前表情包数量 + - `max_count`:最大表情包数量 + - `available_emojis`:当前可用的表情包数量 -**返回字典包含:** -- `current_count`:当前表情包数量 -- `max_count`:最大表情包数量 -- `available_emojis`:可用表情包数量 +### 6. 获取所有可用的情感标签 +```python +def get_emotions() -> List[str]: +``` +获取所有可用的情感标签 **(已经去重)** -#### `get_emotions() -> list` -获取所有可用的场景关键词 - -**返回:** -- `list`:所有表情包的场景关键词列表(去重) - -#### `get_descriptions() -> list` +### 7. 获取所有表情包描述 +```python +def get_descriptions() -> List[str]: +``` 获取所有表情包的描述列表 -**返回:** -- `list`:所有表情包的描述文本列表 - -## 使用示例 - -### 1. 智能表情包选择 - -```python -from src.plugin_system.apis import emoji_api - -async def send_emotion_response(message_text: str, chat_stream): - """根据消息内容智能选择表情包回复""" - - # 分析消息场景 - if "哈哈" in message_text or "好笑" in message_text: - emoji_result = await emoji_api.get_by_description("开心的大笑") - elif "无语" in message_text or "算了" in message_text: - emoji_result = await emoji_api.get_by_description("表示无奈和沮丧") - elif "呵呵" in message_text or "是吗" in message_text: - emoji_result = await emoji_api.get_by_description("轻微的讽刺") - elif "生气" in message_text or "愤怒" in message_text: - emoji_result = await emoji_api.get_by_description("愤怒和不满") - else: - # 随机选择一个表情包 - emoji_result = await emoji_api.get_random() - - if emoji_result: - emoji_base64, description, scene = emoji_result - # 使用send_api发送表情包 - from src.plugin_system.apis import send_api - success = await send_api.emoji_to_group(emoji_base64, chat_stream.group_info.group_id) - return success - - return False -``` - -### 2. 表情包管理功能 - -```python -async def show_emoji_stats(): - """显示表情包统计信息""" - - # 获取基本信息 - count = emoji_api.get_count() - info = emoji_api.get_info() - scenes = emoji_api.get_emotions() # 实际返回的是场景关键词 - - stats = f""" -📊 表情包统计信息: -- 总数量: {count} -- 可用数量: {info['available_emojis']} -- 最大容量: {info['max_count']} -- 支持场景: {len(scenes)}种 - -🎭 支持的场景关键词: {', '.join(scenes[:10])}{'...' if len(scenes) > 10 else ''} - """ - - return stats -``` - -### 3. 表情包测试功能 - -```python -async def test_emoji_system(): - """测试表情包系统的各种功能""" - - print("=== 表情包系统测试 ===") - - # 测试场景描述查找 - test_descriptions = ["开心的大笑", "轻微的讽刺", "表示无奈和沮丧", "愤怒和不满"] - for desc in test_descriptions: - result = await emoji_api.get_by_description(desc) - if result: - _, description, scene = result - print(f"✅ 场景'{desc}' -> {description} ({scene})") - else: - print(f"❌ 场景'{desc}' -> 未找到") - - # 测试关键词查找 - scenes = emoji_api.get_emotions() - if scenes: - test_scene = scenes[0] - result = await emoji_api.get_by_emotion(test_scene) - if result: - print(f"✅ 关键词'{test_scene}' -> 找到匹配表情包") - - # 测试随机获取 - random_result = await emoji_api.get_random() - if random_result: - print("✅ 随机获取 -> 成功") - - print(f"📊 系统信息: {emoji_api.get_info()}") -``` - -### 4. 在Action中使用表情包 - -```python -from src.plugin_system.base import BaseAction - -class EmojiAction(BaseAction): - async def execute(self, action_data, chat_stream): - # 从action_data获取场景描述或关键词 - scene_keyword = action_data.get("scene", "") - scene_description = action_data.get("description", "") - - emoji_result = None - - # 优先使用具体的场景描述 - if scene_description: - emoji_result = await emoji_api.get_by_description(scene_description) - # 其次使用场景关键词 - elif scene_keyword: - emoji_result = await emoji_api.get_by_emotion(scene_keyword) - # 最后随机选择 - else: - emoji_result = await emoji_api.get_random() - - if emoji_result: - emoji_base64, description, scene = emoji_result - return { - "success": True, - "emoji_base64": emoji_base64, - "description": description, - "scene": scene - } - - return {"success": False, "message": "未找到合适的表情包"} -``` - ## 场景描述说明 ### 常用场景描述 -表情包系统支持多种具体的场景描述,常见的包括: +表情包系统支持多种具体的场景描述,举例如下: - **开心类场景**:开心的大笑、满意的微笑、兴奋的手舞足蹈 - **无奈类场景**:表示无奈和沮丧、轻微的讽刺、无语的摇头 @@ -248,8 +117,8 @@ class EmojiAction(BaseAction): - **惊讶类场景**:震惊的表情、意外的发现、困惑的思考 - **可爱类场景**:卖萌的表情、撒娇的动作、害羞的样子 -### 场景关键词示例 -系统支持的场景关键词包括: +### 情感关键词示例 +系统支持的情感关键词举例如下: - 大笑、微笑、兴奋、手舞足蹈 - 无奈、沮丧、讽刺、无语、摇头 - 愤怒、不满、生气、瞪视、抓狂 @@ -263,9 +132,9 @@ class EmojiAction(BaseAction): ## 注意事项 -1. **异步函数**:获取表情包的函数都是异步的,需要使用 `await` +1. **异步函数**:部分函数是异步的,需要使用 `await` 2. **返回格式**:表情包以base64编码返回,可直接用于发送 -3. **错误处理**:所有函数都有错误处理,失败时返回None或默认值 +3. **错误处理**:所有函数都有错误处理,失败时返回None,空列表或默认值 4. **使用统计**:系统会记录表情包的使用次数 5. **文件依赖**:表情包依赖于本地文件,确保表情包文件存在 6. **编码格式**:返回的是base64编码的图片数据,可直接用于网络传输 diff --git a/src/plugin_system/apis/emoji_api.py b/src/plugin_system/apis/emoji_api.py index cafb52df8..479f3aec1 100644 --- a/src/plugin_system/apis/emoji_api.py +++ b/src/plugin_system/apis/emoji_api.py @@ -65,14 +65,14 @@ async def get_by_description(description: str) -> Optional[Tuple[str, str, str]] return None -async def get_random(count: Optional[int] = 1) -> Optional[List[Tuple[str, str, str]]]: +async def get_random(count: Optional[int] = 1) -> List[Tuple[str, str, str]]: """随机获取指定数量的表情包 Args: count: 要获取的表情包数量,默认为1 Returns: - Optional[List[Tuple[str, str, str]]]: 包含(base64编码, 表情包描述, 随机情感标签)的元组列表,如果失败则为None + List[Tuple[str, str, str]]: 包含(base64编码, 表情包描述, 随机情感标签)的元组列表,失败则返回空列表 Raises: TypeError: 如果count不是整数类型 @@ -94,13 +94,13 @@ async def get_random(count: Optional[int] = 1) -> Optional[List[Tuple[str, str, if not all_emojis: logger.warning("[EmojiAPI] 没有可用的表情包") - return None + return [] # 过滤有效表情包 valid_emojis = [emoji for emoji in all_emojis if not emoji.is_deleted] if not valid_emojis: logger.warning("[EmojiAPI] 没有有效的表情包") - return None + return [] if len(valid_emojis) < count: logger.warning( @@ -127,14 +127,14 @@ async def get_random(count: Optional[int] = 1) -> Optional[List[Tuple[str, str, if not results and count > 0: logger.warning("[EmojiAPI] 随机获取表情包失败,没有一个可以成功处理") - return None + return [] logger.info(f"[EmojiAPI] 成功获取 {len(results)} 个随机表情包") return results except Exception as e: logger.error(f"[EmojiAPI] 获取随机表情包失败: {e}") - return None + return [] async def get_by_emotion(emotion: str) -> Optional[Tuple[str, str, str]]: @@ -162,10 +162,11 @@ async def get_by_emotion(emotion: str) -> Optional[Tuple[str, str, str]]: # 筛选匹配情感的表情包 matching_emojis = [] - for emoji_obj in all_emojis: - if not emoji_obj.is_deleted and emotion.lower() in [e.lower() for e in emoji_obj.emotion]: - matching_emojis.append(emoji_obj) - + matching_emojis.extend( + emoji_obj + for emoji_obj in all_emojis + if not emoji_obj.is_deleted and emotion.lower() in [e.lower() for e in emoji_obj.emotion] + ) if not matching_emojis: logger.warning(f"[EmojiAPI] 未找到匹配情感 '{emotion}' 的表情包") return None @@ -256,10 +257,11 @@ def get_descriptions() -> List[str]: emoji_manager = get_emoji_manager() descriptions = [] - for emoji_obj in emoji_manager.emoji_objects: - if not emoji_obj.is_deleted and emoji_obj.description: - descriptions.append(emoji_obj.description) - + descriptions.extend( + emoji_obj.description + for emoji_obj in emoji_manager.emoji_objects + if not emoji_obj.is_deleted and emoji_obj.description + ) return descriptions except Exception as e: logger.error(f"[EmojiAPI] 获取表情包描述失败: {e}") From 96d7ad527aa04586441281bbf70f31e8f1ca56fa Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sun, 27 Jul 2025 16:59:33 +0800 Subject: [PATCH 08/22] =?UTF-8?q?generator=E4=BF=AE=E6=94=B9=E4=B8=8E?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/plugins/api/generator-api.md | 405 ++++++++---------------- src/chat/replyer/default_generator.py | 149 ++++----- src/llm_models/utils_model.py | 2 +- src/plugin_system/apis/generator_api.py | 53 +++- 4 files changed, 243 insertions(+), 366 deletions(-) diff --git a/docs/plugins/api/generator-api.md b/docs/plugins/api/generator-api.md index 964fff84a..690283df0 100644 --- a/docs/plugins/api/generator-api.md +++ b/docs/plugins/api/generator-api.md @@ -6,241 +6,150 @@ ```python from src.plugin_system.apis import generator_api +# 或者 +from src.plugin_system import generator_api ``` ## 主要功能 ### 1. 回复器获取 - -#### `get_replyer(chat_stream=None, platform=None, chat_id=None, is_group=True)` +```python +def get_replyer( + chat_stream: Optional[ChatStream] = None, + chat_id: Optional[str] = None, + model_configs: Optional[List[Dict[str, Any]]] = None, + request_type: str = "replyer", +) -> Optional[DefaultReplyer]: +``` 获取回复器对象 -**参数:** -- `chat_stream`:聊天流对象(优先) -- `platform`:平台名称,如"qq" -- `chat_id`:聊天ID(群ID或用户ID) -- `is_group`:是否为群聊 +优先使用chat_stream,如果没有则使用chat_id直接查找。 -**返回:** -- `DefaultReplyer`:回复器对象,如果获取失败则返回None +使用 ReplyerManager 来管理实例,避免重复创建。 -**示例:** +**Args:** +- `chat_stream`: 聊天流对象 +- `chat_id`: 聊天ID(实际上就是`stream_id`) +- `model_configs`: 模型配置 +- `request_type`: 请求类型,用于记录LLM使用情况,可以不写 + +**Returns:** +- `DefaultReplyer`: 回复器对象,如果获取失败则返回None + +#### 示例 ```python # 使用聊天流获取回复器 replyer = generator_api.get_replyer(chat_stream=chat_stream) -# 使用平台和ID获取回复器 -replyer = generator_api.get_replyer( - platform="qq", - chat_id="123456789", - is_group=True -) +# 使用平台和ID获取回复器 +replyer = generator_api.get_replyer(chat_id="123456789") ``` ### 2. 回复生成 - -#### `generate_reply(chat_stream=None, action_data=None, platform=None, chat_id=None, is_group=True)` +```python +async def generate_reply( + chat_stream: Optional[ChatStream] = None, + chat_id: Optional[str] = None, + action_data: Optional[Dict[str, Any]] = None, + reply_to: str = "", + extra_info: str = "", + available_actions: Optional[Dict[str, ActionInfo]] = None, + enable_tool: bool = False, + enable_splitter: bool = True, + enable_chinese_typo: bool = True, + return_prompt: bool = False, + model_configs: Optional[List[Dict[str, Any]]] = None, + request_type: str = "", +) -> Tuple[bool, List[Tuple[str, Any]], Optional[str]]: +``` 生成回复 -**参数:** -- `chat_stream`:聊天流对象(优先) -- `action_data`:动作数据 -- `platform`:平台名称(备用) -- `chat_id`:聊天ID(备用) -- `is_group`:是否为群聊(备用) +优先使用chat_stream,如果没有则使用chat_id直接查找。 -**返回:** -- `Tuple[bool, List[Tuple[str, Any]]]`:(是否成功, 回复集合) +**Args:** +- `chat_stream`: 聊天流对象 +- `chat_id`: 聊天ID(实际上就是`stream_id`) +- `action_data`: 动作数据(向下兼容,包含`reply_to`和`extra_info`) +- `reply_to`: 回复目标,格式为 `{发送者的person_name:消息内容}` +- `extra_info`: 附加信息 +- `available_actions`: 可用动作字典,格式为 `{"action_name": ActionInfo}` +- `enable_tool`: 是否启用工具 +- `enable_splitter`: 是否启用分割器 +- `enable_chinese_typo`: 是否启用中文错别字 +- `return_prompt`: 是否返回提示词 +- `model_configs`: 模型配置,可选 +- `request_type`: 请求类型,用于记录LLM使用情况 -**示例:** +**Returns:** +- `Tuple[bool, List[Tuple[str, Any]], Optional[str]]`: (是否成功, 回复集合, 提示词) + +#### 示例 ```python -success, reply_set = await generator_api.generate_reply( +success, reply_set, prompt = await generator_api.generate_reply( chat_stream=chat_stream, - action_data={"message": "你好", "intent": "greeting"} + action_data=action_data, + reply_to="麦麦:你好", + available_actions=action_info, + enable_tool=True, + return_prompt=True ) - if success: for reply_type, reply_content in reply_set: print(f"回复类型: {reply_type}, 内容: {reply_content}") + if prompt: + print(f"使用的提示词: {prompt}") ``` -#### `rewrite_reply(chat_stream=None, reply_data=None, platform=None, chat_id=None, is_group=True)` -重写回复 - -**参数:** -- `chat_stream`:聊天流对象(优先) -- `reply_data`:回复数据 -- `platform`:平台名称(备用) -- `chat_id`:聊天ID(备用) -- `is_group`:是否为群聊(备用) - -**返回:** -- `Tuple[bool, List[Tuple[str, Any]]]`:(是否成功, 回复集合) - -**示例:** +### 3. 回复重写 ```python -success, reply_set = await generator_api.rewrite_reply( +async def rewrite_reply( + chat_stream: Optional[ChatStream] = None, + reply_data: Optional[Dict[str, Any]] = None, + chat_id: Optional[str] = None, + enable_splitter: bool = True, + enable_chinese_typo: bool = True, + model_configs: Optional[List[Dict[str, Any]]] = None, + raw_reply: str = "", + reason: str = "", + reply_to: str = "", + return_prompt: bool = False, +) -> Tuple[bool, List[Tuple[str, Any]], Optional[str]]: +``` +重写回复,使用新的内容替换旧的回复内容。 + +优先使用chat_stream,如果没有则使用chat_id直接查找。 + +**Args:** +- `chat_stream`: 聊天流对象 +- `reply_data`: 回复数据,包含`raw_reply`, `reason`和`reply_to`,**(向下兼容备用,当其他参数缺失时从此获取)** +- `chat_id`: 聊天ID(实际上就是`stream_id`) +- `enable_splitter`: 是否启用分割器 +- `enable_chinese_typo`: 是否启用中文错别字 +- `model_configs`: 模型配置,可选 +- `raw_reply`: 原始回复内容 +- `reason`: 重写原因 +- `reply_to`: 回复目标,格式为 `{发送者的person_name:消息内容}` + +**Returns:** +- `Tuple[bool, List[Tuple[str, Any]], Optional[str]]`: (是否成功, 回复集合, 提示词) + +#### 示例 +```python +success, reply_set, prompt = await generator_api.rewrite_reply( chat_stream=chat_stream, - reply_data={"original_text": "原始回复", "style": "more_friendly"} + raw_reply="原始回复内容", + reason="重写原因", + reply_to="麦麦:你好", + return_prompt=True ) +if success: + for reply_type, reply_content in reply_set: + print(f"回复类型: {reply_type}, 内容: {reply_content}") + if prompt: + print(f"使用的提示词: {prompt}") ``` -## 使用示例 - -### 1. 基础回复生成 - -```python -from src.plugin_system.apis import generator_api - -async def generate_greeting_reply(chat_stream, user_name): - """生成问候回复""" - - action_data = { - "intent": "greeting", - "user_name": user_name, - "context": "morning_greeting" - } - - success, reply_set = await generator_api.generate_reply( - chat_stream=chat_stream, - action_data=action_data - ) - - if success and reply_set: - # 获取第一个回复 - reply_type, reply_content = reply_set[0] - return reply_content - - return "你好!" # 默认回复 -``` - -### 2. 在Action中使用回复生成器 - -```python -from src.plugin_system.base import BaseAction - -class ChatAction(BaseAction): - async def execute(self, action_data, chat_stream): - # 准备回复数据 - reply_context = { - "message_type": "response", - "user_input": action_data.get("user_message", ""), - "intent": action_data.get("intent", ""), - "entities": action_data.get("entities", {}), - "context": self.get_conversation_context(chat_stream) - } - - # 生成回复 - success, reply_set = await generator_api.generate_reply( - chat_stream=chat_stream, - action_data=reply_context - ) - - if success: - return { - "success": True, - "replies": reply_set, - "generated_count": len(reply_set) - } - - return { - "success": False, - "error": "回复生成失败", - "fallback_reply": "抱歉,我现在无法理解您的消息。" - } -``` - -### 3. 多样化回复生成 - -```python -async def generate_diverse_replies(chat_stream, topic, count=3): - """生成多个不同风格的回复""" - - styles = ["formal", "casual", "humorous"] - all_replies = [] - - for i, style in enumerate(styles[:count]): - action_data = { - "topic": topic, - "style": style, - "variation": i - } - - success, reply_set = await generator_api.generate_reply( - chat_stream=chat_stream, - action_data=action_data - ) - - if success and reply_set: - all_replies.extend(reply_set) - - return all_replies -``` - -### 4. 回复重写功能 - -```python -async def improve_reply(chat_stream, original_reply, improvement_type="more_friendly"): - """改进原始回复""" - - reply_data = { - "original_text": original_reply, - "improvement_type": improvement_type, - "target_audience": "young_users", - "tone": "positive" - } - - success, improved_replies = await generator_api.rewrite_reply( - chat_stream=chat_stream, - reply_data=reply_data - ) - - if success and improved_replies: - # 返回改进后的第一个回复 - _, improved_content = improved_replies[0] - return improved_content - - return original_reply # 如果改进失败,返回原始回复 -``` - -### 5. 条件回复生成 - -```python -async def conditional_reply_generation(chat_stream, user_message, user_emotion): - """根据用户情感生成条件回复""" - - # 根据情感调整回复策略 - if user_emotion == "sad": - action_data = { - "intent": "comfort", - "tone": "empathetic", - "style": "supportive" - } - elif user_emotion == "angry": - action_data = { - "intent": "calm", - "tone": "peaceful", - "style": "understanding" - } - else: - action_data = { - "intent": "respond", - "tone": "neutral", - "style": "helpful" - } - - action_data["user_message"] = user_message - action_data["user_emotion"] = user_emotion - - success, reply_set = await generator_api.generate_reply( - chat_stream=chat_stream, - action_data=action_data - ) - - return reply_set if success else [] -``` - -## 回复集合格式 +## 回复集合`reply_set`格式 ### 回复类型 生成的回复集合包含多种类型的回复: @@ -260,82 +169,32 @@ reply_set = [ ] ``` -## 高级用法 - -### 1. 自定义回复器配置 - +### 4. 自定义提示词回复 ```python -async def generate_with_custom_config(chat_stream, action_data): - """使用自定义配置生成回复""" - - # 获取回复器 - replyer = generator_api.get_replyer(chat_stream=chat_stream) - - if replyer: - # 可以访问回复器的内部方法 - success, reply_set = await replyer.generate_reply_with_context( - reply_data=action_data, - # 可以传递额外的配置参数 - ) - return success, reply_set - - return False, [] +async def generate_response_custom( + chat_stream: Optional[ChatStream] = None, + chat_id: Optional[str] = None, + model_configs: Optional[List[Dict[str, Any]]] = None, + prompt: str = "", +) -> Optional[str]: ``` +生成自定义提示词回复 -### 2. 回复质量评估 +优先使用chat_stream,如果没有则使用chat_id直接查找。 -```python -async def generate_and_evaluate_replies(chat_stream, action_data): - """生成回复并评估质量""" - - success, reply_set = await generator_api.generate_reply( - chat_stream=chat_stream, - action_data=action_data - ) - - if success: - evaluated_replies = [] - for reply_type, reply_content in reply_set: - # 简单的质量评估 - quality_score = evaluate_reply_quality(reply_content) - evaluated_replies.append({ - "type": reply_type, - "content": reply_content, - "quality": quality_score - }) - - # 按质量排序 - evaluated_replies.sort(key=lambda x: x["quality"], reverse=True) - return evaluated_replies - - return [] +**Args:** +- `chat_stream`: 聊天流对象 +- `chat_id`: 聊天ID(备用) +- `model_configs`: 模型配置列表 +- `prompt`: 自定义提示词 -def evaluate_reply_quality(reply_content): - """简单的回复质量评估""" - if not reply_content: - return 0 - - score = 50 # 基础分 - - # 长度适中加分 - if 5 <= len(reply_content) <= 100: - score += 20 - - # 包含积极词汇加分 - positive_words = ["好", "棒", "不错", "感谢", "开心"] - for word in positive_words: - if word in reply_content: - score += 10 - break - - return min(score, 100) -``` +**Returns:** +- `Optional[str]`: 生成的自定义回复内容,如果生成失败则返回None ## 注意事项 -1. **异步操作**:所有生成函数都是异步的,必须使用`await` -2. **错误处理**:函数内置错误处理,失败时返回False和空列表 -3. **聊天流依赖**:需要有效的聊天流对象才能正常工作 -4. **性能考虑**:回复生成可能需要一些时间,特别是使用LLM时 -5. **回复格式**:返回的回复集合是元组列表,包含类型和内容 -6. **上下文感知**:生成器会考虑聊天上下文和历史消息 \ No newline at end of file +1. **异步操作**:部分函数是异步的,须使用`await` +2. **聊天流依赖**:需要有效的聊天流对象才能正常工作 +3. **性能考虑**:回复生成可能需要一些时间,特别是使用LLM时 +4. **回复格式**:返回的回复集合是元组列表,包含类型和内容 +5. **上下文感知**:生成器会考虑聊天上下文和历史消息,除非你用的是自定义提示词。 \ No newline at end of file diff --git a/src/chat/replyer/default_generator.py b/src/chat/replyer/default_generator.py index cab6a2b41..0e99b6b3a 100644 --- a/src/chat/replyer/default_generator.py +++ b/src/chat/replyer/default_generator.py @@ -40,7 +40,7 @@ def init_prompt(): Prompt("你正在和{sender_name}聊天,这是你们之前聊的内容:", "chat_target_private1") Prompt("在群里聊天", "chat_target_group2") Prompt("和{sender_name}聊天", "chat_target_private2") - + Prompt( """ {expression_habits_block} @@ -155,18 +155,16 @@ class DefaultReplyer: extra_info: str = "", available_actions: Optional[Dict[str, ActionInfo]] = None, enable_tool: bool = True, - enable_timeout: bool = False, ) -> Tuple[bool, Optional[str], Optional[str]]: """ 回复器 (Replier): 负责生成回复文本的核心逻辑。 - + Args: reply_to: 回复对象,格式为 "发送者:消息内容" extra_info: 额外信息,用于补充上下文 available_actions: 可用的动作信息字典 enable_tool: 是否启用工具调用 - enable_timeout: 是否启用超时处理 - + Returns: Tuple[bool, Optional[str], Optional[str]]: (是否成功, 生成的回复内容, 使用的prompt) """ @@ -177,43 +175,25 @@ class DefaultReplyer: # 3. 构建 Prompt with Timer("构建Prompt", {}): # 内部计时器,可选保留 prompt = await self.build_prompt_reply_context( - reply_to = reply_to, + reply_to=reply_to, extra_info=extra_info, available_actions=available_actions, - enable_timeout=enable_timeout, enable_tool=enable_tool, ) - + if not prompt: logger.warning("构建prompt失败,跳过回复生成") return False, None, None # 4. 调用 LLM 生成回复 content = None - reasoning_content = None - model_name = "unknown_model" + # TODO: 复活这里 + # reasoning_content = None + # model_name = "unknown_model" try: - with Timer("LLM生成", {}): # 内部计时器,可选保留 - # 加权随机选择一个模型配置 - selected_model_config = self._select_weighted_model_config() - logger.info( - f"使用模型生成回复: {selected_model_config.get('name', 'N/A')} (选中概率: {selected_model_config.get('weight', 1.0)})" - ) - - express_model = LLMRequest( - model=selected_model_config, - request_type=self.request_type, - ) - - if global_config.debug.show_prompt: - logger.info(f"\n{prompt}\n") - else: - logger.debug(f"\n{prompt}\n") - - content, (reasoning_content, model_name) = await express_model.generate_response_async(prompt) - - logger.debug(f"replyer生成内容: {content}") + content = await self.llm_generate_content(prompt) + logger.debug(f"replyer生成内容: {content}") except Exception as llm_e: # 精简报错信息 @@ -232,22 +212,21 @@ class DefaultReplyer: raw_reply: str = "", reason: str = "", reply_to: str = "", - ) -> Tuple[bool, Optional[str]]: + return_prompt: bool = False, + ) -> Tuple[bool, Optional[str], Optional[str]]: """ 表达器 (Expressor): 负责重写和优化回复文本。 - + Args: raw_reply: 原始回复内容 reason: 回复原因 reply_to: 回复对象,格式为 "发送者:消息内容" relation_info: 关系信息 - + Returns: Tuple[bool, Optional[str]]: (是否成功, 重写后的回复内容) """ try: - - with Timer("构建Prompt", {}): # 内部计时器,可选保留 prompt = await self.build_prompt_rewrite_context( raw_reply=raw_reply, @@ -256,40 +235,28 @@ class DefaultReplyer: ) content = None - reasoning_content = None - model_name = "unknown_model" + # TODO: 复活这里 + # reasoning_content = None + # model_name = "unknown_model" if not prompt: logger.error("Prompt 构建失败,无法生成回复。") - return False, None + return False, None, None try: - with Timer("LLM生成", {}): # 内部计时器,可选保留 - # 加权随机选择一个模型配置 - selected_model_config = self._select_weighted_model_config() - logger.info( - f"使用模型重写回复: {selected_model_config.get('name', 'N/A')} (选中概率: {selected_model_config.get('weight', 1.0)})" - ) - - express_model = LLMRequest( - model=selected_model_config, - request_type=self.request_type, - ) - - content, (reasoning_content, model_name) = await express_model.generate_response_async(prompt) - - logger.info(f"想要表达:{raw_reply}||理由:{reason}||生成回复: {content}\n") + content = await self.llm_generate_content(prompt) + logger.info(f"想要表达:{raw_reply}||理由:{reason}||生成回复: {content}\n") except Exception as llm_e: # 精简报错信息 logger.error(f"LLM 生成失败: {llm_e}") - return False, None # LLM 调用失败则无法生成回复 + return False, None, prompt if return_prompt else None # LLM 调用失败则无法生成回复 - return True, content + return True, content, prompt if return_prompt else None except Exception as e: logger.error(f"回复生成意外失败: {e}") traceback.print_exc() - return False, None + return False, None, prompt if return_prompt else None async def build_relation_info(self, reply_to: str = ""): if not global_config.relationship.enable_relationship: @@ -313,11 +280,11 @@ class DefaultReplyer: async def build_expression_habits(self, chat_history: str, target: str) -> str: """构建表达习惯块 - + Args: chat_history: 聊天历史记录 target: 目标消息内容 - + Returns: str: 表达习惯信息字符串 """ @@ -366,17 +333,15 @@ class DefaultReplyer: if style_habits_str.strip() and grammar_habits_str.strip(): expression_habits_title = "你可以参考以下的语言习惯和句法,如果情景合适就使用,不要盲目使用,不要生硬使用,以合理的方式结合到你的回复中:" - expression_habits_block = f"{expression_habits_title}\n{expression_habits_block}" - - return expression_habits_block + return f"{expression_habits_title}\n{expression_habits_block}" async def build_memory_block(self, chat_history: str, target: str) -> str: """构建记忆块 - + Args: chat_history: 聊天历史记录 target: 目标消息内容 - + Returns: str: 记忆信息字符串 """ @@ -459,10 +424,10 @@ class DefaultReplyer: def _parse_reply_target(self, target_message: str) -> Tuple[str, str]: """解析回复目标消息 - + Args: target_message: 目标消息,格式为 "发送者:消息内容" 或 "发送者:消息内容" - + Returns: Tuple[str, str]: (发送者名称, 消息内容) """ @@ -481,10 +446,10 @@ class DefaultReplyer: async def build_keywords_reaction_prompt(self, target: Optional[str]) -> str: """构建关键词反应提示 - + Args: target: 目标消息内容 - + Returns: str: 关键词反应提示字符串 """ @@ -523,11 +488,11 @@ class DefaultReplyer: async def _time_and_run_task(self, coroutine, name: str) -> Tuple[str, Any, float]: """计时并运行异步任务的辅助函数 - + Args: coroutine: 要执行的协程 name: 任务名称 - + Returns: Tuple[str, Any, float]: (任务名称, 任务结果, 执行耗时) """ @@ -537,7 +502,9 @@ class DefaultReplyer: duration = end_time - start_time return name, result, duration - def build_s4u_chat_history_prompts(self, message_list_before_now: List[Dict[str, Any]], target_user_id: str) -> Tuple[str, str]: + def build_s4u_chat_history_prompts( + self, message_list_before_now: List[Dict[str, Any]], target_user_id: str + ) -> Tuple[str, str]: """ 构建 s4u 风格的分离对话 prompt @@ -612,7 +579,7 @@ class DefaultReplyer: chat_info: str, ) -> Any: """构建 mai_think 上下文信息 - + Args: chat_id: 聊天ID memory_block: 记忆块内容 @@ -625,7 +592,7 @@ class DefaultReplyer: sender: 发送者名称 target: 目标消息内容 chat_info: 聊天信息 - + Returns: Any: mai_think 实例 """ @@ -647,19 +614,17 @@ class DefaultReplyer: reply_to: str, extra_info: str = "", available_actions: Optional[Dict[str, ActionInfo]] = None, - enable_timeout: bool = False, enable_tool: bool = True, ) -> str: # sourcery skip: merge-else-if-into-elif, remove-redundant-if """ 构建回复器上下文 Args: - reply_data: 回复数据 - replay_data 包含以下字段: - structured_info: 结构化信息,一般是工具调用获得的信息 - reply_to: 回复对象 - extra_info/extra_info_block: 额外信息 + reply_to: 回复对象,格式为 "发送者:消息内容" + extra_info: 额外信息,用于补充上下文 available_actions: 可用动作 + enable_timeout: 是否启用超时处理 + enable_tool: 是否启用工具调用 Returns: str: 构建好的上下文 @@ -1011,6 +976,30 @@ class DefaultReplyer: display_message=display_message, ) + async def llm_generate_content(self, prompt: str) -> str: + with Timer("LLM生成", {}): # 内部计时器,可选保留 + # 加权随机选择一个模型配置 + selected_model_config = self._select_weighted_model_config() + logger.info( + f"使用模型生成回复: {selected_model_config.get('name', 'N/A')} (选中概率: {selected_model_config.get('weight', 1.0)})" + ) + + express_model = LLMRequest( + model=selected_model_config, + request_type=self.request_type, + ) + + if global_config.debug.show_prompt: + logger.info(f"\n{prompt}\n") + else: + logger.debug(f"\n{prompt}\n") + + # TODO: 这里的_应该做出替换 + content, _ = await express_model.generate_response_async(prompt) + + logger.debug(f"replyer生成内容: {content}") + return content + def weighted_sample_no_replacement(items, weights, k) -> list: """ @@ -1069,9 +1058,7 @@ async def get_prompt_info(message: str, threshold: float): logger.debug(f"获取知识库内容耗时: {(end_time - start_time):.3f}秒") logger.debug(f"获取知识库内容,相关信息:{related_info[:100]}...,信息长度: {len(related_info)}") - # 格式化知识信息 - formatted_prompt_info = f"你有以下这些**知识**:\n{related_info}\n请你**记住上面的知识**,之后可能会用到。\n" - return formatted_prompt_info + return f"你有以下这些**知识**:\n{related_info}\n请你**记住上面的知识**,之后可能会用到。\n" else: logger.debug("从LPMM知识库获取知识失败,可能是从未导入过知识,返回空知识...") return "" diff --git a/src/llm_models/utils_model.py b/src/llm_models/utils_model.py index 9aca329e0..98d93db13 100644 --- a/src/llm_models/utils_model.py +++ b/src/llm_models/utils_model.py @@ -851,7 +851,7 @@ class LLMRequest: def _default_response_handler( self, result: dict, user_id: str = "system", request_type: str = None, endpoint: str = "/chat/completions" - ) -> Tuple: + ): """默认响应解析""" if "choices" in result and result["choices"]: message = result["choices"][0]["message"] diff --git a/src/plugin_system/apis/generator_api.py b/src/plugin_system/apis/generator_api.py index f911454c2..f8752ac4e 100644 --- a/src/plugin_system/apis/generator_api.py +++ b/src/plugin_system/apis/generator_api.py @@ -84,18 +84,23 @@ async def generate_reply( enable_chinese_typo: bool = True, return_prompt: bool = False, model_configs: Optional[List[Dict[str, Any]]] = None, - request_type: str = "", - enable_timeout: bool = False, + request_type: str = "generator_api", ) -> Tuple[bool, List[Tuple[str, Any]], Optional[str]]: """生成回复 Args: chat_stream: 聊天流对象(优先) chat_id: 聊天ID(备用) - action_data: 动作数据 + action_data: 动作数据(向下兼容,包含reply_to和extra_info) + reply_to: 回复对象,格式为 "发送者:消息内容" + extra_info: 额外信息,用于补充上下文 + available_actions: 可用动作 + enable_tool: 是否启用工具调用 enable_splitter: 是否启用消息分割器 enable_chinese_typo: 是否启用错字生成器 return_prompt: 是否返回提示词 + model_configs: 模型配置列表 + request_type: 请求类型(可选,记录LLM使用) Returns: Tuple[bool, List[Tuple[str, Any]], Optional[str]]: (是否成功, 回复集合, 提示词) """ @@ -107,7 +112,7 @@ async def generate_reply( return False, [], None logger.debug("[GeneratorAPI] 开始生成回复") - + if not reply_to and action_data: reply_to = action_data.get("reply_to", "") if not extra_info and action_data: @@ -118,7 +123,6 @@ async def generate_reply( reply_to=reply_to, extra_info=extra_info, available_actions=available_actions, - enable_timeout=enable_timeout, enable_tool=enable_tool, ) reply_set = [] @@ -154,12 +158,13 @@ async def rewrite_reply( raw_reply: str = "", reason: str = "", reply_to: str = "", -) -> Tuple[bool, List[Tuple[str, Any]]]: + return_prompt: bool = False, +) -> Tuple[bool, List[Tuple[str, Any]], Optional[str]]: """重写回复 Args: chat_stream: 聊天流对象(优先) - reply_data: 回复数据字典(备用,当其他参数缺失时从此获取) + reply_data: 回复数据字典(向下兼容备用,当其他参数缺失时从此获取) chat_id: 聊天ID(备用) enable_splitter: 是否启用消息分割器 enable_chinese_typo: 是否启用错字生成器 @@ -167,6 +172,7 @@ async def rewrite_reply( raw_reply: 原始回复内容 reason: 回复原因 reply_to: 回复对象 + return_prompt: 是否返回提示词 Returns: Tuple[bool, List[Tuple[str, Any]]]: (是否成功, 回复集合) @@ -176,7 +182,7 @@ async def rewrite_reply( replyer = get_replyer(chat_stream, chat_id, model_configs=model_configs) if not replyer: logger.error("[GeneratorAPI] 无法获取回复器") - return False, [] + return False, [], None logger.info("[GeneratorAPI] 开始重写回复") @@ -187,10 +193,11 @@ async def rewrite_reply( reply_to = reply_to or reply_data.get("reply_to", "") # 调用回复器重写回复 - success, content = await replyer.rewrite_reply_with_context( + success, content, prompt = await replyer.rewrite_reply_with_context( raw_reply=raw_reply, reason=reason, reply_to=reply_to, + return_prompt=return_prompt, ) reply_set = [] if content: @@ -201,14 +208,14 @@ async def rewrite_reply( else: logger.warning("[GeneratorAPI] 重写回复失败") - return success, reply_set + return success, reply_set, prompt if return_prompt else None except ValueError as ve: raise ve except Exception as e: logger.error(f"[GeneratorAPI] 重写回复时出错: {e}") - return False, [] + return False, [], None async def process_human_text(content: str, enable_splitter: bool, enable_chinese_typo: bool) -> List[Tuple[str, Any]]: @@ -234,3 +241,27 @@ async def process_human_text(content: str, enable_splitter: bool, enable_chinese except Exception as e: logger.error(f"[GeneratorAPI] 处理人形文本时出错: {e}") return [] + +async def generate_response_custom( + chat_stream: Optional[ChatStream] = None, + chat_id: Optional[str] = None, + model_configs: Optional[List[Dict[str, Any]]] = None, + prompt: str = "", +) -> Optional[str]: + replyer = get_replyer(chat_stream, chat_id, model_configs=model_configs) + if not replyer: + logger.error("[GeneratorAPI] 无法获取回复器") + return None + + try: + logger.debug("[GeneratorAPI] 开始生成自定义回复") + response = await replyer.llm_generate_content(prompt) + if response: + logger.debug("[GeneratorAPI] 自定义回复生成成功") + return response + else: + logger.warning("[GeneratorAPI] 自定义回复生成失败") + return None + except Exception as e: + logger.error(f"[GeneratorAPI] 生成自定义回复时出错: {e}") + return None \ No newline at end of file From 61e5014c6b5facc555be8d17bac5e560ac5912f1 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sun, 27 Jul 2025 17:10:50 +0800 Subject: [PATCH 09/22] llm_api_doc --- docs/plugins/api/llm-api.md | 243 +++--------------------------- src/plugin_system/apis/llm_api.py | 9 +- 2 files changed, 24 insertions(+), 228 deletions(-) diff --git a/docs/plugins/api/llm-api.md b/docs/plugins/api/llm-api.md index e0879ddfc..d778ec8d8 100644 --- a/docs/plugins/api/llm-api.md +++ b/docs/plugins/api/llm-api.md @@ -6,239 +6,34 @@ LLM API模块提供与大语言模型交互的功能,让插件能够使用系 ```python from src.plugin_system.apis import llm_api +# 或者 +from src.plugin_system import llm_api ``` ## 主要功能 -### 1. 模型管理 - -#### `get_available_models() -> Dict[str, Any]` -获取所有可用的模型配置 - -**返回:** -- `Dict[str, Any]`:模型配置字典,key为模型名称,value为模型配置 - -**示例:** +### 1. 查询可用模型 ```python -models = llm_api.get_available_models() -for model_name, model_config in models.items(): - print(f"模型: {model_name}") - print(f"配置: {model_config}") +def get_available_models() -> Dict[str, Any]: ``` +获取所有可用的模型配置。 -### 2. 内容生成 +**Return:** +- `Dict[str, Any]`:模型配置字典,key为模型名称,value为模型配置。 -#### `generate_with_model(prompt, model_config, request_type="plugin.generate", **kwargs)` -使用指定模型生成内容 - -**参数:** -- `prompt`:提示词 -- `model_config`:模型配置(从 get_available_models 获取) -- `request_type`:请求类型标识 -- `**kwargs`:其他模型特定参数,如temperature、max_tokens等 - -**返回:** -- `Tuple[bool, str, str, str]`:(是否成功, 生成的内容, 推理过程, 模型名称) - -**示例:** +### 2. 使用模型生成内容 ```python -models = llm_api.get_available_models() -default_model = models.get("default") - -if default_model: - success, response, reasoning, model_name = await llm_api.generate_with_model( - prompt="请写一首关于春天的诗", - model_config=default_model, - temperature=0.7, - max_tokens=200 - ) - - if success: - print(f"生成内容: {response}") - print(f"使用模型: {model_name}") +async def generate_with_model( + prompt: str, model_config: Dict[str, Any], request_type: str = "plugin.generate", **kwargs +) -> Tuple[bool, str]: ``` +使用指定模型生成内容。 -## 使用示例 +**Args:** +- `prompt`:提示词。 +- `model_config`:模型配置(从 `get_available_models` 获取)。 +- `request_type`:请求类型标识,默认为 `"plugin.generate"`。 +- `**kwargs`:其他模型特定参数,如 `temperature`、`max_tokens` 等。 -### 1. 基础文本生成 - -```python -from src.plugin_system.apis import llm_api - -async def generate_story(topic: str): - """生成故事""" - models = llm_api.get_available_models() - model = models.get("default") - - if not model: - return "未找到可用模型" - - prompt = f"请写一个关于{topic}的短故事,大约100字左右。" - - success, story, reasoning, model_name = await llm_api.generate_with_model( - prompt=prompt, - model_config=model, - request_type="story.generate", - temperature=0.8, - max_tokens=150 - ) - - return story if success else "故事生成失败" -``` - -### 2. 在Action中使用LLM - -```python -from src.plugin_system.base import BaseAction - -class LLMAction(BaseAction): - async def execute(self, action_data, chat_stream): - # 获取用户输入 - user_input = action_data.get("user_message", "") - intent = action_data.get("intent", "chat") - - # 获取模型配置 - models = llm_api.get_available_models() - model = models.get("default") - - if not model: - return {"success": False, "error": "未配置LLM模型"} - - # 构建提示词 - prompt = self.build_prompt(user_input, intent) - - # 生成回复 - success, response, reasoning, model_name = await llm_api.generate_with_model( - prompt=prompt, - model_config=model, - request_type=f"plugin.{self.plugin_name}", - temperature=0.7 - ) - - if success: - return { - "success": True, - "response": response, - "model_used": model_name, - "reasoning": reasoning - } - - return {"success": False, "error": response} - - def build_prompt(self, user_input: str, intent: str) -> str: - """构建提示词""" - base_prompt = "你是一个友善的AI助手。" - - if intent == "question": - return f"{base_prompt}\n\n用户问题:{user_input}\n\n请提供准确、有用的回答:" - elif intent == "chat": - return f"{base_prompt}\n\n用户说:{user_input}\n\n请进行自然的对话:" - else: - return f"{base_prompt}\n\n用户输入:{user_input}\n\n请回复:" -``` - -### 3. 多模型对比 - -```python -async def compare_models(prompt: str): - """使用多个模型生成内容并对比""" - models = llm_api.get_available_models() - results = {} - - for model_name, model_config in models.items(): - success, response, reasoning, actual_model = await llm_api.generate_with_model( - prompt=prompt, - model_config=model_config, - request_type="comparison.test" - ) - - results[model_name] = { - "success": success, - "response": response, - "model": actual_model, - "reasoning": reasoning - } - - return results -``` - -### 4. 智能对话插件 - -```python -class ChatbotPlugin(BasePlugin): - async def handle_action(self, action_data, chat_stream): - user_message = action_data.get("message", "") - - # 获取历史对话上下文 - context = self.get_conversation_context(chat_stream) - - # 构建对话提示词 - prompt = self.build_conversation_prompt(user_message, context) - - # 获取模型配置 - models = llm_api.get_available_models() - chat_model = models.get("chat", models.get("default")) - - if not chat_model: - return {"success": False, "message": "聊天模型未配置"} - - # 生成回复 - success, response, reasoning, model_name = await llm_api.generate_with_model( - prompt=prompt, - model_config=chat_model, - request_type="chat.conversation", - temperature=0.8, - max_tokens=500 - ) - - if success: - # 保存对话历史 - self.save_conversation(chat_stream, user_message, response) - - return { - "success": True, - "reply": response, - "model": model_name - } - - return {"success": False, "message": "回复生成失败"} - - def build_conversation_prompt(self, user_message: str, context: list) -> str: - """构建对话提示词""" - prompt = "你是一个有趣、友善的聊天机器人。请自然地回复用户的消息。\n\n" - - # 添加历史对话 - if context: - prompt += "对话历史:\n" - for msg in context[-5:]: # 只保留最近5条 - prompt += f"用户: {msg['user']}\n机器人: {msg['bot']}\n" - prompt += "\n" - - prompt += f"用户: {user_message}\n机器人: " - return prompt -``` - -## 模型配置说明 - -### 常用模型类型 -- `default`:默认模型 -- `chat`:聊天专用模型 -- `creative`:创意生成模型 -- `code`:代码生成模型 - -### 配置参数 -LLM模型支持的常用参数: -- `temperature`:控制输出随机性(0.0-1.0) -- `max_tokens`:最大生成长度 -- `top_p`:核采样参数 -- `frequency_penalty`:频率惩罚 -- `presence_penalty`:存在惩罚 - -## 注意事项 - -1. **异步操作**:LLM生成是异步的,必须使用`await` -2. **错误处理**:生成失败时返回False和错误信息 -3. **配置依赖**:需要正确配置模型才能使用 -4. **请求类型**:建议为不同用途设置不同的request_type -5. **性能考虑**:LLM调用可能较慢,考虑超时和缓存 -6. **成本控制**:注意控制max_tokens以控制成本 \ No newline at end of file +**Return:** +- `Tuple[bool, str]`:返回一个元组,第一个元素表示是否成功,第二个元素为生成的内容或错误信息。 \ No newline at end of file diff --git a/src/plugin_system/apis/llm_api.py b/src/plugin_system/apis/llm_api.py index 72b865b89..4e9d884fa 100644 --- a/src/plugin_system/apis/llm_api.py +++ b/src/plugin_system/apis/llm_api.py @@ -54,7 +54,7 @@ def get_available_models() -> Dict[str, Any]: async def generate_with_model( prompt: str, model_config: Dict[str, Any], request_type: str = "plugin.generate", **kwargs -) -> Tuple[bool, str, str, str]: +) -> Tuple[bool, str]: """使用指定模型生成内容 Args: @@ -73,10 +73,11 @@ async def generate_with_model( llm_request = LLMRequest(model=model_config, request_type=request_type, **kwargs) - response, (reasoning, model_name) = await llm_request.generate_response_async(prompt) - return True, response, reasoning, model_name + # TODO: 复活这个_ + response, _ = await llm_request.generate_response_async(prompt) + return True, response except Exception as e: error_msg = f"生成内容时出错: {str(e)}" logger.error(f"[LLMAPI] {error_msg}") - return False, error_msg, "", "" + return False, error_msg From e893b625809423bf2c1ede27c3a98d6d75dc7176 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sun, 27 Jul 2025 17:16:25 +0800 Subject: [PATCH 10/22] logging_api_doc --- docs/plugins/api/logging-api.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 docs/plugins/api/logging-api.md diff --git a/docs/plugins/api/logging-api.md b/docs/plugins/api/logging-api.md new file mode 100644 index 000000000..d656f1ef3 --- /dev/null +++ b/docs/plugins/api/logging-api.md @@ -0,0 +1,29 @@ +# Logging API + +Logging API模块提供了获取本体logger的功能,允许插件记录日志信息。 + +## 导入方式 + +```python +from src.plugin_system.apis import logging_api +# 或者 +from src.plugin_system import logging_api +``` + +## 主要功能 +### 1. 获取本体logger +```python +def get_logger(name: str) -> structlog.stdlib.BoundLogger: +``` +获取本体logger实例。 + +**Args:** +- `name` (str): 日志记录器的名称。 + +**Returns:** +- 一个logger实例,有以下方法: + - `debug` + - `info` + - `warning` + - `error` + - `critical` \ No newline at end of file From 55ce050cc2068a3878701013d287d692c3ce85ea Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sun, 27 Jul 2025 22:11:14 +0800 Subject: [PATCH 11/22] message_api_doc --- docs/plugins/api/message-api.md | 531 ++++++++++++++------------ src/plugin_system/apis/message_api.py | 6 +- 2 files changed, 299 insertions(+), 238 deletions(-) diff --git a/docs/plugins/api/message-api.md b/docs/plugins/api/message-api.md index c95a9cc6f..85d83a9bc 100644 --- a/docs/plugins/api/message-api.md +++ b/docs/plugins/api/message-api.md @@ -1,11 +1,13 @@ # 消息API -> 消息API提供了强大的消息查询、计数和格式化功能,让你轻松处理聊天消息数据。 +消息API提供了强大的消息查询、计数和格式化功能,让你轻松处理聊天消息数据。 ## 导入方式 ```python from src.plugin_system.apis import message_api +# 或者 +from src.plugin_system import message_api ``` ## 功能概述 @@ -15,297 +17,356 @@ from src.plugin_system.apis import message_api - **消息计数** - 统计新消息数量 - **消息格式化** - 将消息转换为可读格式 ---- +## 主要功能 -## 消息查询API +### 1. 按照事件查询消息 +```python +def get_messages_by_time( + start_time: float, end_time: float, limit: int = 0, limit_mode: str = "latest", filter_mai: bool = False +) -> List[Dict[str, Any]]: +``` +获取指定时间范围内的消息。 -### 按时间查询消息 - -#### `get_messages_by_time(start_time, end_time, limit=0, limit_mode="latest")` - -获取指定时间范围内的消息 - -**参数:** +**Args:** - `start_time` (float): 开始时间戳 -- `end_time` (float): 结束时间戳 +- `end_time` (float): 结束时间戳 - `limit` (int): 限制返回消息数量,0为不限制 - `limit_mode` (str): 限制模式,`"earliest"`获取最早记录,`"latest"`获取最新记录 +- `filter_mai` (bool): 是否过滤掉机器人的消息,默认False -**返回:** `List[Dict[str, Any]]` - 消息列表 +**Returns:** +- `List[Dict[str, Any]]` - 消息列表 -**示例:** +消息列表中包含的键与`Messages`类的属性一致。(位于`src.common.database.database_model`) + +### 2. 获取指定聊天中指定时间范围内的信息 ```python -import time - -# 获取最近24小时的消息 -now = time.time() -yesterday = now - 24 * 3600 -messages = message_api.get_messages_by_time(yesterday, now, limit=50) +def get_messages_by_time_in_chat( + chat_id: str, + start_time: float, + end_time: float, + limit: int = 0, + limit_mode: str = "latest", + filter_mai: bool = False, +) -> List[Dict[str, Any]]: ``` +获取指定聊天中指定时间范围内的消息。 -### 按聊天查询消息 - -#### `get_messages_by_time_in_chat(chat_id, start_time, end_time, limit=0, limit_mode="latest")` - -获取指定聊天中指定时间范围内的消息 - -**参数:** -- `chat_id` (str): 聊天ID -- 其他参数同上 - -**示例:** -```python -# 获取某个群聊最近的100条消息 -messages = message_api.get_messages_by_time_in_chat( - chat_id="123456789", - start_time=yesterday, - end_time=now, - limit=100 -) -``` - -#### `get_messages_by_time_in_chat_inclusive(chat_id, start_time, end_time, limit=0, limit_mode="latest")` - -获取指定聊天中指定时间范围内的消息(包含边界时间点) - -与 `get_messages_by_time_in_chat` 类似,但包含边界时间戳的消息。 - -#### `get_recent_messages(chat_id, hours=24.0, limit=100, limit_mode="latest")` - -获取指定聊天中最近一段时间的消息(便捷方法) - -**参数:** -- `chat_id` (str): 聊天ID -- `hours` (float): 最近多少小时,默认24小时 -- `limit` (int): 限制返回消息数量,默认100条 -- `limit_mode` (str): 限制模式 - -**示例:** -```python -# 获取最近6小时的消息 -recent_messages = message_api.get_recent_messages( - chat_id="123456789", - hours=6.0, - limit=50 -) -``` - -### 按用户查询消息 - -#### `get_messages_by_time_in_chat_for_users(chat_id, start_time, end_time, person_ids, limit=0, limit_mode="latest")` - -获取指定聊天中指定用户在指定时间范围内的消息 - -**参数:** +**Args:** - `chat_id` (str): 聊天ID - `start_time` (float): 开始时间戳 - `end_time` (float): 结束时间戳 -- `person_ids` (list): 用户ID列表 -- `limit` (int): 限制返回消息数量 -- `limit_mode` (str): 限制模式 +- `limit` (int): 限制返回消息数量,0为不限制 +- `limit_mode` (str): 限制模式,`"earliest"`获取最早记录,`"latest"`获取最新记录 +- `filter_mai` (bool): 是否过滤掉机器人的消息,默认False -**示例:** +**Returns:** +- `List[Dict[str, Any]]` - 消息列表 + + +### 3. 获取指定聊天中指定时间范围内的信息(包含边界) ```python -# 获取特定用户的消息 -user_messages = message_api.get_messages_by_time_in_chat_for_users( - chat_id="123456789", - start_time=yesterday, - end_time=now, - person_ids=["user1", "user2"] -) +def get_messages_by_time_in_chat_inclusive( + chat_id: str, + start_time: float, + end_time: float, + limit: int = 0, + limit_mode: str = "latest", + filter_mai: bool = False, + filter_command: bool = False, +) -> List[Dict[str, Any]]: ``` +获取指定聊天中指定时间范围内的消息(包含边界)。 -#### `get_messages_by_time_for_users(start_time, end_time, person_ids, limit=0, limit_mode="latest")` +**Args:** +- `chat_id` (str): 聊天ID +- `start_time` (float): 开始时间戳(包含) +- `end_time` (float): 结束时间戳(包含) +- `limit` (int): 限制返回消息数量,0为不限制 +- `limit_mode` (str): 限制模式,`"earliest"`获取最早记录,`"latest"`获取最新记录 +- `filter_mai` (bool): 是否过滤掉机器人的消息,默认False +- `filter_command` (bool): 是否过滤命令消息,默认False -获取指定用户在所有聊天中指定时间范围内的消息 +**Returns:** +- `List[Dict[str, Any]]` - 消息列表 -### 其他查询方法 -#### `get_random_chat_messages(start_time, end_time, limit=0, limit_mode="latest")` +### 4. 获取指定聊天中指定用户在指定时间范围内的消息 +```python +def get_messages_by_time_in_chat_for_users( + chat_id: str, + start_time: float, + end_time: float, + person_ids: List[str], + limit: int = 0, + limit_mode: str = "latest", +) -> List[Dict[str, Any]]: +``` +获取指定聊天中指定用户在指定时间范围内的消息。 -随机选择一个聊天,返回该聊天在指定时间范围内的消息 - -#### `get_messages_before_time(timestamp, limit=0)` - -获取指定时间戳之前的消息 - -#### `get_messages_before_time_in_chat(chat_id, timestamp, limit=0)` - -获取指定聊天中指定时间戳之前的消息 - -#### `get_messages_before_time_for_users(timestamp, person_ids, limit=0)` - -获取指定用户在指定时间戳之前的消息 - ---- - -## 消息计数API - -### `count_new_messages(chat_id, start_time=0.0, end_time=None)` - -计算指定聊天中从开始时间到结束时间的新消息数量 - -**参数:** +**Args:** - `chat_id` (str): 聊天ID - `start_time` (float): 开始时间戳 -- `end_time` (float): 结束时间戳,如果为None则使用当前时间 +- `end_time` (float): 结束时间戳 +- `person_ids` (List[str]): 用户ID列表 +- `limit` (int): 限制返回消息数量,0为不限制 +- `limit_mode` (str): 限制模式,`"earliest"`获取最早记录,`"latest"`获取最新记录 -**返回:** `int` - 新消息数量 +**Returns:** +- `List[Dict[str, Any]]` - 消息列表 -**示例:** + +### 5. 随机选择一个聊天,返回该聊天在指定时间范围内的消息 ```python -# 计算最近1小时的新消息数 -import time -now = time.time() -hour_ago = now - 3600 -new_count = message_api.count_new_messages("123456789", hour_ago, now) -print(f"最近1小时有{new_count}条新消息") +def get_random_chat_messages( + start_time: float, + end_time: float, + limit: int = 0, + limit_mode: str = "latest", + filter_mai: bool = False, +) -> List[Dict[str, Any]]: ``` +随机选择一个聊天,返回该聊天在指定时间范围内的消息。 -### `count_new_messages_for_users(chat_id, start_time, end_time, person_ids)` +**Args:** +- `start_time` (float): 开始时间戳 +- `end_time` (float): 结束时间戳 +- `limit` (int): 限制返回消息数量,0为不限制 +- `limit_mode` (str): 限制模式,`"earliest"`获取最早记录,`"latest"`获取最新记录 +- `filter_mai` (bool): 是否过滤掉机器人的消息,默认False -计算指定聊天中指定用户从开始时间到结束时间的新消息数量 +**Returns:** +- `List[Dict[str, Any]]` - 消息列表 ---- -## 消息格式化API +### 6. 获取指定用户在所有聊天中指定时间范围内的消息 +```python +def get_messages_by_time_for_users( + start_time: float, + end_time: float, + person_ids: List[str], + limit: int = 0, + limit_mode: str = "latest", +) -> List[Dict[str, Any]]: +``` +获取指定用户在所有聊天中指定时间范围内的消息。 -### `build_readable_messages_to_str(messages, **options)` +**Args:** +- `start_time` (float): 开始时间戳 +- `end_time` (float): 结束时间戳 +- `person_ids` (List[str]): 用户ID列表 +- `limit` (int): 限制返回消息数量,0为不限制 +- `limit_mode` (str): 限制模式,`"earliest"`获取最早记录,`"latest"`获取最新记录 -将消息列表构建成可读的字符串 +**Returns:** +- `List[Dict[str, Any]]` - 消息列表 -**参数:** + +### 7. 获取指定时间戳之前的消息 +```python +def get_messages_before_time( + timestamp: float, + limit: int = 0, + filter_mai: bool = False, +) -> List[Dict[str, Any]]: +``` +获取指定时间戳之前的消息。 + +**Args:** +- `timestamp` (float): 时间戳 +- `limit` (int): 限制返回消息数量,0为不限制 +- `filter_mai` (bool): 是否过滤掉机器人的消息,默认False + +**Returns:** +- `List[Dict[str, Any]]` - 消息列表 + + +### 8. 获取指定聊天中指定时间戳之前的消息 +```python +def get_messages_before_time_in_chat( + chat_id: str, + timestamp: float, + limit: int = 0, + filter_mai: bool = False, +) -> List[Dict[str, Any]]: +``` +获取指定聊天中指定时间戳之前的消息。 + +**Args:** +- `chat_id` (str): 聊天ID +- `timestamp` (float): 时间戳 +- `limit` (int): 限制返回消息数量,0为不限制 +- `filter_mai` (bool): 是否过滤掉机器人的消息,默认False + +**Returns:** +- `List[Dict[str, Any]]` - 消息列表 + + +### 9. 获取指定用户在指定时间戳之前的消息 +```python +def get_messages_before_time_for_users( + timestamp: float, + person_ids: List[str], + limit: int = 0, +) -> List[Dict[str, Any]]: +``` +获取指定用户在指定时间戳之前的消息。 + +**Args:** +- `timestamp` (float): 时间戳 +- `person_ids` (List[str]): 用户ID列表 +- `limit` (int): 限制返回消息数量,0为不限制 + +**Returns:** +- `List[Dict[str, Any]]` - 消息列表 + + +### 10. 获取指定聊天中最近一段时间的消息 +```python +def get_recent_messages( + chat_id: str, + hours: float = 24.0, + limit: int = 100, + limit_mode: str = "latest", + filter_mai: bool = False, +) -> List[Dict[str, Any]]: +``` +获取指定聊天中最近一段时间的消息。 + +**Args:** +- `chat_id` (str): 聊天ID +- `hours` (float): 最近多少小时,默认24小时 +- `limit` (int): 限制返回消息数量,默认100条 +- `limit_mode` (str): 限制模式,`"earliest"`获取最早记录,`"latest"`获取最新记录 +- `filter_mai` (bool): 是否过滤掉机器人的消息,默认False + +**Returns:** +- `List[Dict[str, Any]]` - 消息列表 + + +### 11. 计算指定聊天中从开始时间到结束时间的新消息数量 +```python +def count_new_messages( + chat_id: str, + start_time: float = 0.0, + end_time: Optional[float] = None, +) -> int: +``` +计算指定聊天中从开始时间到结束时间的新消息数量。 + +**Args:** +- `chat_id` (str): 聊天ID +- `start_time` (float): 开始时间戳 +- `end_time` (Optional[float]): 结束时间戳,如果为None则使用当前时间 + +**Returns:** +- `int` - 新消息数量 + + +### 12. 计算指定聊天中指定用户从开始时间到结束时间的新消息数量 +```python +def count_new_messages_for_users( + chat_id: str, + start_time: float, + end_time: float, + person_ids: List[str], +) -> int: +``` +计算指定聊天中指定用户从开始时间到结束时间的新消息数量。 + +**Args:** +- `chat_id` (str): 聊天ID +- `start_time` (float): 开始时间戳 +- `end_time` (float): 结束时间戳 +- `person_ids` (List[str]): 用户ID列表 + +**Returns:** +- `int` - 新消息数量 + + +### 13. 将消息列表构建成可读的字符串 +```python +def build_readable_messages_to_str( + messages: List[Dict[str, Any]], + replace_bot_name: bool = True, + merge_messages: bool = False, + timestamp_mode: str = "relative", + read_mark: float = 0.0, + truncate: bool = False, + show_actions: bool = False, +) -> str: +``` +将消息列表构建成可读的字符串。 + +**Args:** - `messages` (List[Dict[str, Any]]): 消息列表 -- `replace_bot_name` (bool): 是否将机器人的名称替换为"你",默认True -- `merge_messages` (bool): 是否合并连续消息,默认False -- `timestamp_mode` (str): 时间戳显示模式,`"relative"`或`"absolute"`,默认`"relative"` -- `read_mark` (float): 已读标记时间戳,用于分割已读和未读消息,默认0.0 -- `truncate` (bool): 是否截断长消息,默认False -- `show_actions` (bool): 是否显示动作记录,默认False +- `replace_bot_name` (bool): 是否将机器人的名称替换为"你" +- `merge_messages` (bool): 是否合并连续消息 +- `timestamp_mode` (str): 时间戳显示模式,`"relative"`或`"absolute"` +- `read_mark` (float): 已读标记时间戳,用于分割已读和未读消息 +- `truncate` (bool): 是否截断长消息 +- `show_actions` (bool): 是否显示动作记录 -**返回:** `str` - 格式化后的可读字符串 +**Returns:** +- `str` - 格式化后的可读字符串 -**示例:** + +### 14. 将消息列表构建成可读的字符串,并返回详细信息 ```python -# 获取消息并格式化为可读文本 -messages = message_api.get_recent_messages("123456789", hours=2) -readable_text = message_api.build_readable_messages_to_str( - messages, - replace_bot_name=True, - merge_messages=True, - timestamp_mode="relative" -) -print(readable_text) +async def build_readable_messages_with_details( + messages: List[Dict[str, Any]], + replace_bot_name: bool = True, + merge_messages: bool = False, + timestamp_mode: str = "relative", + truncate: bool = False, +) -> Tuple[str, List[Tuple[float, str, str]]]: ``` +将消息列表构建成可读的字符串,并返回详细信息。 -### `build_readable_messages_with_details(messages, **options)` 异步 +**Args:** +- `messages` (List[Dict[str, Any]]): 消息列表 +- `replace_bot_name` (bool): 是否将机器人的名称替换为"你" +- `merge_messages` (bool): 是否合并连续消息 +- `timestamp_mode` (str): 时间戳显示模式,`"relative"`或`"absolute"` +- `truncate` (bool): 是否截断长消息 -将消息列表构建成可读的字符串,并返回详细信息 +**Returns:** +- `Tuple[str, List[Tuple[float, str, str]]]` - 格式化后的可读字符串和详细信息元组列表(时间戳, 昵称, 内容) -**参数:** 与 `build_readable_messages_to_str` 类似,但不包含 `read_mark` 和 `show_actions` -**返回:** `Tuple[str, List[Tuple[float, str, str]]]` - 格式化字符串和详细信息元组列表(时间戳, 昵称, 内容) - -**示例:** +### 15. 从消息列表中提取不重复的用户ID列表 ```python -# 异步获取详细格式化信息 -readable_text, details = await message_api.build_readable_messages_with_details( - messages, - timestamp_mode="absolute" -) - -for timestamp, nickname, content in details: - print(f"{timestamp}: {nickname} 说: {content}") +async def get_person_ids_from_messages( + messages: List[Dict[str, Any]], +) -> List[str]: ``` +从消息列表中提取不重复的用户ID列表。 -### `get_person_ids_from_messages(messages)` 异步 - -从消息列表中提取不重复的用户ID列表 - -**参数:** +**Args:** - `messages` (List[Dict[str, Any]]): 消息列表 -**返回:** `List[str]` - 用户ID列表 +**Returns:** +- `List[str]` - 用户ID列表 -**示例:** + +### 16. 从消息列表中移除机器人的消息 ```python -# 获取参与对话的所有用户ID -messages = message_api.get_recent_messages("123456789") -person_ids = await message_api.get_person_ids_from_messages(messages) -print(f"参与对话的用户: {person_ids}") +def filter_mai_messages( + messages: List[Dict[str, Any]], +) -> List[Dict[str, Any]]: ``` +从消息列表中移除机器人的消息。 ---- +**Args:** +- `messages` (List[Dict[str, Any]]): 消息列表,每个元素是消息字典 -## 完整使用示例 - -### 场景1:统计活跃度 - -```python -import time -from src.plugin_system.apis import message_api - -async def analyze_chat_activity(chat_id: str): - """分析聊天活跃度""" - now = time.time() - day_ago = now - 24 * 3600 - - # 获取最近24小时的消息 - messages = message_api.get_recent_messages(chat_id, hours=24) - - # 统计消息数量 - total_count = len(messages) - - # 获取参与用户 - person_ids = await message_api.get_person_ids_from_messages(messages) - - # 格式化消息内容 - readable_text = message_api.build_readable_messages_to_str( - messages[-10:], # 最后10条消息 - merge_messages=True, - timestamp_mode="relative" - ) - - return { - "total_messages": total_count, - "active_users": len(person_ids), - "recent_chat": readable_text - } -``` - -### 场景2:查看特定用户的历史消息 - -```python -def get_user_history(chat_id: str, user_id: str, days: int = 7): - """获取用户最近N天的消息历史""" - now = time.time() - start_time = now - days * 24 * 3600 - - # 获取特定用户的消息 - user_messages = message_api.get_messages_by_time_in_chat_for_users( - chat_id=chat_id, - start_time=start_time, - end_time=now, - person_ids=[user_id], - limit=100 - ) - - # 格式化为可读文本 - readable_history = message_api.build_readable_messages_to_str( - user_messages, - replace_bot_name=False, - timestamp_mode="absolute" - ) - - return readable_history -``` - ---- +**Returns:** +- `List[Dict[str, Any]]` - 过滤后的消息列表 ## 注意事项 1. **时间戳格式**:所有时间参数都使用Unix时间戳(float类型) -2. **异步函数**:`build_readable_messages_with_details` 和 `get_person_ids_from_messages` 是异步函数,需要使用 `await` +2. **异步函数**:部分函数是异步函数,需要使用 `await` 3. **性能考虑**:查询大量消息时建议设置合理的 `limit` 参数 4. **消息格式**:返回的消息是字典格式,包含时间戳、发送者、内容等信息 5. **用户ID**:`person_ids` 参数接受字符串列表,用于筛选特定用户的消息 \ No newline at end of file diff --git a/src/plugin_system/apis/message_api.py b/src/plugin_system/apis/message_api.py index 7794ee819..7cf9dc04f 100644 --- a/src/plugin_system/apis/message_api.py +++ b/src/plugin_system/apis/message_api.py @@ -207,7 +207,7 @@ def get_random_chat_messages( def get_messages_by_time_for_users( - start_time: float, end_time: float, person_ids: list, limit: int = 0, limit_mode: str = "latest" + start_time: float, end_time: float, person_ids: List[str], limit: int = 0, limit_mode: str = "latest" ) -> List[Dict[str, Any]]: """ 获取指定用户在所有聊天中指定时间范围内的消息 @@ -287,7 +287,7 @@ def get_messages_before_time_in_chat( return get_raw_msg_before_timestamp_with_chat(chat_id, timestamp, limit) -def get_messages_before_time_for_users(timestamp: float, person_ids: list, limit: int = 0) -> List[Dict[str, Any]]: +def get_messages_before_time_for_users(timestamp: float, person_ids: List[str], limit: int = 0) -> List[Dict[str, Any]]: """ 获取指定用户在指定时间戳之前的消息 @@ -372,7 +372,7 @@ def count_new_messages(chat_id: str, start_time: float = 0.0, end_time: Optional return num_new_messages_since(chat_id, start_time, end_time) -def count_new_messages_for_users(chat_id: str, start_time: float, end_time: float, person_ids: list) -> int: +def count_new_messages_for_users(chat_id: str, start_time: float, end_time: float, person_ids: List[str]) -> int: """ 计算指定聊天中指定用户从开始时间到结束时间的新消息数量 From 6a57ec1d5dc83e5a25b13c856dc900fbe8ddccd2 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sun, 27 Jul 2025 22:32:19 +0800 Subject: [PATCH 12/22] person_api_doc --- docs/plugins/api/person-api.md | 305 +++++---------------------------- 1 file changed, 41 insertions(+), 264 deletions(-) diff --git a/docs/plugins/api/person-api.md b/docs/plugins/api/person-api.md index 3e1bafaf7..f97498dcc 100644 --- a/docs/plugins/api/person-api.md +++ b/docs/plugins/api/person-api.md @@ -6,59 +6,65 @@ ```python from src.plugin_system.apis import person_api +# 或者 +from src.plugin_system import person_api ``` ## 主要功能 -### 1. Person ID管理 - -#### `get_person_id(platform: str, user_id: int) -> str` +### 1. Person ID 获取 +```python +def get_person_id(platform: str, user_id: int) -> str: +``` 根据平台和用户ID获取person_id -**参数:** +**Args:** - `platform`:平台名称,如 "qq", "telegram" 等 - `user_id`:用户ID -**返回:** +**Returns:** - `str`:唯一的person_id(MD5哈希值) -**示例:** +#### 示例 ```python person_id = person_api.get_person_id("qq", 123456) -print(f"Person ID: {person_id}") ``` ### 2. 用户信息查询 +```python +async def get_person_value(person_id: str, field_name: str, default: Any = None) -> Any: +``` +查询单个用户信息字段值 -#### `get_person_value(person_id: str, field_name: str, default: Any = None) -> Any` -根据person_id和字段名获取某个值 - -**参数:** +**Args:** - `person_id`:用户的唯一标识ID -- `field_name`:要获取的字段名,如 "nickname", "impression" 等 -- `default`:当字段不存在或获取失败时返回的默认值 +- `field_name`:要获取的字段名 +- `default`:字段值不存在时的默认值 -**返回:** +**Returns:** - `Any`:字段值或默认值 -**示例:** +#### 示例 ```python nickname = await person_api.get_person_value(person_id, "nickname", "未知用户") impression = await person_api.get_person_value(person_id, "impression") ``` -#### `get_person_values(person_id: str, field_names: list, default_dict: dict = None) -> dict` +### 3. 批量用户信息查询 +```python +async def get_person_values(person_id: str, field_names: list, default_dict: Optional[dict] = None) -> dict: +``` 批量获取用户信息字段值 -**参数:** +**Args:** - `person_id`:用户的唯一标识ID - `field_names`:要获取的字段名列表 - `default_dict`:默认值字典,键为字段名,值为默认值 -**返回:** +**Returns:** - `dict`:字段名到值的映射字典 -**示例:** +#### 示例 ```python values = await person_api.get_person_values( person_id, @@ -67,204 +73,31 @@ values = await person_api.get_person_values( ) ``` -### 3. 用户状态查询 - -#### `is_person_known(platform: str, user_id: int) -> bool` +### 4. 判断用户是否已知 +```python +async def is_person_known(platform: str, user_id: int) -> bool: +``` 判断是否认识某个用户 -**参数:** +**Args:** - `platform`:平台名称 - `user_id`:用户ID -**返回:** +**Returns:** - `bool`:是否认识该用户 -**示例:** +### 5. 根据用户名获取Person ID ```python -known = await person_api.is_person_known("qq", 123456) -if known: - print("这个用户我认识") +def get_person_id_by_name(person_name: str) -> str: ``` - -### 4. 用户名查询 - -#### `get_person_id_by_name(person_name: str) -> str` 根据用户名获取person_id -**参数:** +**Args:** - `person_name`:用户名 -**返回:** +**Returns:** - `str`:person_id,如果未找到返回空字符串 -**示例:** -```python -person_id = person_api.get_person_id_by_name("张三") -if person_id: - print(f"找到用户: {person_id}") -``` - -## 使用示例 - -### 1. 基础用户信息获取 - -```python -from src.plugin_system.apis import person_api - -async def get_user_info(platform: str, user_id: int): - """获取用户基本信息""" - - # 获取person_id - person_id = person_api.get_person_id(platform, user_id) - - # 获取用户信息 - user_info = await person_api.get_person_values( - person_id, - ["nickname", "impression", "know_times", "last_seen"], - { - "nickname": "未知用户", - "impression": "", - "know_times": 0, - "last_seen": 0 - } - ) - - return { - "person_id": person_id, - "nickname": user_info["nickname"], - "impression": user_info["impression"], - "know_times": user_info["know_times"], - "last_seen": user_info["last_seen"] - } -``` - -### 2. 在Action中使用用户信息 - -```python -from src.plugin_system.base import BaseAction - -class PersonalizedAction(BaseAction): - async def execute(self, action_data, chat_stream): - # 获取发送者信息 - user_id = chat_stream.user_info.user_id - platform = chat_stream.platform - - # 获取person_id - person_id = person_api.get_person_id(platform, user_id) - - # 获取用户昵称和印象 - nickname = await person_api.get_person_value(person_id, "nickname", "朋友") - impression = await person_api.get_person_value(person_id, "impression", "") - - # 根据用户信息个性化回复 - if impression: - response = f"你好 {nickname}!根据我对你的了解:{impression}" - else: - response = f"你好 {nickname}!很高兴见到你。" - - return { - "success": True, - "response": response, - "user_info": { - "nickname": nickname, - "impression": impression - } - } -``` - -### 3. 用户识别和欢迎 - -```python -async def welcome_user(chat_stream): - """欢迎用户,区分新老用户""" - - user_id = chat_stream.user_info.user_id - platform = chat_stream.platform - - # 检查是否认识这个用户 - is_known = await person_api.is_person_known(platform, user_id) - - if is_known: - # 老用户,获取详细信息 - person_id = person_api.get_person_id(platform, user_id) - nickname = await person_api.get_person_value(person_id, "nickname", "老朋友") - know_times = await person_api.get_person_value(person_id, "know_times", 0) - - welcome_msg = f"欢迎回来,{nickname}!我们已经聊过 {know_times} 次了。" - else: - # 新用户 - welcome_msg = "你好!很高兴认识你,我是MaiBot。" - - return welcome_msg -``` - -### 4. 用户搜索功能 - -```python -async def find_user_by_name(name: str): - """根据名字查找用户""" - - person_id = person_api.get_person_id_by_name(name) - - if not person_id: - return {"found": False, "message": f"未找到名为 '{name}' 的用户"} - - # 获取用户详细信息 - user_info = await person_api.get_person_values( - person_id, - ["nickname", "platform", "user_id", "impression", "know_times"], - {} - ) - - return { - "found": True, - "person_id": person_id, - "info": user_info - } -``` - -### 5. 用户印象分析 - -```python -async def analyze_user_relationship(chat_stream): - """分析用户关系""" - - user_id = chat_stream.user_info.user_id - platform = chat_stream.platform - person_id = person_api.get_person_id(platform, user_id) - - # 获取关系相关信息 - relationship_info = await person_api.get_person_values( - person_id, - ["nickname", "impression", "know_times", "relationship_level", "last_interaction"], - { - "nickname": "未知", - "impression": "", - "know_times": 0, - "relationship_level": "stranger", - "last_interaction": 0 - } - ) - - # 分析关系程度 - know_times = relationship_info["know_times"] - if know_times == 0: - relationship = "陌生人" - elif know_times < 5: - relationship = "新朋友" - elif know_times < 20: - relationship = "熟人" - else: - relationship = "老朋友" - - return { - "nickname": relationship_info["nickname"], - "relationship": relationship, - "impression": relationship_info["impression"], - "interaction_count": know_times - } -``` - ## 常用字段说明 ### 基础信息字段 @@ -274,69 +107,13 @@ async def analyze_user_relationship(chat_stream): ### 关系信息字段 - `impression`:对用户的印象 -- `know_times`:交互次数 -- `relationship_level`:关系等级 -- `last_seen`:最后见面时间 -- `last_interaction`:最后交互时间 +- `points`: 用户特征点 -### 个性化字段 -- `preferences`:用户偏好 -- `interests`:兴趣爱好 -- `mood_history`:情绪历史 -- `topic_interests`:话题兴趣 - -## 最佳实践 - -### 1. 错误处理 -```python -async def safe_get_user_info(person_id: str, field: str): - """安全获取用户信息""" - try: - value = await person_api.get_person_value(person_id, field) - return value if value is not None else "未设置" - except Exception as e: - logger.error(f"获取用户信息失败: {e}") - return "获取失败" -``` - -### 2. 批量操作 -```python -async def get_complete_user_profile(person_id: str): - """获取完整用户档案""" - - # 一次性获取所有需要的字段 - fields = [ - "nickname", "impression", "know_times", - "preferences", "interests", "relationship_level" - ] - - defaults = { - "nickname": "用户", - "impression": "", - "know_times": 0, - "preferences": "{}", - "interests": "[]", - "relationship_level": "stranger" - } - - profile = await person_api.get_person_values(person_id, fields, defaults) - - # 处理JSON字段 - try: - profile["preferences"] = json.loads(profile["preferences"]) - profile["interests"] = json.loads(profile["interests"]) - except: - profile["preferences"] = {} - profile["interests"] = [] - - return profile -``` +其他字段可以参考`PersonInfo`类的属性(位于`src.common.database.database_model`) ## 注意事项 -1. **异步操作**:大部分查询函数都是异步的,需要使用`await` -2. **错误处理**:所有函数都有错误处理,失败时记录日志并返回默认值 -3. **数据类型**:返回的数据可能是字符串、数字或JSON,需要适当处理 -4. **性能考虑**:批量查询优于单个查询 -5. **隐私保护**:确保用户信息的使用符合隐私政策 -6. **数据一致性**:person_id是用户的唯一标识,应妥善保存和使用 \ No newline at end of file +1. **异步操作**:部分查询函数都是异步的,需要使用`await` +2. **性能考虑**:批量查询优于单个查询 +3. **隐私保护**:确保用户信息的使用符合隐私政策 +4. **数据一致性**:person_id是用户的唯一标识,应妥善保存和使用 \ No newline at end of file From df1090156f70f589161f1b8a21d4c94cc097e3fb Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sun, 27 Jul 2025 23:12:46 +0800 Subject: [PATCH 13/22] component_mamage_api_doc --- docs/plugins/api/component-manage-api.md | 180 +++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 docs/plugins/api/component-manage-api.md diff --git a/docs/plugins/api/component-manage-api.md b/docs/plugins/api/component-manage-api.md new file mode 100644 index 000000000..f6da2adcc --- /dev/null +++ b/docs/plugins/api/component-manage-api.md @@ -0,0 +1,180 @@ +# 组件管理API + +组件管理API模块提供了对插件组件的查询和管理功能,使得插件能够获取和使用组件相关的信息。 + +## 导入方式 +```python +from src.plugin_system.apis import component_manage_api +# 或者 +from src.plugin_system import component_manage_api +``` + +## 功能概述 + +组件管理API主要提供以下功能: +- **插件信息查询** - 获取所有插件或指定插件的信息。 +- **组件查询** - 按名称或类型查询组件信息。 +- **组件管理** - 启用或禁用组件,支持全局和局部操作。 + +## 主要功能 + +### 1. 获取所有插件信息 +```python +def get_all_plugin_info() -> Dict[str, PluginInfo]: +``` +获取所有插件的信息。 + +**Returns:** +- `Dict[str, PluginInfo]` - 包含所有插件信息的字典,键为插件名称,值为 `PluginInfo` 对象。 + +### 2. 获取指定插件信息 +```python +def get_plugin_info(plugin_name: str) -> Optional[PluginInfo]: +``` +获取指定插件的信息。 + +**Args:** +- `plugin_name` (str): 插件名称。 + +**Returns:** +- `Optional[PluginInfo]`: 插件信息对象,如果插件不存在则返回 `None`。 + +### 3. 获取指定组件信息 +```python +def get_component_info(component_name: str, component_type: ComponentType) -> Optional[Union[CommandInfo, ActionInfo, EventHandlerInfo]]: +``` +获取指定组件的信息。 + +**Args:** +- `component_name` (str): 组件名称。 +- `component_type` (ComponentType): 组件类型。 + +**Returns:** +- `Optional[Union[CommandInfo, ActionInfo, EventHandlerInfo]]`: 组件信息对象,如果组件不存在则返回 `None`。 + +### 4. 获取指定类型的所有组件信息 +```python +def get_components_info_by_type(component_type: ComponentType) -> Dict[str, Union[CommandInfo, ActionInfo, EventHandlerInfo]]: +``` +获取指定类型的所有组件信息。 + +**Args:** +- `component_type` (ComponentType): 组件类型。 + +**Returns:** +- `Dict[str, Union[CommandInfo, ActionInfo, EventHandlerInfo]]`: 包含指定类型组件信息的字典,键为组件名称,值为对应的组件信息对象。 + +### 5. 获取指定类型的所有启用的组件信息 +```python +def get_enabled_components_info_by_type(component_type: ComponentType) -> Dict[str, Union[CommandInfo, ActionInfo, EventHandlerInfo]]: +``` +获取指定类型的所有启用的组件信息。 + +**Args:** +- `component_type` (ComponentType): 组件类型。 + +**Returns:** +- `Dict[str, Union[CommandInfo, ActionInfo, EventHandlerInfo]]`: 包含指定类型启用组件信息的字典,键为组件名称,值为对应的组件信息对象。 + +### 6. 获取指定 Action 的注册信息 +```python +def get_registered_action_info(action_name: str) -> Optional[ActionInfo]: +``` +获取指定 Action 的注册信息。 + +**Args:** +- `action_name` (str): Action 名称。 + +**Returns:** +- `Optional[ActionInfo]` - Action 信息对象,如果 Action 不存在则返回 `None`。 + +### 7. 获取指定 Command 的注册信息 +```python +def get_registered_command_info(command_name: str) -> Optional[CommandInfo]: +``` +获取指定 Command 的注册信息。 + +**Args:** +- `command_name` (str): Command 名称。 + +**Returns:** +- `Optional[CommandInfo]` - Command 信息对象,如果 Command 不存在则返回 `None`。 + +### 8. 获取指定 EventHandler 的注册信息 +```python +def get_registered_event_handler_info(event_handler_name: str) -> Optional[EventHandlerInfo]: +``` +获取指定 EventHandler 的注册信息。 + +**Args:** +- `event_handler_name` (str): EventHandler 名称。 + +**Returns:** +- `Optional[EventHandlerInfo]` - EventHandler 信息对象,如果 EventHandler 不存在则返回 `None`。 + +### 9. 全局启用指定组件 +```python +def globally_enable_component(component_name: str, component_type: ComponentType) -> bool: +``` +全局启用指定组件。 + +**Args:** +- `component_name` (str): 组件名称。 +- `component_type` (ComponentType): 组件类型。 + +**Returns:** +- `bool` - 启用成功返回 `True`,否则返回 `False`。 + +### 10. 全局禁用指定组件 +```python +async def globally_disable_component(component_name: str, component_type: ComponentType) -> bool: +``` +全局禁用指定组件。 + +**Args:** +- `component_name` (str): 组件名称。 +- `component_type` (ComponentType): 组件类型。 + +**Returns:** +- `bool` - 禁用成功返回 `True`,否则返回 `False`。 + +### 11. 局部启用指定组件 +```python +def locally_enable_component(component_name: str, component_type: ComponentType, stream_id: str) -> bool: +``` +局部启用指定组件。 + +**Args:** +- `component_name` (str): 组件名称。 +- `component_type` (ComponentType): 组件类型。 +- `stream_id` (str): 消息流 ID。 + +**Returns:** +- `bool` - 启用成功返回 `True`,否则返回 `False`。 + +### 12. 局部禁用指定组件 +```python +def locally_disable_component(component_name: str, component_type: ComponentType, stream_id: str) -> bool: +``` +局部禁用指定组件。 + +**Args:** +- `component_name` (str): 组件名称。 +- `component_type` (ComponentType): 组件类型。 +- `stream_id` (str): 消息流 ID。 + +**Returns:** +- `bool` - 禁用成功返回 `True`,否则返回 `False`。 + +### 13. 获取指定消息流中禁用的组件列表 +```python +def get_locally_disabled_components(stream_id: str, component_type: ComponentType) -> list[str]: +``` +获取指定消息流中禁用的组件列表。 + +**Args:** +- `stream_id` (str): 消息流 ID。 +- `component_type` (ComponentType): 组件类型。 + +**Returns:** +- `list[str]` - 禁用的组件名称列表。 From d8191c493a25b01b90e60fba00e9de7f856eed47 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sun, 27 Jul 2025 23:16:54 +0800 Subject: [PATCH 14/22] plugin_manage_api_doc --- docs/plugins/api/plugin-manage-api.md | 94 +++++++++++++++++++++ src/plugin_system/apis/plugin_manage_api.py | 4 +- 2 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 docs/plugins/api/plugin-manage-api.md diff --git a/docs/plugins/api/plugin-manage-api.md b/docs/plugins/api/plugin-manage-api.md new file mode 100644 index 000000000..7057ff74a --- /dev/null +++ b/docs/plugins/api/plugin-manage-api.md @@ -0,0 +1,94 @@ +# 插件管理API + +插件管理API模块提供了对插件的加载、卸载、重新加载以及目录管理功能。 + +## 导入方式 +```python +from src.plugin_system.apis import plugin_manage_api +# 或者 +from src.plugin_system import plugin_manage_api +``` + +## 功能概述 + +插件管理API主要提供以下功能: +- **插件查询** - 列出当前加载的插件或已注册的插件。 +- **插件管理** - 加载、卸载、重新加载插件。 +- **插件目录管理** - 添加插件目录并重新扫描。 + +## 主要功能 + +### 1. 列出当前加载的插件 +```python +def list_loaded_plugins() -> List[str]: +``` +列出所有当前加载的插件。 + +**Returns:** +- `List[str]` - 当前加载的插件名称列表。 + +### 2. 列出所有已注册的插件 +```python +def list_registered_plugins() -> List[str]: +``` +列出所有已注册的插件。 + +**Returns:** +- `List[str]` - 已注册的插件名称列表。 + +### 3. 卸载指定的插件 +```python +async def remove_plugin(plugin_name: str) -> bool: +``` +卸载指定的插件。 + +**Args:** +- `plugin_name` (str): 要卸载的插件名称。 + +**Returns:** +- `bool` - 卸载是否成功。 + +### 4. 重新加载指定的插件 +```python +async def reload_plugin(plugin_name: str) -> bool: +``` +重新加载指定的插件。 + +**Args:** +- `plugin_name` (str): 要重新加载的插件名称。 + +**Returns:** +- `bool` - 重新加载是否成功。 + +### 5. 加载指定的插件 +```python +def load_plugin(plugin_name: str) -> Tuple[bool, int]: +``` +加载指定的插件。 + +**Args:** +- `plugin_name` (str): 要加载的插件名称。 + +**Returns:** +- `Tuple[bool, int]` - 加载是否成功,成功或失败的个数。 + +### 6. 添加插件目录 +```python +def add_plugin_directory(plugin_directory: str) -> bool: +``` +添加插件目录。 + +**Args:** +- `plugin_directory` (str): 要添加的插件目录路径。 + +**Returns:** +- `bool` - 添加是否成功。 + +### 7. 重新扫描插件目录 +```python +def rescan_plugin_directory() -> Tuple[int, int]: +``` +重新扫描插件目录,加载新插件。 + +**Returns:** +- `Tuple[int, int]` - 成功加载的插件数量和失败的插件数量。 \ No newline at end of file diff --git a/src/plugin_system/apis/plugin_manage_api.py b/src/plugin_system/apis/plugin_manage_api.py index 1c01119b2..c792d7532 100644 --- a/src/plugin_system/apis/plugin_manage_api.py +++ b/src/plugin_system/apis/plugin_manage_api.py @@ -4,7 +4,7 @@ def list_loaded_plugins() -> List[str]: 列出所有当前加载的插件。 Returns: - list: 当前加载的插件名称列表。 + List[str]: 当前加载的插件名称列表。 """ from src.plugin_system.core.plugin_manager import plugin_manager @@ -16,7 +16,7 @@ def list_registered_plugins() -> List[str]: 列出所有已注册的插件。 Returns: - list: 已注册的插件名称列表。 + List[str]: 已注册的插件名称列表。 """ from src.plugin_system.core.plugin_manager import plugin_manager From 0c302c9ca54f7c2d13a3a8d8e1da451a1cad9e14 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Mon, 28 Jul 2025 09:46:40 +0800 Subject: [PATCH 15/22] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=E4=B8=AD=E4=BD=BF=E7=94=A8=E7=9B=B8=E5=AF=B9=E5=AF=BC=E5=85=A5?= =?UTF-8?q?=E6=97=B6=E4=BC=9A=E7=88=86=E7=82=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugin_system/core/plugin_manager.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugin_system/core/plugin_manager.py b/src/plugin_system/core/plugin_manager.py index 98bce4bdb..dfafda184 100644 --- a/src/plugin_system/core/plugin_manager.py +++ b/src/plugin_system/core/plugin_manager.py @@ -289,6 +289,7 @@ class PluginManager: return False module = module_from_spec(spec) + module.__package__ = module_name # 设置模块包名 spec.loader.exec_module(module) logger.debug(f"插件模块加载成功: {plugin_file}") From d643a85a0aec7189daef59f1b18c61b662a91539 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Mon, 28 Jul 2025 11:47:32 +0800 Subject: [PATCH 16/22] =?UTF-8?q?send=5Fapi=5Fdoc=E4=B8=8Ereply=5Fto?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/plugins/api/send-api.md | 381 +++++++--------------------- src/chat/message_receive/message.py | 2 +- src/plugin_system/apis/send_api.py | 259 +------------------ 3 files changed, 103 insertions(+), 539 deletions(-) diff --git a/docs/plugins/api/send-api.md b/docs/plugins/api/send-api.md index 79335c61a..8b3c607fa 100644 --- a/docs/plugins/api/send-api.md +++ b/docs/plugins/api/send-api.md @@ -6,86 +6,108 @@ ```python from src.plugin_system.apis import send_api +# 或者 +from src.plugin_system import send_api ``` ## 主要功能 -### 1. 文本消息发送 +### 1. 发送文本消息 +```python +async def text_to_stream( + text: str, + stream_id: str, + typing: bool = False, + reply_to: str = "", + storage_message: bool = True, +) -> bool: +``` +发送文本消息到指定的流 -#### `text_to_group(text, group_id, platform="qq", typing=False, reply_to="", storage_message=True)` -向群聊发送文本消息 +**Args:** +- `text` (str): 要发送的文本内容 +- `stream_id` (str): 聊天流ID +- `typing` (bool): 是否显示正在输入 +- `reply_to` (str): 回复消息,格式为"发送者:消息内容" +- `storage_message` (bool): 是否存储消息到数据库 -**参数:** -- `text`:要发送的文本内容 -- `group_id`:群聊ID -- `platform`:平台,默认为"qq" -- `typing`:是否显示正在输入 -- `reply_to`:回复消息的格式,如"发送者:消息内容" -- `storage_message`:是否存储到数据库 +**Returns:** +- `bool` - 是否发送成功 -**返回:** -- `bool`:是否发送成功 +### 2. 发送表情包 +```python +async def emoji_to_stream(emoji_base64: str, stream_id: str, storage_message: bool = True) -> bool: +``` +向指定流发送表情包。 -#### `text_to_user(text, user_id, platform="qq", typing=False, reply_to="", storage_message=True)` -向用户发送私聊文本消息 +**Args:** +- `emoji_base64` (str): 表情包的base64编码 +- `stream_id` (str): 聊天流ID +- `storage_message` (bool): 是否存储消息到数据库 -**参数与返回值同上** +**Returns:** +- `bool` - 是否发送成功 -### 2. 表情包发送 +### 3. 发送图片 +```python +async def image_to_stream(image_base64: str, stream_id: str, storage_message: bool = True) -> bool: +``` +向指定流发送图片。 -#### `emoji_to_group(emoji_base64, group_id, platform="qq", storage_message=True)` -向群聊发送表情包 +**Args:** +- `image_base64` (str): 图片的base64编码 +- `stream_id` (str): 聊天流ID +- `storage_message` (bool): 是否存储消息到数据库 -**参数:** -- `emoji_base64`:表情包的base64编码 -- `group_id`:群聊ID -- `platform`:平台,默认为"qq" -- `storage_message`:是否存储到数据库 +**Returns:** +- `bool` - 是否发送成功 -#### `emoji_to_user(emoji_base64, user_id, platform="qq", storage_message=True)` -向用户发送表情包 +### 4. 发送命令 +```python +async def command_to_stream(command: Union[str, dict], stream_id: str, storage_message: bool = True, display_message: str = "") -> bool: +``` +向指定流发送命令。 -### 3. 图片发送 +**Args:** +- `command` (Union[str, dict]): 命令内容 +- `stream_id` (str): 聊天流ID +- `storage_message` (bool): 是否存储消息到数据库 +- `display_message` (str): 显示消息 -#### `image_to_group(image_base64, group_id, platform="qq", storage_message=True)` -向群聊发送图片 +**Returns:** +- `bool` - 是否发送成功 -#### `image_to_user(image_base64, user_id, platform="qq", storage_message=True)` -向用户发送图片 +### 5. 发送自定义类型消息 +```python +async def custom_to_stream( + message_type: str, + content: str, + stream_id: str, + display_message: str = "", + typing: bool = False, + reply_to: str = "", + storage_message: bool = True, + show_log: bool = True, +) -> bool: +``` +向指定流发送自定义类型消息。 -### 4. 命令发送 +**Args:** +- `message_type` (str): 消息类型,如"text"、"image"、"emoji"、"video"、"file"等 +- `content` (str): 消息内容(通常是base64编码或文本) +- `stream_id` (str): 聊天流ID +- `display_message` (str): 显示消息 +- `typing` (bool): 是否显示正在输入 +- `reply_to` (str): 回复消息,格式为"发送者:消息内容" +- `storage_message` (bool): 是否存储消息到数据库 +- `show_log` (bool): 是否显示日志 -#### `command_to_group(command, group_id, platform="qq", storage_message=True)` -向群聊发送命令 - -#### `command_to_user(command, user_id, platform="qq", storage_message=True)` -向用户发送命令 - -### 5. 自定义消息发送 - -#### `custom_to_group(message_type, content, group_id, platform="qq", display_message="", typing=False, reply_to="", storage_message=True)` -向群聊发送自定义类型消息 - -#### `custom_to_user(message_type, content, user_id, platform="qq", display_message="", typing=False, reply_to="", storage_message=True)` -向用户发送自定义类型消息 - -#### `custom_message(message_type, content, target_id, is_group=True, platform="qq", display_message="", typing=False, reply_to="", storage_message=True)` -通用的自定义消息发送 - -**参数:** -- `message_type`:消息类型,如"text"、"image"、"emoji"等 -- `content`:消息内容 -- `target_id`:目标ID(群ID或用户ID) -- `is_group`:是否为群聊 -- `platform`:平台 -- `display_message`:显示消息 -- `typing`:是否显示正在输入 -- `reply_to`:回复消息 -- `storage_message`:是否存储 +**Returns:** +- `bool` - 是否发送成功 ## 使用示例 -### 1. 基础文本发送 +### 1. 基础文本发送,并回复消息 ```python from src.plugin_system.apis import send_api @@ -93,57 +115,23 @@ from src.plugin_system.apis import send_api async def send_hello(chat_stream): """发送问候消息""" - if chat_stream.group_info: - # 群聊 - success = await send_api.text_to_group( - text="大家好!", - group_id=chat_stream.group_info.group_id, - typing=True - ) - else: - # 私聊 - success = await send_api.text_to_user( - text="你好!", - user_id=chat_stream.user_info.user_id, - typing=True - ) + success = await send_api.text_to_stream( + text="Hello, world!", + stream_id=chat_stream.stream_id, + typing=True, + reply_to="User:How are you?", + storage_message=True + ) return success ``` -### 2. 回复特定消息 - -```python -async def reply_to_message(chat_stream, reply_text, original_sender, original_message): - """回复特定消息""" - - # 构建回复格式 - reply_to = f"{original_sender}:{original_message}" - - if chat_stream.group_info: - success = await send_api.text_to_group( - text=reply_text, - group_id=chat_stream.group_info.group_id, - reply_to=reply_to - ) - else: - success = await send_api.text_to_user( - text=reply_text, - user_id=chat_stream.user_info.user_id, - reply_to=reply_to - ) - - return success -``` - -### 3. 发送表情包 +### 2. 发送表情包 ```python +from src.plugin_system.apis import emoji_api async def send_emoji_reaction(chat_stream, emotion): """根据情感发送表情包""" - - from src.plugin_system.apis import emoji_api - # 获取表情包 emoji_result = await emoji_api.get_by_emotion(emotion) if not emoji_result: @@ -152,107 +140,10 @@ async def send_emoji_reaction(chat_stream, emotion): emoji_base64, description, matched_emotion = emoji_result # 发送表情包 - if chat_stream.group_info: - success = await send_api.emoji_to_group( - emoji_base64=emoji_base64, - group_id=chat_stream.group_info.group_id - ) - else: - success = await send_api.emoji_to_user( - emoji_base64=emoji_base64, - user_id=chat_stream.user_info.user_id - ) - - return success -``` - -### 4. 在Action中发送消息 - -```python -from src.plugin_system.base import BaseAction - -class MessageAction(BaseAction): - async def execute(self, action_data, chat_stream): - message_type = action_data.get("type", "text") - content = action_data.get("content", "") - - if message_type == "text": - success = await self.send_text(chat_stream, content) - elif message_type == "emoji": - success = await self.send_emoji(chat_stream, content) - elif message_type == "image": - success = await self.send_image(chat_stream, content) - else: - success = False - - return {"success": success} - - async def send_text(self, chat_stream, text): - if chat_stream.group_info: - return await send_api.text_to_group(text, chat_stream.group_info.group_id) - else: - return await send_api.text_to_user(text, chat_stream.user_info.user_id) - - async def send_emoji(self, chat_stream, emoji_base64): - if chat_stream.group_info: - return await send_api.emoji_to_group(emoji_base64, chat_stream.group_info.group_id) - else: - return await send_api.emoji_to_user(emoji_base64, chat_stream.user_info.user_id) - - async def send_image(self, chat_stream, image_base64): - if chat_stream.group_info: - return await send_api.image_to_group(image_base64, chat_stream.group_info.group_id) - else: - return await send_api.image_to_user(image_base64, chat_stream.user_info.user_id) -``` - -### 5. 批量发送消息 - -```python -async def broadcast_message(message: str, target_groups: list): - """向多个群组广播消息""" - - results = {} - - for group_id in target_groups: - try: - success = await send_api.text_to_group( - text=message, - group_id=group_id, - typing=True - ) - results[group_id] = success - except Exception as e: - results[group_id] = False - print(f"发送到群 {group_id} 失败: {e}") - - return results -``` - -### 6. 智能消息发送 - -```python -async def smart_send(chat_stream, message_data): - """智能发送不同类型的消息""" - - message_type = message_data.get("type", "text") - content = message_data.get("content", "") - options = message_data.get("options", {}) - - # 根据聊天流类型选择发送方法 - target_id = (chat_stream.group_info.group_id if chat_stream.group_info - else chat_stream.user_info.user_id) - is_group = chat_stream.group_info is not None - - # 使用通用发送方法 - success = await send_api.custom_message( - message_type=message_type, - content=content, - target_id=target_id, - is_group=is_group, - typing=options.get("typing", False), - reply_to=options.get("reply_to", ""), - display_message=options.get("display_message", "") + success = await send_api.emoji_to_stream( + emoji_base64=emoji_base64, + stream_id=chat_stream.stream_id, + storage_message=False # 不存储到数据库 ) return success @@ -273,90 +164,6 @@ async def smart_send(chat_stream, message_data): 系统会自动查找匹配的原始消息并进行回复。 -## 高级用法 - -### 1. 消息发送队列 - -```python -import asyncio - -class MessageQueue: - def __init__(self): - self.queue = asyncio.Queue() - self.running = False - - async def add_message(self, chat_stream, message_type, content, options=None): - """添加消息到队列""" - message_item = { - "chat_stream": chat_stream, - "type": message_type, - "content": content, - "options": options or {} - } - await self.queue.put(message_item) - - async def process_queue(self): - """处理消息队列""" - self.running = True - - while self.running: - try: - message_item = await asyncio.wait_for(self.queue.get(), timeout=1.0) - - # 发送消息 - success = await smart_send( - message_item["chat_stream"], - { - "type": message_item["type"], - "content": message_item["content"], - "options": message_item["options"] - } - ) - - # 标记任务完成 - self.queue.task_done() - - # 发送间隔 - await asyncio.sleep(0.5) - - except asyncio.TimeoutError: - continue - except Exception as e: - print(f"处理消息队列出错: {e}") -``` - -### 2. 消息模板系统 - -```python -class MessageTemplate: - def __init__(self): - self.templates = { - "welcome": "欢迎 {nickname} 加入群聊!", - "goodbye": "{nickname} 离开了群聊。", - "notification": "🔔 通知:{message}", - "error": "❌ 错误:{error_message}", - "success": "✅ 成功:{message}" - } - - def format_message(self, template_name: str, **kwargs) -> str: - """格式化消息模板""" - template = self.templates.get(template_name, "{message}") - return template.format(**kwargs) - - async def send_template(self, chat_stream, template_name: str, **kwargs): - """发送模板消息""" - message = self.format_message(template_name, **kwargs) - - if chat_stream.group_info: - return await send_api.text_to_group(message, chat_stream.group_info.group_id) - else: - return await send_api.text_to_user(message, chat_stream.user_info.user_id) - -# 使用示例 -template_system = MessageTemplate() -await template_system.send_template(chat_stream, "welcome", nickname="张三") -``` - ## 注意事项 1. **异步操作**:所有发送函数都是异步的,必须使用`await` diff --git a/src/chat/message_receive/message.py b/src/chat/message_receive/message.py index 7a18dcf07..56ccd33d0 100644 --- a/src/chat/message_receive/message.py +++ b/src/chat/message_receive/message.py @@ -444,7 +444,7 @@ class MessageSending(MessageProcessBase): is_emoji: bool = False, thinking_start_time: float = 0, apply_set_reply_logic: bool = False, - reply_to: str = None, # type: ignore + reply_to: Optional[str] = None, ): # 调用父类初始化 super().__init__( diff --git a/src/plugin_system/apis/send_api.py b/src/plugin_system/apis/send_api.py index f7af02591..873b18958 100644 --- a/src/plugin_system/apis/send_api.py +++ b/src/plugin_system/apis/send_api.py @@ -49,7 +49,6 @@ async def _send_to_target( display_message: str = "", typing: bool = False, reply_to: str = "", - reply_to_platform_id: str = "", storage_message: bool = True, show_log: bool = True, ) -> bool: @@ -60,8 +59,10 @@ async def _send_to_target( content: 消息内容 stream_id: 目标流ID display_message: 显示消息 - typing: 是否显示正在输入 - reply_to: 回复消息的格式,如"发送者:消息内容" + typing: 是否模拟打字等待。 + reply_to: 回复消息,格式为"发送者:消息内容" + storage_message: 是否存储消息到数据库 + show_log: 发送是否显示日志 Returns: bool: 是否发送成功 @@ -95,8 +96,11 @@ async def _send_to_target( # 处理回复消息 anchor_message = None + reply_to_platform_id: Optional[str] = None if reply_to: anchor_message = await _find_reply_message(target_stream, reply_to) + if anchor_message and anchor_message.message_info.user_info: + reply_to_platform_id = f"{anchor_message.message_info.platform}:{anchor_message.message_info.user_info.user_id}" # 构建发送消息对象 bot_message = MessageSending( @@ -252,7 +256,6 @@ async def text_to_stream( stream_id: str, typing: bool = False, reply_to: str = "", - reply_to_platform_id: str = "", storage_message: bool = True, ) -> bool: """向指定流发送文本消息 @@ -267,7 +270,7 @@ async def text_to_stream( Returns: bool: 是否发送成功 """ - return await _send_to_target("text", text, stream_id, "", typing, reply_to, reply_to_platform_id, storage_message) + return await _send_to_target("text", text, stream_id, "", typing, reply_to, storage_message=storage_message) async def emoji_to_stream(emoji_base64: str, stream_id: str, storage_message: bool = True) -> bool: @@ -350,249 +353,3 @@ async def custom_to_stream( storage_message=storage_message, show_log=show_log, ) - - -async def text_to_group( - text: str, - group_id: str, - platform: str = "qq", - typing: bool = False, - reply_to: str = "", - storage_message: bool = True, -) -> bool: - """向群聊发送文本消息 - - Args: - text: 要发送的文本内容 - group_id: 群聊ID - platform: 平台,默认为"qq" - typing: 是否显示正在输入 - reply_to: 回复消息,格式为"发送者:消息内容" - - Returns: - bool: 是否发送成功 - """ - stream_id = get_chat_manager().get_stream_id(platform, group_id, True) - - return await _send_to_target("text", text, stream_id, "", typing, reply_to, storage_message=storage_message) - - -async def text_to_user( - text: str, - user_id: str, - platform: str = "qq", - typing: bool = False, - reply_to: str = "", - storage_message: bool = True, -) -> bool: - """向用户发送私聊文本消息 - - Args: - text: 要发送的文本内容 - user_id: 用户ID - platform: 平台,默认为"qq" - typing: 是否显示正在输入 - reply_to: 回复消息,格式为"发送者:消息内容" - - Returns: - bool: 是否发送成功 - """ - stream_id = get_chat_manager().get_stream_id(platform, user_id, False) - return await _send_to_target("text", text, stream_id, "", typing, reply_to, storage_message=storage_message) - - -async def emoji_to_group(emoji_base64: str, group_id: str, platform: str = "qq", storage_message: bool = True) -> bool: - """向群聊发送表情包 - - Args: - emoji_base64: 表情包的base64编码 - group_id: 群聊ID - platform: 平台,默认为"qq" - - Returns: - bool: 是否发送成功 - """ - stream_id = get_chat_manager().get_stream_id(platform, group_id, True) - return await _send_to_target("emoji", emoji_base64, stream_id, "", typing=False, storage_message=storage_message) - - -async def emoji_to_user(emoji_base64: str, user_id: str, platform: str = "qq", storage_message: bool = True) -> bool: - """向用户发送表情包 - - Args: - emoji_base64: 表情包的base64编码 - user_id: 用户ID - platform: 平台,默认为"qq" - - Returns: - bool: 是否发送成功 - """ - stream_id = get_chat_manager().get_stream_id(platform, user_id, False) - return await _send_to_target("emoji", emoji_base64, stream_id, "", typing=False, storage_message=storage_message) - - -async def image_to_group(image_base64: str, group_id: str, platform: str = "qq", storage_message: bool = True) -> bool: - """向群聊发送图片 - - Args: - image_base64: 图片的base64编码 - group_id: 群聊ID - platform: 平台,默认为"qq" - - Returns: - bool: 是否发送成功 - """ - stream_id = get_chat_manager().get_stream_id(platform, group_id, True) - return await _send_to_target("image", image_base64, stream_id, "", typing=False, storage_message=storage_message) - - -async def image_to_user(image_base64: str, user_id: str, platform: str = "qq", storage_message: bool = True) -> bool: - """向用户发送图片 - - Args: - image_base64: 图片的base64编码 - user_id: 用户ID - platform: 平台,默认为"qq" - - Returns: - bool: 是否发送成功 - """ - stream_id = get_chat_manager().get_stream_id(platform, user_id, False) - return await _send_to_target("image", image_base64, stream_id, "", typing=False) - - -async def command_to_group(command: str, group_id: str, platform: str = "qq", storage_message: bool = True) -> bool: - """向群聊发送命令 - - Args: - command: 命令 - group_id: 群聊ID - platform: 平台,默认为"qq" - - Returns: - bool: 是否发送成功 - """ - stream_id = get_chat_manager().get_stream_id(platform, group_id, True) - return await _send_to_target("command", command, stream_id, "", typing=False, storage_message=storage_message) - - -async def command_to_user(command: str, user_id: str, platform: str = "qq", storage_message: bool = True) -> bool: - """向用户发送命令 - - Args: - command: 命令 - user_id: 用户ID - platform: 平台,默认为"qq" - - Returns: - bool: 是否发送成功 - """ - stream_id = get_chat_manager().get_stream_id(platform, user_id, False) - return await _send_to_target("command", command, stream_id, "", typing=False, storage_message=storage_message) - - -# ============================================================================= -# 通用发送函数 - 支持任意消息类型 -# ============================================================================= - - -async def custom_to_group( - message_type: str, - content: str, - group_id: str, - platform: str = "qq", - display_message: str = "", - typing: bool = False, - reply_to: str = "", - storage_message: bool = True, -) -> bool: - """向群聊发送自定义类型消息 - - Args: - message_type: 消息类型,如"text"、"image"、"emoji"、"video"、"file"等 - content: 消息内容(通常是base64编码或文本) - group_id: 群聊ID - platform: 平台,默认为"qq" - display_message: 显示消息 - typing: 是否显示正在输入 - reply_to: 回复消息,格式为"发送者:消息内容" - - Returns: - bool: 是否发送成功 - """ - stream_id = get_chat_manager().get_stream_id(platform, group_id, True) - return await _send_to_target( - message_type, content, stream_id, display_message, typing, reply_to, storage_message=storage_message - ) - - -async def custom_to_user( - message_type: str, - content: str, - user_id: str, - platform: str = "qq", - display_message: str = "", - typing: bool = False, - reply_to: str = "", - storage_message: bool = True, -) -> bool: - """向用户发送自定义类型消息 - - Args: - message_type: 消息类型,如"text"、"image"、"emoji"、"video"、"file"等 - content: 消息内容(通常是base64编码或文本) - user_id: 用户ID - platform: 平台,默认为"qq" - display_message: 显示消息 - typing: 是否显示正在输入 - reply_to: 回复消息,格式为"发送者:消息内容" - - Returns: - bool: 是否发送成功 - """ - stream_id = get_chat_manager().get_stream_id(platform, user_id, False) - return await _send_to_target( - message_type, content, stream_id, display_message, typing, reply_to, storage_message=storage_message - ) - - -async def custom_message( - message_type: str, - content: str, - target_id: str, - is_group: bool = True, - platform: str = "qq", - display_message: str = "", - typing: bool = False, - reply_to: str = "", - storage_message: bool = True, -) -> bool: - """发送自定义消息的通用接口 - - Args: - message_type: 消息类型,如"text"、"image"、"emoji"、"video"、"file"、"audio"等 - content: 消息内容 - target_id: 目标ID(群ID或用户ID) - is_group: 是否为群聊,True为群聊,False为私聊 - platform: 平台,默认为"qq" - display_message: 显示消息 - typing: 是否显示正在输入 - reply_to: 回复消息,格式为"发送者:消息内容" - - Returns: - bool: 是否发送成功 - - 示例: - # 发送视频到群聊 - await send_api.custom_message("video", video_base64, "123456", True) - - # 发送文件到用户 - await send_api.custom_message("file", file_base64, "987654", False) - - # 发送音频到群聊并回复特定消息 - await send_api.custom_message("audio", audio_base64, "123456", True, reply_to="张三:你好") - """ - stream_id = get_chat_manager().get_stream_id(platform, target_id, is_group) - return await _send_to_target( - message_type, content, stream_id, display_message, typing, reply_to, storage_message=storage_message - ) From 81ef6f4897c4830c5b28446b9d43e15b11b2eb63 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Mon, 28 Jul 2025 11:54:43 +0800 Subject: [PATCH 17/22] =?UTF-8?q?eomji=E6=8F=92=E4=BB=B6=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E4=B8=8E=E7=AE=A1=E7=90=86=E6=8F=92=E4=BB=B6=E6=9B=B4=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/built_in/core_actions/emoji.py | 2 +- .../built_in/plugin_management/plugin.py | 129 ++++++++++-------- 2 files changed, 72 insertions(+), 59 deletions(-) diff --git a/src/plugins/built_in/core_actions/emoji.py b/src/plugins/built_in/core_actions/emoji.py index fa922dc1f..257686b18 100644 --- a/src/plugins/built_in/core_actions/emoji.py +++ b/src/plugins/built_in/core_actions/emoji.py @@ -120,7 +120,7 @@ class EmojiAction(BaseAction): logger.error(f"{self.log_prefix} 未找到'utils_small'模型配置,无法调用LLM") return False, "未找到'utils_small'模型配置" - success, chosen_emotion, _, _ = await llm_api.generate_with_model( + success, chosen_emotion = await llm_api.generate_with_model( prompt, model_config=chat_model_config, request_type="emoji" ) diff --git a/src/plugins/built_in/plugin_management/plugin.py b/src/plugins/built_in/plugin_management/plugin.py index de846dd5c..c2489a380 100644 --- a/src/plugins/built_in/plugin_management/plugin.py +++ b/src/plugins/built_in/plugin_management/plugin.py @@ -11,6 +11,7 @@ from src.plugin_system import ( component_manage_api, ComponentInfo, ComponentType, + send_api, ) @@ -27,8 +28,15 @@ class ManagementCommand(BaseCommand): or not self.message.message_info.user_info or str(self.message.message_info.user_info.user_id) not in self.get_config("plugin.permission", []) # type: ignore ): - await self.send_text("你没有权限使用插件管理命令") + await self._send_message("你没有权限使用插件管理命令") return False, "没有权限", True + if not self.message.chat_stream: + await self._send_message("无法获取聊天流信息") + return False, "无法获取聊天流信息", True + self.stream_id = self.message.chat_stream.stream_id + if not self.stream_id: + await self._send_message("无法获取聊天流信息") + return False, "无法获取聊天流信息", True command_list = self.matched_groups["manage_command"].strip().split(" ") if len(command_list) == 1: await self.show_help("all") @@ -42,7 +50,7 @@ class ManagementCommand(BaseCommand): case "help": await self.show_help("all") case _: - await self.send_text("插件管理命令不合法") + await self._send_message("插件管理命令不合法") return False, "命令不合法", True if len(command_list) == 3: if command_list[1] == "plugin": @@ -56,7 +64,7 @@ class ManagementCommand(BaseCommand): case "rescan": await self._rescan_plugin_dirs() case _: - await self.send_text("插件管理命令不合法") + await self._send_message("插件管理命令不合法") return False, "命令不合法", True elif command_list[1] == "component": if command_list[2] == "list": @@ -64,10 +72,10 @@ class ManagementCommand(BaseCommand): elif command_list[2] == "help": await self.show_help("component") else: - await self.send_text("插件管理命令不合法") + await self._send_message("插件管理命令不合法") return False, "命令不合法", True else: - await self.send_text("插件管理命令不合法") + await self._send_message("插件管理命令不合法") return False, "命令不合法", True if len(command_list) == 4: if command_list[1] == "plugin": @@ -81,28 +89,28 @@ class ManagementCommand(BaseCommand): case "add_dir": await self._add_dir(command_list[3]) case _: - await self.send_text("插件管理命令不合法") + await self._send_message("插件管理命令不合法") return False, "命令不合法", True elif command_list[1] == "component": if command_list[2] != "list": - await self.send_text("插件管理命令不合法") + await self._send_message("插件管理命令不合法") return False, "命令不合法", True if command_list[3] == "enabled": await self._list_enabled_components() elif command_list[3] == "disabled": await self._list_disabled_components() else: - await self.send_text("插件管理命令不合法") + await self._send_message("插件管理命令不合法") return False, "命令不合法", True else: - await self.send_text("插件管理命令不合法") + await self._send_message("插件管理命令不合法") return False, "命令不合法", True if len(command_list) == 5: if command_list[1] != "component": - await self.send_text("插件管理命令不合法") + await self._send_message("插件管理命令不合法") return False, "命令不合法", True if command_list[2] != "list": - await self.send_text("插件管理命令不合法") + await self._send_message("插件管理命令不合法") return False, "命令不合法", True if command_list[3] == "enabled": await self._list_enabled_components(target_type=command_list[4]) @@ -111,11 +119,11 @@ class ManagementCommand(BaseCommand): elif command_list[3] == "type": await self._list_registered_components_by_type(command_list[4]) else: - await self.send_text("插件管理命令不合法") + await self._send_message("插件管理命令不合法") return False, "命令不合法", True if len(command_list) == 6: if command_list[1] != "component": - await self.send_text("插件管理命令不合法") + await self._send_message("插件管理命令不合法") return False, "命令不合法", True if command_list[2] == "enable": if command_list[3] == "global": @@ -123,7 +131,7 @@ class ManagementCommand(BaseCommand): elif command_list[3] == "local": await self._locally_enable_component(command_list[4], command_list[5]) else: - await self.send_text("插件管理命令不合法") + await self._send_message("插件管理命令不合法") return False, "命令不合法", True elif command_list[2] == "disable": if command_list[3] == "global": @@ -131,10 +139,10 @@ class ManagementCommand(BaseCommand): elif command_list[3] == "local": await self._locally_disable_component(command_list[4], command_list[5]) else: - await self.send_text("插件管理命令不合法") + await self._send_message("插件管理命令不合法") return False, "命令不合法", True else: - await self.send_text("插件管理命令不合法") + await self._send_message("插件管理命令不合法") return False, "命令不合法", True return True, "命令执行完成", True @@ -180,51 +188,51 @@ class ManagementCommand(BaseCommand): ) case _: return - await self.send_text(help_msg) + await self._send_message(help_msg) async def _list_loaded_plugins(self): plugins = plugin_manage_api.list_loaded_plugins() - await self.send_text(f"已加载的插件: {', '.join(plugins)}") + await self._send_message(f"已加载的插件: {', '.join(plugins)}") async def _list_registered_plugins(self): plugins = plugin_manage_api.list_registered_plugins() - await self.send_text(f"已注册的插件: {', '.join(plugins)}") + await self._send_message(f"已注册的插件: {', '.join(plugins)}") async def _rescan_plugin_dirs(self): plugin_manage_api.rescan_plugin_directory() - await self.send_text("插件目录重新扫描执行中") + await self._send_message("插件目录重新扫描执行中") async def _load_plugin(self, plugin_name: str): success, count = plugin_manage_api.load_plugin(plugin_name) if success: - await self.send_text(f"插件加载成功: {plugin_name}") + await self._send_message(f"插件加载成功: {plugin_name}") else: if count == 0: - await self.send_text(f"插件{plugin_name}为禁用状态") - await self.send_text(f"插件加载失败: {plugin_name}") + await self._send_message(f"插件{plugin_name}为禁用状态") + await self._send_message(f"插件加载失败: {plugin_name}") async def _unload_plugin(self, plugin_name: str): success = await plugin_manage_api.remove_plugin(plugin_name) if success: - await self.send_text(f"插件卸载成功: {plugin_name}") + await self._send_message(f"插件卸载成功: {plugin_name}") else: - await self.send_text(f"插件卸载失败: {plugin_name}") + await self._send_message(f"插件卸载失败: {plugin_name}") async def _reload_plugin(self, plugin_name: str): success = await plugin_manage_api.reload_plugin(plugin_name) if success: - await self.send_text(f"插件重新加载成功: {plugin_name}") + await self._send_message(f"插件重新加载成功: {plugin_name}") else: - await self.send_text(f"插件重新加载失败: {plugin_name}") + await self._send_message(f"插件重新加载失败: {plugin_name}") async def _add_dir(self, dir_path: str): - await self.send_text(f"正在添加插件目录: {dir_path}") + await self._send_message(f"正在添加插件目录: {dir_path}") success = plugin_manage_api.add_plugin_directory(dir_path) await asyncio.sleep(0.5) # 防止乱序发送 if success: - await self.send_text(f"插件目录添加成功: {dir_path}") + await self._send_message(f"插件目录添加成功: {dir_path}") else: - await self.send_text(f"插件目录添加失败: {dir_path}") + await self._send_message(f"插件目录添加失败: {dir_path}") def _fetch_all_registered_components(self) -> List[ComponentInfo]: all_plugin_info = component_manage_api.get_all_plugin_info() @@ -255,29 +263,29 @@ class ManagementCommand(BaseCommand): async def _list_all_registered_components(self): components_info = self._fetch_all_registered_components() if not components_info: - await self.send_text("没有注册的组件") + await self._send_message("没有注册的组件") return all_components_str = ", ".join( f"{component.name} ({component.component_type})" for component in components_info ) - await self.send_text(f"已注册的组件: {all_components_str}") + await self._send_message(f"已注册的组件: {all_components_str}") async def _list_enabled_components(self, target_type: str = "global"): components_info = self._fetch_all_registered_components() if not components_info: - await self.send_text("没有注册的组件") + await self._send_message("没有注册的组件") return if target_type == "global": enabled_components = [component for component in components_info if component.enabled] if not enabled_components: - await self.send_text("没有满足条件的已启用全局组件") + await self._send_message("没有满足条件的已启用全局组件") return enabled_components_str = ", ".join( f"{component.name} ({component.component_type})" for component in enabled_components ) - await self.send_text(f"满足条件的已启用全局组件: {enabled_components_str}") + await self._send_message(f"满足条件的已启用全局组件: {enabled_components_str}") elif target_type == "local": locally_disabled_components = self._fetch_locally_disabled_components() enabled_components = [ @@ -286,28 +294,28 @@ class ManagementCommand(BaseCommand): if (component.name not in locally_disabled_components and component.enabled) ] if not enabled_components: - await self.send_text("本聊天没有满足条件的已启用组件") + await self._send_message("本聊天没有满足条件的已启用组件") return enabled_components_str = ", ".join( f"{component.name} ({component.component_type})" for component in enabled_components ) - await self.send_text(f"本聊天满足条件的已启用组件: {enabled_components_str}") + await self._send_message(f"本聊天满足条件的已启用组件: {enabled_components_str}") async def _list_disabled_components(self, target_type: str = "global"): components_info = self._fetch_all_registered_components() if not components_info: - await self.send_text("没有注册的组件") + await self._send_message("没有注册的组件") return if target_type == "global": disabled_components = [component for component in components_info if not component.enabled] if not disabled_components: - await self.send_text("没有满足条件的已禁用全局组件") + await self._send_message("没有满足条件的已禁用全局组件") return disabled_components_str = ", ".join( f"{component.name} ({component.component_type})" for component in disabled_components ) - await self.send_text(f"满足条件的已禁用全局组件: {disabled_components_str}") + await self._send_message(f"满足条件的已禁用全局组件: {disabled_components_str}") elif target_type == "local": locally_disabled_components = self._fetch_locally_disabled_components() disabled_components = [ @@ -316,12 +324,12 @@ class ManagementCommand(BaseCommand): if (component.name in locally_disabled_components or not component.enabled) ] if not disabled_components: - await self.send_text("本聊天没有满足条件的已禁用组件") + await self._send_message("本聊天没有满足条件的已禁用组件") return disabled_components_str = ", ".join( f"{component.name} ({component.component_type})" for component in disabled_components ) - await self.send_text(f"本聊天满足条件的已禁用组件: {disabled_components_str}") + await self._send_message(f"本聊天满足条件的已禁用组件: {disabled_components_str}") async def _list_registered_components_by_type(self, target_type: str): match target_type: @@ -332,18 +340,18 @@ class ManagementCommand(BaseCommand): case "event_handler": component_type = ComponentType.EVENT_HANDLER case _: - await self.send_text(f"未知组件类型: {target_type}") + await self._send_message(f"未知组件类型: {target_type}") return components_info = component_manage_api.get_components_info_by_type(component_type) if not components_info: - await self.send_text(f"没有注册的 {target_type} 组件") + await self._send_message(f"没有注册的 {target_type} 组件") return components_str = ", ".join( f"{name} ({component.component_type})" for name, component in components_info.items() ) - await self.send_text(f"注册的 {target_type} 组件: {components_str}") + await self._send_message(f"注册的 {target_type} 组件: {components_str}") async def _globally_enable_component(self, component_name: str, component_type: str): match component_type: @@ -354,12 +362,12 @@ class ManagementCommand(BaseCommand): case "event_handler": target_component_type = ComponentType.EVENT_HANDLER case _: - await self.send_text(f"未知组件类型: {component_type}") + await self._send_message(f"未知组件类型: {component_type}") return if component_manage_api.globally_enable_component(component_name, target_component_type): - await self.send_text(f"全局启用组件成功: {component_name}") + await self._send_message(f"全局启用组件成功: {component_name}") else: - await self.send_text(f"全局启用组件失败: {component_name}") + await self._send_message(f"全局启用组件失败: {component_name}") async def _globally_disable_component(self, component_name: str, component_type: str): match component_type: @@ -370,13 +378,13 @@ class ManagementCommand(BaseCommand): case "event_handler": target_component_type = ComponentType.EVENT_HANDLER case _: - await self.send_text(f"未知组件类型: {component_type}") + await self._send_message(f"未知组件类型: {component_type}") return success = await component_manage_api.globally_disable_component(component_name, target_component_type) if success: - await self.send_text(f"全局禁用组件成功: {component_name}") + await self._send_message(f"全局禁用组件成功: {component_name}") else: - await self.send_text(f"全局禁用组件失败: {component_name}") + await self._send_message(f"全局禁用组件失败: {component_name}") async def _locally_enable_component(self, component_name: str, component_type: str): match component_type: @@ -387,16 +395,16 @@ class ManagementCommand(BaseCommand): case "event_handler": target_component_type = ComponentType.EVENT_HANDLER case _: - await self.send_text(f"未知组件类型: {component_type}") + await self._send_message(f"未知组件类型: {component_type}") return if component_manage_api.locally_enable_component( component_name, target_component_type, self.message.chat_stream.stream_id, ): - await self.send_text(f"本地启用组件成功: {component_name}") + await self._send_message(f"本地启用组件成功: {component_name}") else: - await self.send_text(f"本地启用组件失败: {component_name}") + await self._send_message(f"本地启用组件失败: {component_name}") async def _locally_disable_component(self, component_name: str, component_type: str): match component_type: @@ -407,16 +415,19 @@ class ManagementCommand(BaseCommand): case "event_handler": target_component_type = ComponentType.EVENT_HANDLER case _: - await self.send_text(f"未知组件类型: {component_type}") + await self._send_message(f"未知组件类型: {component_type}") return if component_manage_api.locally_disable_component( component_name, target_component_type, self.message.chat_stream.stream_id, ): - await self.send_text(f"本地禁用组件成功: {component_name}") + await self._send_message(f"本地禁用组件成功: {component_name}") else: - await self.send_text(f"本地禁用组件失败: {component_name}") + await self._send_message(f"本地禁用组件失败: {component_name}") + + async def _send_message(self, message: str): + await send_api.text_to_stream(message, self.stream_id, typing=False, storage_message=False) @register_plugin @@ -430,7 +441,9 @@ class PluginManagementPlugin(BasePlugin): "plugin": { "enabled": ConfigField(bool, default=False, description="是否启用插件"), "config_version": ConfigField(type=str, default="1.1.0", description="配置文件版本"), - "permission": ConfigField(list, default=[], description="有权限使用插件管理命令的用户列表,请填写字符串形式的用户ID"), + "permission": ConfigField( + list, default=[], description="有权限使用插件管理命令的用户列表,请填写字符串形式的用户ID" + ), }, } From c0375f5dd9e2cda93319d19156d246ba19644c6e Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Mon, 28 Jul 2025 12:37:37 +0800 Subject: [PATCH 18/22] =?UTF-8?q?=E5=90=88=E5=B9=B6utils=5Fapi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/plugins/api/plugin-manage-api.md | 21 +- docs/plugins/api/utils-api.md | 435 -------------------- src/plugin_system/apis/plugin_manage_api.py | 31 +- src/plugin_system/apis/utils_api.py | 168 -------- src/plugin_system/base/base_action.py | 3 +- src/plugin_system/core/plugin_manager.py | 12 + 6 files changed, 57 insertions(+), 613 deletions(-) delete mode 100644 docs/plugins/api/utils-api.md delete mode 100644 src/plugin_system/apis/utils_api.py diff --git a/docs/plugins/api/plugin-manage-api.md b/docs/plugins/api/plugin-manage-api.md index 7057ff74a..688ea9ef8 100644 --- a/docs/plugins/api/plugin-manage-api.md +++ b/docs/plugins/api/plugin-manage-api.md @@ -36,7 +36,18 @@ def list_registered_plugins() -> List[str]: **Returns:** - `List[str]` - 已注册的插件名称列表。 -### 3. 卸载指定的插件 +### 3. 获取插件路径 +```python +def get_plugin_path(plugin_name: str) -> str: +``` +获取指定插件的路径。 + +**Args:** +- `plugin_name` (str): 要查询的插件名称。 +**Returns:** +- `str` - 插件的路径,如果插件不存在则 raise ValueError。 + +### 4. 卸载指定的插件 ```python async def remove_plugin(plugin_name: str) -> bool: ``` @@ -48,7 +59,7 @@ async def remove_plugin(plugin_name: str) -> bool: **Returns:** - `bool` - 卸载是否成功。 -### 4. 重新加载指定的插件 +### 5. 重新加载指定的插件 ```python async def reload_plugin(plugin_name: str) -> bool: ``` @@ -60,7 +71,7 @@ async def reload_plugin(plugin_name: str) -> bool: **Returns:** - `bool` - 重新加载是否成功。 -### 5. 加载指定的插件 +### 6. 加载指定的插件 ```python def load_plugin(plugin_name: str) -> Tuple[bool, int]: ``` @@ -72,7 +83,7 @@ def load_plugin(plugin_name: str) -> Tuple[bool, int]: **Returns:** - `Tuple[bool, int]` - 加载是否成功,成功或失败的个数。 -### 6. 添加插件目录 +### 7. 添加插件目录 ```python def add_plugin_directory(plugin_directory: str) -> bool: ``` @@ -84,7 +95,7 @@ def add_plugin_directory(plugin_directory: str) -> bool: **Returns:** - `bool` - 添加是否成功。 -### 7. 重新扫描插件目录 +### 8. 重新扫描插件目录 ```python def rescan_plugin_directory() -> Tuple[int, int]: ``` diff --git a/docs/plugins/api/utils-api.md b/docs/plugins/api/utils-api.md deleted file mode 100644 index bbab092e6..000000000 --- a/docs/plugins/api/utils-api.md +++ /dev/null @@ -1,435 +0,0 @@ -# 工具API - -工具API模块提供了各种辅助功能,包括文件操作、时间处理、唯一ID生成等常用工具函数。 - -## 导入方式 - -```python -from src.plugin_system.apis import utils_api -``` - -## 主要功能 - -### 1. 文件操作 - -#### `get_plugin_path(caller_frame=None) -> str` -获取调用者插件的路径 - -**参数:** -- `caller_frame`:调用者的栈帧,默认为None(自动获取) - -**返回:** -- `str`:插件目录的绝对路径 - -**示例:** -```python -plugin_path = utils_api.get_plugin_path() -print(f"插件路径: {plugin_path}") -``` - -#### `read_json_file(file_path: str, default: Any = None) -> Any` -读取JSON文件 - -**参数:** -- `file_path`:文件路径,可以是相对于插件目录的路径 -- `default`:如果文件不存在或读取失败时返回的默认值 - -**返回:** -- `Any`:JSON数据或默认值 - -**示例:** -```python -# 读取插件配置文件 -config = utils_api.read_json_file("config.json", {}) -settings = utils_api.read_json_file("data/settings.json", {"enabled": True}) -``` - -#### `write_json_file(file_path: str, data: Any, indent: int = 2) -> bool` -写入JSON文件 - -**参数:** -- `file_path`:文件路径,可以是相对于插件目录的路径 -- `data`:要写入的数据 -- `indent`:JSON缩进 - -**返回:** -- `bool`:是否写入成功 - -**示例:** -```python -data = {"name": "test", "value": 123} -success = utils_api.write_json_file("output.json", data) -``` - -### 2. 时间相关 - -#### `get_timestamp() -> int` -获取当前时间戳 - -**返回:** -- `int`:当前时间戳(秒) - -#### `format_time(timestamp: Optional[int] = None, format_str: str = "%Y-%m-%d %H:%M:%S") -> str` -格式化时间 - -**参数:** -- `timestamp`:时间戳,如果为None则使用当前时间 -- `format_str`:时间格式字符串 - -**返回:** -- `str`:格式化后的时间字符串 - -#### `parse_time(time_str: str, format_str: str = "%Y-%m-%d %H:%M:%S") -> int` -解析时间字符串为时间戳 - -**参数:** -- `time_str`:时间字符串 -- `format_str`:时间格式字符串 - -**返回:** -- `int`:时间戳(秒) - -### 3. 其他工具 - -#### `generate_unique_id() -> str` -生成唯一ID - -**返回:** -- `str`:唯一ID - -## 使用示例 - -### 1. 插件数据管理 - -```python -from src.plugin_system.apis import utils_api - -class DataPlugin(BasePlugin): - def __init__(self): - self.plugin_path = utils_api.get_plugin_path() - self.data_file = "plugin_data.json" - self.load_data() - - def load_data(self): - """加载插件数据""" - default_data = { - "users": {}, - "settings": {"enabled": True}, - "stats": {"message_count": 0} - } - self.data = utils_api.read_json_file(self.data_file, default_data) - - def save_data(self): - """保存插件数据""" - return utils_api.write_json_file(self.data_file, self.data) - - async def handle_action(self, action_data, chat_stream): - # 更新统计信息 - self.data["stats"]["message_count"] += 1 - self.data["stats"]["last_update"] = utils_api.get_timestamp() - - # 保存数据 - if self.save_data(): - return {"success": True, "message": "数据已保存"} - else: - return {"success": False, "message": "数据保存失败"} -``` - -### 2. 日志记录系统 - -```python -class PluginLogger: - def __init__(self, plugin_name: str): - self.plugin_name = plugin_name - self.log_file = f"{plugin_name}_log.json" - self.logs = utils_api.read_json_file(self.log_file, []) - - def log_event(self, event_type: str, message: str, data: dict = None): - """记录事件""" - log_entry = { - "id": utils_api.generate_unique_id(), - "timestamp": utils_api.get_timestamp(), - "formatted_time": utils_api.format_time(), - "event_type": event_type, - "message": message, - "data": data or {} - } - - self.logs.append(log_entry) - - # 保持最新的100条记录 - if len(self.logs) > 100: - self.logs = self.logs[-100:] - - # 保存到文件 - utils_api.write_json_file(self.log_file, self.logs) - - def get_logs_by_type(self, event_type: str) -> list: - """获取指定类型的日志""" - return [log for log in self.logs if log["event_type"] == event_type] - - def get_recent_logs(self, count: int = 10) -> list: - """获取最近的日志""" - return self.logs[-count:] - -# 使用示例 -logger = PluginLogger("my_plugin") -logger.log_event("user_action", "用户发送了消息", {"user_id": "123", "message": "hello"}) -``` - -### 3. 配置管理系统 - -```python -class ConfigManager: - def __init__(self, config_file: str = "plugin_config.json"): - self.config_file = config_file - self.default_config = { - "enabled": True, - "debug": False, - "max_users": 100, - "response_delay": 1.0, - "features": { - "auto_reply": True, - "logging": True - } - } - self.config = self.load_config() - - def load_config(self) -> dict: - """加载配置""" - return utils_api.read_json_file(self.config_file, self.default_config) - - def save_config(self) -> bool: - """保存配置""" - return utils_api.write_json_file(self.config_file, self.config, indent=4) - - def get(self, key: str, default=None): - """获取配置值,支持嵌套访问""" - keys = key.split('.') - value = self.config - - for k in keys: - if isinstance(value, dict) and k in value: - value = value[k] - else: - return default - - return value - - def set(self, key: str, value): - """设置配置值,支持嵌套设置""" - keys = key.split('.') - config = self.config - - for k in keys[:-1]: - if k not in config: - config[k] = {} - config = config[k] - - config[keys[-1]] = value - - def update_config(self, updates: dict): - """批量更新配置""" - def deep_update(base, updates): - for key, value in updates.items(): - if isinstance(value, dict) and key in base and isinstance(base[key], dict): - deep_update(base[key], value) - else: - base[key] = value - - deep_update(self.config, updates) - -# 使用示例 -config = ConfigManager() -print(f"调试模式: {config.get('debug', False)}") -print(f"自动回复: {config.get('features.auto_reply', True)}") - -config.set('features.new_feature', True) -config.save_config() -``` - -### 4. 缓存系统 - -```python -class PluginCache: - def __init__(self, cache_file: str = "plugin_cache.json", ttl: int = 3600): - self.cache_file = cache_file - self.ttl = ttl # 缓存过期时间(秒) - self.cache = self.load_cache() - - def load_cache(self) -> dict: - """加载缓存""" - return utils_api.read_json_file(self.cache_file, {}) - - def save_cache(self): - """保存缓存""" - return utils_api.write_json_file(self.cache_file, self.cache) - - def get(self, key: str): - """获取缓存值""" - if key not in self.cache: - return None - - item = self.cache[key] - current_time = utils_api.get_timestamp() - - # 检查是否过期 - if current_time - item["timestamp"] > self.ttl: - del self.cache[key] - return None - - return item["value"] - - def set(self, key: str, value): - """设置缓存值""" - self.cache[key] = { - "value": value, - "timestamp": utils_api.get_timestamp() - } - self.save_cache() - - def clear_expired(self): - """清理过期缓存""" - current_time = utils_api.get_timestamp() - expired_keys = [] - - for key, item in self.cache.items(): - if current_time - item["timestamp"] > self.ttl: - expired_keys.append(key) - - for key in expired_keys: - del self.cache[key] - - if expired_keys: - self.save_cache() - - return len(expired_keys) - -# 使用示例 -cache = PluginCache(ttl=1800) # 30分钟过期 -cache.set("user_data_123", {"name": "张三", "score": 100}) -user_data = cache.get("user_data_123") -``` - -### 5. 时间处理工具 - -```python -class TimeHelper: - @staticmethod - def get_time_info(): - """获取当前时间的详细信息""" - timestamp = utils_api.get_timestamp() - return { - "timestamp": timestamp, - "datetime": utils_api.format_time(timestamp), - "date": utils_api.format_time(timestamp, "%Y-%m-%d"), - "time": utils_api.format_time(timestamp, "%H:%M:%S"), - "year": utils_api.format_time(timestamp, "%Y"), - "month": utils_api.format_time(timestamp, "%m"), - "day": utils_api.format_time(timestamp, "%d"), - "weekday": utils_api.format_time(timestamp, "%A") - } - - @staticmethod - def time_ago(timestamp: int) -> str: - """计算时间差""" - current = utils_api.get_timestamp() - diff = current - timestamp - - if diff < 60: - return f"{diff}秒前" - elif diff < 3600: - return f"{diff // 60}分钟前" - elif diff < 86400: - return f"{diff // 3600}小时前" - else: - return f"{diff // 86400}天前" - - @staticmethod - def parse_duration(duration_str: str) -> int: - """解析时间段字符串,返回秒数""" - import re - - pattern = r'(\d+)([smhd])' - matches = re.findall(pattern, duration_str.lower()) - - total_seconds = 0 - for value, unit in matches: - value = int(value) - if unit == 's': - total_seconds += value - elif unit == 'm': - total_seconds += value * 60 - elif unit == 'h': - total_seconds += value * 3600 - elif unit == 'd': - total_seconds += value * 86400 - - return total_seconds - -# 使用示例 -time_info = TimeHelper.get_time_info() -print(f"当前时间: {time_info['datetime']}") - -last_seen = 1699000000 -print(f"最后见面: {TimeHelper.time_ago(last_seen)}") - -duration = TimeHelper.parse_duration("1h30m") # 1小时30分钟 = 5400秒 -``` - -## 最佳实践 - -### 1. 错误处理 -```python -def safe_file_operation(file_path: str, data: dict): - """安全的文件操作""" - try: - success = utils_api.write_json_file(file_path, data) - if not success: - logger.warning(f"文件写入失败: {file_path}") - return success - except Exception as e: - logger.error(f"文件操作出错: {e}") - return False -``` - -### 2. 路径处理 -```python -import os - -def get_data_path(filename: str) -> str: - """获取数据文件的完整路径""" - plugin_path = utils_api.get_plugin_path() - data_dir = os.path.join(plugin_path, "data") - - # 确保数据目录存在 - os.makedirs(data_dir, exist_ok=True) - - return os.path.join(data_dir, filename) -``` - -### 3. 定期清理 -```python -async def cleanup_old_files(): - """清理旧文件""" - plugin_path = utils_api.get_plugin_path() - current_time = utils_api.get_timestamp() - - for filename in os.listdir(plugin_path): - if filename.endswith('.tmp'): - file_path = os.path.join(plugin_path, filename) - file_time = os.path.getmtime(file_path) - - # 删除超过24小时的临时文件 - if current_time - file_time > 86400: - os.remove(file_path) -``` - -## 注意事项 - -1. **相对路径**:文件路径支持相对于插件目录的路径 -2. **自动创建目录**:写入文件时会自动创建必要的目录 -3. **错误处理**:所有函数都有错误处理,失败时返回默认值 -4. **编码格式**:文件读写使用UTF-8编码 -5. **时间格式**:时间戳使用秒为单位 -6. **JSON格式**:JSON文件使用可读性好的缩进格式 \ No newline at end of file diff --git a/src/plugin_system/apis/plugin_manage_api.py b/src/plugin_system/apis/plugin_manage_api.py index c792d7532..693e42b44 100644 --- a/src/plugin_system/apis/plugin_manage_api.py +++ b/src/plugin_system/apis/plugin_manage_api.py @@ -1,4 +1,6 @@ from typing import Tuple, List + + def list_loaded_plugins() -> List[str]: """ 列出所有当前加载的插件。 @@ -23,10 +25,31 @@ def list_registered_plugins() -> List[str]: return plugin_manager.list_registered_plugins() +def get_plugin_path(plugin_name: str) -> str: + """ + 获取指定插件的路径。 + + Args: + plugin_name (str): 插件名称。 + + Returns: + str: 插件目录的绝对路径。 + + Raises: + ValueError: 如果插件不存在。 + """ + from src.plugin_system.core.plugin_manager import plugin_manager + + if plugin_path := plugin_manager.get_plugin_path(plugin_name): + return plugin_path + else: + raise ValueError(f"插件 '{plugin_name}' 不存在。") + + async def remove_plugin(plugin_name: str) -> bool: """ 卸载指定的插件。 - + **此函数是异步的,确保在异步环境中调用。** Args: @@ -43,7 +66,7 @@ async def remove_plugin(plugin_name: str) -> bool: async def reload_plugin(plugin_name: str) -> bool: """ 重新加载指定的插件。 - + **此函数是异步的,确保在异步环境中调用。** Args: @@ -71,6 +94,7 @@ def load_plugin(plugin_name: str) -> Tuple[bool, int]: return plugin_manager.load_registered_plugin_classes(plugin_name) + def add_plugin_directory(plugin_directory: str) -> bool: """ 添加插件目录。 @@ -84,6 +108,7 @@ def add_plugin_directory(plugin_directory: str) -> bool: return plugin_manager.add_plugin_directory(plugin_directory) + def rescan_plugin_directory() -> Tuple[int, int]: """ 重新扫描插件目录,加载新插件。 @@ -92,4 +117,4 @@ def rescan_plugin_directory() -> Tuple[int, int]: """ from src.plugin_system.core.plugin_manager import plugin_manager - return plugin_manager.rescan_plugin_directory() \ No newline at end of file + return plugin_manager.rescan_plugin_directory() diff --git a/src/plugin_system/apis/utils_api.py b/src/plugin_system/apis/utils_api.py deleted file mode 100644 index 45996df5c..000000000 --- a/src/plugin_system/apis/utils_api.py +++ /dev/null @@ -1,168 +0,0 @@ -"""工具类API模块 - -提供了各种辅助功能 -使用方式: - from src.plugin_system.apis import utils_api - plugin_path = utils_api.get_plugin_path() - data = utils_api.read_json_file("data.json") - timestamp = utils_api.get_timestamp() -""" - -import os -import json -import time -import inspect -import datetime -import uuid -from typing import Any, Optional -from src.common.logger import get_logger - -logger = get_logger("utils_api") - - -# ============================================================================= -# 文件操作API函数 -# ============================================================================= - - -def get_plugin_path(caller_frame=None) -> str: - """获取调用者插件的路径 - - Args: - caller_frame: 调用者的栈帧,默认为None(自动获取) - - Returns: - str: 插件目录的绝对路径 - """ - try: - if caller_frame is None: - caller_frame = inspect.currentframe().f_back # type: ignore - - plugin_module_path = inspect.getfile(caller_frame) # type: ignore - plugin_dir = os.path.dirname(plugin_module_path) - return plugin_dir - except Exception as e: - logger.error(f"[UtilsAPI] 获取插件路径失败: {e}") - return "" - - -def read_json_file(file_path: str, default: Any = None) -> Any: - """读取JSON文件 - - Args: - file_path: 文件路径,可以是相对于插件目录的路径 - default: 如果文件不存在或读取失败时返回的默认值 - - Returns: - Any: JSON数据或默认值 - """ - try: - # 如果是相对路径,则相对于调用者的插件目录 - if not os.path.isabs(file_path): - caller_frame = inspect.currentframe().f_back # type: ignore - plugin_dir = get_plugin_path(caller_frame) - file_path = os.path.join(plugin_dir, file_path) - - if not os.path.exists(file_path): - logger.warning(f"[UtilsAPI] 文件不存在: {file_path}") - return default - - with open(file_path, "r", encoding="utf-8") as f: - return json.load(f) - except Exception as e: - logger.error(f"[UtilsAPI] 读取JSON文件出错: {e}") - return default - - -def write_json_file(file_path: str, data: Any, indent: int = 2) -> bool: - """写入JSON文件 - - Args: - file_path: 文件路径,可以是相对于插件目录的路径 - data: 要写入的数据 - indent: JSON缩进 - - Returns: - bool: 是否写入成功 - """ - try: - # 如果是相对路径,则相对于调用者的插件目录 - if not os.path.isabs(file_path): - caller_frame = inspect.currentframe().f_back # type: ignore - plugin_dir = get_plugin_path(caller_frame) - file_path = os.path.join(plugin_dir, file_path) - - # 确保目录存在 - os.makedirs(os.path.dirname(file_path), exist_ok=True) - - with open(file_path, "w", encoding="utf-8") as f: - json.dump(data, f, ensure_ascii=False, indent=indent) - return True - except Exception as e: - logger.error(f"[UtilsAPI] 写入JSON文件出错: {e}") - return False - - -# ============================================================================= -# 时间相关API函数 -# ============================================================================= - - -def get_timestamp() -> int: - """获取当前时间戳 - - Returns: - int: 当前时间戳(秒) - """ - return int(time.time()) - - -def format_time(timestamp: Optional[int | float] = None, format_str: str = "%Y-%m-%d %H:%M:%S") -> str: - """格式化时间 - - Args: - timestamp: 时间戳,如果为None则使用当前时间 - format_str: 时间格式字符串 - - Returns: - str: 格式化后的时间字符串 - """ - try: - if timestamp is None: - timestamp = time.time() - return datetime.datetime.fromtimestamp(timestamp).strftime(format_str) - except Exception as e: - logger.error(f"[UtilsAPI] 格式化时间失败: {e}") - return "" - - -def parse_time(time_str: str, format_str: str = "%Y-%m-%d %H:%M:%S") -> int: - """解析时间字符串为时间戳 - - Args: - time_str: 时间字符串 - format_str: 时间格式字符串 - - Returns: - int: 时间戳(秒) - """ - try: - dt = datetime.datetime.strptime(time_str, format_str) - return int(dt.timestamp()) - except Exception as e: - logger.error(f"[UtilsAPI] 解析时间失败: {e}") - return 0 - - -# ============================================================================= -# 其他工具函数 -# ============================================================================= - - -def generate_unique_id() -> str: - """生成唯一ID - - Returns: - str: 唯一ID - """ - return str(uuid.uuid4()) diff --git a/src/plugin_system/base/base_action.py b/src/plugin_system/base/base_action.py index 7acd14a48..66d723f5e 100644 --- a/src/plugin_system/base/base_action.py +++ b/src/plugin_system/base/base_action.py @@ -208,7 +208,7 @@ class BaseAction(ABC): return False, f"等待新消息失败: {str(e)}" async def send_text( - self, content: str, reply_to: str = "", reply_to_platform_id: str = "", typing: bool = False + self, content: str, reply_to: str = "", typing: bool = False ) -> bool: """发送文本消息 @@ -227,7 +227,6 @@ class BaseAction(ABC): text=content, stream_id=self.chat_id, reply_to=reply_to, - reply_to_platform_id=reply_to_platform_id, typing=typing, ) diff --git a/src/plugin_system/core/plugin_manager.py b/src/plugin_system/core/plugin_manager.py index dfafda184..ded03a18a 100644 --- a/src/plugin_system/core/plugin_manager.py +++ b/src/plugin_system/core/plugin_manager.py @@ -224,6 +224,18 @@ class PluginManager: list: 已注册的插件类名称列表。 """ return list(self.plugin_classes.keys()) + + def get_plugin_path(self, plugin_name: str) -> Optional[str]: + """ + 获取指定插件的路径。 + + Args: + plugin_name: 插件名称 + + Returns: + Optional[str]: 插件目录的绝对路径,如果插件不存在则返回None。 + """ + return self.plugin_paths.get(plugin_name) # === 私有方法 === # == 目录管理 == From 64c282d0e881d9cee3437b901ac0d38e0c150d23 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Mon, 28 Jul 2025 12:44:23 +0800 Subject: [PATCH 19/22] index update --- docs/plugins/index.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/plugins/index.md b/docs/plugins/index.md index af8fad852..2502b7a96 100644 --- a/docs/plugins/index.md +++ b/docs/plugins/index.md @@ -53,8 +53,9 @@ Command vs Action 选择指南 - [🗄️ 数据库API](api/database-api.md) - 数据库操作接口 - [⚙️ 配置API](api/config-api.md) - 配置读取和用户信息接口 -### 工具API -- [工具API](api/utils-api.md) - 文件操作、时间处理等工具函数 +### 插件和组件管理API +- [🔌 插件API](api/plugin-manage-api.md) - 插件加载和管理接口 +- [🧩 组件API](api/component-manage-api.md) - 组件注册和管理接口 ## 实验性 From 493e9b58a3706177d73435d1ed46ae4ef344095b Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Mon, 28 Jul 2025 12:48:47 +0800 Subject: [PATCH 20/22] index update --- docs/plugins/index.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/plugins/index.md b/docs/plugins/index.md index 2502b7a96..2ca4bb364 100644 --- a/docs/plugins/index.md +++ b/docs/plugins/index.md @@ -43,10 +43,10 @@ Command vs Action 选择指南 - [LLM API](api/llm-api.md) - 大语言模型交互接口,可以使用内置LLM生成内容 - [✨ 回复生成器API](api/generator-api.md) - 智能回复生成接口,可以使用内置风格化生成器 -### 表情包api +### 表情包API - [😊 表情包API](api/emoji-api.md) - 表情包选择和管理接口 -### 关系系统api +### 关系系统API - [人物信息API](api/person-api.md) - 用户信息,处理麦麦认识的人和关系的接口 ### 数据与配置API @@ -57,6 +57,8 @@ Command vs Action 选择指南 - [🔌 插件API](api/plugin-manage-api.md) - 插件加载和管理接口 - [🧩 组件API](api/component-manage-api.md) - 组件注册和管理接口 +### 日志API +- [📜 日志API](api/logging-api.md) - logger实例获取接口 ## 实验性 From 576bb34b6980b996fab0d0847a17ed9fdc76510d Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Mon, 28 Jul 2025 13:03:28 +0800 Subject: [PATCH 21/22] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dsend=5Fapi=E7=88=86?= =?UTF-8?q?=E7=82=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugin_system/apis/send_api.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/plugin_system/apis/send_api.py b/src/plugin_system/apis/send_api.py index 873b18958..46b3bddd7 100644 --- a/src/plugin_system/apis/send_api.py +++ b/src/plugin_system/apis/send_api.py @@ -49,6 +49,7 @@ async def _send_to_target( display_message: str = "", typing: bool = False, reply_to: str = "", + reply_to_platform_id: Optional[str] = None, storage_message: bool = True, show_log: bool = True, ) -> bool: @@ -61,6 +62,7 @@ async def _send_to_target( display_message: 显示消息 typing: 是否模拟打字等待。 reply_to: 回复消息,格式为"发送者:消息内容" + reply_to_platform_id: 回复消息,格式为"平台:用户ID",如果不提供则自动查找(插件开发者禁用!) storage_message: 是否存储消息到数据库 show_log: 发送是否显示日志 @@ -96,11 +98,12 @@ async def _send_to_target( # 处理回复消息 anchor_message = None - reply_to_platform_id: Optional[str] = None if reply_to: anchor_message = await _find_reply_message(target_stream, reply_to) - if anchor_message and anchor_message.message_info.user_info: - reply_to_platform_id = f"{anchor_message.message_info.platform}:{anchor_message.message_info.user_info.user_id}" + if anchor_message and anchor_message.message_info.user_info and not reply_to_platform_id: + reply_to_platform_id = ( + f"{anchor_message.message_info.platform}:{anchor_message.message_info.user_info.user_id}" + ) # 构建发送消息对象 bot_message = MessageSending( @@ -256,6 +259,7 @@ async def text_to_stream( stream_id: str, typing: bool = False, reply_to: str = "", + reply_to_platform_id: str = "", storage_message: bool = True, ) -> bool: """向指定流发送文本消息 @@ -265,12 +269,22 @@ async def text_to_stream( stream_id: 聊天流ID typing: 是否显示正在输入 reply_to: 回复消息,格式为"发送者:消息内容" + reply_to_platform_id: 回复消息,格式为"平台:用户ID",如果不提供则自动查找(插件开发者禁用!) storage_message: 是否存储消息到数据库 Returns: bool: 是否发送成功 """ - return await _send_to_target("text", text, stream_id, "", typing, reply_to, storage_message=storage_message) + return await _send_to_target( + "text", + text, + stream_id, + "", + typing, + reply_to, + reply_to_platform_id=reply_to_platform_id, + storage_message=storage_message, + ) async def emoji_to_stream(emoji_base64: str, stream_id: str, storage_message: bool = True) -> bool: From 97a10c554f4d20a55955bcce6d3e286e1ec73136 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Mon, 28 Jul 2025 13:09:33 +0800 Subject: [PATCH 22/22] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=AF=BC=E5=85=A5?= =?UTF-8?q?=E7=88=86=E7=82=B8=E5=92=8C=E6=96=87=E6=A1=A3=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/plugins/api/logging-api.md | 4 ++-- src/plugin_system/__init__.py | 2 -- src/plugin_system/apis/__init__.py | 2 -- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/plugins/api/logging-api.md b/docs/plugins/api/logging-api.md index d656f1ef3..5576bf5cd 100644 --- a/docs/plugins/api/logging-api.md +++ b/docs/plugins/api/logging-api.md @@ -5,9 +5,9 @@ Logging API模块提供了获取本体logger的功能,允许插件记录日志 ## 导入方式 ```python -from src.plugin_system.apis import logging_api +from src.plugin_system.apis import get_logger # 或者 -from src.plugin_system import logging_api +from src.plugin_system import get_logger ``` ## 主要功能 diff --git a/src/plugin_system/__init__.py b/src/plugin_system/__init__.py index eb07dbc92..cb73d8e6c 100644 --- a/src/plugin_system/__init__.py +++ b/src/plugin_system/__init__.py @@ -44,7 +44,6 @@ from .apis import ( person_api, plugin_manage_api, send_api, - utils_api, register_plugin, get_logger, ) @@ -65,7 +64,6 @@ __all__ = [ "person_api", "plugin_manage_api", "send_api", - "utils_api", "register_plugin", "get_logger", # 基础类 diff --git a/src/plugin_system/apis/__init__.py b/src/plugin_system/apis/__init__.py index 0882fbdc6..c9705c451 100644 --- a/src/plugin_system/apis/__init__.py +++ b/src/plugin_system/apis/__init__.py @@ -17,7 +17,6 @@ from src.plugin_system.apis import ( person_api, plugin_manage_api, send_api, - utils_api, ) from .logging_api import get_logger from .plugin_register_api import register_plugin @@ -35,7 +34,6 @@ __all__ = [ "person_api", "plugin_manage_api", "send_api", - "utils_api", "get_logger", "register_plugin", ]