diff --git a/src/chat/chat_loop/proactive/event_scheduler.py b/src/chat/chat_loop/proactive/event_scheduler.py new file mode 100644 index 000000000..767360dd8 --- /dev/null +++ b/src/chat/chat_loop/proactive/event_scheduler.py @@ -0,0 +1,239 @@ +""" +事件驱动的智能调度器 +基于asyncio的精确定时事件调度系统,替代轮询机制 +""" + +import asyncio +import time +import traceback +from datetime import datetime, timedelta +from typing import Dict, Callable, Any, Optional +from dataclasses import dataclass +from src.common.logger import get_logger + +logger = get_logger("event_scheduler") + + +@dataclass +class ScheduledEvent: + """调度事件数据类""" + event_id: str + trigger_time: datetime + callback: Callable + metadata: Dict[str, Any] + task: Optional[asyncio.Task] = None + + +class EventDrivenScheduler: + """事件驱动的调度器""" + + def __init__(self): + self.scheduled_events: Dict[str, ScheduledEvent] = {} + self._shutdown = False + + async def schedule_event( + self, + event_id: str, + trigger_time: datetime, + callback: Callable, + metadata: Dict[str, Any] = None + ) -> bool: + """ + 调度一个事件在指定时间触发 + + Args: + event_id: 事件唯一标识 + trigger_time: 触发时间 + callback: 回调函数 + metadata: 事件元数据 + + Returns: + bool: 调度成功返回True + """ + try: + if metadata is None: + metadata = {} + + # 如果事件已存在,先取消 + if event_id in self.scheduled_events: + await self.cancel_event(event_id) + + # 计算延迟时间 + now = datetime.now() + delay = (trigger_time - now).total_seconds() + + if delay <= 0: + logger.warning(f"事件 {event_id} 的触发时间已过,立即执行") + # 立即执行 + asyncio.create_task(self._execute_callback(event_id, callback, metadata)) + return True + + # 创建调度事件 + scheduled_event = ScheduledEvent( + event_id=event_id, + trigger_time=trigger_time, + callback=callback, + metadata=metadata + ) + + # 创建异步任务 + scheduled_event.task = asyncio.create_task( + self._wait_and_execute(scheduled_event) + ) + + self.scheduled_events[event_id] = scheduled_event + logger.info(f"调度事件 {event_id} 将在 {trigger_time} 触发 (延迟 {delay:.1f} 秒)") + return True + + except Exception as e: + logger.error(f"调度事件失败: {e}") + return False + + async def _wait_and_execute(self, event: ScheduledEvent): + """等待并执行事件""" + try: + now = datetime.now() + delay = (event.trigger_time - now).total_seconds() + + if delay > 0: + await asyncio.sleep(delay) + + # 检查是否被取消 + if self._shutdown or event.event_id not in self.scheduled_events: + return + + # 执行回调 + await self._execute_callback(event.event_id, event.callback, event.metadata) + + except asyncio.CancelledError: + logger.info(f"事件 {event.event_id} 被取消") + except Exception as e: + logger.error(f"执行事件 {event.event_id} 时出错: {e}") + finally: + # 清理已完成的事件 + if event.event_id in self.scheduled_events: + del self.scheduled_events[event.event_id] + + async def _execute_callback(self, event_id: str, callback: Callable, metadata: Dict[str, Any]): + """执行回调函数""" + try: + logger.info(f"执行调度事件: {event_id}") + + # 根据回调函数签名调用 + if asyncio.iscoroutinefunction(callback): + await callback(metadata) + else: + callback(metadata) + + except Exception as e: + logger.error(f"执行回调函数失败: {e}") + logger.error(traceback.format_exc()) + + async def cancel_event(self, event_id: str) -> bool: + """ + 取消一个调度事件 + + Args: + event_id: 事件ID + + Returns: + bool: 取消成功返回True + """ + try: + if event_id in self.scheduled_events: + event = self.scheduled_events[event_id] + if event.task and not event.task.done(): + event.task.cancel() + del self.scheduled_events[event_id] + logger.info(f"取消调度事件: {event_id}") + return True + return False + except Exception as e: + logger.error(f"取消事件失败: {e}") + return False + + async def shutdown(self): + """关闭调度器,取消所有事件""" + self._shutdown = True + for event_id in list(self.scheduled_events.keys()): + await self.cancel_event(event_id) + logger.info("事件调度器已关闭") + + def get_scheduled_events(self) -> Dict[str, ScheduledEvent]: + """获取所有调度事件""" + return self.scheduled_events.copy() + + def get_event_count(self) -> int: + """获取调度事件数量""" + return len(self.scheduled_events) + + +# 全局事件调度器实例 +event_scheduler = EventDrivenScheduler() + + +# 便捷函数 +async def schedule_reminder( + reminder_id: str, + reminder_time: datetime, + chat_id: str, + reminder_content: str, + callback: Callable +): + """ + 调度提醒事件的便捷函数 + + Args: + reminder_id: 提醒唯一标识 + reminder_time: 提醒时间 + chat_id: 聊天ID + reminder_content: 提醒内容 + callback: 回调函数 + """ + metadata = { + "type": "reminder", + "chat_id": chat_id, + "content": reminder_content, + "created_at": datetime.now().isoformat() + } + + return await event_scheduler.schedule_event( + event_id=reminder_id, + trigger_time=reminder_time, + callback=callback, + metadata=metadata + ) + + +async def _execute_reminder_callback(subheartflow_id: str, reminder_text: str): + """执行提醒回调函数""" + try: + # 获取对应的subheartflow实例 + from src.chat.heart_flow.heartflow import heartflow + + subflow = await heartflow.get_or_create_subheartflow(subheartflow_id) + if not subflow: + logger.error(f"无法获取subheartflow实例: {subheartflow_id}") + return + + # 创建主动思考事件,触发完整的思考流程 + from src.chat.chat_loop.proactive.events import ProactiveTriggerEvent + + event = ProactiveTriggerEvent( + source="reminder_system", + reason=f"定时提醒:{reminder_text}", + metadata={ + "reminder_text": reminder_text, + "trigger_time": datetime.now().isoformat() + } + ) + + # 通过subflow的HeartFChatting实例触发主动思考 + await subflow.heart_fc_instance.proactive_thinker.think(event) + + logger.info(f"已触发提醒的主动思考,内容: {reminder_text}") + + except Exception as e: + logger.error(f"执行提醒回调时发生错误: {e}") + import traceback + traceback.print_exc() \ No newline at end of file diff --git a/src/chat/chat_loop/proactive/proactive_thinker.py b/src/chat/chat_loop/proactive/proactive_thinker.py index 3522c0dd4..9303607b3 100644 --- a/src/chat/chat_loop/proactive/proactive_thinker.py +++ b/src/chat/chat_loop/proactive/proactive_thinker.py @@ -160,12 +160,22 @@ class ProactiveThinker: try: web_search_tool = tool_api.get_tool_instance("web_search") if web_search_tool: - tool_args = {"query": topic, "max_results": 10} - # 调用工具,并传递参数 - search_result_dict = await web_search_tool.execute(**tool_args) + # 检查工具的execute方法签名,使用正确的参数名 + try: + search_result_dict = await web_search_tool.execute(search_query=topic, max_results=10) + except TypeError: + # 如果search_query不工作,尝试其他可能的参数名 + try: + search_result_dict = await web_search_tool.execute(keyword=topic, max_results=10) + except TypeError: + # 跳过网络搜索,避免影响主动思考 + logger.warning(f"{self.context.log_prefix} 网络搜索工具参数不匹配,跳过搜索") + news_block = "跳过网络搜索。" + search_result_dict = None + if search_result_dict and not search_result_dict.get("error"): news_block = search_result_dict.get("content", "未能提取有效资讯。") - else: + elif search_result_dict: logger.warning(f"{self.context.log_prefix} 网络搜索返回错误: {search_result_dict.get('error')}") else: logger.warning(f"{self.context.log_prefix} 未找到 web_search 工具实例。") @@ -180,7 +190,49 @@ class ProactiveThinker: ) chat_context_block, _ = build_readable_messages_with_id(messages=message_list) - # 4. 构建最终的生成提示词 + # 4. 使用决策模型进行二次确认(节省珍贵的回复模型调用) + from src.llm_models.utils_model import LLMRequest + from src.config.config import model_config + + bot_name = global_config.bot.nickname + + # 构建二次确认提示词 + confirmation_prompt = f"""# 主动回复二次确认 + +## 基本信息 +你的名字是{bot_name},准备主动发起关于"{topic}"的话题。 + +## 最近的聊天内容 +{chat_context_block} + +## 合理判断标准 +请检查以下条件,如果**大部分条件都合理**就可以回复: + +1. **时间合理性**:当前时间是否在深夜(凌晨2点-6点)这种不适合主动聊天的时段? +2. **内容价值**:这个话题"{topic}"是否有意义,不是完全无关紧要的内容? +3. **重复避免**:你准备说的话题是否与最近2条消息明显重复? +4. **自然性**:在当前上下文中主动提起这个话题是否自然合理? + +## 输出要求 +如果判断应该跳过(比如深夜时段、完全无意义话题、明显重复内容),输出:SKIP_PROACTIVE_REPLY +其他情况都应该输出:PROCEED_TO_REPLY + +请严格按照上述格式输出,不要添加任何解释。""" + + # 使用决策模型进行二次确认 + planner_llm = LLMRequest( + model_set=model_config.model_task_config.planner, + request_type="planner" + ) + + confirmation_result, _ = await planner_llm.generate_response_async(prompt=confirmation_prompt) + + # 检查二次确认结果 + if not confirmation_result or "SKIP_PROACTIVE_REPLY" in confirmation_result: + logger.info(f"{self.context.log_prefix} 决策模型二次确认决定跳过主动回复") + return + + # 5. 只有通过二次确认才调用珍贵的回复模型 bot_name = global_config.bot.nickname personality = global_config.personality identity_block = ( @@ -200,29 +252,30 @@ class ProactiveThinker: ## 你今天的日程安排 {schedule_block} -## 关于你准备讨论的话题“{topic}”的最新信息 +## 关于你准备讨论的话题"{topic}"的最新信息 {news_block} ## 最近的聊天内容 {chat_context_block} ## 任务 -你之前决定要发起一个关于“{topic}”的对话。现在,请结合以上所有信息,自然地开启这个话题。 +你现在想要主动说些什么。话题是"{topic}",但这只是一个参考方向。 + +根据最近的聊天内容,你可以: +- 如果是想关心朋友,就自然地询问他们的情况 +- 如果想起了之前的话题,就问问后来怎么样了 +- 如果有什么想分享的想法,就自然地开启话题 +- 如果只是想闲聊,就随意地说些什么 ## 要求 -- 你的发言要听起来像是自发的,而不是在念报告。 -- 巧妙地将日程安排或最新信息融入到你的开场白中。 -- 风格要符合你的角色设定。 -- 直接输出你想要说的内容,不要包含其他额外信息。 +- 像真正的朋友一样,自然地表达关心或好奇 +- 不要过于正式,要口语化和亲切 +- 结合你的角色设定,保持温暖的风格 +- 直接输出你想说的话,不要解释为什么要说 -你的回复应该: -1. 可以分享你的看法、提出相关问题,或者开个合适的玩笑。 -2. 目的是让对话更有趣、更深入。 -3. 不要浮夸,不要夸张修辞,不要输出多余内容(包括前后缀,冒号和引号,括号(),表情包,at或 @等 )。 -最终请输出一条简短、完整且口语化的回复。 +请输出一条简短、自然的主动发言。 """ - # 5. 调用生成器API并发送 response_text = await generator_api.generate_response_custom( chat_stream=self.context.chat_stream, prompt=final_prompt, diff --git a/src/chat/chat_loop/proactive/smart_reminder_analyzer.py b/src/chat/chat_loop/proactive/smart_reminder_analyzer.py new file mode 100644 index 000000000..bfd7c3e61 --- /dev/null +++ b/src/chat/chat_loop/proactive/smart_reminder_analyzer.py @@ -0,0 +1,260 @@ +""" +智能提醒分析器 + +使用LLM分析用户消息,识别提醒请求并提取时间和内容信息 +""" + +import re +import json +from datetime import datetime, timedelta +from typing import Optional + +from src.common.logger import get_logger +from src.llm_models.utils_model import LLMRequest +from src.config.config import model_config + +logger = get_logger("smart_reminder") + + +class ReminderEvent: + """提醒事件数据类""" + def __init__(self, user_id: str, reminder_time: datetime, content: str, confidence: float): + self.user_id = user_id + self.reminder_time = reminder_time + self.content = content + self.confidence = confidence + + def __repr__(self): + return f"ReminderEvent(user_id={self.user_id}, time={self.reminder_time}, content={self.content}, confidence={self.confidence})" + + def to_dict(self): + return { + 'user_id': self.user_id, + 'reminder_time': self.reminder_time.isoformat(), + 'content': self.content, + 'confidence': self.confidence + } + + +class SmartReminderAnalyzer: + """智能提醒分析器""" + + def __init__(self): + self.confidence_threshold = 0.7 + # 使用规划器模型进行分析 + self.analyzer_llm = LLMRequest( + model_set=model_config.model_task_config.planner_small, + request_type="reminder_analyzer" + ) + + async def analyze_message(self, user_id: str, message: str) -> Optional[ReminderEvent]: + """分析消息是否包含提醒请求 + + Args: + user_id: 用户ID + message: 用户消息内容 + + Returns: + ReminderEvent对象,如果没有检测到提醒请求则返回None + """ + if not message or len(message.strip()) == 0: + return None + + logger.debug(f"分析消息中的提醒请求: {message}") + + # 使用LLM分析消息 + analysis_result = await self._analyze_with_llm(message) + + if not analysis_result or analysis_result.get('confidence', 0) < 0.5: # 降低置信度阈值 + return None + + try: + # 解析时间 + reminder_time = self._parse_relative_time(analysis_result['relative_time']) + if not reminder_time: + return None + + # 创建提醒事件 + reminder_event = ReminderEvent( + user_id=user_id, + reminder_time=reminder_time, + content=analysis_result.get('content', '提醒'), + confidence=analysis_result['confidence'] + ) + + logger.info(f"检测到提醒请求: {reminder_event}") + return reminder_event + + except Exception as e: + logger.error(f"创建提醒事件失败: {e}") + return None + + async def _analyze_with_llm(self, message: str) -> Optional[dict]: + """使用LLM分析消息中的提醒请求""" + try: + prompt = f"""分析以下消息是否包含提醒请求。 + +消息: {message} +当前时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} + +请判断用户是否想要设置提醒,如果是,请提取: +1. 是否包含提醒请求 (has_reminder: true/false) +2. 置信度 (confidence: 0.0-1.0) +3. 相对时间表达 (relative_time: 如"3分钟后", "2小时后") +4. 提醒内容 (content: 提醒的具体内容) +5. 分析原因 (reasoning: 判断理由) + +请以JSON格式输出: +{{ + "has_reminder": true/false, + "confidence": 0.0-1.0, + "relative_time": "时间表达", + "content": "提醒内容", + "reasoning": "判断理由" +}}""" + + response, _ = await self.analyzer_llm.generate_response_async(prompt=prompt) + if not response: + return None + + # 解析JSON响应,处理可能的markdown包装 + try: + # 清理响应文本 + cleaned_response = response.strip() + + # 移除markdown代码块包装 + if cleaned_response.startswith('```json'): + cleaned_response = cleaned_response[7:] # 移除 ```json + elif cleaned_response.startswith('```'): + cleaned_response = cleaned_response[3:] # 移除 ``` + + if cleaned_response.endswith('```'): + cleaned_response = cleaned_response[:-3] # 移除结尾的 ``` + + cleaned_response = cleaned_response.strip() + + # 解析JSON + result = json.loads(cleaned_response) + if result.get('has_reminder', False): + logger.info(f"LLM分析结果: {result}") + return result + except json.JSONDecodeError as e: + logger.error(f"LLM响应JSON解析失败: {response}, Error: {e}") + # 尝试使用更宽松的JSON修复 + try: + import re + # 提取JSON部分的正则表达式 + json_match = re.search(r'\{.*\}', cleaned_response, re.DOTALL) + if json_match: + json_str = json_match.group() + result = json.loads(json_str) + if result.get('has_reminder', False): + logger.info(f"备用解析成功: {result}") + return result + except Exception as fallback_error: + logger.error(f"备用JSON解析也失败: {fallback_error}") + + except Exception as e: + logger.error(f"LLM分析失败: {e}") + + return None + + def _parse_relative_time(self, time_expr: str) -> Optional[datetime]: + """解析时间表达式(支持相对时间和绝对时间)""" + try: + now = datetime.now() + + # 1. 匹配相对时间:X分钟后,包括中文数字 + # 先尝试匹配阿拉伯数字 + minutes_match = re.search(r'(\d+)\s*分钟后', time_expr) + if minutes_match: + minutes = int(minutes_match.group(1)) + result = now + timedelta(minutes=minutes) + logger.info(f"相对时间解析结果: timedelta(minutes={minutes}) -> {result}") + return result + + # 匹配中文数字分钟 + chinese_minutes_patterns = [ + (r'一分钟后', 1), (r'二分钟后', 2), (r'两分钟后', 2), (r'三分钟后', 3), (r'四分钟后', 4), (r'五分钟后', 5), + (r'六分钟后', 6), (r'七分钟后', 7), (r'八分钟后', 8), (r'九分钟后', 9), (r'十分钟后', 10), + (r'十一分钟后', 11), (r'十二分钟后', 12), (r'十三分钟后', 13), (r'十四分钟后', 14), (r'十五分钟后', 15), + (r'二十分钟后', 20), (r'三十分钟后', 30), (r'四十分钟后', 40), (r'五十分钟后', 50), (r'六十分钟后', 60) + ] + + for pattern, minutes in chinese_minutes_patterns: + if re.search(pattern, time_expr): + result = now + timedelta(minutes=minutes) + logger.info(f"中文时间解析结果: {pattern} -> {minutes}分钟 -> {result}") + return result + + # 2. 匹配相对时间:X小时后 + hours_match = re.search(r'(\d+)\s*小时后', time_expr) + if hours_match: + hours = int(hours_match.group(1)) + result = now + timedelta(hours=hours) + logger.info(f"相对时间解析结果: timedelta(hours={hours})") + return result + + # 3. 匹配相对时间:X秒后 + seconds_match = re.search(r'(\d+)\s*秒后', time_expr) + if seconds_match: + seconds = int(seconds_match.group(1)) + result = now + timedelta(seconds=seconds) + logger.info(f"相对时间解析结果: timedelta(seconds={seconds})") + return result + + # 4. 匹配明天+具体时间:明天下午2点、明天上午10点 + tomorrow_match = re.search(r'明天.*?(\d{1,2})\s*[点时]', time_expr) + if tomorrow_match: + hour = int(tomorrow_match.group(1)) + # 如果是下午且小于12,加12小时 + if '下午' in time_expr and hour < 12: + hour += 12 + elif '上午' in time_expr and hour == 12: + hour = 0 + + tomorrow = now + timedelta(days=1) + result = tomorrow.replace(hour=hour, minute=0, second=0, microsecond=0) + logger.info(f"绝对时间解析结果: 明天{hour}点") + return result + + # 5. 匹配今天+具体时间:今天下午3点、今天晚上8点 + today_match = re.search(r'今天.*?(\d{1,2})\s*[点时]', time_expr) + if today_match: + hour = int(today_match.group(1)) + # 如果是下午且小于12,加12小时 + if '下午' in time_expr and hour < 12: + hour += 12 + elif '晚上' in time_expr and hour < 12: + hour += 12 + elif '上午' in time_expr and hour == 12: + hour = 0 + + result = now.replace(hour=hour, minute=0, second=0, microsecond=0) + # 如果时间已过,设为明天 + if result <= now: + result += timedelta(days=1) + + logger.info(f"绝对时间解析结果: 今天{hour}点") + return result + + # 6. 匹配纯数字时间:14点、2点 + pure_time_match = re.search(r'(\d{1,2})\s*[点时]', time_expr) + if pure_time_match: + hour = int(pure_time_match.group(1)) + result = now.replace(hour=hour, minute=0, second=0, microsecond=0) + # 如果时间已过,设为明天 + if result <= now: + result += timedelta(days=1) + + logger.info(f"绝对时间解析结果: {hour}点") + return result + + except Exception as e: + logger.error(f"时间解析失败: {time_expr}, Error: {e}") + + return None + + +# 全局智能提醒分析器实例 +smart_reminder_analyzer = SmartReminderAnalyzer() \ No newline at end of file diff --git a/src/chat/heart_flow/heartflow_message_processor.py b/src/chat/heart_flow/heartflow_message_processor.py index 76d83ebac..a3988af2f 100644 --- a/src/chat/heart_flow/heartflow_message_processor.py +++ b/src/chat/heart_flow/heartflow_message_processor.py @@ -2,6 +2,7 @@ import asyncio import re import math import traceback +from datetime import datetime from typing import Tuple, TYPE_CHECKING @@ -15,7 +16,7 @@ from src.chat.utils.timer_calculator import Timer from src.chat.utils.chat_message_builder import replace_user_references_sync from src.common.logger import get_logger from src.mood.mood_manager import mood_manager -from src.person_info.person_info import Person +from src.chat.message_receive.chat_stream import get_chat_manager if TYPE_CHECKING: from src.chat.heart_flow.sub_heartflow import SubHeartflow @@ -96,10 +97,11 @@ class HeartFCMessageReceiver: 主要流程: 1. 消息解析与初始化 - 2. 消息缓冲处理 - 3. 过滤检查 - 4. 兴趣度计算 - 5. 关系处理 + 2. 智能提醒分析 + 3. 消息缓冲处理 + 4. 过滤检查 + 5. 兴趣度计算 + 6. 关系处理 Args: message_data: 原始消息字符串 @@ -109,7 +111,92 @@ class HeartFCMessageReceiver: userinfo = message.message_info.user_info chat = message.chat_stream - # 2. 兴趣度计算与更新 + # 2. 智能提醒分析 - 检查用户是否请求提醒 + from src.chat.chat_loop.proactive.smart_reminder_analyzer import smart_reminder_analyzer + from src.chat.chat_loop.proactive.event_scheduler import event_scheduler + + try: + reminder_event = await smart_reminder_analyzer.analyze_message( + userinfo.user_id, # type: ignore + message.processed_plain_text + ) + if reminder_event: + logger.info(f"检测到提醒请求: {reminder_event}") + + # 创建提醒回调函数 + async def reminder_callback(metadata): + """提醒执行回调函数 - 触发完整的主动思考流程""" + try: + # 获取对应的subheartflow实例 + from src.chat.heart_flow.heartflow import heartflow + + subflow = await heartflow.get_or_create_subheartflow(chat.stream_id) + if not subflow: + logger.error(f"无法获取subheartflow实例: {chat.stream_id}") + return + + # 创建主动思考事件,触发完整的思考流程 + from src.chat.chat_loop.proactive.events import ProactiveTriggerEvent + + reminder_content = metadata.get('content', '提醒时间到了') + event = ProactiveTriggerEvent( + source="reminder_system", + reason=f"定时提醒:{reminder_content}", + metadata={ + "reminder_text": reminder_content, + "trigger_time": datetime.now().isoformat() + } + ) + + # 通过subflow的HeartFChatting实例触发主动思考 + await subflow.heart_fc_instance.proactive_thinker.think(event) + + logger.info(f"已触发提醒的主动思考,内容: {reminder_content}") + + except Exception as callback_error: + logger.error(f"执行提醒回调失败: {callback_error}") + import traceback + logger.error(traceback.format_exc()) + + # Fallback: 如果主动思考失败,直接发送提醒消息 + try: + reminder_content = metadata.get('content', '提醒时间到了') + await text_to_stream( + text=f"⏰ 提醒:{reminder_content}", + stream_id=chat.stream_id, + typing=False + ) + logger.info(f"Fallback提醒消息已发送: {reminder_content}") + except Exception as fallback_error: + logger.error(f"Fallback提醒也失败了: {fallback_error}") + + # 调度提醒事件 + event_id = f"reminder_{reminder_event.user_id}_{int(reminder_event.reminder_time.timestamp())}" + metadata = { + "type": "reminder", + "user_id": reminder_event.user_id, + "chat_id": chat.stream_id, + "content": reminder_event.content, + "confidence": reminder_event.confidence, + "created_at": datetime.now().isoformat() + } + + success = await event_scheduler.schedule_event( + event_id=event_id, + trigger_time=reminder_event.reminder_time, + callback=reminder_callback, + metadata=metadata + ) + + if success: + logger.info(f"提醒事件调度成功: {event_id}") + else: + logger.error(f"提醒事件调度失败: {event_id}") + + except Exception as e: + logger.error(f"智能提醒分析失败: {e}") + + # 3. 兴趣度计算与更新 interested_rate, is_mentioned, keywords = await _calculate_interest(message) message.interest_value = interested_rate message.is_mentioned = is_mentioned diff --git a/src/chat/planner_actions/planner.py b/src/chat/planner_actions/planner.py index 107b5b10d..1c3e28863 100644 --- a/src/chat/planner_actions/planner.py +++ b/src/chat/planner_actions/planner.py @@ -102,29 +102,38 @@ def init_prompt(): {actions_before_now_block} ## 任务 -基于以上所有信息(特别是最近的聊天内容),分析当前情况,决定是否适合主动开启一个**新的、但又与当前氛围相关**的话题。 +你现在要决定是否主动说些什么。就像一个真实的人一样,有时候会突然想起之前聊到的话题,或者对朋友的近况感到好奇,想主动询问或关心一下。 + +请基于聊天内容,用你的判断力来决定是否要主动发言。不要按照固定规则,而是像人类一样自然地思考: +- 是否想起了什么之前提到的事情,想问问后来怎么样了? +- 是否注意到朋友提到了什么值得关心的事情? +- 是否有什么话题突然想到,觉得现在聊聊很合适? +- 或者觉得现在保持沉默更好? ## 可用动作 动作:proactive_reply -动作描述:在当前对话的基础上,主动发起一个新的对话,分享一个有趣的想法、见闻或者对未来的计划。 -- 当你觉得可以说些什么来活跃气氛,并且内容与当前聊天氛围不冲突时 -- 当你有一些新的想法或计划想要分享,并且可以自然地衔接当前话题时 +动作描述:主动发起对话,可以是关心朋友、询问近况、延续之前的话题,或分享想法。 +- 当你突然想起之前的话题,想询问进展时 +- 当你想关心朋友的情况时 +- 当你有什么想法想分享时 +- 当你觉得现在是个合适的聊天时机时 {{ "action": "proactive_reply", - "reason": "决定主动发起对话的具体原因", - "topic": "你想要发起对话的主题或内容(需要简洁)" + "reason": "你决定主动发言的具体原因", + "topic": "你想说的内容主题(简洁描述)" }} 动作:do_nothing -动作描述:保持沉默,不主动发起任何动作或对话。 -- 当你分析了所有信息后,觉得当前不是一个发起互动的好时机时 -- 当最近的聊天内容很连贯,你的插入会打断别人时 +动作描述:保持沉默,不主动发起对话。 +- 当你觉得现在不是合适的时机时 +- 当最近已经说得够多了时 +- 当对话氛围不适合插入时 {{ "action": "do_nothing", - "reason":"决定保持沉默的具体原因" + "reason": "决定保持沉默的原因" }} -你必须从上面列出的可用action中选择一个。 +你必须从上面列出的可用action中选择一个。要像真人一样自然地思考和决策。 请以严格的 JSON 格式输出,且仅包含 JSON 内容: """, "proactive_planner_prompt", @@ -497,53 +506,59 @@ class ActionPlanner: # --- 2. 启动小脑并行思考 --- all_sub_planner_results: List[Dict[str, Any]] = [] - try: - sub_planner_actions: Dict[str, ActionInfo] = {} - for action_name, action_info in available_actions.items(): + + # PROACTIVE模式下禁用小脑,避免与大脑的主动思考决策冲突 + if mode != ChatMode.PROACTIVE: + try: + sub_planner_actions: Dict[str, ActionInfo] = {} + for action_name, action_info in available_actions.items(): - if action_info.activation_type in [ActionActivationType.LLM_JUDGE, ActionActivationType.ALWAYS]: - sub_planner_actions[action_name] = action_info - elif action_info.activation_type == ActionActivationType.RANDOM: - if random.random() < action_info.random_activation_probability: - sub_planner_actions[action_name] = action_info - elif action_info.activation_type == ActionActivationType.KEYWORD: - if any(keyword in chat_content_block_short for keyword in action_info.activation_keywords): + if action_info.activation_type in [ActionActivationType.LLM_JUDGE, ActionActivationType.ALWAYS]: sub_planner_actions[action_name] = action_info + elif action_info.activation_type == ActionActivationType.RANDOM: + if random.random() < action_info.random_activation_probability: + sub_planner_actions[action_name] = action_info + elif action_info.activation_type == ActionActivationType.KEYWORD: + if any(keyword in chat_content_block_short for keyword in action_info.activation_keywords): + sub_planner_actions[action_name] = action_info - if sub_planner_actions: - sub_planner_actions_num = len(sub_planner_actions) - planner_size_config = global_config.chat.planner_size - sub_planner_size = int(planner_size_config) + ( - 1 if random.random() < planner_size_config - int(planner_size_config) else 0 - ) - sub_planner_num = math.ceil(sub_planner_actions_num / sub_planner_size) - logger.info(f"{self.log_prefix}使用{sub_planner_num}个小脑进行思考 (尺寸: {sub_planner_size})") - - action_items = list(sub_planner_actions.items()) - random.shuffle(action_items) - sub_planner_lists = [action_items[i::sub_planner_num] for i in range(sub_planner_num)] - - sub_plan_tasks = [ - self.sub_plan( - action_list=action_group, - chat_content_block=chat_content_block_short, - message_id_list=message_id_list_short, - is_group_chat=is_group_chat, - chat_target_info=chat_target_info, + if sub_planner_actions: + sub_planner_actions_num = len(sub_planner_actions) + planner_size_config = global_config.chat.planner_size + sub_planner_size = int(planner_size_config) + ( + 1 if random.random() < planner_size_config - int(planner_size_config) else 0 ) - for action_group in sub_planner_lists - ] - sub_plan_results = await asyncio.gather(*sub_plan_tasks) - for sub_result in sub_plan_results: - all_sub_planner_results.extend(sub_result) - - sub_actions_str = ", ".join( - a["action_type"] for a in all_sub_planner_results if a["action_type"] != "no_action" - ) or "no_action" - logger.info(f"{self.log_prefix}小脑决策: [{sub_actions_str}]") + sub_planner_num = math.ceil(sub_planner_actions_num / sub_planner_size) + logger.info(f"{self.log_prefix}使用{sub_planner_num}个小脑进行思考 (尺寸: {sub_planner_size})") - except Exception as e: - logger.error(f"{self.log_prefix}小脑调度过程中出错: {e}\n{traceback.format_exc()}") + action_items = list(sub_planner_actions.items()) + random.shuffle(action_items) + sub_planner_lists = [action_items[i::sub_planner_num] for i in range(sub_planner_num)] + + sub_plan_tasks = [ + self.sub_plan( + action_list=action_group, + chat_content_block=chat_content_block_short, + message_id_list=message_id_list_short, + is_group_chat=is_group_chat, + chat_target_info=chat_target_info, + ) + for action_group in sub_planner_lists + ] + sub_plan_results = await asyncio.gather(*sub_plan_tasks) + for sub_result in sub_plan_results: + all_sub_planner_results.extend(sub_result) + + sub_actions_str = ", ".join( + a["action_type"] for a in all_sub_planner_results if a["action_type"] != "no_action" + ) or "no_action" + logger.info(f"{self.log_prefix}小脑决策: [{sub_actions_str}]") + + except Exception as e: + logger.error(f"{self.log_prefix}小脑调度过程中出错: {e}\n{traceback.format_exc()}") + else: + # PROACTIVE模式下小脑保持沉默,让大脑专注于主动思考决策 + logger.info(f"{self.log_prefix}PROACTIVE模式:小脑保持沉默,主动思考交给大脑决策") # --- 3. 大脑独立思考是否回复 --- action, reasoning, action_data, target_message = "no_reply", "大脑初始化默认", {}, None