主动思考大优化
This commit is contained in:
239
src/chat/chat_loop/proactive/event_scheduler.py
Normal file
239
src/chat/chat_loop/proactive/event_scheduler.py
Normal file
@@ -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()
|
||||
@@ -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,
|
||||
|
||||
260
src/chat/chat_loop/proactive/smart_reminder_analyzer.py
Normal file
260
src/chat/chat_loop/proactive/smart_reminder_analyzer.py
Normal file
@@ -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()
|
||||
@@ -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
|
||||
|
||||
@@ -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,6 +506,9 @@ class ActionPlanner:
|
||||
|
||||
# --- 2. 启动小脑并行思考 ---
|
||||
all_sub_planner_results: List[Dict[str, Any]] = []
|
||||
|
||||
# PROACTIVE模式下禁用小脑,避免与大脑的主动思考决策冲突
|
||||
if mode != ChatMode.PROACTIVE:
|
||||
try:
|
||||
sub_planner_actions: Dict[str, ActionInfo] = {}
|
||||
for action_name, action_info in available_actions.items():
|
||||
@@ -544,6 +556,9 @@ class ActionPlanner:
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user