From 94537cf57c33eb768529a908e8a07d9d4ed61c8d Mon Sep 17 00:00:00 2001 From: Windpicker-owo <3431391539@qq.com> Date: Fri, 31 Oct 2025 15:09:32 +0800 Subject: [PATCH] =?UTF-8?q?feat(proactive-thinking):=20=E5=88=A9=E7=94=A8?= =?UTF-8?q?=E8=81=9A=E7=84=A6=E8=83=BD=E9=87=8F=E5=A2=9E=E5=BC=BA=E8=83=BD?= =?UTF-8?q?=E9=87=8F=E8=AE=A1=E7=AE=97=E5=92=8C=E8=B0=83=E5=BA=A6=20-?= =?UTF-8?q?=E9=87=8D=E6=9E=84=E5=85=B3=E7=B3=BBEnergyCalculator=EF=BC=8C?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E8=81=8A=E5=A4=A9=E6=B5=81=E5=85=B4=E8=B6=A3?= =?UTF-8?q?=E8=AF=84=E5=88=86=E4=BB=A3=E6=9B=BF=E7=94=A8=E6=88=B7=E5=85=B3?= =?UTF-8?q?=E7=B3=BB=E8=AF=84=E5=88=86=20-=E6=9B=B4=E6=96=B0=E4=B8=BB?= =?UTF-8?q?=E5=8A=A8=E6=80=9D=E7=BB=B4=E8=B0=83=E5=BA=A6=E5=99=A8=EF=BC=8C?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E8=81=8A=E5=A4=A9=E6=B5=81=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=E7=84=A6=E7=82=B9=E8=83=BD=E9=87=8F=E8=BF=9B=E8=A1=8C=E9=97=B4?= =?UTF-8?q?=E9=9A=94=E8=AE=A1=E7=AE=97=20-=E9=80=9A=E8=BF=87=E6=9B=B4?= =?UTF-8?q?=E8=AF=A6=E7=BB=86=E7=9A=84=E4=BF=A1=E6=81=AF=E6=94=B9=E8=BF=9B?= =?UTF-8?q?=E6=95=B4=E4=B8=AA=E4=B8=BB=E5=8A=A8=E6=80=9D=E7=BB=B4=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E7=9A=84=E6=97=A5=E5=BF=97=E8=AE=B0=E5=BD=95=20-?= =?UTF-8?q?=E5=B0=86chat=5Fstream=E5=8F=82=E6=95=B0=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=88=B0=E6=8F=92=E4=BB=B6=E5=B7=A5=E5=85=B7=E6=9E=84=E9=80=A0?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E4=B8=AD=EF=BC=8C=E4=BB=A5=E6=9B=B4=E5=A5=BD?= =?UTF-8?q?=E5=9C=B0=E5=A4=84=E7=90=86=E4=B8=8A=E4=B8=8B=E6=96=87=20-?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E8=B0=83=E5=BA=A6=E5=92=8C=E4=BA=8B=E4=BB=B6?= =?UTF-8?q?=E5=A4=84=E7=90=86=E4=B8=AD=E7=9A=84=E9=94=99=E8=AF=AF=E5=A4=84?= =?UTF-8?q?=E7=90=86=E5=92=8C=E8=B0=83=E8=AF=95=E4=BF=A1=E6=81=AF=20?= =?UTF-8?q?=E8=BF=99=E4=BA=9B=E5=8F=98=E5=8C=96=E9=80=9A=E8=BF=87=E4=BB=A5?= =?UTF-8?q?=E4=B8=8B=E6=96=B9=E5=BC=8F=E6=94=B9=E5=96=84=E4=BA=86=E4=B8=BB?= =?UTF-8?q?=E5=8A=A8=E6=80=9D=E7=BB=B4=E7=B3=BB=E7=BB=9F=EF=BC=9A=201.?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E8=81=8A=E5=A4=A9=E6=B5=81=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=E5=AE=9E=E6=97=B6=E7=84=A6=E7=82=B9=E8=83=BD=E9=87=8F=EF=BC=8C?= =?UTF-8?q?=E8=80=8C=E4=B8=8D=E6=98=AF=E9=9D=99=E6=80=81=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=85=B3=E7=B3=BB=E8=AF=84=E5=88=86=202.=E6=A0=B9=E6=8D=AE?= =?UTF-8?q?=E5=BD=93=E5=89=8D=E5=AF=B9=E8=AF=9D=E5=8F=82=E4=B8=8E=E5=BA=A6?= =?UTF-8?q?=E6=8F=90=E4=BE=9B=E6=9B=B4=E5=87=86=E7=A1=AE=E5=92=8C=E5=8A=A8?= =?UTF-8?q?=E6=80=81=E7=9A=84=E6=97=A5=E7=A8=8B=E5=AE=89=E6=8E=92=203.?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=85=A8=E9=9D=A2=E7=9A=84=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=EF=BC=8C=E4=BB=A5=E4=BE=BF=E6=9B=B4=E5=A5=BD?= =?UTF-8?q?=E5=9C=B0=E8=BF=9B=E8=A1=8C=E8=B0=83=E8=AF=95=E5=92=8C=E7=9B=91?= =?UTF-8?q?=E6=8E=A7=204.=E9=80=9A=E8=BF=87=E5=B7=A5=E5=85=B7=E4=B8=AD?= =?UTF-8?q?=E7=9A=84chat=5Fstream=E5=8F=82=E6=95=B0=E7=A1=AE=E4=BF=9D?= =?UTF-8?q?=E6=AD=A3=E7=A1=AE=E7=9A=84=E4=B8=8A=E4=B8=8B=E6=96=87=E4=BC=A0?= =?UTF-8?q?=E6=92=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/bilibli/plugin.py | 4 +- src/chat/energy_system/energy_manager.py | 31 ++++--- .../proactive_thinking_event.py | 25 +++--- .../proactive_thinking_scheduler.py | 90 +++++++++++-------- .../web_search_tool/tools/url_parser.py | 4 +- .../web_search_tool/tools/web_search.py | 4 +- 6 files changed, 96 insertions(+), 62 deletions(-) diff --git a/plugins/bilibli/plugin.py b/plugins/bilibli/plugin.py index 8200f9272..dd2b14c80 100644 --- a/plugins/bilibli/plugin.py +++ b/plugins/bilibli/plugin.py @@ -38,8 +38,8 @@ class BilibiliTool(BaseTool): ), ] - def __init__(self, plugin_config: dict | None = None): - super().__init__(plugin_config) + def __init__(self, plugin_config: dict | None = None, chat_stream=None): + super().__init__(plugin_config, chat_stream) self.analyzer = get_bilibili_analyzer() async def execute(self, function_args: dict[str, Any]) -> dict[str, Any]: diff --git a/src/chat/energy_system/energy_manager.py b/src/chat/energy_system/energy_manager.py index 7cf84eda7..982cfccce 100644 --- a/src/chat/energy_system/energy_manager.py +++ b/src/chat/energy_system/energy_manager.py @@ -191,24 +191,35 @@ class RecencyEnergyCalculator(EnergyCalculator): class RelationshipEnergyCalculator(EnergyCalculator): - """关系能量计算器""" + """关系能量计算器 - 基于聊天流兴趣度""" async def calculate(self, context: dict[str, Any]) -> float: - """基于关系计算能量""" - user_id = context.get("user_id") - if not user_id: + """基于聊天流兴趣度计算能量""" + stream_id = context.get("stream_id") + if not stream_id: return 0.3 - # 使用统一的评分API获取关系分 + # 从数据库获取聊天流兴趣分数 try: - from src.plugin_system.apis.scoring_api import scoring_api + from src.common.database.sqlalchemy_database_api import get_db_session + from src.common.database.sqlalchemy_models import ChatStreams + from sqlalchemy import select - relationship_score = await scoring_api.get_user_relationship_score(user_id) - logger.debug(f"使用统一评分API计算关系分: {relationship_score:.3f}") - return relationship_score + async with get_db_session() as session: + stmt = select(ChatStreams).where(ChatStreams.stream_id == stream_id) + result = await session.execute(stmt) + stream = result.scalar_one_or_none() + + if stream and stream.stream_interest_score is not None: + interest_score = float(stream.stream_interest_score) + logger.debug(f"使用聊天流兴趣度计算关系能量: {interest_score:.3f}") + return interest_score + else: + logger.debug(f"聊天流 {stream_id} 无兴趣分数,使用默认值") + return 0.3 except Exception as e: - logger.warning(f"关系分计算失败,使用默认值: {e}") + logger.warning(f"获取聊天流兴趣度失败,使用默认值: {e}") return 0.3 # 默认基础分 def get_weight(self) -> float: diff --git a/src/plugins/built_in/affinity_flow_chatter/proactive_thinking_event.py b/src/plugins/built_in/affinity_flow_chatter/proactive_thinking_event.py index c5fcc6856..faad361e3 100644 --- a/src/plugins/built_in/affinity_flow_chatter/proactive_thinking_event.py +++ b/src/plugins/built_in/affinity_flow_chatter/proactive_thinking_event.py @@ -34,46 +34,51 @@ class ProactiveThinkingReplyHandler(BaseEventHandler): Returns: HandlerResult: 处理结果 """ - logger.info("[事件] ProactiveThinkingReplyHandler 开始执行") + logger.info("[主动思考事件] ProactiveThinkingReplyHandler 开始执行") + logger.info(f"[主动思考事件] 接收到的参数: {kwargs}") if not kwargs: + logger.warning("[主动思考事件] kwargs 为空,跳过处理") return HandlerResult(success=True, continue_process=True, message=None) stream_id = kwargs.get("stream_id") if not stream_id: - logger.warning("Reply事件缺少stream_id参数") + logger.warning(f"[主动思考事件] Reply事件缺少stream_id参数,kwargs={kwargs}") return HandlerResult(success=True, continue_process=True, message=None) - logger.info(f"[事件] 收到 AFTER_SEND 事件,stream_id={stream_id}") + logger.info(f"[主动思考事件] 收到 AFTER_SEND 事件,stream_id={stream_id}") try: from src.config.config import global_config # 检查是否启用reply重置 + logger.info(f"[主动思考事件] reply_reset_enabled={global_config.proactive_thinking.reply_reset_enabled}") if not global_config.proactive_thinking.reply_reset_enabled: + logger.info(f"[主动思考事件] reply_reset_enabled 为 False,跳过重置") return HandlerResult(success=True, continue_process=True, message=None) # 检查是否被暂停 was_paused = await proactive_thinking_scheduler.is_paused(stream_id) + logger.info(f"[主动思考事件] 聊天流 {stream_id} 暂停状态: {was_paused}") if was_paused: - logger.info(f"检测到reply事件,聊天流 {stream_id} 之前因抛出话题而暂停,现在恢复") + logger.info(f"[主动思考事件] 检测到reply事件,聊天流 {stream_id} 之前因抛出话题而暂停,现在恢复") # 重置定时任务(这会自动清除暂停标记并创建新任务) - logger.debug(f"[事件] 准备调用 schedule_proactive_thinking") + logger.info(f"[主动思考事件] 准备调用 schedule_proactive_thinking,stream_id={stream_id}") success = await proactive_thinking_scheduler.schedule_proactive_thinking(stream_id) - logger.debug(f"[事件] schedule_proactive_thinking 调用完成,success={success}") + logger.info(f"[主动思考事件] schedule_proactive_thinking 调用完成,success={success}") if success: if was_paused: - logger.info(f"聊天流 {stream_id} 的主动思考已恢复并重置") + logger.info(f"[主动思考事件] ✅ 聊天流 {stream_id} 的主动思考已恢复并重置") else: - logger.debug(f"聊天流 {stream_id} 的主动思考定时任务已重置") + logger.info(f"[主动思考事件] ✅ 聊天流 {stream_id} 的主动思考定时任务已重置") else: - logger.warning(f"重置聊天流 {stream_id} 的主动思考任务失败") + logger.warning(f"[主动思考事件] ❌ 重置聊天流 {stream_id} 的主动思考任务失败") except Exception as e: - logger.error(f"处理reply事件时出错: {e}", exc_info=True) + logger.error(f"[主动思考事件] ❌ 处理reply事件时出错: {e}", exc_info=True) # 总是继续处理其他handler return HandlerResult(success=True, continue_process=True, message=None) diff --git a/src/plugins/built_in/affinity_flow_chatter/proactive_thinking_scheduler.py b/src/plugins/built_in/affinity_flow_chatter/proactive_thinking_scheduler.py index 27070ec5e..2893eb4da 100644 --- a/src/plugins/built_in/affinity_flow_chatter/proactive_thinking_scheduler.py +++ b/src/plugins/built_in/affinity_flow_chatter/proactive_thinking_scheduler.py @@ -44,37 +44,37 @@ class ProactiveThinkingScheduler: from src.config.config import global_config self.config = global_config.proactive_thinking - def _calculate_interval(self, interest_score: float) -> int: - """根据兴趣分数计算触发间隔 + def _calculate_interval(self, focus_energy: float) -> int: + """根据 focus_energy 计算触发间隔 Args: - interest_score: 聊天流兴趣分数 (0.0-1.0) + focus_energy: 聊天流的 focus_energy 值 (0.0-1.0) Returns: int: 触发间隔(秒) 公式: - - 兴趣分数越高,间隔越短(更频繁思考) - - interval = base_interval * (factor - interest_score) - - 例如:interest_score=0.5 -> interval=1800*1.5=2700秒(45分钟) - - interest_score=0.8 -> interval=1800*1.2=2160秒(36分钟) - - interest_score=0.2 -> interval=1800*1.8=3240秒(54分钟) + - focus_energy 越高,间隔越短(更频繁思考) + - interval = base_interval * (factor - focus_energy) + - 例如:focus_energy=0.5 -> interval=1800*1.5=2700秒(45分钟) + - focus_energy=0.8 -> interval=1800*1.2=2160秒(36分钟) + - focus_energy=0.2 -> interval=1800*1.8=3240秒(54分钟) """ - # 如果不使用兴趣分数,直接返回基础间隔 + # 如果不使用 focus_energy,直接返回基础间隔 if not self.config.use_interest_score: return self.config.base_interval - # 确保分数在有效范围内 - interest_score = max(0.0, min(1.0, interest_score)) + # 确保值在有效范围内 + focus_energy = max(0.0, min(1.0, focus_energy)) - # 计算间隔:分数越高,系数越小,间隔越短 - factor = self.config.interest_score_factor - interest_score + # 计算间隔:focus_energy 越高,系数越小,间隔越短 + factor = self.config.interest_score_factor - focus_energy interval = int(self.config.base_interval * factor) # 限制在最小和最大间隔之间 interval = max(self.config.min_interval, min(self.config.max_interval, interval)) - logger.debug(f"兴趣分数 {interest_score:.2f} -> 触发间隔 {interval}秒 ({interval/60:.1f}分钟)") + logger.debug(f"Focus Energy {focus_energy:.3f} -> 触发间隔 {interval}秒 ({interval/60:.1f}分钟)") return interval def _check_whitelist_blacklist(self, stream_config: str) -> bool: @@ -195,28 +195,36 @@ class ProactiveThinkingScheduler: else: return current_time >= start or current_time <= end - async def _get_stream_interest_score(self, stream_id: str) -> float: - """从数据库获取聊天流的兴趣分数 + async def _get_stream_focus_energy(self, stream_id: str) -> float: + """获取聊天流的 focus_energy Args: stream_id: 聊天流ID Returns: - float: 兴趣分数,默认0.5 + float: focus_energy 值,默认0.5 """ try: - async with get_db_session() as session: - stmt = select(ChatStreams).where(ChatStreams.stream_id == stream_id) - result = await session.execute(stmt) - stream = result.scalar_one_or_none() - - if stream and stream.stream_interest_score is not None: - return float(stream.stream_interest_score) - else: - return 0.5 # 默认中等兴趣 + # 从聊天管理器获取聊天流 + from src.chat.message_receive.chat_stream import get_chat_manager + + logger.debug(f"[调度器] 获取聊天管理器") + chat_manager = get_chat_manager() + logger.debug(f"[调度器] 从聊天管理器获取聊天流 {stream_id}") + chat_stream = await chat_manager.get_stream(stream_id) + + if chat_stream: + # 计算并获取最新的 focus_energy + logger.debug(f"[调度器] 找到聊天流,开始计算 focus_energy") + focus_energy = await chat_stream.calculate_focus_energy() + logger.info(f"[调度器] 聊天流 {stream_id} 的 focus_energy: {focus_energy:.3f}") + return focus_energy + else: + logger.warning(f"[调度器] ⚠️ 未找到聊天流 {stream_id},使用默认 focus_energy=0.5") + return 0.5 except Exception as e: - logger.error(f"获取聊天流 {stream_id} 兴趣分数失败: {e}") + logger.error(f"[调度器] ❌ 获取聊天流 {stream_id} 的 focus_energy 失败: {e}", exc_info=True) return 0.5 async def schedule_proactive_thinking(self, stream_id: str) -> bool: @@ -228,22 +236,28 @@ class ProactiveThinkingScheduler: Returns: bool: 是否成功创建/重置任务 """ + logger.info(f"[调度器] 开始为聊天流 {stream_id} 创建/重置主动思考任务") try: async with self._lock: # 如果该流因抛出话题而暂停,先清除暂停标记 if stream_id in self._paused_streams: - logger.info(f"清除聊天流 {stream_id} 的暂停标记") + logger.info(f"[调度器] 清除聊天流 {stream_id} 的暂停标记") self._paused_streams.discard(stream_id) # 如果已经有任务,先移除 if stream_id in self._stream_schedules: old_schedule_id = self._stream_schedules[stream_id] + logger.info(f"[调度器] 移除聊天流 {stream_id} 的旧任务,schedule_id={old_schedule_id}") await unified_scheduler.remove_schedule(old_schedule_id) - logger.debug(f"移除聊天流 {stream_id} 的旧任务") + logger.info(f"[调度器] 旧任务已移除") - # 获取兴趣分数并计算间隔 - interest_score = await self._get_stream_interest_score(stream_id) - interval_seconds = self._calculate_interval(interest_score) + # 获取 focus_energy 并计算间隔 + logger.info(f"[调度器] 开始获取聊天流 {stream_id} 的 focus_energy") + focus_energy = await self._get_stream_focus_energy(stream_id) + logger.info(f"[调度器] 获取到 focus_energy={focus_energy:.3f}") + + interval_seconds = self._calculate_interval(focus_energy) + logger.info(f"[调度器] 计算得到触发间隔={interval_seconds}秒 ({interval_seconds/60:.1f}分钟)") # 导入回调函数(延迟导入避免循环依赖) from src.plugins.built_in.affinity_flow_chatter.proactive_thinking_executor import ( @@ -251,6 +265,7 @@ class ProactiveThinkingScheduler: ) # 创建新任务 + logger.info(f"[调度器] 开始创建新的调度任务") schedule_id = await unified_scheduler.create_schedule( callback=execute_proactive_thinking, trigger_type=TriggerType.TIME, @@ -263,19 +278,22 @@ class ProactiveThinkingScheduler: ) self._stream_schedules[stream_id] = schedule_id + logger.info(f"[调度器] 新任务已创建,schedule_id={schedule_id}") # 计算下次触发时间 next_run_time = datetime.now() + timedelta(seconds=interval_seconds) logger.info( - f"为聊天流 {stream_id} 创建主动思考任务\n" - f" - 兴趣分数: {interest_score:.2f}\n" + f"[调度器] ✅ 为聊天流 {stream_id} 创建主动思考任务成功\n" + f" - Focus Energy: {focus_energy:.3f}\n" f" - 触发间隔: {interval_seconds}秒 ({interval_seconds/60:.1f}分钟)\n" - f" - 下次触发: {next_run_time.strftime('%Y-%m-%d %H:%M:%S')}" + f" - 下次触发: {next_run_time.strftime('%Y-%m-%d %H:%M:%S')}\n" + f" - Schedule ID: {schedule_id}" ) return True except Exception as e: + logger.error(f"[调度器] ❌ 为聊天流 {stream_id} 创建主动思考任务失败: {e}", exc_info=True) logger.error(f"为聊天流 {stream_id} 创建主动思考任务失败: {e}", exc_info=True) return False @@ -308,7 +326,7 @@ class ProactiveThinkingScheduler: return success except Exception as e: - logger.error(f"暂停聊天流 {stream_id} 的主动思考任务失败: {e}", exc_info=True) + # 错误日志已在上面记录 return False async def resume_proactive_thinking(self, stream_id: str) -> bool: diff --git a/src/plugins/built_in/web_search_tool/tools/url_parser.py b/src/plugins/built_in/web_search_tool/tools/url_parser.py index 6e9bf5a03..510f9e784 100644 --- a/src/plugins/built_in/web_search_tool/tools/url_parser.py +++ b/src/plugins/built_in/web_search_tool/tools/url_parser.py @@ -34,8 +34,8 @@ class URLParserTool(BaseTool): ("urls", ToolParamType.STRING, "要理解的网站", True, None), ] - def __init__(self, plugin_config=None): - super().__init__(plugin_config) + def __init__(self, plugin_config=None, chat_stream=None): + super().__init__(plugin_config, chat_stream) self._initialize_exa_clients() def _initialize_exa_clients(self): diff --git a/src/plugins/built_in/web_search_tool/tools/web_search.py b/src/plugins/built_in/web_search_tool/tools/web_search.py index 0a2579802..dc99b3917 100644 --- a/src/plugins/built_in/web_search_tool/tools/web_search.py +++ b/src/plugins/built_in/web_search_tool/tools/web_search.py @@ -43,8 +43,8 @@ class WebSurfingTool(BaseTool): ), ] # type: ignore - def __init__(self, plugin_config=None): - super().__init__(plugin_config) + def __init__(self, plugin_config=None, chat_stream=None): + super().__init__(plugin_config, chat_stream) # 初始化搜索引擎 self.engines = { "exa": ExaSearchEngine(),