From 62d0498ea25373350ed7148d2e168182da00a845 Mon Sep 17 00:00:00 2001 From: minecraft1024a Date: Wed, 1 Oct 2025 22:00:26 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E5=8A=A0=E5=85=A5=E4=BA=86=E5=B0=9A?= =?UTF-8?q?=E6=9C=AA=E5=AE=8C=E6=88=90=E6=88=96=E8=80=85=E8=AF=B4=E6=A0=B9?= =?UTF-8?q?=E6=9C=AC=E5=B0=B1=E6=B2=A1=E5=81=9Ade=E4=B8=BB=E5=8A=A8?= =?UTF-8?q?=E6=80=9D=E8=80=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config/config.py | 2 + src/config/official_configs.py | 200 +++--------------- .../built_in/proactive_thinker/__init__.py | 0 .../built_in/proactive_thinker/_manifest.json | 25 +++ .../built_in/proactive_thinker/plugin.py | 45 ++++ .../proacive_thinker_event.py | 23 ++ .../proactive_chatter_refactor_plan.md | 199 +++++++++++++++++ 7 files changed, 324 insertions(+), 170 deletions(-) create mode 100644 src/plugins/built_in/proactive_thinker/__init__.py create mode 100644 src/plugins/built_in/proactive_thinker/_manifest.json create mode 100644 src/plugins/built_in/proactive_thinker/plugin.py create mode 100644 src/plugins/built_in/proactive_thinker/proacive_thinker_event.py create mode 100644 src/plugins/built_in/proactive_thinker/proactive_chatter_refactor_plan.md diff --git a/src/config/config.py b/src/config/config.py index c338ed543..eb98238d4 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -45,6 +45,7 @@ from src.config.official_configs import ( CommandConfig, PlanningSystemConfig, AffinityFlowConfig, + ProactiveThinkingConfig ) from .api_ada_configs import ( @@ -419,6 +420,7 @@ class Config(ValidatedConfigBase): default_factory=lambda: CrossContextConfig(), description="跨群聊上下文共享配置" ) affinity_flow: AffinityFlowConfig = Field(default_factory=lambda: AffinityFlowConfig(), description="亲和流配置") + ProactiveThinking: ProactiveThinkingConfig = Field(default_factory=lambda: AffinityFlowConfig(), description="主动思考配置") class APIAdapterConfig(ValidatedConfigBase): diff --git a/src/config/official_configs.py b/src/config/official_configs.py index 5437e04a4..7d8c0f137 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -110,7 +110,6 @@ class ChatConfig(ValidatedConfigBase): mentioned_bot_inevitable_reply: bool = Field(default=False, description="提到机器人的必然回复") at_bot_inevitable_reply: bool = Field(default=False, description="@机器人的必然回复") allow_reply_self: bool = Field(default=False, description="是否允许回复自己说的话") - talk_frequency_adjust: list[list[str]] = Field(default_factory=lambda: [], description="聊天频率调整") focus_value: float = Field(default=1.0, description="专注值") focus_mode_quiet_groups: List[str] = Field( default_factory=list, @@ -121,19 +120,6 @@ class ChatConfig(ValidatedConfigBase): timestamp_display_mode: Literal["normal", "normal_no_YMD", "relative"] = Field( default="normal_no_YMD", description="时间戳显示模式" ) - enable_proactive_thinking: bool = Field(default=False, description="启用主动思考") - proactive_thinking_interval: int = Field(default=1500, description="主动思考间隔") - The_scope_that_proactive_thinking_can_trigger: str = Field(default="all", description="主动思考可以触发的范围") - proactive_thinking_in_private: bool = Field(default=True, description="主动思考可以在私聊里面启用") - proactive_thinking_in_group: bool = Field(default=True, description="主动思考可以在群聊里面启用") - proactive_thinking_enable_in_private: List[str] = Field( - default_factory=list, description="启用主动思考的私聊范围,格式:platform:user_id,为空则不限制" - ) - proactive_thinking_enable_in_groups: List[str] = Field( - default_factory=list, description="启用主动思考的群聊范围,格式:platform:group_id,为空则不限制" - ) - delta_sigma: int = Field(default=120, description="采用正态分布随机时间间隔") - # 消息打断系统配置 interruption_enabled: bool = Field(default=True, description="是否启用消息打断系统") interruption_max_limit: int = Field(default=3, ge=0, description="每个聊天流的最大打断次数") @@ -162,161 +148,6 @@ class ChatConfig(ValidatedConfigBase): default=10, ge=1, le=100, description="最大并发处理的消息流数量" ) - def get_current_talk_frequency(self, chat_stream_id: Optional[str] = None) -> float: - """ - 根据当前时间和聊天流获取对应的 talk_frequency - - Args: - chat_stream_id: 聊天流ID,格式为 "platform:chat_id:type" - - Returns: - float: 对应的频率值 - """ - if not self.talk_frequency_adjust: - return self.talk_frequency - - # 优先检查聊天流特定的配置 - if chat_stream_id: - stream_frequency = self._get_stream_specific_frequency(chat_stream_id) - if stream_frequency is not None: - return stream_frequency - - # 检查全局时段配置(第一个元素为空字符串的配置) - global_frequency = self._get_global_frequency() - return self.talk_frequency if global_frequency is None else global_frequency - - @staticmethod - def _get_time_based_frequency(time_freq_list: list[str]) -> Optional[float]: - """ - 根据时间配置列表获取当前时段的频率 - - Args: - time_freq_list: 时间频率配置列表,格式为 ["HH:MM,frequency", ...] - - Returns: - float: 频率值,如果没有配置则返回 None - """ - from datetime import datetime - - current_time = datetime.now().strftime("%H:%M") - current_hour, current_minute = map(int, current_time.split(":")) - current_minutes = current_hour * 60 + current_minute - - # 解析时间频率配置 - time_freq_pairs = [] - for time_freq_str in time_freq_list: - try: - time_str, freq_str = time_freq_str.split(",") - hour, minute = map(int, time_str.split(":")) - frequency = float(freq_str) - minutes = hour * 60 + minute - time_freq_pairs.append((minutes, frequency)) - except (ValueError, IndexError): - continue - - if not time_freq_pairs: - return None - - # 按时间排序 - time_freq_pairs.sort(key=lambda x: x[0]) - - # 查找当前时间对应的频率 - current_frequency = None - for minutes, frequency in time_freq_pairs: - if current_minutes >= minutes: - current_frequency = frequency - else: - break - - # 如果当前时间在所有配置时间之前,使用最后一个时间段的频率(跨天逻辑) - if current_frequency is None and time_freq_pairs: - current_frequency = time_freq_pairs[-1][1] - - return current_frequency - - def _get_stream_specific_frequency(self, chat_stream_id: str): - """ - 获取特定聊天流在当前时间的频率 - - Args: - chat_stream_id: 聊天流ID(哈希值) - - Returns: - float: 频率值,如果没有配置则返回 None - """ - # 查找匹配的聊天流配置 - for config_item in self.talk_frequency_adjust: - if not config_item or len(config_item) < 2: - continue - - stream_config_str = config_item[0] # 例如 "qq:1026294844:group" - - # 解析配置字符串并生成对应的 chat_id - config_chat_id = self._parse_stream_config_to_chat_id(stream_config_str) - if config_chat_id is None: - continue - - # 比较生成的 chat_id - if config_chat_id != chat_stream_id: - continue - - # 使用通用的时间频率解析方法 - return self._get_time_based_frequency(config_item[1:]) - - return None - - @staticmethod - def _parse_stream_config_to_chat_id(stream_config_str: str) -> Optional[str]: - """ - 解析流配置字符串并生成对应的 chat_id - - Args: - stream_config_str: 格式为 "platform:id:type" 的字符串 - - Returns: - str: 生成的 chat_id,如果解析失败则返回 None - """ - try: - parts = stream_config_str.split(":") - if len(parts) != 3: - return None - - platform = parts[0] - id_str = parts[1] - stream_type = parts[2] - - # 判断是否为群聊 - is_group = stream_type == "group" - - # 使用与 ChatStream.get_stream_id 相同的逻辑生成 chat_id - import hashlib - - if is_group: - components = [platform, str(id_str)] - else: - components = [platform, str(id_str), "private"] - key = "_".join(components) - return hashlib.md5(key.encode()).hexdigest() - - except (ValueError, IndexError): - return None - - def _get_global_frequency(self) -> Optional[float]: - """ - 获取全局默认频率配置 - - Returns: - float: 频率值,如果没有配置则返回 None - """ - for config_item in self.talk_frequency_adjust: - if not config_item or len(config_item) < 2: - continue - - # 检查是否为全局默认配置(第一个元素为空字符串) - if config_item[0] == "": - return self._get_time_based_frequency(config_item[1:]) - - return None class MessageReceiveConfig(ValidatedConfigBase): @@ -723,7 +554,6 @@ class SleepSystemConfig(ValidatedConfigBase): default="我准备睡觉了,请生成一句简短自然的晚安问候。", description="用于生成睡前消息的提示" ) - class ContextGroup(ValidatedConfigBase): """上下文共享组配置""" @@ -785,3 +615,33 @@ class AffinityFlowConfig(ValidatedConfigBase): mention_bot_adjustment_threshold: float = Field(default=0.3, description="提及bot后的调整阈值") mention_bot_interest_score: float = Field(default=0.6, description="提及bot的兴趣分") base_relationship_score: float = Field(default=0.5, description="基础人物关系分") + +class ProactiveThinkingConfig(ValidatedConfigBase): + """主动思考(主动发起对话)功能配置""" + + # --- 总开关 --- + enable: bool = Field(default=False, description="是否启用主动发起对话功能") + + # --- 触发时机 --- + interval: int = Field(default=1500, description="基础触发间隔(秒),AI会围绕这个时间点主动发起对话") + interval_sigma: int = Field(default=120, description="间隔随机化标准差(秒),让触发时间更自然。设为0则为固定间隔。") + talk_frequency_adjust: list[list[str]] = Field( + default_factory=lambda: [['', '8:00,1', '12:00,1.2', '18:00,1.5', '01:00,0.6']], + description='每日活跃度调整,格式:[["", "HH:MM,factor", ...], ["stream_id", ...]]' + ) + + # --- 作用范围 --- + enable_in_private: bool = Field(default=True, description="是否允许在私聊中主动发起对话") + enable_in_group: bool = Field(default=True, description="是否允许在群聊中主动发起对话") + enabled_private_chats: List[str] = Field( + default_factory=list, + description='私聊白名单,为空则对所有私聊生效。格式: ["platform:user_id", ...]' + ) + enabled_group_chats: List[str] = Field( + default_factory=list, + description='群聊白名单,为空则对所有群聊生效。格式: ["platform:group_id", ...]' + ) + + # --- 冷启动配置 (针对私聊) --- + enable_cold_start: bool = Field(default=True, description="对于白名单中不活跃的私聊,是否允许进行一次“冷启动”问候") + cold_start_cooldown: int = Field(default=86400, description="冷启动后,该私聊的下一次主动思考需要等待的最小时间(秒)") diff --git a/src/plugins/built_in/proactive_thinker/__init__.py b/src/plugins/built_in/proactive_thinker/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/plugins/built_in/proactive_thinker/_manifest.json b/src/plugins/built_in/proactive_thinker/_manifest.json new file mode 100644 index 000000000..0ad67e293 --- /dev/null +++ b/src/plugins/built_in/proactive_thinker/_manifest.json @@ -0,0 +1,25 @@ +{ + "manifest_version": 1, + "name": "MoFox-Bot主动思考", + "version": "1.0.0", + "description": "主动思考插件", + "author": { + "name": "MoFox-Studio", + "url": "https://github.com/MoFox-Studio" + }, + "license": "GPL-v3.0-or-later", + + "host_application": { + "min_version": "0.10.0" + }, + "keywords": ["emoji", "reaction", "like", "表情", "回应", "点赞"], + "categories": ["Chat", "Integration"], + + "default_locale": "zh-CN", + "locales_path": "_locales", + + "plugin_info": { + "is_built_in": "true", + "plugin_type": "functional" + } +} diff --git a/src/plugins/built_in/proactive_thinker/plugin.py b/src/plugins/built_in/proactive_thinker/plugin.py new file mode 100644 index 000000000..0a37531ff --- /dev/null +++ b/src/plugins/built_in/proactive_thinker/plugin.py @@ -0,0 +1,45 @@ +from typing import List, Tuple, Union, Type, Optional + +from src.common.logger import get_logger +from src.config.official_configs import AffinityFlowConfig +from src.plugin_system.base.base_plugin import BasePlugin +from src.plugin_system import ( + BasePlugin, + ConfigField, + register_plugin, + plugin_manage_api, + component_manage_api, + ComponentInfo, + ComponentType, + EventHandlerInfo, + EventType, + BaseEventHandler, +) +from .proacive_thinker_event import ProactiveThinkerEventHandler + +logger = get_logger(__name__) + +@register_plugin +class ProactiveThinkerPlugin(BasePlugin): + """一个主动思考的插件,但现在还只是个空壳子""" + plugin_name: str = "proactive_thinker" + enable_plugin: bool = True + dependencies: list[str] = [] + python_dependencies: list[str] = [] + config_file_name: str = "config.toml" + config_schema: dict = { + "plugin": { + "enabled": ConfigField(bool, default=False, description="是否启用插件"), + "config_version": ConfigField(type=str, default="1.1.0", description="配置文件版本"), + }, + } + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def get_plugin_components(self) -> List[Tuple[EventHandlerInfo, Type[BaseEventHandler]]]: + """返回插件的EventHandler组件""" + components: List[Tuple[EventHandlerInfo, Type[BaseEventHandler]]] = [ + (ProactiveThinkerEventHandler.get_handler_info(), ProactiveThinkerEventHandler) + ] + return components + diff --git a/src/plugins/built_in/proactive_thinker/proacive_thinker_event.py b/src/plugins/built_in/proactive_thinker/proacive_thinker_event.py new file mode 100644 index 000000000..91b802afc --- /dev/null +++ b/src/plugins/built_in/proactive_thinker/proacive_thinker_event.py @@ -0,0 +1,23 @@ +from typing import List, Union, Type, Optional +from src.common.logger import get_logger + +logger = get_logger(__name__) +from src.plugin_system import ( + EventType, + BaseEventHandler, + HandlerResult, +) + + +class ProactiveThinkerEventHandler(BaseEventHandler): + """主动思考需要的启动时触发的事件处理器""" + + handler_name: str = "proactive_thinker_on_start" + handler_description: str = "主动思考插件的启动事件处理器" + init_subscribe: List[Union[EventType, str]] = [EventType.ON_START] + + async def execute(self, kwargs: dict | None) -> "HandlerResult": + """执行事件处理""" + logger.info("ProactiveThinkerPlugin on_start event triggered.") + # 返回 (是否执行成功, 是否需要继续处理, 可选的返回消息) + return HandlerResult(success=True, continue_process=True, message=None) diff --git a/src/plugins/built_in/proactive_thinker/proactive_chatter_refactor_plan.md b/src/plugins/built_in/proactive_thinker/proactive_chatter_refactor_plan.md new file mode 100644 index 000000000..2ad01b307 --- /dev/null +++ b/src/plugins/built_in/proactive_thinker/proactive_chatter_refactor_plan.md @@ -0,0 +1,199 @@ +# 主动聊天功能重构与设计方案 + +本文档旨在规划一个全新的、真正的“主动发起对话”功能。方案的核心是创建一个独立的、可配置的插件,并重构现有配置,使其更具模块化和可扩展性。 + +## 1. 配置文件重构 (`bot_config.toml`) + +为了提高清晰度和模块化,我们将创建一个新的配置节 `[proactive_thinking]`。 + +### 1.1. 移除旧配置 + +以下配置项将从 `[chat]` 配置节中 **移除**: + +```toml +# mmc/config/bot_config.toml + +# 从 line 132 开始移除以下所有行 +talk_frequency_adjust = [['', '8:00,1', '12:00,1.2', '18:00,1.5', '01:00,0.6'], ['qq:114514:group', '12:20,1', '16:10,2', '20:10,1', '00:10,0.3'], ['qq:1919810:private', '8:20,1', '12:10,2', '20:10,1.5', '00:10,0.2']] +# ... (所有 talk_frequency_adjust 的注释) ... + +# 主动思考功能配置(仅在focus模式下生效) + +enable_proactive_thinking = false +proactive_thinking_interval = 1500 +# ... (所有 proactive_thinking 的注释和相关配置) ... +delta_sigma = 120 +# ... (所有 delta_sigma 的注释和相关配置) ... +``` + +### 1.2. 新增 `[proactive_thinking]` 配置节 + +在 `bot_config.toml` 文件 **末尾**,添加以下全新配置节: + +```toml +# mmc/config/bot_config.toml + +[proactive_thinking] # 主动思考(主动发起对话)功能配置 +# --- 总开关 --- +enable = false # 是否启用主动发起对话功能 + +# --- 触发时机 --- +# 基础触发间隔(秒),AI会围绕这个时间点主动发起对话 +interval = 1500 # 默认25分钟 +# 间隔随机化标准差(秒),让触发时间更自然。设为0则为固定间隔。 +interval_sigma = 120 +# 每日活跃度调整,格式:[["", "HH:MM,factor", ...], ["stream_id", ...]] +# factor > 1.0 会缩短思考间隔,更活跃;factor < 1.0 会延长间隔。 +talk_frequency_adjust = [['', '8:00,1', '12:00,1.2', '18:00,1.5', '01:00,0.6']] + +# --- 作用范围 --- +enable_in_private = true # 是否允许在私聊中主动发起对话 +enable_in_group = true # 是否允许在群聊中主动发起对话 +# 私聊白名单,为空则对所有私聊生效 +# 格式: ["platform:user_id", ...] e.g., ["qq:123456"] +enabled_private_chats = [] +# 群聊白名单,为空则对所有群聊生效 +# 格式: ["platform:group_id", ...] e.g., ["qq:7891011"] +enabled_group_chats = [] + +# --- 冷启动配置 (针对私聊) --- +# 对于白名单中不活跃的私聊,是否允许进行一次“冷启动”问候 +enable_cold_start = true +# 冷启动后,该私聊的下一次主动思考需要等待的最小时间(秒) +cold_start_cooldown = 86400 # 默认24小时 +``` + +## 2. 新插件架构设计 (`proactive_initiation_chatter`) + +我们将创建一个全新的插件来实现此功能。 + +### 2.1. 文件结构 + +``` +mmc/src/plugins/built_in/proactive_initiation_chatter/ +├── __init__.py +├── _manifest.json +├── plugin.py # 插件主入口,负责启动和管理触发器 +├── trigger_manager.py # 核心触发器,内置于插件中 +├── initiation_chatter.py # Chatter实现,监听触发事件 +└── initiation_planner.py # 规划器,负责决定“说什么” +``` + +### 2.2. 核心组件设计 + +#### `plugin.py` - `ProactiveInitiationPlugin` +- **职责**: 作为插件的入口,它将在插件被加载时,读取 `[proactive_thinking]` 配置,并根据配置启动 `ProactiveTriggerManager`。 +- **启动逻辑 (参考 `maizone_refactored`)**: + +#### `trigger_manager.py` - `ProactiveTriggerManager` +- **职责**: 这是一个后台服务类,负责管理所有聊天流的触发计时器,并实现包括“冷启动”在内的所有复杂触发逻辑。 +- **核心逻辑 (参考 `SchedulerService`)**: + - 维护一个异步主循环,定期检查所有符合条件的聊天流。 + - 根据配置的间隔和活跃度调整,计算下次触发时间。 + - 在触发时,调用 `InitiationPlanner` 来决定具体内容,并通过事件管理器派发 `ProactiveInitiationEvent` 或 `ColdStartInitiationEvent`。 + +--- + +## 3. 核心交互与依赖 + +新的 `proactive_initiation_chatter` 插件将与以下核心系统模块进行交互,以确保其决策的智能性和合规性: + +- **`Config`**: `TriggerManager` 和 `Planner` 将从全局配置中读取 `[proactive_thinking]` 配置节,以获取所有行为参数。 +- **`EventManager`**: `TriggerManager` 将通过事件管理器派发 `ProactiveInitiationEvent` 和 `ColdStartInitiationEvent` 事件。`InitiationChatter` 则会监听这些事件以触发执行。 +- **`AsyncTaskManager`**: `ProactiveInitiationPlugin` 将使用此管理器来安全地在后台运行 `TriggerManager` 的主循环。 +- **`ChatManager` (from `chat_stream.py`)**: 这是实现“冷启动”的核心。`TriggerManager` 将调用 `chat_manager.get_or_create_stream()` 方法来按需“唤醒”或创建不活跃的聊天流实例及其附带的空上下文。 +- **`SleepManager`**: 在每次触发决策前,`TriggerManager` **必须**查询 `SleepManager` 以确认AI当前未处于睡眠状态。 +- **`ScheduleManager` / `MonthlyPlanManager`**: `InitiationPlanner` 的“待办任务驱动”策略会查询这些管理器,以获取可作为聊天话题的日程或计划。 +- **`MemoryManager` / `ContextManager`**: `InitiationPlanner` 的“记忆驱动”策略会查询长期记忆和短期上下文,以寻找关联性话题。 +- **`RelationshipManager`**: `InitiationPlanner` 可以查询关系分数,作为执行某些话题策略的门槛。 + +## 4. 插件清单文件 (`_manifest.json`) + +插件的清单文件将定义其元数据和依赖。 + +```json +{ + "manifest_version": 1, + "name": "ProactiveInitiationChatter", + "version": "1.0.0", + "author": "Kilo Code", + "description": "一个真正的主动发起对话插件,由内置的、可高度配置的触发器驱动。", + "dependencies": [], + "python_dependencies": [] +} +``` + +--- + +## 5. 上下文获取与“唤醒”机制详解 + +本设计区分了“热启动”(针对活跃聊天)和“冷启动”(针对非活跃聊天)两种场景,并利用 `ChatManager` 的不同方法来优雅地处理。 + +### 热启动流程 (Hot Start - 针对活跃聊天) + +这是最常见的场景。当一个聊天流近期有过对话,其实例存在于 `ChatManager` 的内存缓存中。 + +1. **获取现有上下文**: `ProactiveTriggerManager` 决定对一个活跃的 `stream_id` 发起对话时,它会调用 `chat_manager.get_stream(stream_id)`。 +2. **返回缓存实例**: `ChatManager` 会直接从内存中返回缓存的 `ChatStream` 实例。 +3. **传递丰富上下文**: 这个实例中包含了**完整的、包含近期对话历史**的 `stream_context`。 +4. **智能决策**: `TriggerManager` 将这个**充满信息**的上下文派发给 `InitiationPlanner`。`Planner` 因此可以优先使用“记忆驱动”等高级策略,生成与前文高度相关的话题,使对话显得自然、连贯。 + +### 冷启动流程 (Cold Start - “唤醒”非活跃聊天) + +针对在白名单中,但当前未加载到内存的私聊。 + +**核心方法:** `ChatManager.get_or_create_stream(platform, user_info, group_info)` + +**唤醒流程:** + +1. `ProactiveTriggerManager` 在主循环中识别到一个需要“冷启动”的私聊 `stream_id`。 +2. `TriggerManager` 构造出必要的 `UserInfo` 对象。 +3. 它调用 `get_chat_manager()`,然后执行核心的唤醒调用: + ```python + # (伪代码) + chat_stream = await chat_manager.get_or_create_stream(...) + ``` +4. 此调用会从数据库加载或全新创建一个 `ChatStream` 实例,该实例内部会自动创建一个**不包含任何历史消息的空上下文**。 +5. `TriggerManager` 将这个**空的 `StreamContext`** 连同 `ColdStartInitiationEvent` 事件一同派发出去,以触发通用的问候语。 + +此双轨制流程无需修改任何核心系统代码,仅通过合理调用现有接口即可实现,保证了方案的稳定性和兼容性。 + +--- + +这份经过强化的设计文档详细说明了配置文件的修改方案、新插件的内部架构以及与核心系统的交互模式。请您审阅。如果这份蓝图符合您的预期,我们就可以准备将此计划交付实施。 + +另外附加:我计划在 InitiationPlanner 中实现一个策略选择系统。每次被 TriggerManager 触发时,它会评估多种“主动聊天策略”的“适宜度分数”,然后选择分数最高的策略来执行。 + +以下是我初步设计的几种策略: + +ColdStartGreetingStrategy (冷启动问候策略) + +触发条件:仅在 TriggerManager 派发 ColdStartInitiationEvent 事件时触发。 +核心逻辑:生成一句通用的、友好的问候语,比如“你好呀!”或者“最近怎么样?”。这是为了“唤醒”那些很久没聊天的私聊对象。 +适宜度分数:固定高分(例如 1.0),确保在冷启动时优先执行。 +MemoryDrivenStrategy (记忆驱动策略) + +触发条件:常规触发 (ProactiveInitiationEvent),且当前聊天流的上下文不为空。 +核心逻辑: +查询 MemoryManager,获取关于当前聊天对象的长期记忆或近期摘要。 +查询 ContextManager,分析最近的几条对话,寻找可以延续的话题。 +利用 LLM 生成一个与上下文或记忆相关的话题。例如:“我们上次聊到的那个项目,后来进展如何了?” +适宜度分数计算 (借鉴AFC): +context_relevance_score (上下文相关性):上下文越丰富、越接近现在,分数越高。 +relationship_score (关系分):从 RelationshipManager 获取,关系越好,越适合深入聊记忆话题。 +final_score = (context_relevance_score * 权重) + (relationship_score * 权重) +TaskDrivenStrategy (任务/日程驱动策略) + +触发条件:常规触发。 +核心逻辑: +查询 ScheduleManager 或 MonthlyPlanManager,看看今天或最近有没有“待办事项”或“计划”。 +如果有,可以围绕这个任务发起对话。例如:“我看到日程表上说今天要去图书馆,准备好了吗?” +适宜度分数计算: +task_urgency_score (任务紧急度):任务越紧急,分数越高。 +task_relevance_score (任务相关度):如果任务与当前聊天对象有关,分数更高。 +final_score = (task_urgency_score * 权重) + (task_relevance_score * 权重) +GenericTopicStrategy (通用话题策略) + +触发条件:作为所有其他策略都无法执行时的“兜底”策略。 +核心逻辑:从一个预设的话题库(或者让 LLM 随机生成)中挑选一个通用的话题,比如“今天天气不错,适合出门散步呢”或者“最近有什么有趣的新闻吗?”。 +适宜度分数:固定低分(例如 0.1),确保它是最后的选择。 \ No newline at end of file From 91cfc3b8a27fd20df2633c952d407e2943aca8cd Mon Sep 17 00:00:00 2001 From: tt-P607 <68868379+tt-P607@users.noreply.github.com> Date: Wed, 1 Oct 2025 22:57:59 +0800 Subject: [PATCH 2/3] =?UTF-8?q?perf(napcat):=20=E4=B8=BA=20API=20=E8=B0=83?= =?UTF-8?q?=E7=94=A8=E6=B7=BB=E5=8A=A0=E7=BC=93=E5=AD=98=E5=B9=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E5=BC=82=E6=AD=A5=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为 NapCat 适配器中的 `get_group_info`, `get_member_info` 和 `get_self_info` 函数实现了一个简单的内存缓存(5分钟过期)。此举旨在减少对后端服务的重复 API 请求,从而提升性能并降低被限速的风险。 - 将 `ContextManager` 中对 `start_stream_loop` 的调用修改为 `asyncio.create_task`,使其成为一个非阻塞操作,避免在添加消息时因等待循环启动而造成延迟。 --- src/chat/message_manager/context_manager.py | 4 +- .../napcat_adapter_plugin/src/utils.py | 75 ++++++++++++++++--- 2 files changed, 65 insertions(+), 14 deletions(-) diff --git a/src/chat/message_manager/context_manager.py b/src/chat/message_manager/context_manager.py index 77d03aff0..0f744b129 100644 --- a/src/chat/message_manager/context_manager.py +++ b/src/chat/message_manager/context_manager.py @@ -59,7 +59,7 @@ class SingleStreamContextManager: self.total_messages += 1 self.last_access_time = time.time() # 启动流的循环任务(如果还未启动) - await stream_loop_manager.start_stream_loop(self.stream_id) + asyncio.create_task(stream_loop_manager.start_stream_loop(self.stream_id)) logger.debug(f"添加消息{message.processed_plain_text}到单流上下文: {self.stream_id}") return True except Exception as e: @@ -275,7 +275,7 @@ class SingleStreamContextManager: self.last_access_time = time.time() # 启动流的循环任务(如果还未启动) - await stream_loop_manager.start_stream_loop(self.stream_id) + asyncio.create_task(stream_loop_manager.start_stream_loop(self.stream_id)) logger.debug(f"添加消息到单流上下文(异步): {self.stream_id} (兴趣度待计算)") return True diff --git a/src/plugins/built_in/napcat_adapter_plugin/src/utils.py b/src/plugins/built_in/napcat_adapter_plugin/src/utils.py index e36fc93fd..1a8c3a95d 100644 --- a/src/plugins/built_in/napcat_adapter_plugin/src/utils.py +++ b/src/plugins/built_in/napcat_adapter_plugin/src/utils.py @@ -6,6 +6,33 @@ import urllib3 import ssl import io +import asyncio +import time +from asyncio import Lock + +_internal_cache = {} +_cache_lock = Lock() +CACHE_TIMEOUT = 300 # 缓存5分钟 + + +async def get_from_cache(key: str): + async with _cache_lock: + data = _internal_cache.get(key) + if not data: + return None + + result, timestamp = data + if time.time() - timestamp < CACHE_TIMEOUT: + logger.debug(f"从缓存命中: {key}") + return result + return None + + +async def set_to_cache(key: str, value: any): + async with _cache_lock: + _internal_cache[key] = (value, time.time()) + + from .database import BanUser, db_manager from src.common.logger import get_logger @@ -27,11 +54,16 @@ class SSLAdapter(urllib3.PoolManager): async def get_group_info(websocket: Server.ServerConnection, group_id: int) -> dict | None: """ - 获取群相关信息 + 获取群相关信息 (带缓存) 返回值需要处理可能为空的情况 """ - logger.debug("获取群聊信息中") + cache_key = f"group_info:{group_id}" + cached_data = await get_from_cache(cache_key) + if cached_data: + return cached_data + + logger.debug(f"获取群聊信息中 (无缓存): {group_id}") request_uuid = str(uuid.uuid4()) payload = json.dumps({"action": "get_group_info", "params": {"group_id": group_id}, "echo": request_uuid}) try: @@ -43,8 +75,11 @@ async def get_group_info(websocket: Server.ServerConnection, group_id: int) -> d except Exception as e: logger.error(f"获取群信息失败: {e}") return None - logger.debug(socket_response) - return socket_response.get("data") + + data = socket_response.get("data") + if data: + await set_to_cache(cache_key, data) + return data async def get_group_detail_info(websocket: Server.ServerConnection, group_id: int) -> dict | None: @@ -71,11 +106,16 @@ async def get_group_detail_info(websocket: Server.ServerConnection, group_id: in async def get_member_info(websocket: Server.ServerConnection, group_id: int, user_id: int) -> dict | None: """ - 获取群成员信息 + 获取群成员信息 (带缓存) 返回值需要处理可能为空的情况 """ - logger.debug("获取群成员信息中") + cache_key = f"member_info:{group_id}:{user_id}" + cached_data = await get_from_cache(cache_key) + if cached_data: + return cached_data + + logger.debug(f"获取群成员信息中 (无缓存): group={group_id}, user={user_id}") request_uuid = str(uuid.uuid4()) payload = json.dumps( { @@ -93,8 +133,11 @@ async def get_member_info(websocket: Server.ServerConnection, group_id: int, use except Exception as e: logger.error(f"获取成员信息失败: {e}") return None - logger.debug(socket_response) - return socket_response.get("data") + + data = socket_response.get("data") + if data: + await set_to_cache(cache_key, data) + return data async def get_image_base64(url: str) -> str: @@ -137,13 +180,18 @@ def convert_image_to_gif(image_base64: str) -> str: async def get_self_info(websocket: Server.ServerConnection) -> dict | None: """ - 获取自身信息 + 获取自身信息 (带缓存) Parameters: websocket: WebSocket连接对象 Returns: data: dict: 返回的自身信息 """ - logger.debug("获取自身信息中") + cache_key = "self_info" + cached_data = await get_from_cache(cache_key) + if cached_data: + return cached_data + + logger.debug("获取自身信息中 (无缓存)") request_uuid = str(uuid.uuid4()) payload = json.dumps({"action": "get_login_info", "params": {}, "echo": request_uuid}) try: @@ -155,8 +203,11 @@ async def get_self_info(websocket: Server.ServerConnection) -> dict | None: except Exception as e: logger.error(f"获取自身信息失败: {e}") return None - logger.debug(response) - return response.get("data") + + data = response.get("data") + if data: + await set_to_cache(cache_key, data) + return data def get_image_format(raw_data: str) -> str: From ba5e0b0eafafd963d23bd35d1deeee3b1fa5fba0 Mon Sep 17 00:00:00 2001 From: tt-P607 <68868379+tt-P607@users.noreply.github.com> Date: Wed, 1 Oct 2025 23:49:44 +0800 Subject: [PATCH 3/3] =?UTF-8?q?refactor(plugin=5Fsystem):=20=E9=87=8D?= =?UTF-8?q?=E6=9E=84=20send=5Fcommand=20=E4=BB=A5=E5=AF=B9=E6=8E=A5?= =?UTF-8?q?=E9=80=82=E9=85=8D=E5=99=A8=E4=B8=93=E7=94=A8=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BaseAction 中的 send_command 方法已重构,从使用通用的 `command_to_stream` API 切换到新的 `adapter_command_to_stream` API。 这一变更带来了以下改进: - **接口统一**: 所有与平台适配器直接交互的命令现在都通过专用的接口,使得逻辑更清晰。 - **参数结构化**: 调用参数从旧的 `{"name": ..., "args": ...}` 格式更新为更明确的 `action`, `params` 和 `platform`。 - **健壮的响应处理**: 现在会解析 API 返回的结构化 JSON 响应(包含 status 和 message),以实现更精确的成功/失败判断和错误日志记录。 BREAKING CHANGE: `send_command` 调用的 `command_name` 现在需要与目标平台适配器定义的 `action` 名称完全匹配。例如,在 `social_toolkit_plugin` 中,`set_emoji_like` 已更新为 `set_msg_emoji_like`。所有使用此方法的插件可能需要更新其命令名称。 --- src/plugin_system/base/base_action.py | 20 ++++++++++--------- .../built_in/social_toolkit_plugin/plugin.py | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/plugin_system/base/base_action.py b/src/plugin_system/base/base_action.py index 26b79d4df..9fd7c0002 100644 --- a/src/plugin_system/base/base_action.py +++ b/src/plugin_system/base/base_action.py @@ -382,19 +382,21 @@ class BaseAction(ABC): # 构造命令数据 command_data = {"name": command_name, "args": args or {}} - success = await send_api.command_to_stream( - command=command_data, + response = await send_api.adapter_command_to_stream( + action=command_name, + params=args or {}, stream_id=self.chat_id, - storage_message=storage_message, - display_message=display_message, + platform=self.platform ) - if success: - logger.info(f"{self.log_prefix} 成功发送命令: {command_name}") + # 根据响应判断成功与否 + if response and response.get("status") == "ok": + logger.info(f"{self.log_prefix} 成功执行适配器命令: {command_name}, 响应: {response.get('data')}") + return True else: - logger.error(f"{self.log_prefix} 发送命令失败: {command_name}") - - return success + error_message = response.get('message', '未知错误') + logger.error(f"{self.log_prefix} 执行适配器命令失败: {command_name}, 错误: {error_message}") + return False except Exception as e: logger.error(f"{self.log_prefix} 发送命令时出错: {e}") diff --git a/src/plugins/built_in/social_toolkit_plugin/plugin.py b/src/plugins/built_in/social_toolkit_plugin/plugin.py index 98430bbf8..d31d34dee 100644 --- a/src/plugins/built_in/social_toolkit_plugin/plugin.py +++ b/src/plugins/built_in/social_toolkit_plugin/plugin.py @@ -316,7 +316,7 @@ class SetEmojiLikeAction(BaseAction): try: # 使用适配器API发送贴表情命令 success = await self.send_command( - command_name="set_emoji_like", + command_name="set_msg_emoji_like", args={"message_id": message_id, "emoji_id": emoji_id, "set": set_like}, storage_message=False, )