diff --git a/docker-compose.yml b/docker-compose.yml index bcc8a57a8..2240541c6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,8 +27,8 @@ services: # image: infinitycat/maibot:dev environment: - TZ=Asia/Shanghai -# - EULA_AGREE=bda99dca873f5d8044e9987eac417e01 # 同意EULA -# - PRIVACY_AGREE=42dddb3cbe2b784b45a2781407b298a1 # 同意EULA +# - EULA_AGREE=99f08e0cab0190de853cb6af7d64d4de # 同意EULA +# - PRIVACY_AGREE=9943b855e72199d0f5016ea39052f1b6 # 同意EULA # ports: # - "8000:8000" volumes: diff --git a/plugins/hello_world_plugin/_manifest.json b/plugins/hello_world_plugin/_manifest.json index 86f01afc3..b1a4c4eb8 100644 --- a/plugins/hello_world_plugin/_manifest.json +++ b/plugins/hello_world_plugin/_manifest.json @@ -10,8 +10,7 @@ "license": "GPL-v3.0-or-later", "host_application": { - "min_version": "0.8.0", - "max_version": "0.8.0" + "min_version": "0.8.0" }, "homepage_url": "https://github.com/MaiM-with-u/maibot", "repository_url": "https://github.com/MaiM-with-u/maibot", diff --git a/src/audio/mock_audio.py b/src/audio/mock_audio.py deleted file mode 100644 index 9772fdad9..000000000 --- a/src/audio/mock_audio.py +++ /dev/null @@ -1,62 +0,0 @@ -import asyncio -from src.common.logger import get_logger - -logger = get_logger("MockAudio") - - -class MockAudioPlayer: - """ - 一个模拟的音频播放器,它会根据音频数据的"长度"来模拟播放时间。 - """ - - def __init__(self, audio_data: bytes): - self._audio_data = audio_data - # 模拟音频时长:假设每 1024 字节代表 0.5 秒的音频 - self._duration = (len(audio_data) / 1024.0) * 0.5 - - async def play(self): - """模拟播放音频。该过程可以被中断。""" - if self._duration <= 0: - return - logger.info(f"开始播放模拟音频,预计时长: {self._duration:.2f} 秒...") - try: - await asyncio.sleep(self._duration) - logger.info("模拟音频播放完毕。") - except asyncio.CancelledError: - logger.info("音频播放被中断。") - raise # 重新抛出异常,以便上层逻辑可以捕获它 - - -class MockAudioGenerator: - """ - 一个模拟的文本到语音(TTS)生成器。 - """ - - def __init__(self): - # 模拟生成速度:每秒生成的字符数 - self.chars_per_second = 25.0 - - async def generate(self, text: str) -> bytes: - """ - 模拟从文本生成音频数据。该过程可以被中断。 - - Args: - text: 需要转换为音频的文本。 - - Returns: - 模拟的音频数据(bytes)。 - """ - if not text: - return b"" - - generation_time = len(text) / self.chars_per_second - logger.info(f"模拟生成音频... 文本长度: {len(text)}, 预计耗时: {generation_time:.2f} 秒...") - try: - await asyncio.sleep(generation_time) - # 生成虚拟的音频数据,其长度与文本长度成正比 - mock_audio_data = b"\x01\x02\x03" * (len(text) * 40) - logger.info(f"模拟音频生成完毕,数据大小: {len(mock_audio_data) / 1024:.2f} KB。") - return mock_audio_data - except asyncio.CancelledError: - logger.info("音频生成被中断。") - raise # 重新抛出异常 diff --git a/src/chat/__init__.py b/src/chat/__init__.py index c69d5205e..a569c0226 100644 --- a/src/chat/__init__.py +++ b/src/chat/__init__.py @@ -5,11 +5,9 @@ MaiBot模块系统 from src.chat.message_receive.chat_stream import get_chat_manager from src.chat.emoji_system.emoji_manager import get_emoji_manager -from src.chat.normal_chat.willing.willing_manager import get_willing_manager # 导出主要组件供外部使用 __all__ = [ "get_chat_manager", "get_emoji_manager", - "get_willing_manager", ] diff --git a/src/chat/focus_chat/focus_loop_info.py b/src/chat/focus_chat/focus_loop_info.py deleted file mode 100644 index 827c544a2..000000000 --- a/src/chat/focus_chat/focus_loop_info.py +++ /dev/null @@ -1,91 +0,0 @@ -# 定义了来自外部世界的信息 -# 外部世界可以是某个聊天 不同平台的聊天 也可以是任意媒体 -from datetime import datetime -from typing import List - -from src.common.logger import get_logger -from src.chat.focus_chat.hfc_utils import CycleDetail - -logger = get_logger("loop_info") - - -# 所有观察的基类 -class FocusLoopInfo: - def __init__(self, observe_id): - self.observe_id = observe_id - self.last_observe_time = datetime.now().timestamp() # 初始化为当前时间 - self.history_loop: List[CycleDetail] = [] - - def add_loop_info(self, loop_info: CycleDetail): - self.history_loop.append(loop_info) - - async def observe(self): - recent_active_cycles: List[CycleDetail] = [] - for cycle in reversed(self.history_loop): - # 只关心实际执行了动作的循环 - # action_taken = cycle.loop_action_info["action_taken"] - # if action_taken: - recent_active_cycles.append(cycle) - if len(recent_active_cycles) == 5: - break - - cycle_info_block = "" - action_detailed_str = "" - consecutive_text_replies = 0 - responses_for_prompt = [] - - cycle_last_reason = "" - - # 检查这最近的活动循环中有多少是连续的文本回复 (从最近的开始看) - for cycle in recent_active_cycles: - action_result = cycle.loop_plan_info.get("action_result", {}) - action_type = action_result.get("action_type", "unknown") - action_reasoning = action_result.get("reasoning", "未提供理由") - is_taken = cycle.loop_action_info.get("action_taken", False) - action_taken_time = cycle.loop_action_info.get("taken_time", 0) - action_taken_time_str = ( - datetime.fromtimestamp(action_taken_time).strftime("%H:%M:%S") if action_taken_time > 0 else "未知时间" - ) - if action_reasoning != cycle_last_reason: - cycle_last_reason = action_reasoning - action_reasoning_str = f"你选择这个action的原因是:{action_reasoning}" - else: - action_reasoning_str = "" - - if action_type == "reply": - consecutive_text_replies += 1 - response_text = cycle.loop_action_info.get("reply_text", "") - responses_for_prompt.append(response_text) - - if is_taken: - action_detailed_str += f"{action_taken_time_str}时,你选择回复(action:{action_type},内容是:'{response_text}')。{action_reasoning_str}\n" - else: - action_detailed_str += f"{action_taken_time_str}时,你选择回复(action:{action_type},内容是:'{response_text}'),但是动作失败了。{action_reasoning_str}\n" - elif action_type == "no_reply": - pass - else: - if is_taken: - action_detailed_str += ( - f"{action_taken_time_str}时,你选择执行了(action:{action_type}),{action_reasoning_str}\n" - ) - else: - action_detailed_str += f"{action_taken_time_str}时,你选择执行了(action:{action_type}),但是动作失败了。{action_reasoning_str}\n" - - if action_detailed_str: - cycle_info_block = f"\n你最近做的事:\n{action_detailed_str}\n" - else: - cycle_info_block = "\n" - - # 获取history_loop中最新添加的 - if self.history_loop: - last_loop = self.history_loop[0] - start_time = last_loop.start_time - end_time = last_loop.end_time - if start_time is not None and end_time is not None: - time_diff = int(end_time - start_time) - if time_diff > 60: - cycle_info_block += f"距离你上一次阅读消息并思考和规划,已经过去了{int(time_diff / 60)}分钟\n" - else: - cycle_info_block += f"距离你上一次阅读消息并思考和规划,已经过去了{time_diff}秒\n" - else: - cycle_info_block += "你还没看过消息\n" diff --git a/src/chat/focus_chat/heartFC_chat.py b/src/chat/focus_chat/heartFC_chat.py index 05600c256..98386a50c 100644 --- a/src/chat/focus_chat/heartFC_chat.py +++ b/src/chat/focus_chat/heartFC_chat.py @@ -1,5 +1,4 @@ import asyncio -import contextlib import time import traceback from collections import deque @@ -14,11 +13,44 @@ from src.chat.utils.timer_calculator import Timer from src.chat.planner_actions.planner import ActionPlanner from src.chat.planner_actions.action_modifier import ActionModifier from src.chat.planner_actions.action_manager import ActionManager -from src.chat.focus_chat.focus_loop_info import FocusLoopInfo -from src.chat.focus_chat.hfc_performance_logger import HFCPerformanceLogger from src.chat.focus_chat.hfc_utils import CycleDetail from src.person_info.relationship_builder_manager import relationship_builder_manager from src.plugin_system.base.component_types import ChatMode +import random +from src.chat.focus_chat.hfc_utils import get_recent_message_stats +from src.person_info.person_info import get_person_info_manager +from src.plugin_system.apis import generator_api, send_api, message_api +from src.chat.willing.willing_manager import get_willing_manager +from .priority_manager import PriorityManager +from src.chat.utils.chat_message_builder import get_raw_msg_by_timestamp_with_chat + + +ERROR_LOOP_INFO = { + "loop_plan_info": { + "action_result": { + "action_type": "error", + "action_data": {}, + "reasoning": "循环处理失败", + }, + }, + "loop_action_info": { + "action_taken": False, + "reply_text": "", + "command": "", + "taken_time": time.time(), + }, +} + +NO_ACTION = { + "action_result": { + "action_type": "no_action", + "action_data": {}, + "reasoning": "规划器初始化默认", + "is_parallel": True, + }, + "chat_context": "", + "action_prompt": "", +} install(extra_lines=3) @@ -37,7 +69,6 @@ class HeartFChatting: def __init__( self, chat_id: str, - on_stop_focus_chat: Optional[Callable[[], Awaitable[None]]] = None, ): """ HeartFChatting 初始化函数 @@ -56,6 +87,8 @@ class HeartFChatting: self.relationship_builder = relationship_builder_manager.get_or_create_builder(self.stream_id) + self.loop_mode = "normal" + # 新增:消息计数器和疲惫阈值 self._message_count = 0 # 发送的消息计数 # 基于exit_focus_threshold动态计算疲惫阈值 @@ -63,73 +96,61 @@ class HeartFChatting: self._message_threshold = max(10, int(30 * global_config.chat.exit_focus_threshold)) self._fatigue_triggered = False # 是否已触发疲惫退出 - self.loop_info: FocusLoopInfo = FocusLoopInfo(observe_id=self.stream_id) - self.action_manager = ActionManager() self.action_planner = ActionPlanner(chat_id=self.stream_id, action_manager=self.action_manager) self.action_modifier = ActionModifier(action_manager=self.action_manager, chat_id=self.stream_id) - self._processing_lock = asyncio.Lock() - # 循环控制内部状态 - self._loop_active: bool = False # 循环是否正在运行 + self.running: bool = False self._loop_task: Optional[asyncio.Task] = None # 主循环任务 # 添加循环信息管理相关的属性 + self.history_loop: List[CycleDetail] = [] self._cycle_counter = 0 - self._cycle_history: Deque[CycleDetail] = deque(maxlen=10) # 保留最近10个循环的信息 self._current_cycle_detail: Optional[CycleDetail] = None - self._shutting_down: bool = False # 关闭标志位 - - # 存储回调函数 - self.on_stop_focus_chat = on_stop_focus_chat self.reply_timeout_count = 0 self.plan_timeout_count = 0 - # 初始化性能记录器 - # 如果没有指定版本号,则使用全局版本管理器的版本号 + self.last_read_time = time.time() - 1 - self.performance_logger = HFCPerformanceLogger(chat_id) + self.willing_amplifier = 1 + self.willing_manager = get_willing_manager() + + self.reply_mode = self.chat_stream.context.get_priority_mode() + if self.reply_mode == "priority": + self.priority_manager = PriorityManager( + normal_queue_max_size=5, + ) + self.loop_mode = "priority" + else: + self.priority_manager = None logger.info( f"{self.log_prefix} HeartFChatting 初始化完成,消息疲惫阈值: {self._message_threshold}条(基于exit_focus_threshold={global_config.chat.exit_focus_threshold}计算,仅在auto模式下生效)" ) + self.energy_value = 100 + async def start(self): """检查是否需要启动主循环,如果未激活则启动。""" # 如果循环已经激活,直接返回 - if self._loop_active: + if self.running: logger.debug(f"{self.log_prefix} HeartFChatting 已激活,无需重复启动") return try: - # 重置消息计数器,开始新的focus会话 - self.reset_message_count() - # 标记为活动状态,防止重复启动 - self._loop_active = True + self.running = True - # 检查是否已有任务在运行(理论上不应该,因为 _loop_active=False) - if self._loop_task and not self._loop_task.done(): - logger.warning(f"{self.log_prefix} 发现之前的循环任务仍在运行(不符合预期)。取消旧任务。") - self._loop_task.cancel() - try: - # 等待旧任务确实被取消 - await asyncio.wait_for(self._loop_task, timeout=5.0) - except Exception as e: - logger.warning(f"{self.log_prefix} 等待旧任务取消时出错: {e}") - self._loop_task = None # 清理旧任务引用 - - logger.debug(f"{self.log_prefix} 创建新的 HeartFChatting 主循环任务") - self._loop_task = asyncio.create_task(self._run_focus_chat()) + self._loop_task = asyncio.create_task(self._main_chat_loop()) self._loop_task.add_done_callback(self._handle_loop_completion) - logger.debug(f"{self.log_prefix} HeartFChatting 启动完成") + logger.info(f"{self.log_prefix} HeartFChatting 启动完成") except Exception as e: # 启动失败时重置状态 - self._loop_active = False + self.running = False self._loop_task = None logger.error(f"{self.log_prefix} HeartFChatting 启动失败: {e}") raise @@ -143,266 +164,203 @@ class HeartFChatting: else: logger.info(f"{self.log_prefix} HeartFChatting: 脱离了聊天 (外部停止)") except asyncio.CancelledError: - logger.info(f"{self.log_prefix} HeartFChatting: 脱离了聊天(任务取消)") - finally: - self._loop_active = False - self._loop_task = None - if self._processing_lock.locked(): - logger.warning(f"{self.log_prefix} HeartFChatting: 处理锁在循环结束时仍被锁定,强制释放。") - self._processing_lock.release() + logger.info(f"{self.log_prefix} HeartFChatting: 结束了聊天") - async def _run_focus_chat(self): - """主循环,持续进行计划并可能回复消息,直到被外部取消。""" - try: - while True: # 主循环 - logger.debug(f"{self.log_prefix} 开始第{self._cycle_counter}次循环") + def start_cycle(self): + self._cycle_counter += 1 + self._current_cycle_detail = CycleDetail(self._cycle_counter) + self._current_cycle_detail.thinking_id = "tid" + str(round(time.time(), 2)) + cycle_timers = {} + return cycle_timers, self._current_cycle_detail.thinking_id - # 检查关闭标志 - if self._shutting_down: - logger.info(f"{self.log_prefix} 检测到关闭标志,退出 Focus Chat 循环。") - break + def end_cycle(self, loop_info, cycle_timers): + self._current_cycle_detail.set_loop_info(loop_info) + self.history_loop.append(self._current_cycle_detail) + self._current_cycle_detail.timers = cycle_timers + self._current_cycle_detail.end_time = time.time() - # 创建新的循环信息 - self._cycle_counter += 1 - self._current_cycle_detail = CycleDetail(self._cycle_counter) - self._current_cycle_detail.prefix = self.log_prefix + def print_cycle_info(self, cycle_timers): + # 记录循环信息和计时器结果 + timer_strings = [] + for name, elapsed in cycle_timers.items(): + formatted_time = f"{elapsed * 1000:.2f}毫秒" if elapsed < 1 else f"{elapsed:.2f}秒" + timer_strings.append(f"{name}: {formatted_time}") - # 初始化周期状态 - cycle_timers = {} + logger.info( + f"{self.log_prefix} 第{self._current_cycle_detail.cycle_id}次思考," + f"耗时: {self._current_cycle_detail.end_time - self._current_cycle_detail.start_time:.1f}秒, " # type: ignore + f"选择动作: {self._current_cycle_detail.loop_plan_info.get('action_result', {}).get('action_type', '未知动作')}" + + (f"\n详情: {'; '.join(timer_strings)}" if timer_strings else "") + ) - # 执行规划和处理阶段 - try: - async with self._get_cycle_context(): - thinking_id = f"tid{str(round(time.time(), 2))}" - self._current_cycle_detail.set_thinking_id(thinking_id) + async def _loopbody(self): + if self.loop_mode == "focus": + self.energy_value -= 5 * (1 / global_config.chat.exit_focus_threshold) + if self.energy_value <= 0: + self.loop_mode = "normal" + return True - # 使用异步上下文管理器处理消息 - try: - async with global_prompt_manager.async_message_scope( - self.chat_stream.context.get_template_name() - ): - # 在上下文内部检查关闭状态 - if self._shutting_down: - logger.info(f"{self.log_prefix} 在处理上下文中检测到关闭信号,退出") - break + return await self._observe() + elif self.loop_mode == "normal": + new_messages_data = get_raw_msg_by_timestamp_with_chat( + chat_id=self.stream_id, + timestamp_start=self.last_read_time, + timestamp_end=time.time(), + limit=10, + limit_mode="earliest", + fliter_bot=True, + ) - logger.debug(f"模板 {self.chat_stream.context.get_template_name()}") - loop_info = await self._observe_process_plan_action_loop(cycle_timers, thinking_id) + if len(new_messages_data) > 4 * global_config.chat.auto_focus_threshold: + self.loop_mode = "focus" + self.energy_value = 100 + return True - if loop_info["loop_action_info"]["command"] == "stop_focus_chat": - logger.info(f"{self.log_prefix} 麦麦决定停止专注聊天") + if new_messages_data: + earliest_messages_data = new_messages_data[0] + self.last_read_time = earliest_messages_data.get("time") - # 如果设置了回调函数,则调用它 - if self.on_stop_focus_chat: - try: - await self.on_stop_focus_chat() - logger.info(f"{self.log_prefix} 成功调用回调函数处理停止专注聊天") - except Exception as e: - logger.error(f"{self.log_prefix} 调用停止专注聊天回调函数时出错: {e}") - logger.error(traceback.format_exc()) - break + await self.normal_response(earliest_messages_data) + return True - except asyncio.CancelledError: - logger.info(f"{self.log_prefix} 处理上下文时任务被取消") - break - except Exception as e: - logger.error(f"{self.log_prefix} 处理上下文时出错: {e}") - # 为当前循环设置错误状态,防止后续重复报错 - error_loop_info = { - "loop_plan_info": { - "action_result": { - "action_type": "error", - "action_data": {}, - }, - }, - "loop_action_info": { - "action_taken": False, - "reply_text": "", - "command": "", - "taken_time": time.time(), - }, - } - self._current_cycle_detail.set_loop_info(error_loop_info) - self._current_cycle_detail.complete_cycle() + await asyncio.sleep(1) - # 上下文处理失败,跳过当前循环 - await asyncio.sleep(1) - continue + return True - self._current_cycle_detail.set_loop_info(loop_info) + async def build_reply_to_str(self, message_data: dict): + person_info_manager = get_person_info_manager() + person_id = person_info_manager.get_person_id( + message_data.get("chat_info_platform"), message_data.get("user_id") + ) + person_name = await person_info_manager.get_value(person_id, "person_name") + reply_to_str = f"{person_name}:{message_data.get('processed_plain_text')}" + return reply_to_str - self.loop_info.add_loop_info(self._current_cycle_detail) + async def _observe(self, message_data: dict = None): + # 创建新的循环信息 + cycle_timers, thinking_id = self.start_cycle() - self._current_cycle_detail.timers = cycle_timers + logger.info(f"{self.log_prefix} 开始第{self._cycle_counter}次思考[模式:{self.loop_mode}]") - # 完成当前循环并保存历史 - self._current_cycle_detail.complete_cycle() - self._cycle_history.append(self._current_cycle_detail) - - # 记录循环信息和计时器结果 - timer_strings = [] - for name, elapsed in cycle_timers.items(): - formatted_time = f"{elapsed * 1000:.2f}毫秒" if elapsed < 1 else f"{elapsed:.2f}秒" - timer_strings.append(f"{name}: {formatted_time}") - - logger.info( - f"{self.log_prefix} 第{self._current_cycle_detail.cycle_id}次思考," - f"耗时: {self._current_cycle_detail.end_time - self._current_cycle_detail.start_time:.1f}秒, " # type: ignore - f"选择动作: {self._current_cycle_detail.loop_plan_info.get('action_result', {}).get('action_type', '未知动作')}" - + (f"\n详情: {'; '.join(timer_strings)}" if timer_strings else "") - ) - - # 记录性能数据 - try: - action_result = self._current_cycle_detail.loop_plan_info.get("action_result", {}) - cycle_performance_data = { - "cycle_id": self._current_cycle_detail.cycle_id, - "action_type": action_result.get("action_type", "unknown"), - "total_time": self._current_cycle_detail.end_time - self._current_cycle_detail.start_time, # type: ignore - "step_times": cycle_timers.copy(), - "reasoning": action_result.get("reasoning", ""), - "success": self._current_cycle_detail.loop_action_info.get("action_taken", False), - } - self.performance_logger.record_cycle(cycle_performance_data) - except Exception as perf_e: - logger.warning(f"{self.log_prefix} 记录性能数据失败: {perf_e}") - - await asyncio.sleep(global_config.focus_chat.think_interval) - - except asyncio.CancelledError: - logger.info(f"{self.log_prefix} 循环处理时任务被取消") - break - except Exception as e: - logger.error(f"{self.log_prefix} 循环处理时出错: {e}") - logger.error(traceback.format_exc()) - - # 如果_current_cycle_detail存在但未完成,为其设置错误状态 - if self._current_cycle_detail and not hasattr(self._current_cycle_detail, "end_time"): - error_loop_info = { - "loop_plan_info": { - "action_result": { - "action_type": "error", - "action_data": {}, - "reasoning": f"循环处理失败: {e}", - }, - }, - "loop_action_info": { - "action_taken": False, - "reply_text": "", - "command": "", - "taken_time": time.time(), - }, - } - try: - self._current_cycle_detail.set_loop_info(error_loop_info) - self._current_cycle_detail.complete_cycle() - except Exception as inner_e: - logger.error(f"{self.log_prefix} 设置错误状态时出错: {inner_e}") - - await asyncio.sleep(1) # 出错后等待一秒再继续 - - except asyncio.CancelledError: - # 设置了关闭标志位后被取消是正常流程 - if not self._shutting_down: - logger.warning(f"{self.log_prefix} 麦麦Focus聊天模式意外被取消") - else: - logger.info(f"{self.log_prefix} 麦麦已离开Focus聊天模式") - except Exception as e: - logger.error(f"{self.log_prefix} 麦麦Focus聊天模式意外错误: {e}") - print(traceback.format_exc()) - - @contextlib.asynccontextmanager - async def _get_cycle_context(self): - """ - 循环周期的上下文管理器 - - 用于确保资源的正确获取和释放: - 1. 获取处理锁 - 2. 执行操作 - 3. 释放锁 - """ - acquired = False - try: - await self._processing_lock.acquire() - acquired = True - yield acquired - finally: - if acquired and self._processing_lock.locked(): - self._processing_lock.release() - - async def _observe_process_plan_action_loop(self, cycle_timers: dict, thinking_id: str) -> dict: - try: + async with global_prompt_manager.async_message_scope(self.chat_stream.context.get_template_name()): loop_start_time = time.time() - await self.loop_info.observe() - + # await self.loop_info.observe() await self.relationship_builder.build_relation() - # 顺序执行调整动作和处理器阶段 # 第一步:动作修改 with Timer("动作修改", cycle_timers): try: - # 调用完整的动作修改流程 - await self.action_modifier.modify_actions( - loop_info=self.loop_info, - mode=ChatMode.FOCUS, - ) + await self.action_modifier.modify_actions() + available_actions = self.action_manager.get_using_actions() except Exception as e: logger.error(f"{self.log_prefix} 动作修改失败: {e}") - # 继续执行,不中断流程 + + # 如果normal,开始一个回复生成进程,先准备好回复(其实是和planer同时进行的) + if self.loop_mode == "normal": + reply_to_str = await self.build_reply_to_str(message_data) + gen_task = asyncio.create_task(self._generate_response(message_data, available_actions, reply_to_str)) with Timer("规划器", cycle_timers): - plan_result = await self.action_planner.plan() + plan_result = await self.action_planner.plan(mode=self.loop_mode) - loop_plan_info = { - "action_result": plan_result.get("action_result", {}), - } - - action_type, action_data, reasoning = ( - plan_result.get("action_result", {}).get("action_type", "error"), - plan_result.get("action_result", {}).get("action_data", {}), - plan_result.get("action_result", {}).get("reasoning", "未提供理由"), + action_result = plan_result.get("action_result", {}) + action_type, action_data, reasoning, is_parallel = ( + action_result.get("action_type", "error"), + action_result.get("action_data", {}), + action_result.get("reasoning", "未提供理由"), + action_result.get("is_parallel", True), ) action_data["loop_start_time"] = loop_start_time - if action_type == "reply": - action_str = "回复" - elif action_type == "no_reply": - action_str = "不回复" + if self.loop_mode == "normal": + if action_type == "no_action": + logger.info(f"[{self.log_prefix}] {global_config.bot.nickname} 决定进行回复") + elif is_parallel: + logger.info( + f"[{self.log_prefix}] {global_config.bot.nickname} 决定进行回复, 同时执行{action_type}动作" + ) + else: + logger.info(f"[{self.log_prefix}] {global_config.bot.nickname} 决定执行{action_type}动作") + + if action_type == "no_action": + # 等待回复生成完毕 + gather_timeout = global_config.chat.thinking_timeout + try: + response_set = await asyncio.wait_for(gen_task, timeout=gather_timeout) + except asyncio.TimeoutError: + response_set = None + + if response_set: + content = " ".join([item[1] for item in response_set if item[0] == "text"]) + + # 模型炸了,没有回复内容生成 + if not response_set or (action_type not in ["no_action"] and not is_parallel): + if not response_set: + logger.warning(f"[{self.log_prefix}] 模型未生成回复内容") + elif action_type not in ["no_action"] and not is_parallel: + logger.info( + f"[{self.log_prefix}] {global_config.bot.nickname} 原本想要回复:{content},但选择执行{action_type},不发表回复" + ) + return False + + logger.info(f"[{self.log_prefix}] {global_config.bot.nickname} 决定的回复内容: {content}") + + # 发送回复 (不再需要传入 chat) + await self._send_response(response_set, reply_to_str, loop_start_time) + + return True + else: - action_str = action_type + # 动作执行计时 + with Timer("动作执行", cycle_timers): + success, reply_text, command = await self._handle_action( + action_type, reasoning, action_data, cycle_timers, thinking_id + ) - logger.debug(f"{self.log_prefix} 麦麦想要:'{action_str}',理由是:{reasoning}") - - # 动作执行计时 - with Timer("动作执行", cycle_timers): - success, reply_text, command = await self._handle_action( - action_type, reasoning, action_data, cycle_timers, thinking_id - ) - - loop_action_info = { - "action_taken": success, - "reply_text": reply_text, - "command": command, - "taken_time": time.time(), + loop_info = { + "loop_plan_info": { + "action_result": plan_result.get("action_result", {}), + }, + "loop_action_info": { + "action_taken": success, + "reply_text": reply_text, + "command": command, + "taken_time": time.time(), + }, } - loop_info = { - "loop_plan_info": loop_plan_info, - "loop_action_info": loop_action_info, - } + if loop_info["loop_action_info"]["command"] == "stop_focus_chat": + logger.info(f"{self.log_prefix} 麦麦决定停止专注聊天") + return False + # 停止该聊天模式的循环 - return loop_info + self.end_cycle(loop_info, cycle_timers) + self.print_cycle_info(cycle_timers) - except Exception as e: - logger.error(f"{self.log_prefix} FOCUS聊天处理失败: {e}") - logger.error(traceback.format_exc()) - return { - "loop_plan_info": { - "action_result": {"action_type": "error", "action_data": {}, "reasoning": f"处理失败: {e}"}, - }, - "loop_action_info": {"action_taken": False, "reply_text": "", "command": "", "taken_time": time.time()}, - } + if self.loop_mode == "normal": + await self.willing_manager.after_generate_reply_handle(message_data.get("message_id")) + + return True + + async def _main_chat_loop(self): + """主循环,持续进行计划并可能回复消息,直到被外部取消。""" + try: + while self.running: # 主循环 + success = await self._loopbody() + await asyncio.sleep(0.1) + if not success: + break + + logger.info(f"{self.log_prefix} 麦麦已强制离开聊天") + except asyncio.CancelledError: + # 设置了关闭标志位后被取消是正常流程 + logger.info(f"{self.log_prefix} 麦麦已关闭聊天") + except Exception: + logger.error(f"{self.log_prefix} 麦麦聊天意外错误") + print(traceback.format_exc()) + # 理论上不能到这里 + logger.error(f"{self.log_prefix} 麦麦聊天意外错误,结束了聊天循环") async def _handle_action( self, @@ -436,7 +394,6 @@ class HeartFChatting: thinking_id=thinking_id, chat_stream=self.chat_stream, log_prefix=self.log_prefix, - shutting_down=self._shutting_down, ) except Exception as e: logger.error(f"{self.log_prefix} 创建动作处理器时出错: {e}") @@ -452,32 +409,7 @@ class HeartFChatting: success, reply_text = result command = "" - # 检查action_data中是否有系统命令,优先使用系统命令 - if "_system_command" in action_data: - command = action_data["_system_command"] - logger.debug(f"{self.log_prefix} 从action_data中获取系统命令: {command}") - - # 新增:消息计数和疲惫检查 - if action == "reply" and success: - self._message_count += 1 - current_threshold = self._get_current_fatigue_threshold() - logger.info( - f"{self.log_prefix} 已发送第 {self._message_count} 条消息(动态阈值: {current_threshold}, exit_focus_threshold: {global_config.chat.exit_focus_threshold})" - ) - - # 检查是否达到疲惫阈值(只有在auto模式下才会自动退出) - if ( - global_config.chat.chat_mode == "auto" - and self._message_count >= current_threshold - and not self._fatigue_triggered - ): - self._fatigue_triggered = True - logger.info( - f"{self.log_prefix} [auto模式] 已发送 {self._message_count} 条消息,达到疲惫阈值 {current_threshold},麦麦感到疲惫了,准备退出专注聊天模式" - ) - # 设置系统命令,在下次循环检查时触发退出 - command = "stop_focus_chat" - elif reply_text == "timeout": + if reply_text == "timeout": self.reply_timeout_count += 1 if self.reply_timeout_count > 5: logger.warning( @@ -493,38 +425,10 @@ class HeartFChatting: traceback.print_exc() return False, "", "" - def _get_current_fatigue_threshold(self) -> int: - """动态获取当前的疲惫阈值,基于exit_focus_threshold配置 - - Returns: - int: 当前的疲惫阈值 - """ - return max(10, int(30 / global_config.chat.exit_focus_threshold)) - - def get_message_count_info(self) -> dict: - """获取消息计数信息 - - Returns: - dict: 包含消息计数信息的字典 - """ - current_threshold = self._get_current_fatigue_threshold() - return { - "current_count": self._message_count, - "threshold": current_threshold, - "fatigue_triggered": self._fatigue_triggered, - "remaining": max(0, current_threshold - self._message_count), - } - - def reset_message_count(self): - """重置消息计数器(用于重新启动focus模式时)""" - self._message_count = 0 - self._fatigue_triggered = False - logger.info(f"{self.log_prefix} 消息计数器已重置") - async def shutdown(self): """优雅关闭HeartFChatting实例,取消活动循环任务""" logger.info(f"{self.log_prefix} 正在关闭HeartFChatting...") - self._shutting_down = True # <-- 在开始关闭时设置标志位 + self.running = False # <-- 在开始关闭时设置标志位 # 记录最终的消息统计 if self._message_count > 0: @@ -547,34 +451,183 @@ class HeartFChatting: logger.info(f"{self.log_prefix} 没有活动的HeartFChatting循环任务") # 清理状态 - self._loop_active = False + self.running = False self._loop_task = None - if self._processing_lock.locked(): - self._processing_lock.release() - logger.warning(f"{self.log_prefix} 已释放处理锁") - - # 完成性能统计 - try: - self.performance_logger.finalize_session() - logger.info(f"{self.log_prefix} 性能统计已完成") - except Exception as e: - logger.warning(f"{self.log_prefix} 完成性能统计时出错: {e}") # 重置消息计数器,为下次启动做准备 self.reset_message_count() logger.info(f"{self.log_prefix} HeartFChatting关闭完成") - def get_cycle_history(self, last_n: Optional[int] = None) -> List[Dict[str, Any]]: - """获取循环历史记录 - - 参数: - last_n: 获取最近n个循环的信息,如果为None则获取所有历史记录 - - 返回: - List[Dict[str, Any]]: 循环历史记录列表 + def adjust_reply_frequency(self): """ - history = list(self._cycle_history) - if last_n is not None: - history = history[-last_n:] - return [cycle.to_dict() for cycle in history] + 根据预设规则动态调整回复意愿(willing_amplifier)。 + - 评估周期:10分钟 + - 目标频率:由 global_config.chat.talk_frequency 定义(例如 1条/分钟) + - 调整逻辑: + - 0条回复 -> 5.0x 意愿 + - 达到目标回复数 -> 1.0x 意愿(基准) + - 达到目标2倍回复数 -> 0.2x 意愿 + - 中间值线性变化 + - 增益抑制:如果最近5分钟回复过快,则不增加意愿。 + """ + # --- 1. 定义参数 --- + evaluation_minutes = 10.0 + target_replies_per_min = global_config.chat.get_current_talk_frequency( + self.stream_id + ) # 目标频率:e.g. 1条/分钟 + target_replies_in_window = target_replies_per_min * evaluation_minutes # 10分钟内的目标回复数 + + if target_replies_in_window <= 0: + logger.debug(f"[{self.log_prefix}] 目标回复频率为0或负数,不调整意愿放大器。") + return + + # --- 2. 获取近期统计数据 --- + stats_10_min = get_recent_message_stats(minutes=evaluation_minutes, chat_id=self.stream_id) + bot_reply_count_10_min = stats_10_min["bot_reply_count"] + + # --- 3. 计算新的意愿放大器 (willing_amplifier) --- + # 基于回复数在 [0, target*2] 区间内进行分段线性映射 + if bot_reply_count_10_min <= target_replies_in_window: + # 在 [0, 目标数] 区间,意愿从 5.0 线性下降到 1.0 + new_amplifier = 5.0 + (bot_reply_count_10_min - 0) * (1.0 - 5.0) / (target_replies_in_window - 0) + elif bot_reply_count_10_min <= target_replies_in_window * 2: + # 在 [目标数, 目标数*2] 区间,意愿从 1.0 线性下降到 0.2 + over_target_cap = target_replies_in_window * 2 + new_amplifier = 1.0 + (bot_reply_count_10_min - target_replies_in_window) * (0.2 - 1.0) / ( + over_target_cap - target_replies_in_window + ) + else: + # 超过目标数2倍,直接设为最小值 + new_amplifier = 0.2 + + # --- 4. 检查是否需要抑制增益 --- + # "如果邻近5分钟内,回复数量 > 频率/2,就不再进行增益" + suppress_gain = False + if new_amplifier > self.willing_amplifier: # 仅在计算结果为增益时检查 + suppression_minutes = 5.0 + # 5分钟内目标回复数的一半 + suppression_threshold = (target_replies_per_min / 2) * suppression_minutes # e.g., (1/2)*5 = 2.5 + stats_5_min = get_recent_message_stats(minutes=suppression_minutes, chat_id=self.stream_id) + bot_reply_count_5_min = stats_5_min["bot_reply_count"] + + if bot_reply_count_5_min > suppression_threshold: + suppress_gain = True + + # --- 5. 更新意愿放大器 --- + if suppress_gain: + logger.debug( + f"[{self.log_prefix}] 回复增益被抑制。最近5分钟内回复数 ({bot_reply_count_5_min}) " + f"> 阈值 ({suppression_threshold:.1f})。意愿放大器保持在 {self.willing_amplifier:.2f}" + ) + # 不做任何改动 + else: + # 限制最终值在 [0.2, 5.0] 范围内 + self.willing_amplifier = max(0.2, min(5.0, new_amplifier)) + logger.debug( + f"[{self.log_prefix}] 调整回复意愿。10分钟内回复: {bot_reply_count_10_min} (目标: {target_replies_in_window:.0f}) -> " + f"意愿放大器更新为: {self.willing_amplifier:.2f}" + ) + + async def normal_response(self, message_data: dict) -> None: + """ + 处理接收到的消息。 + 在"兴趣"模式下,判断是否回复并生成内容。 + """ + + is_mentioned = message_data.get("is_mentioned", False) + interested_rate = message_data.get("interest_rate", 0.0) * self.willing_amplifier + + reply_probability = ( + 1.0 if is_mentioned and global_config.normal_chat.mentioned_bot_inevitable_reply else 0.0 + ) # 如果被提及,且开启了提及必回复,则基础概率为1,否则需要意愿判断 + + # 意愿管理器:设置当前message信息 + self.willing_manager.setup(message_data, self.chat_stream) + + # 获取回复概率 + # 仅在未被提及或基础概率不为1时查询意愿概率 + if reply_probability < 1: # 简化逻辑,如果未提及 (reply_probability 为 0),则获取意愿概率 + # is_willing = True + reply_probability = await self.willing_manager.get_reply_probability(message_data.get("message_id")) + + additional_config = message_data.get("additional_config", {}) + if additional_config and "maimcore_reply_probability_gain" in additional_config: + reply_probability += additional_config["maimcore_reply_probability_gain"] + reply_probability = min(max(reply_probability, 0), 1) # 确保概率在 0-1 之间 + + # 处理表情包 + if message_data.get("is_emoji") or message_data.get("is_picid"): + reply_probability = 0 + + # 打印消息信息 + mes_name = self.chat_stream.group_info.group_name if self.chat_stream.group_info else "私聊" + if reply_probability > 0.1: + logger.info( + f"[{mes_name}]" + f"{message_data.get('user_nickname')}:" + f"{message_data.get('processed_plain_text')}[兴趣:{interested_rate:.2f}][回复概率:{reply_probability * 100:.1f}%]" + ) + + if random.random() < reply_probability: + await self.willing_manager.before_generate_reply_handle(message_data.get("message_id")) + await self._observe(message_data=message_data) + + # 意愿管理器:注销当前message信息 (无论是否回复,只要处理过就删除) + self.willing_manager.delete(message_data.get("message_id")) + + return True + + async def _generate_response( + self, message_data: dict, available_actions: Optional[list], reply_to: str + ) -> Optional[list]: + """生成普通回复""" + try: + success, reply_set = await generator_api.generate_reply( + chat_stream=self.chat_stream, + reply_to=reply_to, + available_actions=available_actions, + enable_tool=global_config.tool.enable_in_normal_chat, + request_type="normal.replyer", + ) + + if not success or not reply_set: + logger.info(f"对 {message_data.get('processed_plain_text')} 的回复生成失败") + return None + + return reply_set + + except Exception as e: + logger.error(f"[{self.log_prefix}] 回复生成出现错误:{str(e)} {traceback.format_exc()}") + return None + + async def _send_response(self, reply_set, reply_to, thinking_start_time): + current_time = time.time() + new_message_count = message_api.count_new_messages( + chat_id=self.chat_stream.stream_id, start_time=thinking_start_time, end_time=current_time + ) + + need_reply = new_message_count >= random.randint(2, 4) + + logger.info( + f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,{'使用' if need_reply else '不使用'}引用回复" + ) + + reply_text = "" + first_replyed = False + for reply_seg in reply_set: + data = reply_seg[1] + if not first_replyed: + if need_reply: + await send_api.text_to_stream( + text=data, stream_id=self.chat_stream.stream_id, reply_to=reply_to, typing=False + ) + first_replyed = True + else: + await send_api.text_to_stream(text=data, stream_id=self.chat_stream.stream_id, typing=False) + first_replyed = True + else: + await send_api.text_to_stream(text=data, stream_id=self.chat_stream.stream_id, typing=True) + reply_text += data + + return reply_text diff --git a/src/chat/focus_chat/hfc_performance_logger.py b/src/chat/focus_chat/hfc_performance_logger.py deleted file mode 100644 index 702a8445f..000000000 --- a/src/chat/focus_chat/hfc_performance_logger.py +++ /dev/null @@ -1,162 +0,0 @@ -import json -from datetime import datetime -from typing import Dict, Any -from pathlib import Path - -from src.common.logger import get_logger - -logger = get_logger("hfc_performance") - - -class HFCPerformanceLogger: - """HFC性能记录管理器""" - - # 版本号常量,可在启动时修改 - INTERNAL_VERSION = "v7.0.0" - - def __init__(self, chat_id: str): - self.chat_id = chat_id - self.version = self.INTERNAL_VERSION - self.log_dir = Path("log/hfc_loop") - self.session_start_time = datetime.now() - - # 确保目录存在 - self.log_dir.mkdir(parents=True, exist_ok=True) - - # 当前会话的日志文件,包含版本号 - version_suffix = self.version.replace(".", "_") - self.session_file = ( - self.log_dir / f"{chat_id}_{version_suffix}_{self.session_start_time.strftime('%Y%m%d_%H%M%S')}.json" - ) - self.current_session_data = [] - - def record_cycle(self, cycle_data: Dict[str, Any]): - """记录单次循环数据""" - try: - # 构建记录数据 - record = { - "timestamp": datetime.now().isoformat(), - "version": self.version, - "cycle_id": cycle_data.get("cycle_id"), - "chat_id": self.chat_id, - "action_type": cycle_data.get("action_type", "unknown"), - "total_time": cycle_data.get("total_time", 0), - "step_times": cycle_data.get("step_times", {}), - "reasoning": cycle_data.get("reasoning", ""), - "success": cycle_data.get("success", False), - } - - # 添加到当前会话数据 - self.current_session_data.append(record) - - # 立即写入文件(防止数据丢失) - self._write_session_data() - - # 构建详细的日志信息 - log_parts = [ - f"cycle_id={record['cycle_id']}", - f"action={record['action_type']}", - f"time={record['total_time']:.2f}s", - ] - - logger.debug(f"记录HFC循环数据: {', '.join(log_parts)}") - - except Exception as e: - logger.error(f"记录HFC循环数据失败: {e}") - - def _write_session_data(self): - """写入当前会话数据到文件""" - try: - with open(self.session_file, "w", encoding="utf-8") as f: - json.dump(self.current_session_data, f, ensure_ascii=False, indent=2) - except Exception as e: - logger.error(f"写入会话数据失败: {e}") - - def get_current_session_stats(self) -> Dict[str, Any]: - """获取当前会话的基本信息""" - if not self.current_session_data: - return {} - - return { - "chat_id": self.chat_id, - "version": self.version, - "session_file": str(self.session_file), - "record_count": len(self.current_session_data), - "start_time": self.session_start_time.isoformat(), - } - - def finalize_session(self): - """结束会话""" - try: - if self.current_session_data: - logger.info(f"完成会话,当前会话 {len(self.current_session_data)} 条记录") - except Exception as e: - logger.error(f"结束会话失败: {e}") - - @classmethod - def cleanup_old_logs(cls, max_size_mb: float = 50.0): - """ - 清理旧的HFC日志文件,保持目录大小在指定限制内 - - Args: - max_size_mb: 最大目录大小限制(MB) - """ - log_dir = Path("log/hfc_loop") - if not log_dir.exists(): - logger.info("HFC日志目录不存在,跳过日志清理") - return - - # 获取所有日志文件及其信息 - log_files = [] - total_size = 0 - - for log_file in log_dir.glob("*.json"): - try: - file_stat = log_file.stat() - log_files.append({"path": log_file, "size": file_stat.st_size, "mtime": file_stat.st_mtime}) - total_size += file_stat.st_size - except Exception as e: - logger.warning(f"无法获取文件信息 {log_file}: {e}") - - if not log_files: - logger.info("没有找到HFC日志文件") - return - - max_size_bytes = max_size_mb * 1024 * 1024 - current_size_mb = total_size / (1024 * 1024) - - logger.info(f"HFC日志目录当前大小: {current_size_mb:.2f}MB,限制: {max_size_mb}MB") - - if total_size <= max_size_bytes: - logger.info("HFC日志目录大小在限制范围内,无需清理") - return - - # 按修改时间排序(最早的在前面) - log_files.sort(key=lambda x: x["mtime"]) - - deleted_count = 0 - deleted_size = 0 - - for file_info in log_files: - if total_size <= max_size_bytes: - break - - try: - file_size = file_info["size"] - file_path = file_info["path"] - - file_path.unlink() - total_size -= file_size - deleted_size += file_size - deleted_count += 1 - - logger.info(f"删除旧日志文件: {file_path.name} ({file_size / 1024:.1f}KB)") - - except Exception as e: - logger.error(f"删除日志文件失败 {file_info['path']}: {e}") - - final_size_mb = total_size / (1024 * 1024) - deleted_size_mb = deleted_size / (1024 * 1024) - - logger.info(f"HFC日志清理完成: 删除了{deleted_count}个文件,释放{deleted_size_mb:.2f}MB空间") - logger.info(f"清理后目录大小: {final_size_mb:.2f}MB") diff --git a/src/chat/focus_chat/hfc_utils.py b/src/chat/focus_chat/hfc_utils.py index 0393c2175..fb674ead8 100644 --- a/src/chat/focus_chat/hfc_utils.py +++ b/src/chat/focus_chat/hfc_utils.py @@ -3,22 +3,21 @@ import json from typing import Optional, Dict, Any +from src.config.config import global_config +from src.common.message_repository import count_messages +from src.common.logger import get_logger from src.chat.message_receive.message import MessageRecv, BaseMessageInfo from src.chat.message_receive.chat_stream import ChatStream from src.chat.message_receive.message import UserInfo -from src.common.logger import get_logger logger = get_logger(__name__) -log_dir = "log/log_cycle_debug/" - class CycleDetail: """循环信息记录类""" def __init__(self, cycle_id: int): self.cycle_id = cycle_id - self.prefix = "" self.thinking_id = "" self.start_time = time.time() self.end_time: Optional[float] = None @@ -80,85 +79,34 @@ class CycleDetail: "loop_action_info": convert_to_serializable(self.loop_action_info), } - def complete_cycle(self): - """完成循环,记录结束时间""" - self.end_time = time.time() - - # 处理 prefix,只保留中英文字符和基本标点 - if not self.prefix: - self.prefix = "group" - else: - # 只保留中文、英文字母、数字和基本标点 - allowed_chars = set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_") - self.prefix = ( - "".join(char for char in self.prefix if "\u4e00" <= char <= "\u9fff" or char in allowed_chars) - or "group" - ) - - def set_thinking_id(self, thinking_id: str): - """设置思考消息ID""" - self.thinking_id = thinking_id - def set_loop_info(self, loop_info: Dict[str, Any]): """设置循环信息""" self.loop_plan_info = loop_info["loop_plan_info"] self.loop_action_info = loop_info["loop_action_info"] -async def create_empty_anchor_message( - platform: str, group_info: dict, chat_stream: ChatStream -) -> Optional[MessageRecv]: +def get_recent_message_stats(minutes: int = 30, chat_id: str = None) -> dict: """ - 重构观察到的最后一条消息作为回复的锚点, - 如果重构失败或观察为空,则创建一个占位符。 + Args: + minutes (int): 检索的分钟数,默认30分钟 + chat_id (str, optional): 指定的chat_id,仅统计该chat下的消息。为None时统计全部。 + Returns: + dict: {"bot_reply_count": int, "total_message_count": int} """ - placeholder_id = f"mid_pf_{int(time.time() * 1000)}" - placeholder_user = UserInfo(user_id="system_trigger", user_nickname="System Trigger", platform=platform) - placeholder_msg_info = BaseMessageInfo( - message_id=placeholder_id, - platform=platform, - group_info=group_info, # type: ignore - user_info=placeholder_user, - time=time.time(), - ) - placeholder_msg_dict = { - "message_info": placeholder_msg_info.to_dict(), - "processed_plain_text": "[System Trigger Context]", - "raw_message": "", - "time": placeholder_msg_info.time, - } - anchor_message = MessageRecv(placeholder_msg_dict) - anchor_message.update_chat_stream(chat_stream) + now = time.time() + start_time = now - minutes * 60 + bot_id = global_config.bot.qq_account - return anchor_message + filter_base = {"time": {"$gte": start_time}} + if chat_id is not None: + filter_base["chat_id"] = chat_id + # 总消息数 + total_message_count = count_messages(filter_base) + # bot自身回复数 + bot_filter = filter_base.copy() + bot_filter["user_id"] = bot_id + bot_reply_count = count_messages(bot_filter) -def parse_thinking_id_to_timestamp(thinking_id: str) -> float: - """ - 将形如 'tid' 的 thinking_id 解析回 float 时间戳 - 例如: 'tid1718251234.56' -> 1718251234.56 - """ - if not thinking_id.startswith("tid"): - raise ValueError("thinking_id 格式不正确") - ts_str = thinking_id[3:] - return float(ts_str) - - -def get_keywords_from_json(json_str: str) -> list[str]: - # 提取JSON内容 - start = json_str.find("{") - end = json_str.rfind("}") + 1 - if start == -1 or end == 0: - logger.error("未找到有效的JSON内容") - return [] - - json_content = json_str[start:end] - - # 解析JSON - try: - json_data = json.loads(json_content) - return json_data.get("keywords", []) - except json.JSONDecodeError as e: - logger.error(f"JSON解析失败: {e}") - return [] + return {"bot_reply_count": bot_reply_count, "total_message_count": total_message_count} diff --git a/src/chat/normal_chat/priority_manager.py b/src/chat/focus_chat/priority_manager.py similarity index 72% rename from src/chat/normal_chat/priority_manager.py rename to src/chat/focus_chat/priority_manager.py index 8c1c0e731..4c69c7eab 100644 --- a/src/chat/normal_chat/priority_manager.py +++ b/src/chat/focus_chat/priority_manager.py @@ -1,9 +1,8 @@ import time import heapq import math -from typing import List, Dict, Optional - -from src.chat.message_receive.message import MessageRecv +import json +from typing import List, Optional from src.common.logger import get_logger logger = get_logger("normal_chat") @@ -12,8 +11,8 @@ logger = get_logger("normal_chat") class PrioritizedMessage: """带有优先级的消息对象""" - def __init__(self, message: MessageRecv, interest_scores: List[float], is_vip: bool = False): - self.message = message + def __init__(self, message_data: dict, interest_scores: List[float], is_vip: bool = False): + self.message_data = message_data self.arrival_time = time.time() self.interest_scores = interest_scores self.is_vip = is_vip @@ -39,25 +38,28 @@ class PriorityManager: 管理消息队列,根据优先级选择消息进行处理。 """ - def __init__(self, interest_dict: Dict[str, float], normal_queue_max_size: int = 5): + def __init__(self, normal_queue_max_size: int = 5): self.vip_queue: List[PrioritizedMessage] = [] # VIP 消息队列 (最大堆) self.normal_queue: List[PrioritizedMessage] = [] # 普通消息队列 (最大堆) - self.interest_dict = interest_dict if interest_dict is not None else {} self.normal_queue_max_size = normal_queue_max_size - def _get_interest_score(self, user_id: str) -> float: - """获取用户的兴趣分,默认为1.0""" - return self.interest_dict.get("interests", {}).get(user_id, 1.0) - - def add_message(self, message: MessageRecv, interest_score: Optional[float] = None): + def add_message(self, message_data: dict, interest_score: Optional[float] = None): """ 添加新消息到合适的队列中。 """ - user_id = message.message_info.user_info.user_id # type: ignore - is_vip = message.priority_info.get("message_type") == "vip" if message.priority_info else False - message_priority = message.priority_info.get("message_priority", 0.0) if message.priority_info else 0.0 + user_id = message_data.get("user_id") - p_message = PrioritizedMessage(message, [interest_score, message_priority], is_vip) + priority_info_raw = message_data.get("priority_info") + priority_info = {} + if isinstance(priority_info_raw, str): + priority_info = json.loads(priority_info_raw) + elif isinstance(priority_info_raw, dict): + priority_info = priority_info_raw + + is_vip = priority_info.get("message_type") == "vip" + message_priority = priority_info.get("message_priority", 0.0) + + p_message = PrioritizedMessage(message_data, [interest_score, message_priority], is_vip) if is_vip: heapq.heappush(self.vip_queue, p_message) @@ -76,7 +78,7 @@ class PriorityManager: f"消息来自普通用户 {user_id}, 已添加到普通队列. 当前普通队列长度: {len(self.normal_queue)}" ) - def get_highest_priority_message(self) -> Optional[MessageRecv]: + def get_highest_priority_message(self) -> Optional[dict]: """ 从VIP和普通队列中获取当前最高优先级的消息。 """ @@ -94,9 +96,9 @@ class PriorityManager: normal_msg = self.normal_queue[0] if self.normal_queue else None if vip_msg: - return heapq.heappop(self.vip_queue).message + return heapq.heappop(self.vip_queue).message_data elif normal_msg: - return heapq.heappop(self.normal_queue).message + return heapq.heappop(self.normal_queue).message_data else: return None diff --git a/src/chat/heart_flow/chat_state_info.py b/src/chat/heart_flow/chat_state_info.py deleted file mode 100644 index 9f137a953..000000000 --- a/src/chat/heart_flow/chat_state_info.py +++ /dev/null @@ -1,16 +0,0 @@ -import enum - - -class ChatState(enum.Enum): - ABSENT = "没在看群" - NORMAL = "随便水群" - FOCUSED = "认真水群" - - def __str__(self): - return self.name - - -class ChatStateInfo: - def __init__(self): - self.chat_status: ChatState = ChatState.NORMAL - self.current_state_time = 120 diff --git a/src/chat/heart_flow/heartflow.py b/src/chat/heart_flow/heartflow.py index 4c5285259..111b37e64 100644 --- a/src/chat/heart_flow/heartflow.py +++ b/src/chat/heart_flow/heartflow.py @@ -1,7 +1,8 @@ +import traceback from typing import Any, Optional, Dict from src.common.logger import get_logger -from src.chat.heart_flow.sub_heartflow import SubHeartflow, ChatState +from src.chat.heart_flow.sub_heartflow import SubHeartflow from src.chat.message_receive.chat_stream import get_chat_manager logger = get_logger("heartflow") @@ -27,27 +28,13 @@ class Heartflow: # 注册子心流 self.subheartflows[subheartflow_id] = new_subflow heartflow_name = get_chat_manager().get_stream_name(subheartflow_id) or subheartflow_id - logger.debug(f"[{heartflow_name}] 开始接收消息") + logger.info(f"[{heartflow_name}] 开始接收消息") return new_subflow except Exception as e: logger.error(f"创建子心流 {subheartflow_id} 失败: {e}", exc_info=True) + traceback.print_exc() return None - async def force_change_subheartflow_status(self, subheartflow_id: str, status: ChatState) -> bool: - """强制改变子心流的状态""" - # 这里的 message 是可选的,可能是一个消息对象,也可能是其他类型的数据 - return await self.force_change_state(subheartflow_id, status) - - async def force_change_state(self, subflow_id: Any, target_state: ChatState) -> bool: - """强制改变指定子心流的状态""" - subflow = self.subheartflows.get(subflow_id) - if not subflow: - logger.warning(f"[强制状态转换]尝试转换不存在的子心流{subflow_id} 到 {target_state.value}") - return False - await subflow.change_chat_state(target_state) - logger.info(f"[强制状态转换]子心流 {subflow_id} 已转换到 {target_state.value}") - return True - heartflow = Heartflow() diff --git a/src/chat/heart_flow/heartflow_message_processor.py b/src/chat/heart_flow/heartflow_message_processor.py index dd267b079..076ef0c06 100644 --- a/src/chat/heart_flow/heartflow_message_processor.py +++ b/src/chat/heart_flow/heartflow_message_processor.py @@ -104,12 +104,13 @@ class HeartFCMessageReceiver: # 2. 兴趣度计算与更新 interested_rate, is_mentioned = await _calculate_interest(message) message.interest_value = interested_rate + message.is_mentioned = is_mentioned await self.storage.store_message(message, chat) subheartflow: SubHeartflow = await heartflow.get_or_create_subheartflow(chat.stream_id) # type: ignore - subheartflow.add_message_to_normal_chat_cache(message, interested_rate, is_mentioned) + # subheartflow.add_message_to_normal_chat_cache(message, interested_rate, is_mentioned) chat_mood = mood_manager.get_mood_by_chat_id(subheartflow.chat_id) # type: ignore asyncio.create_task(chat_mood.update_mood_by_message(message, interested_rate)) diff --git a/src/chat/heart_flow/sub_heartflow.py b/src/chat/heart_flow/sub_heartflow.py index fc230e255..383fbb74e 100644 --- a/src/chat/heart_flow/sub_heartflow.py +++ b/src/chat/heart_flow/sub_heartflow.py @@ -10,8 +10,6 @@ from src.config.config import global_config from src.chat.message_receive.message import MessageRecv from src.chat.message_receive.chat_stream import get_chat_manager from src.chat.focus_chat.heartFC_chat import HeartFChatting -from src.chat.normal_chat.normal_chat import NormalChat -from src.chat.heart_flow.chat_state_info import ChatState, ChatStateInfo from src.chat.utils.utils import get_chat_type_and_target_info logger = get_logger("sub_heartflow") @@ -33,278 +31,60 @@ class SubHeartflow: self.subheartflow_id = subheartflow_id self.chat_id = subheartflow_id - # 这个聊天流的状态 - self.chat_state: ChatStateInfo = ChatStateInfo() - self.chat_state_changed_time: float = time.time() - self.chat_state_last_time: float = 0 - self.history_chat_state: List[Tuple[ChatState, float]] = [] - self.is_group_chat, self.chat_target_info = get_chat_type_and_target_info(self.chat_id) self.log_prefix = get_chat_manager().get_stream_name(self.subheartflow_id) or self.subheartflow_id - # 兴趣消息集合 - self.interest_dict: Dict[str, Tuple[MessageRecv, float, bool]] = {} # focus模式退出冷却时间管理 self.last_focus_exit_time: float = 0 # 上次退出focus模式的时间 # 随便水群 normal_chat 和 认真水群 focus_chat 实例 # CHAT模式激活 随便水群 FOCUS模式激活 认真水群 - self.heart_fc_instance: Optional[HeartFChatting] = None # 该sub_heartflow的HeartFChatting实例 - self.normal_chat_instance: Optional[NormalChat] = None # 该sub_heartflow的NormalChat实例 + self.heart_fc_instance: Optional[HeartFChatting] = HeartFChatting( + chat_id=self.subheartflow_id, + ) # 该sub_heartflow的HeartFChatting实例 async def initialize(self): """异步初始化方法,创建兴趣流并确定聊天类型""" - - # 根据配置决定初始状态 - if not self.is_group_chat: - logger.debug(f"{self.log_prefix} 检测到是私聊,将直接尝试进入 FOCUSED 状态。") - await self.change_chat_state(ChatState.FOCUSED) - elif global_config.chat.chat_mode == "focus": - logger.debug(f"{self.log_prefix} 配置为 focus 模式,将直接尝试进入 FOCUSED 状态。") - await self.change_chat_state(ChatState.FOCUSED) - else: # "auto" 或其他模式保持原有逻辑或默认为 NORMAL - logger.debug(f"{self.log_prefix} 配置为 auto 或其他模式,将尝试进入 NORMAL 状态。") - await self.change_chat_state(ChatState.NORMAL) - - def update_last_chat_state_time(self): - self.chat_state_last_time = time.time() - self.chat_state_changed_time - - async def _stop_normal_chat(self): - """ - 停止 NormalChat 实例 - 切出 CHAT 状态时使用 - """ - if not self.normal_chat_instance: - return - logger.info(f"{self.log_prefix} 离开normal模式") - try: - logger.debug(f"{self.log_prefix} 开始调用 stop_chat()") - # 使用更短的超时时间,强制快速停止 - await asyncio.wait_for(self.normal_chat_instance.stop_chat(), timeout=3.0) - logger.debug(f"{self.log_prefix} stop_chat() 调用完成") - except asyncio.TimeoutError: - logger.warning(f"{self.log_prefix} 停止 NormalChat 超时,强制清理") - # 超时时强制清理实例 - self.normal_chat_instance = None - except Exception as e: - logger.error(f"{self.log_prefix} 停止 NormalChat 监控任务时出错: {e}") - # 出错时也要清理实例,避免状态不一致 - self.normal_chat_instance = None - finally: - # 确保实例被清理 - if self.normal_chat_instance: - logger.warning(f"{self.log_prefix} 强制清理 NormalChat 实例") - self.normal_chat_instance = None - logger.debug(f"{self.log_prefix} _stop_normal_chat 完成") - - async def _start_normal_chat(self, rewind=False) -> bool: - """ - 启动 NormalChat 实例,并进行异步初始化。 - 进入 CHAT 状态时使用。 - 确保 HeartFChatting 已停止。 - """ - await self._stop_heart_fc_chat() # 确保 专注聊天已停止 - - self.interest_dict.clear() - - log_prefix = self.log_prefix - try: - # 获取聊天流并创建 NormalChat 实例 (同步部分) - chat_stream = get_chat_manager().get_stream(self.chat_id) - if not chat_stream: - logger.error(f"{log_prefix} 无法获取 chat_stream,无法启动 NormalChat。") - return False - # 在 rewind 为 True 或 NormalChat 实例尚未创建时,创建新实例 - if rewind or not self.normal_chat_instance: - # 提供回调函数,用于接收需要切换到focus模式的通知 - self.normal_chat_instance = NormalChat( - chat_stream=chat_stream, - interest_dict=self.interest_dict, - on_switch_to_focus_callback=self._handle_switch_to_focus_request, - get_cooldown_progress_callback=self.get_cooldown_progress, - ) - - logger.info(f"{log_prefix} 开始普通聊天,随便水群...") - await self.normal_chat_instance.start_chat() # start_chat now ensures init is called again if needed - return True - except Exception as e: - logger.error(f"{log_prefix} 启动 NormalChat 或其初始化时出错: {e}") - logger.error(traceback.format_exc()) - self.normal_chat_instance = None # 启动/初始化失败,清理实例 - return False - - async def _handle_switch_to_focus_request(self) -> bool: - """ - 处理来自NormalChat的切换到focus模式的请求 - - Args: - stream_id: 请求切换的stream_id - Returns: - bool: 切换成功返回True,失败返回False - """ - logger.info(f"{self.log_prefix} 收到NormalChat请求切换到focus模式") - - # 检查是否在focus冷却期内 - if self.is_in_focus_cooldown(): - logger.info(f"{self.log_prefix} 正在focus冷却期内,忽略切换到focus模式的请求") - return False - - # 切换到focus模式 - current_state = self.chat_state.chat_status - if current_state == ChatState.NORMAL: - await self.change_chat_state(ChatState.FOCUSED) - logger.info(f"{self.log_prefix} 已根据NormalChat请求从NORMAL切换到FOCUSED状态") - return True - else: - logger.warning(f"{self.log_prefix} 当前状态为{current_state.value},无法切换到FOCUSED状态") - return False - - async def _handle_stop_focus_chat_request(self) -> None: - """ - 处理来自HeartFChatting的停止focus模式的请求 - 当收到stop_focus_chat命令时被调用 - """ - logger.info(f"{self.log_prefix} 收到HeartFChatting请求停止focus模式") - - # 切换到normal模式 - current_state = self.chat_state.chat_status - if current_state == ChatState.FOCUSED: - await self.change_chat_state(ChatState.NORMAL) - logger.info(f"{self.log_prefix} 已根据HeartFChatting请求从FOCUSED切换到NORMAL状态") - else: - logger.warning(f"{self.log_prefix} 当前状态为{current_state.value},无法切换到NORMAL状态") + await self.heart_fc_instance.start() async def _stop_heart_fc_chat(self): """停止并清理 HeartFChatting 实例""" - if self.heart_fc_instance: - logger.debug(f"{self.log_prefix} 结束专注聊天...") + if self.heart_fc_instance.running: + logger.info(f"{self.log_prefix} 结束专注聊天...") try: await self.heart_fc_instance.shutdown() except Exception as e: logger.error(f"{self.log_prefix} 关闭 HeartFChatting 实例时出错: {e}") logger.error(traceback.format_exc()) - finally: - # 无论是否成功关闭,都清理引用 - self.heart_fc_instance = None + else: + logger.info(f"{self.log_prefix} 没有专注聊天实例,无需停止专注聊天") async def _start_heart_fc_chat(self) -> bool: """启动 HeartFChatting 实例,确保 NormalChat 已停止""" - logger.debug(f"{self.log_prefix} 开始启动 HeartFChatting") - try: - # 确保普通聊天监控已停止 - await self._stop_normal_chat() - self.interest_dict.clear() - - log_prefix = self.log_prefix - # 如果实例已存在,检查其循环任务状态 - if self.heart_fc_instance: - logger.debug(f"{log_prefix} HeartFChatting 实例已存在,检查状态") - # 如果任务已完成或不存在,则尝试重新启动 - if self.heart_fc_instance._loop_task is None or self.heart_fc_instance._loop_task.done(): - logger.info(f"{log_prefix} HeartFChatting 实例存在但循环未运行,尝试启动...") - try: - # 添加超时保护 - await asyncio.wait_for(self.heart_fc_instance.start(), timeout=15.0) - logger.info(f"{log_prefix} HeartFChatting 循环已启动。") - return True - except Exception as e: - logger.error(f"{log_prefix} 尝试启动现有 HeartFChatting 循环时出错: {e}") - logger.error(traceback.format_exc()) - # 出错时清理实例,准备重新创建 - self.heart_fc_instance = None - else: - # 任务正在运行 - logger.debug(f"{log_prefix} HeartFChatting 已在运行中。") - return True # 已经在运行 - - # 如果实例不存在,则创建并启动 - logger.info(f"{log_prefix} 麦麦准备开始专注聊天...") - try: - logger.debug(f"{log_prefix} 创建新的 HeartFChatting 实例") - self.heart_fc_instance = HeartFChatting( - chat_id=self.subheartflow_id, - on_stop_focus_chat=self._handle_stop_focus_chat_request, - ) - - logger.debug(f"{log_prefix} 启动 HeartFChatting 实例") - # 添加超时保护 - await asyncio.wait_for(self.heart_fc_instance.start(), timeout=15.0) - logger.debug(f"{log_prefix} 麦麦已成功进入专注聊天模式 (新实例已启动)。") - return True - - except Exception as e: - logger.error(f"{log_prefix} 创建或启动 HeartFChatting 实例时出错: {e}") - logger.error(traceback.format_exc()) - self.heart_fc_instance = None # 创建或初始化异常,清理实例 - return False + # 如果任务已完成或不存在,则尝试重新启动 + if self.heart_fc_instance._loop_task is None or self.heart_fc_instance._loop_task.done(): + logger.info(f"{self.log_prefix} HeartFChatting 实例存在但循环未运行,尝试启动...") + try: + # 添加超时保护 + await asyncio.wait_for(self.heart_fc_instance.start(), timeout=15.0) + logger.info(f"{self.log_prefix} HeartFChatting 循环已启动。") + return True + except Exception as e: + logger.error(f"{self.log_prefix} 尝试启动现有 HeartFChatting 循环时出错: {e}") + logger.error(traceback.format_exc()) + # 出错时清理实例,准备重新创建 + self.heart_fc_instance = None + else: + # 任务正在运行 + logger.debug(f"{self.log_prefix} HeartFChatting 已在运行中。") + return True # 已经在运行 except Exception as e: logger.error(f"{self.log_prefix} _start_heart_fc_chat 执行时出错: {e}") logger.error(traceback.format_exc()) return False - async def change_chat_state(self, new_state: ChatState) -> None: - """ - 改变聊天状态。 - 如果转换到CHAT或FOCUSED状态时超过限制,会保持当前状态。 - """ - current_state = self.chat_state.chat_status - state_changed = False - log_prefix = f"[{self.log_prefix}]" - - if new_state == ChatState.NORMAL: - logger.debug(f"{log_prefix} 准备进入 normal聊天 状态") - if await self._start_normal_chat(): - logger.debug(f"{log_prefix} 成功进入或保持 NormalChat 状态。") - state_changed = True - else: - logger.error(f"{log_prefix} 启动 NormalChat 失败,无法进入 CHAT 状态。") - # 启动失败时,保持当前状态 - return - - elif new_state == ChatState.FOCUSED: - logger.debug(f"{log_prefix} 准备进入 focus聊天 状态") - if await self._start_heart_fc_chat(): - logger.debug(f"{log_prefix} 成功进入或保持 HeartFChatting 状态。") - state_changed = True - else: - logger.error(f"{log_prefix} 启动 HeartFChatting 失败,无法进入 FOCUSED 状态。") - # 启动失败时,保持当前状态 - return - - elif new_state == ChatState.ABSENT: - logger.info(f"{log_prefix} 进入 ABSENT 状态,停止所有聊天活动...") - self.interest_dict.clear() - await self._stop_normal_chat() - await self._stop_heart_fc_chat() - state_changed = True - - # --- 记录focus模式退出时间 --- - if state_changed and current_state == ChatState.FOCUSED and new_state != ChatState.FOCUSED: - self.last_focus_exit_time = time.time() - logger.debug(f"{log_prefix} 记录focus模式退出时间: {self.last_focus_exit_time}") - - # --- 更新状态和最后活动时间 --- - if state_changed: - self.update_last_chat_state_time() - self.history_chat_state.append((current_state, self.chat_state_last_time)) - - self.chat_state.chat_status = new_state - self.chat_state_last_time = 0 - self.chat_state_changed_time = time.time() - else: - logger.debug( - f"{log_prefix} 尝试将状态从 {current_state.value} 变为 {new_state.value},但未成功或未执行更改。" - ) - - def add_message_to_normal_chat_cache(self, message: MessageRecv, interest_value: float, is_mentioned: bool): - self.interest_dict[message.message_info.message_id] = (message, interest_value, is_mentioned) # type: ignore - # 如果字典长度超过10,删除最旧的消息 - if len(self.interest_dict) > 30: - oldest_key = next(iter(self.interest_dict)) - self.interest_dict.pop(oldest_key) - def is_in_focus_cooldown(self) -> bool: """检查是否在focus模式的冷却期内 diff --git a/src/chat/message_receive/__init__.py b/src/chat/message_receive/__init__.py index d01bea726..44b9eee36 100644 --- a/src/chat/message_receive/__init__.py +++ b/src/chat/message_receive/__init__.py @@ -1,12 +1,10 @@ from src.chat.emoji_system.emoji_manager import get_emoji_manager from src.chat.message_receive.chat_stream import get_chat_manager -from src.chat.message_receive.normal_message_sender import message_manager from src.chat.message_receive.storage import MessageStorage __all__ = [ "get_emoji_manager", "get_chat_manager", - "message_manager", "MessageStorage", ] diff --git a/src/chat/message_receive/bot.py b/src/chat/message_receive/bot.py index 3d1f1e341..13e2a743e 100644 --- a/src/chat/message_receive/bot.py +++ b/src/chat/message_receive/bot.py @@ -84,7 +84,6 @@ class ChatBot: # 创建初始化PFC管理器的任务,会在_ensure_started时执行 self.only_process_chat = MessageProcessor() - self.pfc_manager = PFCManager.get_instance() self.s4u_message_processor = S4UMessageProcessor() async def _ensure_started(self): diff --git a/src/chat/message_receive/message.py b/src/chat/message_receive/message.py index f444c768f..511a4b704 100644 --- a/src/chat/message_receive/message.py +++ b/src/chat/message_receive/message.py @@ -438,3 +438,52 @@ class MessageSet: def __len__(self) -> int: return len(self.messages) + + +def message_recv_from_dict(message_dict: dict) -> MessageRecv: + return MessageRecv(message_dict) + + +def message_from_db_dict(db_dict: dict) -> MessageRecv: + """从数据库字典创建MessageRecv实例""" + # 转换扁平的数据库字典为嵌套结构 + message_info_dict = { + "platform": db_dict.get("chat_info_platform"), + "message_id": db_dict.get("message_id"), + "time": db_dict.get("time"), + "group_info": { + "platform": db_dict.get("chat_info_group_platform"), + "group_id": db_dict.get("chat_info_group_id"), + "group_name": db_dict.get("chat_info_group_name"), + }, + "user_info": { + "platform": db_dict.get("user_platform"), + "user_id": db_dict.get("user_id"), + "user_nickname": db_dict.get("user_nickname"), + "user_cardname": db_dict.get("user_cardname"), + }, + } + + processed_text = db_dict.get("processed_plain_text", "") + + # 构建 MessageRecv 需要的字典 + recv_dict = { + "message_info": message_info_dict, + "message_segment": {"type": "text", "data": processed_text}, # 从纯文本重建消息段 + "raw_message": None, # 数据库中未存储原始消息 + "processed_plain_text": processed_text, + "detailed_plain_text": db_dict.get("detailed_plain_text", ""), + } + + # 创建 MessageRecv 实例 + msg = MessageRecv(recv_dict) + + # 从数据库字典中填充其他可选字段 + msg.interest_value = db_dict.get("interest_value") + msg.is_mentioned = db_dict.get("is_mentioned") + msg.priority_mode = db_dict.get("priority_mode", "interest") + msg.priority_info = db_dict.get("priority_info") + msg.is_emoji = db_dict.get("is_emoji", False) + msg.is_picid = db_dict.get("is_picid", False) + + return msg diff --git a/src/chat/message_receive/normal_message_sender.py b/src/chat/message_receive/normal_message_sender.py deleted file mode 100644 index 95d296473..000000000 --- a/src/chat/message_receive/normal_message_sender.py +++ /dev/null @@ -1,297 +0,0 @@ -import asyncio -import time -from asyncio import Task -from typing import Union -from rich.traceback import install - -from src.common.logger import get_logger -from src.common.message.api import get_global_api -from src.chat.message_receive.storage import MessageStorage -from src.chat.utils.utils import truncate_message, calculate_typing_time, count_messages_between -from .message import MessageSending, MessageThinking, MessageSet - -install(extra_lines=3) - -logger = get_logger("sender") - - -async def send_via_ws(message: MessageSending) -> None: - """通过 WebSocket 发送消息""" - try: - await get_global_api().send_message(message) - except Exception as e: - logger.error(f"WS发送失败: {e}") - raise ValueError(f"未找到平台:{message.message_info.platform} 的url配置,请检查配置文件") from e - - -async def send_message( - message: MessageSending, -) -> None: - """发送消息(核心发送逻辑)""" - - # --- 添加计算打字和延迟的逻辑 (从 heartflow_message_sender 移动并调整) --- - typing_time = calculate_typing_time( - input_string=message.processed_plain_text, - thinking_start_time=message.thinking_start_time, - is_emoji=message.is_emoji, - ) - # logger.debug(f"{message.processed_plain_text},{typing_time},计算输入时间结束") # 减少日志 - await asyncio.sleep(typing_time) - # logger.debug(f"{message.processed_plain_text},{typing_time},等待输入时间结束") # 减少日志 - # --- 结束打字延迟 --- - - message_preview = truncate_message(message.processed_plain_text) - - try: - await send_via_ws(message) - logger.info(f"发送消息 '{message_preview}' 成功") # 调整日志格式 - except Exception as e: - logger.error(f"发送消息 '{message_preview}' 失败: {str(e)}") - - -class MessageSender: - """发送器 (不再是单例)""" - - def __init__(self): - self.message_interval = (0.5, 1) # 消息间隔时间范围(秒) - self.last_send_time = 0 - self._current_bot = None - - def set_bot(self, bot): - """设置当前bot实例""" - pass - - -class MessageContainer: - """单个聊天流的发送/思考消息容器""" - - def __init__(self, chat_id: str, max_size: int = 100): - self.chat_id = chat_id - self.max_size = max_size - self.messages: list[MessageThinking | MessageSending] = [] # 明确类型 - self.last_send_time = 0 - self.thinking_wait_timeout = 20 # 思考等待超时时间(秒) - 从旧 sender 合并 - - def count_thinking_messages(self) -> int: - """计算当前容器中思考消息的数量""" - return sum(isinstance(msg, MessageThinking) for msg in self.messages) - - def get_timeout_sending_messages(self) -> list[MessageSending]: - # sourcery skip: merge-nested-ifs - """获取所有超时的MessageSending对象(思考时间超过20秒),按thinking_start_time排序 - 从旧 sender 合并""" - current_time = time.time() - timeout_messages = [] - - for msg in self.messages: - # 只检查 MessageSending 类型 - if isinstance(msg, MessageSending): - # 确保 thinking_start_time 有效 - if msg.thinking_start_time and current_time - msg.thinking_start_time > self.thinking_wait_timeout: - timeout_messages.append(msg) - - # 按thinking_start_time排序,时间早的在前面 - timeout_messages.sort(key=lambda x: x.thinking_start_time) - return timeout_messages - - def get_earliest_message(self): - """获取thinking_start_time最早的消息对象""" - if not self.messages: - return None - earliest_time = float("inf") - earliest_message = None - for msg in self.messages: - # 确保消息有 thinking_start_time 属性 - msg_time = getattr(msg, "thinking_start_time", float("inf")) - if msg_time < earliest_time: - earliest_time = msg_time - earliest_message = msg - return earliest_message - - def add_message(self, message: Union[MessageThinking, MessageSending, MessageSet]): - """添加消息到队列""" - if isinstance(message, MessageSet): - for single_message in message.messages: - self.messages.append(single_message) - else: - self.messages.append(message) - - def remove_message(self, message_to_remove: Union[MessageThinking, MessageSending]): - """移除指定的消息对象,如果消息存在则返回True,否则返回False""" - try: - _initial_len = len(self.messages) - # 使用列表推导式或 message_filter 创建新列表,排除要删除的元素 - # self.messages = [msg for msg in self.messages if msg is not message_to_remove] - # 或者直接 remove (如果确定对象唯一性) - if message_to_remove in self.messages: - self.messages.remove(message_to_remove) - return True - # logger.debug(f"Removed message {getattr(message_to_remove, 'message_info', {}).get('message_id', 'UNKNOWN')}. Old len: {initial_len}, New len: {len(self.messages)}") - # return len(self.messages) < initial_len - return False - - except Exception as e: - logger.exception(f"移除消息时发生错误: {e}") - return False - - def has_messages(self) -> bool: - """检查是否有待发送的消息""" - return bool(self.messages) - - def get_all_messages(self) -> list[MessageThinking | MessageSending]: - """获取所有消息""" - return list(self.messages) # 返回副本 - - -class MessageManager: - """管理所有聊天流的消息容器 (不再是单例)""" - - def __init__(self): - self._processor_task: Task | None = None - self.containers: dict[str, MessageContainer] = {} - self.storage = MessageStorage() # 添加 storage 实例 - self._running = True # 处理器运行状态 - self._container_lock = asyncio.Lock() # 保护 containers 字典的锁 - # self.message_sender = MessageSender() # 创建发送器实例 (改为全局实例) - - async def start(self): - """启动后台处理器任务。""" - # 检查是否已有任务在运行,避免重复启动 - if self._processor_task is not None and not self._processor_task.done(): - logger.warning("Processor task already running.") - return - self._processor_task = asyncio.create_task(self._start_processor_loop()) - logger.debug("MessageManager processor task started.") - - def stop(self): - """停止后台处理器任务。""" - self._running = False - if self._processor_task is not None and not self._processor_task.done(): - self._processor_task.cancel() - logger.debug("MessageManager processor task stopping.") - else: - logger.debug("MessageManager processor task not running or already stopped.") - - async def get_container(self, chat_id: str) -> MessageContainer: - """获取或创建聊天流的消息容器 (异步,使用锁)""" - async with self._container_lock: - if chat_id not in self.containers: - self.containers[chat_id] = MessageContainer(chat_id) - return self.containers[chat_id] - - async def add_message(self, message: Union[MessageThinking, MessageSending, MessageSet]) -> None: - """添加消息到对应容器""" - chat_stream = message.chat_stream - if not chat_stream: - logger.error("消息缺少 chat_stream,无法添加到容器") - return # 或者抛出异常 - container = await self.get_container(chat_stream.stream_id) - container.add_message(message) - - async def _handle_sending_message(self, container: MessageContainer, message: MessageSending): - """处理单个 MessageSending 消息 (包含 set_reply 逻辑)""" - try: - _ = message.update_thinking_time() # 更新思考时间 - thinking_start_time = message.thinking_start_time - now_time = time.time() - # logger.debug(f"thinking_start_time:{thinking_start_time},now_time:{now_time}") - thinking_messages_count, thinking_messages_length = count_messages_between( - start_time=thinking_start_time, end_time=now_time, stream_id=message.chat_stream.stream_id - ) - - if ( - message.is_head - and (thinking_messages_count > 3 or thinking_messages_length > 200) - and not message.is_private_message() - ): - logger.debug( - f"[{message.chat_stream.stream_id}] 应用 set_reply 逻辑: {message.processed_plain_text[:20]}..." - ) - message.build_reply() - # --- 结束条件 set_reply --- - - await message.process() # 预处理消息内容 - - # logger.debug(f"{message}") - - # 使用全局 message_sender 实例 - await send_message(message) - await self.storage.store_message(message, message.chat_stream) - - # 移除消息要在发送 *之后* - container.remove_message(message) - # logger.debug(f"[{message.chat_stream.stream_id}] Sent and removed message: {message.message_info.message_id}") - - except Exception as e: - logger.error( - f"[{message.chat_stream.stream_id}] 处理发送消息 {getattr(message.message_info, 'message_id', 'N/A')} 时出错: {e}" - ) - logger.exception("详细错误信息:") - if container.remove_message(message): - logger.warning(f"[{message.chat_stream.stream_id}] 已移除处理出错的消息。") - - async def _process_chat_messages(self, chat_id: str): - """处理单个聊天流消息 (合并后的逻辑)""" - container = await self.get_container(chat_id) # 获取容器是异步的了 - - if container.has_messages(): - message_earliest = container.get_earliest_message() - - if not message_earliest: # 如果最早消息为空,则退出 - return - - if isinstance(message_earliest, MessageThinking): - # --- 处理思考消息 (来自旧 sender) --- - message_earliest.update_thinking_time() - thinking_time = message_earliest.thinking_time - # 减少控制台刷新频率或只在时间显著变化时打印 - if int(thinking_time) % 5 == 0: # 每5秒打印一次 - print( - f"消息 {message_earliest.message_info.message_id} 正在思考中,已思考 {int(thinking_time)} 秒\r", - end="", - flush=True, - ) - - elif isinstance(message_earliest, MessageSending): - # --- 处理发送消息 --- - await self._handle_sending_message(container, message_earliest) - - if timeout_sending_messages := container.get_timeout_sending_messages(): - logger.debug(f"[{chat_id}] 发现 {len(timeout_sending_messages)} 条超时的发送消息") - for msg in timeout_sending_messages: - # 确保不是刚刚处理过的最早消息 (虽然理论上应该已被移除,但以防万一) - if msg is message_earliest: - continue - logger.info(f"[{chat_id}] 处理超时发送消息: {msg.message_info.message_id}") - await self._handle_sending_message(container, msg) # 复用处理逻辑 - - async def _start_processor_loop(self): - # sourcery skip: list-comprehension, move-assign-in-block, use-named-expression - """消息处理器主循环""" - while self._running: - tasks = [] - # 使用异步锁保护迭代器创建过程 - async with self._container_lock: - # 创建 keys 的快照以安全迭代 - chat_ids = list(self.containers.keys()) - - tasks.extend(asyncio.create_task(self._process_chat_messages(chat_id)) for chat_id in chat_ids) - if tasks: - try: - # 等待当前批次的所有任务完成 - await asyncio.gather(*tasks) - except Exception as e: - logger.error(f"消息处理循环 gather 出错: {e}") - - # 等待一小段时间,避免CPU空转 - try: - await asyncio.sleep(0.1) # 稍微降低轮询频率 - except asyncio.CancelledError: - logger.info("Processor loop sleep cancelled.") - break # 退出循环 - logger.info("MessageManager processor loop finished.") - - -# --- 创建全局实例 --- -message_manager = MessageManager() -message_sender = MessageSender() -# --- 结束全局实例 --- diff --git a/src/chat/message_receive/storage.py b/src/chat/message_receive/storage.py index 2ed2078f9..820b534c3 100644 --- a/src/chat/message_receive/storage.py +++ b/src/chat/message_receive/storage.py @@ -37,11 +37,21 @@ class MessageStorage: else: filtered_display_message = "" interest_value = 0 + is_mentioned = False reply_to = message.reply_to + priority_mode = "" + priority_info = {} + is_emoji = False + is_picid = False else: filtered_display_message = "" interest_value = message.interest_value + is_mentioned = message.is_mentioned reply_to = "" + priority_mode = message.priority_mode + priority_info = message.priority_info + is_emoji = message.is_emoji + is_picid = message.is_picid chat_info_dict = chat_stream.to_dict() user_info_dict = message.message_info.user_info.to_dict() # type: ignore @@ -60,6 +70,7 @@ class MessageStorage: chat_id=chat_stream.stream_id, # Flattened chat_info reply_to=reply_to, + is_mentioned=is_mentioned, chat_info_stream_id=chat_info_dict.get("stream_id"), chat_info_platform=chat_info_dict.get("platform"), chat_info_user_platform=user_info_from_chat.get("platform"), @@ -81,6 +92,10 @@ class MessageStorage: display_message=filtered_display_message, memorized_times=message.memorized_times, interest_value=interest_value, + priority_mode=priority_mode, + priority_info=priority_info, + is_emoji=is_emoji, + is_picid=is_picid, ) except Exception: logger.exception("存储消息失败") diff --git a/src/chat/message_receive/uni_message_sender.py b/src/chat/message_receive/uni_message_sender.py index 663bf23a8..e75f43634 100644 --- a/src/chat/message_receive/uni_message_sender.py +++ b/src/chat/message_receive/uni_message_sender.py @@ -1,12 +1,11 @@ import asyncio import traceback -from typing import Dict, Optional from rich.traceback import install from src.common.message.api import get_global_api from src.common.logger import get_logger -from src.chat.message_receive.message import MessageSending, MessageThinking +from src.chat.message_receive.message import MessageSending from src.chat.message_receive.storage import MessageStorage from src.chat.utils.utils import truncate_message from src.chat.utils.utils import calculate_typing_time @@ -37,42 +36,6 @@ class HeartFCSender: def __init__(self): self.storage = MessageStorage() - # 用于存储活跃的思考消息 - self.thinking_messages: Dict[str, Dict[str, MessageThinking]] = {} - self._thinking_lock = asyncio.Lock() # 保护 thinking_messages 的锁 - - async def register_thinking(self, thinking_message: MessageThinking): - """注册一个思考中的消息。""" - if not thinking_message.chat_stream or not thinking_message.message_info.message_id: - logger.error("无法注册缺少 chat_stream 或 message_id 的思考消息") - return - - chat_id = thinking_message.chat_stream.stream_id - message_id = thinking_message.message_info.message_id - - async with self._thinking_lock: - if chat_id not in self.thinking_messages: - self.thinking_messages[chat_id] = {} - if message_id in self.thinking_messages[chat_id]: - logger.warning(f"[{chat_id}] 尝试注册已存在的思考消息 ID: {message_id}") - self.thinking_messages[chat_id][message_id] = thinking_message - logger.debug(f"[{chat_id}] Registered thinking message: {message_id}") - - async def complete_thinking(self, chat_id: str, message_id: str): - """完成并移除一个思考中的消息记录。""" - async with self._thinking_lock: - if chat_id in self.thinking_messages and message_id in self.thinking_messages[chat_id]: - del self.thinking_messages[chat_id][message_id] - logger.debug(f"[{chat_id}] Completed thinking message: {message_id}") - if not self.thinking_messages[chat_id]: - del self.thinking_messages[chat_id] - logger.debug(f"[{chat_id}] Removed empty thinking message container.") - - async def get_thinking_start_time(self, chat_id: str, message_id: str) -> Optional[float]: - """获取已注册思考消息的开始时间。""" - async with self._thinking_lock: - thinking_message = self.thinking_messages.get(chat_id, {}).get(message_id) - return thinking_message.thinking_start_time if thinking_message else None async def send_message(self, message: MessageSending, typing=False, set_reply=False, storage_message=True): """ @@ -122,5 +85,3 @@ class HeartFCSender: except Exception as e: logger.error(f"[{chat_id}] 处理或存储消息 {message_id} 时出错: {e}") raise e - finally: - await self.complete_thinking(chat_id, message_id) diff --git a/src/chat/normal_chat/normal_chat.py b/src/chat/normal_chat/normal_chat.py index 414d607a1..0d6445f77 100644 --- a/src/chat/normal_chat/normal_chat.py +++ b/src/chat/normal_chat/normal_chat.py @@ -2,35 +2,28 @@ import asyncio import time import traceback -from random import random -from typing import List, Optional, Dict -from maim_message import UserInfo, Seg +from typing import Optional from src.config.config import global_config from src.common.logger import get_logger -from src.common.message_repository import count_messages -from src.plugin_system.apis import generator_api -from src.plugin_system.base.component_types import ChatMode, ActionInfo +from src.plugin_system.base.component_types import ChatMode from src.chat.message_receive.chat_stream import ChatStream, get_chat_manager -from src.chat.message_receive.message import MessageSending, MessageRecv, MessageThinking, MessageSet -from src.chat.message_receive.normal_message_sender import message_manager -from src.chat.normal_chat.willing.willing_manager import get_willing_manager +from src.chat.message_receive.message import MessageThinking from src.chat.planner_actions.action_manager import ActionManager +from src.person_info.relationship_builder_manager import relationship_builder_manager +from src.chat.focus_chat.priority_manager import PriorityManager from src.chat.planner_actions.planner import ActionPlanner from src.chat.planner_actions.action_modifier import ActionModifier +from src.chat.utils.chat_message_builder import get_raw_msg_by_timestamp_with_chat_inclusive from src.chat.utils.utils import get_chat_type_and_target_info -from src.chat.utils.prompt_builder import global_prompt_manager -from src.chat.utils.timer_calculator import Timer -from src.mood.mood_manager import mood_manager -from src.person_info.person_info import get_person_info_manager -from src.person_info.relationship_builder_manager import relationship_builder_manager -from .priority_manager import PriorityManager willing_manager = get_willing_manager() logger = get_logger("normal_chat") +LOOP_INTERVAL = 0.3 + class NormalChat: """ @@ -41,7 +34,6 @@ class NormalChat: def __init__( self, chat_stream: ChatStream, - interest_dict: Optional[Dict] = None, on_switch_to_focus_callback=None, get_cooldown_progress_callback=None, ): @@ -53,22 +45,22 @@ class NormalChat: """ self.chat_stream = chat_stream self.stream_id = chat_stream.stream_id + self.last_read_time = time.time() - 1 self.stream_name = get_chat_manager().get_stream_name(self.stream_id) or self.stream_id self.relationship_builder = relationship_builder_manager.get_or_create_builder(self.stream_id) - # Interest dict - self.interest_dict = interest_dict - self.is_group_chat, self.chat_target_info = get_chat_type_and_target_info(self.stream_id) self.willing_amplifier = 1 self.start_time = time.time() - self.mood_manager = mood_manager + # self.mood_manager = mood_manager self.start_time = time.time() + self.running = False + self._initialized = False # Track initialization status # Planner相关初始化 @@ -96,14 +88,13 @@ class NormalChat: # 任务管理 self._chat_task: Optional[asyncio.Task] = None + self._priority_chat_task: Optional[asyncio.Task] = None # for priority mode consumer self._disabled = False # 停用标志 # 新增:回复模式和优先级管理器 self.reply_mode = self.chat_stream.context.get_priority_mode() if self.reply_mode == "priority": - interest_dict = interest_dict or {} self.priority_manager = PriorityManager( - interest_dict=interest_dict, normal_queue_max_size=5, ) else: @@ -118,28 +109,84 @@ class NormalChat: self._priority_chat_task.cancel() logger.info(f"[{self.stream_name}] NormalChat 已停用。") - async def _priority_chat_loop_add_message(self): - while not self._disabled: - try: - # 创建字典条目的副本以避免在迭代时发生修改 - items_to_process = list(self.interest_dict.items()) - for msg_id, value in items_to_process: - # 尝试从原始字典中弹出条目,如果它已被其他任务处理,则跳过 - if self.interest_dict.pop(msg_id, None) is None: - continue # 条目已被其他任务处理 + # async def _interest_mode_loopbody(self): + # try: + # await asyncio.sleep(LOOP_INTERVAL) - message, interest_value, _ = value - if not self._disabled and self.priority_manager: - self.priority_manager.add_message(message, interest_value) + # if self._disabled: + # return False - except Exception: - logger.error( - f"[{self.stream_name}] 优先级聊天循环添加消息时出现错误: {traceback.format_exc()}", exc_info=True - ) - print(traceback.format_exc()) - # 出现错误时,等待一段时间再重试 - raise - await asyncio.sleep(0.1) + # now = time.time() + # new_messages_data = get_raw_msg_by_timestamp_with_chat_inclusive( + # chat_id=self.stream_id, timestamp_start=self.last_read_time, timestamp_end=now, limit_mode="earliest" + # ) + + # if new_messages_data: + # self.last_read_time = now + + # for msg_data in new_messages_data: + # try: + # self.adjust_reply_frequency() + # await self.normal_response( + # message_data=msg_data, + # is_mentioned=msg_data.get("is_mentioned", False), + # interested_rate=msg_data.get("interest_rate", 0.0) * self.willing_amplifier, + # ) + # return True + # except Exception as e: + # logger.error(f"[{self.stream_name}] 处理消息时出错: {e} {traceback.format_exc()}") + + # except asyncio.CancelledError: + # logger.info(f"[{self.stream_name}] 兴趣模式轮询任务被取消") + # return False + # except Exception: + # logger.error(f"[{self.stream_name}] 兴趣模式轮询循环出现错误: {traceback.format_exc()}", exc_info=True) + # await asyncio.sleep(10) + + async def _priority_mode_loopbody(self): + try: + await asyncio.sleep(LOOP_INTERVAL) + + if self._disabled: + return False + + now = time.time() + new_messages_data = get_raw_msg_by_timestamp_with_chat_inclusive( + chat_id=self.stream_id, timestamp_start=self.last_read_time, timestamp_end=now, limit_mode="earliest" + ) + + if new_messages_data: + self.last_read_time = now + + for msg_data in new_messages_data: + try: + if self.priority_manager: + self.priority_manager.add_message(msg_data, msg_data.get("interest_rate", 0.0)) + return True + except Exception as e: + logger.error(f"[{self.stream_name}] 添加消息到优先级队列时出错: {e} {traceback.format_exc()}") + + except asyncio.CancelledError: + logger.info(f"[{self.stream_name}] 优先级消息生产者任务被取消") + return False + except Exception: + logger.error(f"[{self.stream_name}] 优先级消息生产者循环出现错误: {traceback.format_exc()}", exc_info=True) + await asyncio.sleep(10) + + # async def _interest_message_polling_loop(self): + # """ + # [Interest Mode] 通过轮询数据库获取新消息并直接处理。 + # """ + # logger.info(f"[{self.stream_name}] 兴趣模式消息轮询任务开始") + # try: + # while not self._disabled: + # success = await self._interest_mode_loopbody() + + # if not success: + # break + + # except asyncio.CancelledError: + # logger.info(f"[{self.stream_name}] 兴趣模式消息轮询任务被优雅地取消了") async def _priority_chat_loop(self): """ @@ -147,13 +194,16 @@ class NormalChat: """ while not self._disabled: try: - if not self.priority_manager.is_empty(): - if message := self.priority_manager.get_highest_priority_message(): + if self.priority_manager and not self.priority_manager.is_empty(): + # 获取最高优先级的消息,现在是字典 + message_data = self.priority_manager.get_highest_priority_message() + + if message_data: logger.info( - f"[{self.stream_name}] 从队列中取出消息进行处理: User {message.message_info.user_info.user_id}, Time: {time.strftime('%H:%M:%S', time.localtime(message.message_info.time))}" + f"[{self.stream_name}] 从队列中取出消息进行处理: User {message_data.get('user_id')}, Time: {time.strftime('%H:%M:%S', time.localtime(message_data.get('time')))}" ) - do_reply = await self.reply_one_message(message) + do_reply = await self.reply_one_message(message_data) response_set = do_reply if do_reply else [] factor = 0.5 cnt = sum([len(r) for r in response_set]) @@ -171,520 +221,407 @@ class NormalChat: await asyncio.sleep(10) # 改为实例方法 - async def _create_thinking_message(self, message: MessageRecv, timestamp: Optional[float] = None) -> str: - """创建思考消息""" - message_info = message.message_info + # async def _create_thinking_message(self, message_data: dict, timestamp: Optional[float] = None) -> str: + # """创建思考消息""" + # bot_user_info = UserInfo( + # user_id=global_config.bot.qq_account, + # user_nickname=global_config.bot.nickname, + # platform=message_data.get("chat_info_platform"), + # ) - bot_user_info = UserInfo( - user_id=global_config.bot.qq_account, - user_nickname=global_config.bot.nickname, - platform=message_info.platform, - ) + # thinking_time_point = round(time.time(), 2) + # thinking_id = "tid" + str(thinking_time_point) + # thinking_message = MessageThinking( + # message_id=thinking_id, + # chat_stream=self.chat_stream, + # bot_user_info=bot_user_info, + # reply=None, + # thinking_start_time=thinking_time_point, + # timestamp=timestamp if timestamp is not None else None, + # ) - thinking_time_point = round(time.time(), 2) - thinking_id = "tid" + str(thinking_time_point) - thinking_message = MessageThinking( - message_id=thinking_id, - chat_stream=self.chat_stream, - bot_user_info=bot_user_info, - reply=message, - thinking_start_time=thinking_time_point, - timestamp=timestamp if timestamp is not None else None, - ) - - await message_manager.add_message(thinking_message) - return thinking_id + # await message_manager.add_message(thinking_message) + # return thinking_id # 改为实例方法 - async def _add_messages_to_manager( - self, message: MessageRecv, response_set: List[str], thinking_id - ) -> Optional[MessageSending]: - """发送回复消息""" - container = await message_manager.get_container(self.stream_id) # 使用 self.stream_id - thinking_message = None + # async def _add_messages_to_manager( + # self, message_data: dict, response_set: List[str], thinking_id + # ) -> Optional[MessageSending]: + # """发送回复消息""" + # container = await message_manager.get_container(self.stream_id) # 使用 self.stream_id + # thinking_message = None - for msg in container.messages[:]: - if isinstance(msg, MessageThinking) and msg.message_info.message_id == thinking_id: - thinking_message = msg - container.messages.remove(msg) - break + # for msg in container.messages[:]: + # if isinstance(msg, MessageThinking) and msg.message_info.message_id == thinking_id: + # thinking_message = msg + # container.messages.remove(msg) + # break - if not thinking_message: - logger.warning(f"[{self.stream_name}] 未找到对应的思考消息 {thinking_id},可能已超时被移除") - return None + # if not thinking_message: + # logger.warning(f"[{self.stream_name}] 未找到对应的思考消息 {thinking_id},可能已超时被移除") + # return None - thinking_start_time = thinking_message.thinking_start_time - message_set = MessageSet(self.chat_stream, thinking_id) # 使用 self.chat_stream + # thinking_start_time = thinking_message.thinking_start_time + # message_set = MessageSet(self.chat_stream, thinking_id) # 使用 self.chat_stream - mark_head = False - first_bot_msg = None - for msg in response_set: - if global_config.debug.debug_show_chat_mode: - msg += "ⁿ" - message_segment = Seg(type="text", data=msg) - bot_message = MessageSending( - message_id=thinking_id, - chat_stream=self.chat_stream, # 使用 self.chat_stream - bot_user_info=UserInfo( - user_id=global_config.bot.qq_account, - user_nickname=global_config.bot.nickname, - platform=message.message_info.platform, - ), - sender_info=message.message_info.user_info, - message_segment=message_segment, - reply=message, - is_head=not mark_head, - is_emoji=False, - thinking_start_time=thinking_start_time, - apply_set_reply_logic=True, - ) - if not mark_head: - mark_head = True - first_bot_msg = bot_message - message_set.add_message(bot_message) + # sender_info = UserInfo( + # user_id=message_data.get("user_id"), + # user_nickname=message_data.get("user_nickname"), + # platform=message_data.get("chat_info_platform"), + # ) - await message_manager.add_message(message_set) + # reply = message_from_db_dict(message_data) - return first_bot_msg + # mark_head = False + # first_bot_msg = None + # for msg in response_set: + # if global_config.debug.debug_show_chat_mode: + # msg += "ⁿ" + # message_segment = Seg(type="text", data=msg) + # bot_message = MessageSending( + # message_id=thinking_id, + # chat_stream=self.chat_stream, # 使用 self.chat_stream + # bot_user_info=UserInfo( + # user_id=global_config.bot.qq_account, + # user_nickname=global_config.bot.nickname, + # platform=message_data.get("chat_info_platform"), + # ), + # sender_info=sender_info, + # message_segment=message_segment, + # reply=reply, + # is_head=not mark_head, + # is_emoji=False, + # thinking_start_time=thinking_start_time, + # apply_set_reply_logic=True, + # ) + # if not mark_head: + # mark_head = True + # first_bot_msg = bot_message + # message_set.add_message(bot_message) - async def _reply_interested_message(self) -> None: - """ - 后台任务方法,轮询当前实例关联chat的兴趣消息 - 通常由start_monitoring_interest()启动 - """ - logger.debug(f"[{self.stream_name}] 兴趣监控任务开始") + # await message_manager.add_message(message_set) - try: - while True: - # 第一层检查:立即检查取消和停用状态 - if self._disabled: - logger.info(f"[{self.stream_name}] 检测到停用标志,退出兴趣监控") - break - - # 检查当前任务是否已被取消 - current_task = asyncio.current_task() - if current_task and current_task.cancelled(): - logger.info(f"[{self.stream_name}] 当前任务已被取消,退出") - break - - try: - # 短暂等待,让出控制权 - await asyncio.sleep(0.1) - - # 第二层检查:睡眠后再次检查状态 - if self._disabled: - logger.info(f"[{self.stream_name}] 睡眠后检测到停用标志,退出") - break - - # 获取待处理消息 - items_to_process = list(self.interest_dict.items()) - if not items_to_process: - # 没有消息时继续下一轮循环 - continue - - # 第三层检查:在处理消息前最后检查一次 - if self._disabled: - logger.info(f"[{self.stream_name}] 处理消息前检测到停用标志,退出") - break - - # 使用异步上下文管理器处理消息 - try: - async with global_prompt_manager.async_message_scope( - self.chat_stream.context.get_template_name() - ): - # 在上下文内部再次检查取消状态 - if self._disabled: - logger.info(f"[{self.stream_name}] 在处理上下文中检测到停止信号,退出") - break - - semaphore = asyncio.Semaphore(5) - - async def process_and_acquire(msg_id, message, interest_value, is_mentioned, semaphore): - """处理单个兴趣消息并管理信号量""" - async with semaphore: - try: - # 在处理每个消息前检查停止状态 - if self._disabled: - logger.debug( - f"[{self.stream_name}] 处理消息时检测到停用,跳过消息 {msg_id}" - ) - return - - # 处理消息 - self.adjust_reply_frequency() - - await self.normal_response( - message=message, - is_mentioned=is_mentioned, - interested_rate=interest_value * self.willing_amplifier, - ) - except asyncio.CancelledError: - logger.debug(f"[{self.stream_name}] 处理消息 {msg_id} 时被取消") - raise # 重新抛出取消异常 - except Exception as e: - logger.error(f"[{self.stream_name}] 处理兴趣消息{msg_id}时出错: {e}") - # 不打印完整traceback,避免日志污染 - finally: - # 无论如何都要清理消息 - self.interest_dict.pop(msg_id, None) - - tasks = [ - process_and_acquire(msg_id, message, interest_value, is_mentioned, semaphore) - for msg_id, (message, interest_value, is_mentioned) in items_to_process - ] - - if tasks: - await asyncio.gather(*tasks, return_exceptions=True) - - except asyncio.CancelledError: - logger.info(f"[{self.stream_name}] 处理上下文时任务被取消") - break - except Exception as e: - logger.error(f"[{self.stream_name}] 处理上下文时出错: {e}") - # 出错后短暂等待,避免快速重试 - await asyncio.sleep(0.5) - - except asyncio.CancelledError: - logger.info(f"[{self.stream_name}] 主循环中任务被取消") - break - except Exception as e: - logger.error(f"[{self.stream_name}] 主循环出错: {e}") - # 出错后等待一秒再继续 - await asyncio.sleep(1.0) - - except asyncio.CancelledError: - logger.info(f"[{self.stream_name}] 兴趣监控任务被取消") - except Exception as e: - logger.error(f"[{self.stream_name}] 兴趣监控任务严重错误: {e}") - finally: - logger.debug(f"[{self.stream_name}] 兴趣监控任务结束") + # return first_bot_msg # 改为实例方法, 移除 chat 参数 - async def normal_response(self, message: MessageRecv, is_mentioned: bool, interested_rate: float) -> None: - """ - 处理接收到的消息。 - 在"兴趣"模式下,判断是否回复并生成内容。 - """ - if self._disabled: - return + # async def normal_response(self, message_data: dict, is_mentioned: bool, interested_rate: float) -> None: + # """ + # 处理接收到的消息。 + # 在"兴趣"模式下,判断是否回复并生成内容。 + # """ + # if self._disabled: + # return - # 新增:在auto模式下检查是否需要直接切换到focus模式 - if global_config.chat.chat_mode == "auto": - if await self._check_should_switch_to_focus(): - logger.info(f"[{self.stream_name}] 检测到切换到focus聊天模式的条件,尝试执行切换") - if self.on_switch_to_focus_callback: - switched_successfully = await self.on_switch_to_focus_callback() - if switched_successfully: - logger.info(f"[{self.stream_name}] 成功切换到focus模式,中止NormalChat处理") - return - else: - logger.info(f"[{self.stream_name}] 切换到focus模式失败(可能在冷却中),继续NormalChat处理") - else: - logger.warning(f"[{self.stream_name}] 没有设置切换到focus聊天模式的回调函数,无法执行切换") + # # 新增:在auto模式下检查是否需要直接切换到focus模式 + # if global_config.chat.chat_mode == "auto": + # if await self._check_should_switch_to_focus(): + # logger.info(f"[{self.stream_name}] 检测到切换到focus聊天模式的条件,尝试执行切换") + # if self.on_switch_to_focus_callback: + # switched_successfully = await self.on_switch_to_focus_callback() + # if switched_successfully: + # logger.info(f"[{self.stream_name}] 成功切换到focus模式,中止NormalChat处理") + # return + # else: + # logger.info(f"[{self.stream_name}] 切换到focus模式失败(可能在冷却中),继续NormalChat处理") + # else: + # logger.warning(f"[{self.stream_name}] 没有设置切换到focus聊天模式的回调函数,无法执行切换") - # --- 以下为 "兴趣" 模式逻辑 (从 _process_message 合并而来) --- - timing_results = {} - reply_probability = ( - 1.0 if is_mentioned and global_config.normal_chat.mentioned_bot_inevitable_reply else 0.0 - ) # 如果被提及,且开启了提及必回复,则基础概率为1,否则需要意愿判断 + # # --- 以下为 "兴趣" 模式逻辑 (从 _process_message 合并而来) --- + # timing_results = {} + # reply_probability = ( + # 1.0 if is_mentioned and global_config.normal_chat.mentioned_bot_inevitable_reply else 0.0 + # ) # 如果被提及,且开启了提及必回复,则基础概率为1,否则需要意愿判断 - # 意愿管理器:设置当前message信息 - willing_manager.setup(message, self.chat_stream, is_mentioned, interested_rate) + # # 意愿管理器:设置当前message信息 + # willing_manager.setup(message_data, self.chat_stream) - # 获取回复概率 - # is_willing = False - # 仅在未被提及或基础概率不为1时查询意愿概率 - if reply_probability < 1: # 简化逻辑,如果未提及 (reply_probability 为 0),则获取意愿概率 - # is_willing = True - reply_probability = await willing_manager.get_reply_probability(message.message_info.message_id) + # # 获取回复概率 + # # is_willing = False + # # 仅在未被提及或基础概率不为1时查询意愿概率 + # if reply_probability < 1: # 简化逻辑,如果未提及 (reply_probability 为 0),则获取意愿概率 + # # is_willing = True + # reply_probability = await willing_manager.get_reply_probability(message_data.get("message_id")) - if message.message_info.additional_config: - if "maimcore_reply_probability_gain" in message.message_info.additional_config.keys(): - reply_probability += message.message_info.additional_config["maimcore_reply_probability_gain"] - reply_probability = min(max(reply_probability, 0), 1) # 确保概率在 0-1 之间 + # additional_config = message_data.get("additional_config", {}) + # if additional_config and "maimcore_reply_probability_gain" in additional_config: + # reply_probability += additional_config["maimcore_reply_probability_gain"] + # reply_probability = min(max(reply_probability, 0), 1) # 确保概率在 0-1 之间 - # 处理表情包 - if message.is_emoji or message.is_picid: - reply_probability = 0 + # # 处理表情包 + # if message_data.get("is_emoji") or message_data.get("is_picid"): + # reply_probability = 0 - # 应用疲劳期回复频率调整 - fatigue_multiplier = self._get_fatigue_reply_multiplier() - original_probability = reply_probability - reply_probability *= fatigue_multiplier + # # 应用疲劳期回复频率调整 + # fatigue_multiplier = self._get_fatigue_reply_multiplier() + # original_probability = reply_probability + # reply_probability *= fatigue_multiplier - # 如果应用了疲劳调整,记录日志 - if fatigue_multiplier < 1.0: - logger.info( - f"[{self.stream_name}] 疲劳期回复频率调整: {original_probability * 100:.1f}% -> {reply_probability * 100:.1f}% (系数: {fatigue_multiplier:.2f})" - ) + # # 如果应用了疲劳调整,记录日志 + # if fatigue_multiplier < 1.0: + # logger.info( + # f"[{self.stream_name}] 疲劳期回复频率调整: {original_probability * 100:.1f}% -> {reply_probability * 100:.1f}% (系数: {fatigue_multiplier:.2f})" + # ) - # 打印消息信息 - mes_name = self.chat_stream.group_info.group_name if self.chat_stream.group_info else "私聊" - # current_time = time.strftime("%H:%M:%S", time.localtime(message.message_info.time)) - # 使用 self.stream_id - # willing_log = f"[激活值:{await willing_manager.get_willing(self.stream_id):.2f}]" if is_willing else "" - if reply_probability > 0.1: - logger.info( - f"[{mes_name}]" - f"{message.message_info.user_info.user_nickname}:" # 使用 self.chat_stream - f"{message.processed_plain_text}[兴趣:{interested_rate:.2f}][回复概率:{reply_probability * 100:.1f}%]" - ) - do_reply = False - response_set = None # 初始化 response_set - if random() < reply_probability: - with Timer("获取回复", timing_results): - await willing_manager.before_generate_reply_handle(message.message_info.message_id) - do_reply = await self.reply_one_message(message) - response_set = do_reply if do_reply else None + # # 打印消息信息 + # mes_name = self.chat_stream.group_info.group_name if self.chat_stream.group_info else "私聊" + # if reply_probability > 0.1: + # logger.info( + # f"[{mes_name}]" + # f"{message_data.get('user_nickname')}:" + # f"{message_data.get('processed_plain_text')}[兴趣:{interested_rate:.2f}][回复概率:{reply_probability * 100:.1f}%]" + # ) + # do_reply = False + # response_set = None # 初始化 response_set + # if random() < reply_probability: + # with Timer("获取回复", timing_results): + # await willing_manager.before_generate_reply_handle(message_data.get("message_id")) + # do_reply = await self.reply_one_message(message_data) + # response_set = do_reply if do_reply else None - # 输出性能计时结果 - if do_reply and response_set: # 确保 response_set 不是 None - timing_str = " | ".join([f"{step}: {duration:.2f}秒" for step, duration in timing_results.items()]) - trigger_msg = message.processed_plain_text - response_msg = " ".join([item[1] for item in response_set if item[0] == "text"]) - logger.info( - f"[{self.stream_name}]回复消息: {trigger_msg[:30]}... | 回复内容: {response_msg[:30]}... | 计时: {timing_str}" - ) - await willing_manager.after_generate_reply_handle(message.message_info.message_id) - elif not do_reply: - # 不回复处理 - await willing_manager.not_reply_handle(message.message_info.message_id) + # # 输出性能计时结果 + # if do_reply and response_set: # 确保 response_set 不是 None + # timing_str = " | ".join([f"{step}: {duration:.2f}秒" for step, duration in timing_results.items()]) + # trigger_msg = message_data.get("processed_plain_text") + # response_msg = " ".join([item[1] for item in response_set if item[0] == "text"]) + # logger.info( + # f"[{self.stream_name}]回复消息: {trigger_msg[:30]}... | 回复内容: {response_msg[:30]}... | 计时: {timing_str}" + # ) + # await willing_manager.after_generate_reply_handle(message_data.get("message_id")) + # elif not do_reply: + # # 不回复处理 + # await willing_manager.not_reply_handle(message_data.get("message_id")) - # 意愿管理器:注销当前message信息 (无论是否回复,只要处理过就删除) - willing_manager.delete(message.message_info.message_id) + # # 意愿管理器:注销当前message信息 (无论是否回复,只要处理过就删除) + # willing_manager.delete(message_data.get("message_id")) - async def _generate_normal_response( - self, message: MessageRecv, available_actions: Optional[Dict[str, ActionInfo]] - ) -> Optional[list]: - """生成普通回复""" - try: - person_info_manager = get_person_info_manager() - person_id = person_info_manager.get_person_id( - message.chat_stream.user_info.platform, message.chat_stream.user_info.user_id - ) - person_name = await person_info_manager.get_value(person_id, "person_name") - reply_to_str = f"{person_name}:{message.processed_plain_text}" + # async def _generate_normal_response( + # self, message_data: dict, available_actions: Optional[list] + # ) -> Optional[list]: + # """生成普通回复""" + # try: + # person_info_manager = get_person_info_manager() + # person_id = person_info_manager.get_person_id( + # message_data.get("chat_info_platform"), message_data.get("user_id") + # ) + # person_name = await person_info_manager.get_value(person_id, "person_name") + # reply_to_str = f"{person_name}:{message_data.get('processed_plain_text')}" - success, reply_set = await generator_api.generate_reply( - chat_stream=message.chat_stream, - reply_to=reply_to_str, - available_actions=available_actions, - enable_tool=global_config.tool.enable_in_normal_chat, - request_type="normal.replyer", - ) + # success, reply_set = await generator_api.generate_reply( + # chat_stream=self.chat_stream, + # reply_to=reply_to_str, + # available_actions=available_actions, + # enable_tool=global_config.tool.enable_in_normal_chat, + # request_type="normal.replyer", + # ) - if not success or not reply_set: - logger.info(f"对 {message.processed_plain_text} 的回复生成失败") - return None + # if not success or not reply_set: + # logger.info(f"对 {message_data.get('processed_plain_text')} 的回复生成失败") + # return None - return reply_set + # return reply_set - except Exception as e: - logger.error(f"[{self.stream_name}] 回复生成出现错误:{str(e)} {traceback.format_exc()}") - return None + # except Exception as e: + # logger.error(f"[{self.stream_name}] 回复生成出现错误:{str(e)} {traceback.format_exc()}") + # return None - async def _plan_and_execute_actions(self, message: MessageRecv, thinking_id: str) -> Optional[dict]: - """规划和执行额外动作""" - no_action = { - "action_result": { - "action_type": "no_action", - "action_data": {}, - "reasoning": "规划器初始化默认", - "is_parallel": True, - }, - "chat_context": "", - "action_prompt": "", - } + # async def _plan_and_execute_actions(self, message_data: dict, thinking_id: str) -> Optional[dict]: + # """规划和执行额外动作""" + # no_action = { + # "action_result": { + # "action_type": "no_action", + # "action_data": {}, + # "reasoning": "规划器初始化默认", + # "is_parallel": True, + # }, + # "chat_context": "", + # "action_prompt": "", + # } - if not self.enable_planner: - logger.debug(f"[{self.stream_name}] Planner未启用,跳过动作规划") - return no_action + # if not self.enable_planner: + # logger.debug(f"[{self.stream_name}] Planner未启用,跳过动作规划") + # return no_action - try: - # 检查是否应该跳过规划 - if self.action_modifier.should_skip_planning(): - logger.debug(f"[{self.stream_name}] 没有可用动作,跳过规划") - self.action_type = "no_action" - return no_action + # try: + # # 检查是否应该跳过规划 + # if self.action_modifier.should_skip_planning(): + # logger.debug(f"[{self.stream_name}] 没有可用动作,跳过规划") + # self.action_type = "no_action" + # return no_action - # 执行规划 - plan_result = await self.planner.plan() - action_type = plan_result["action_result"]["action_type"] - action_data = plan_result["action_result"]["action_data"] - reasoning = plan_result["action_result"]["reasoning"] - is_parallel = plan_result["action_result"].get("is_parallel", False) + # # 执行规划 + # plan_result = await self.planner.plan() + # action_type = plan_result["action_result"]["action_type"] + # action_data = plan_result["action_result"]["action_data"] + # reasoning = plan_result["action_result"]["reasoning"] + # is_parallel = plan_result["action_result"].get("is_parallel", False) - if action_type == "no_action": - logger.info(f"[{self.stream_name}] {global_config.bot.nickname} 决定进行回复") - elif is_parallel: - logger.info( - f"[{self.stream_name}] {global_config.bot.nickname} 决定进行回复, 同时执行{action_type}动作" - ) - else: - logger.info(f"[{self.stream_name}] {global_config.bot.nickname} 决定执行{action_type}动作") + # if action_type == "no_action": + # logger.info(f"[{self.stream_name}] {global_config.bot.nickname} 决定进行回复") + # elif is_parallel: + # logger.info( + # f"[{self.stream_name}] {global_config.bot.nickname} 决定进行回复, 同时执行{action_type}动作" + # ) + # else: + # logger.info(f"[{self.stream_name}] {global_config.bot.nickname} 决定执行{action_type}动作") - self.action_type = action_type # 更新实例属性 - self.is_parallel_action = is_parallel # 新增:保存并行执行标志 + # self.action_type = action_type # 更新实例属性 + # self.is_parallel_action = is_parallel # 新增:保存并行执行标志 - # 如果规划器决定不执行任何动作 - if action_type == "no_action": - logger.debug(f"[{self.stream_name}] Planner决定不执行任何额外动作") - return no_action + # # 如果规划器决定不执行任何动作 + # if action_type == "no_action": + # logger.debug(f"[{self.stream_name}] Planner决定不执行任何额外动作") + # return no_action - # 执行额外的动作(不影响回复生成) - action_result = await self._execute_action(action_type, action_data, message, thinking_id) - if action_result is not None: - logger.info(f"[{self.stream_name}] 额外动作 {action_type} 执行完成") - else: - logger.warning(f"[{self.stream_name}] 额外动作 {action_type} 执行失败") + # # 执行额外的动作(不影响回复生成) + # action_result = await self._handle_action(action_type, action_data, message_data, thinking_id) + # if action_result is not None: + # logger.info(f"[{self.stream_name}] 额外动作 {action_type} 执行完成") + # else: + # logger.warning(f"[{self.stream_name}] 额外动作 {action_type} 执行失败") - return { - "action_type": action_type, - "action_data": action_data, - "reasoning": reasoning, - "is_parallel": is_parallel, - } + # return { + # "action_type": action_type, + # "action_data": action_data, + # "reasoning": reasoning, + # "is_parallel": is_parallel, + # } - except Exception as e: - logger.error(f"[{self.stream_name}] Planner执行失败: {e}") - return no_action + # except Exception as e: + # logger.error(f"[{self.stream_name}] Planner执行失败: {e}") + # return no_action - async def reply_one_message(self, message: MessageRecv) -> None: - # 回复前处理 - await self.relationship_builder.build_relation() + # async def reply_one_message(self, message_data: dict) -> None: + # # 回复前处理 + # await self.relationship_builder.build_relation() - thinking_id = await self._create_thinking_message(message) + # thinking_id = await self._create_thinking_message(message_data) - # 如果启用planner,预先修改可用actions(避免在并行任务中重复调用) - available_actions = None - if self.enable_planner: - try: - await self.action_modifier.modify_actions( - mode=ChatMode.NORMAL, message_content=message.processed_plain_text - ) - available_actions = self.action_manager.get_using_actions_for_mode(ChatMode.NORMAL) - except Exception as e: - logger.warning(f"[{self.stream_name}] 获取available_actions失败: {e}") - available_actions = None + # # 如果启用planner,预先修改可用actions(避免在并行任务中重复调用) + # available_actions = None + # if self.enable_planner: + # try: + # await self.action_modifier.modify_actions(mode="normal", message_content=message_data.get("processed_plain_text")) + # available_actions = self.action_manager.get_using_actions_for_mode("normal") + # except Exception as e: + # logger.warning(f"[{self.stream_name}] 获取available_actions失败: {e}") + # available_actions = None - # 并行执行回复生成和动作规划 - self.action_type = None # 初始化动作类型 - self.is_parallel_action = False # 初始化并行动作标志 + # # 并行执行回复生成和动作规划 + # self.action_type = None # 初始化动作类型 + # self.is_parallel_action = False # 初始化并行动作标志 - gen_task = asyncio.create_task(self._generate_normal_response(message, available_actions)) - plan_task = asyncio.create_task(self._plan_and_execute_actions(message, thinking_id)) + # gen_task = asyncio.create_task(self._generate_normal_response(message_data, available_actions)) + # plan_task = asyncio.create_task(self._plan_and_execute_actions(message_data, thinking_id)) - try: - gather_timeout = global_config.chat.thinking_timeout - results = await asyncio.wait_for( - asyncio.gather(gen_task, plan_task, return_exceptions=True), - timeout=gather_timeout, - ) - response_set, plan_result = results - except asyncio.TimeoutError: - gen_timed_out = not gen_task.done() - plan_timed_out = not plan_task.done() + # try: + # gather_timeout = global_config.chat.thinking_timeout + # results = await asyncio.wait_for( + # asyncio.gather(gen_task, plan_task, return_exceptions=True), + # timeout=gather_timeout, + # ) + # response_set, plan_result = results + # except asyncio.TimeoutError: + # gen_timed_out = not gen_task.done() + # plan_timed_out = not plan_task.done() - timeout_details = [] - if gen_timed_out: - timeout_details.append("回复生成(gen)") - if plan_timed_out: - timeout_details.append("动作规划(plan)") + # timeout_details = [] + # if gen_timed_out: + # timeout_details.append("回复生成(gen)") + # if plan_timed_out: + # timeout_details.append("动作规划(plan)") - timeout_source = " 和 ".join(timeout_details) + # timeout_source = " 和 ".join(timeout_details) - logger.warning( - f"[{self.stream_name}] {timeout_source} 任务超时 ({global_config.chat.thinking_timeout}秒),正在取消相关任务..." - ) - # print(f"111{self.timeout_count}") - self.timeout_count += 1 - if self.timeout_count > 5: - logger.warning( - f"[{self.stream_name}] 连续回复超时次数过多,{global_config.chat.thinking_timeout}秒 内大模型没有返回有效内容,请检查你的api是否速度过慢或配置错误。建议不要使用推理模型,推理模型生成速度过慢。或者尝试拉高thinking_timeout参数,这可能导致回复时间过长。" - ) + # logger.warning( + # f"[{self.stream_name}] {timeout_source} 任务超时 ({global_config.chat.thinking_timeout}秒),正在取消相关任务..." + # ) + # # print(f"111{self.timeout_count}") + # self.timeout_count += 1 + # if self.timeout_count > 5: + # logger.warning( + # f"[{self.stream_name}] 连续回复超时次数过多,{global_config.chat.thinking_timeout}秒 内大模型没有返回有效内容,请检查你的api是否速度过慢或配置错误。建议不要使用推理模型,推理模型生成速度过慢。或者尝试拉高thinking_timeout参数,这可能导致回复时间过长。" + # ) - # 取消未完成的任务 - if not gen_task.done(): - gen_task.cancel() - if not plan_task.done(): - plan_task.cancel() + # # 取消未完成的任务 + # if not gen_task.done(): + # gen_task.cancel() + # if not plan_task.done(): + # plan_task.cancel() - # 清理思考消息 - await self._cleanup_thinking_message_by_id(thinking_id) + # # 清理思考消息 + # await self._cleanup_thinking_message_by_id(thinking_id) - response_set = None - plan_result = None + # response_set = None + # plan_result = None - # 处理生成回复的结果 - if isinstance(response_set, Exception): - logger.error(f"[{self.stream_name}] 回复生成异常: {response_set}") - response_set = None + # # 处理生成回复的结果 + # if isinstance(response_set, Exception): + # logger.error(f"[{self.stream_name}] 回复生成异常: {response_set}") + # response_set = None - # 处理规划结果(可选,不影响回复) - if isinstance(plan_result, Exception): - logger.error(f"[{self.stream_name}] 动作规划异常: {plan_result}") - elif plan_result: - logger.debug(f"[{self.stream_name}] 额外动作处理完成: {self.action_type}") + # # 处理规划结果(可选,不影响回复) + # if isinstance(plan_result, Exception): + # logger.error(f"[{self.stream_name}] 动作规划异常: {plan_result}") + # elif plan_result: + # logger.debug(f"[{self.stream_name}] 额外动作处理完成: {self.action_type}") - if response_set: - content = " ".join([item[1] for item in response_set if item[0] == "text"]) + # if response_set: + # content = " ".join([item[1] for item in response_set if item[0] == "text"]) - if not response_set or ( - self.enable_planner and self.action_type not in ["no_action"] and not self.is_parallel_action - ): - if not response_set: - logger.warning(f"[{self.stream_name}] 模型未生成回复内容") - elif self.enable_planner and self.action_type not in ["no_action"] and not self.is_parallel_action: - logger.info( - f"[{self.stream_name}] {global_config.bot.nickname} 原本想要回复:{content},但选择执行{self.action_type},不发表回复" - ) - # 如果模型未生成回复,移除思考消息 - await self._cleanup_thinking_message_by_id(thinking_id) - return False + # if not response_set or ( + # self.enable_planner and self.action_type not in ["no_action"] and not self.is_parallel_action + # ): + # if not response_set: + # logger.warning(f"[{self.stream_name}] 模型未生成回复内容") + # elif self.enable_planner and self.action_type not in ["no_action"] and not self.is_parallel_action: + # logger.info( + # f"[{self.stream_name}] {global_config.bot.nickname} 原本想要回复:{content},但选择执行{self.action_type},不发表回复" + # ) + # # 如果模型未生成回复,移除思考消息 + # await self._cleanup_thinking_message_by_id(thinking_id) + # return False - logger.info(f"[{self.stream_name}] {global_config.bot.nickname} 决定的回复内容: {content}") + # logger.info(f"[{self.stream_name}] {global_config.bot.nickname} 决定的回复内容: {content}") - if self._disabled: - logger.info(f"[{self.stream_name}] 已停用,忽略 normal_response。") - return False + # if self._disabled: + # logger.info(f"[{self.stream_name}] 已停用,忽略 normal_response。") + # return False - # 提取回复文本 - reply_texts = [item[1] for item in response_set if item[0] == "text"] - if not reply_texts: - logger.info(f"[{self.stream_name}] 回复内容中没有文本,不发送消息") - await self._cleanup_thinking_message_by_id(thinking_id) - return False + # # 提取回复文本 + # reply_texts = [item[1] for item in response_set if item[0] == "text"] + # if not reply_texts: + # logger.info(f"[{self.stream_name}] 回复内容中没有文本,不发送消息") + # await self._cleanup_thinking_message_by_id(thinking_id) + # return False - # 发送回复 (不再需要传入 chat) - first_bot_msg = await self._add_messages_to_manager(message, reply_texts, thinking_id) + # # 发送回复 (不再需要传入 chat) + # first_bot_msg = await add_messages_to_manager(message_data, reply_texts, thinking_id) - # 检查 first_bot_msg 是否为 None (例如思考消息已被移除的情况) - if first_bot_msg: - # 消息段已在接收消息时更新,这里不需要额外处理 + # # 检查 first_bot_msg 是否为 None (例如思考消息已被移除的情况) + # if first_bot_msg: + # # 消息段已在接收消息时更新,这里不需要额外处理 - # 记录回复信息到最近回复列表中 - reply_info = { - "time": time.time(), - "user_message": message.processed_plain_text, - "user_info": { - "user_id": message.message_info.user_info.user_id, - "user_nickname": message.message_info.user_info.user_nickname, - }, - "response": response_set, - "is_reference_reply": message.reply is not None, # 判断是否为引用回复 - } - self.recent_replies.append(reply_info) - # 保持最近回复历史在限定数量内 - if len(self.recent_replies) > self.max_replies_history: - self.recent_replies = self.recent_replies[-self.max_replies_history :] - return response_set if response_set else False + # # 记录回复信息到最近回复列表中 + # reply_info = { + # "time": time.time(), + # "user_message": message_data.get("processed_plain_text"), + # "user_info": { + # "user_id": message_data.get("user_id"), + # "user_nickname": message_data.get("user_nickname"), + # }, + # "response": response_set, + # "is_reference_reply": message_data.get("reply") is not None, # 判断是否为引用回复 + # } + # self.recent_replies.append(reply_info) + # # 保持最近回复历史在限定数量内 + # if len(self.recent_replies) > self.max_replies_history: + # self.recent_replies = self.recent_replies[-self.max_replies_history :] + # return response_set if response_set else False # 改为实例方法, 移除 chat 参数 async def start_chat(self): """启动聊天任务。""" - logger.debug(f"[{self.stream_name}] 开始启动聊天任务") - # 重置停用标志 self._disabled = False @@ -696,246 +633,216 @@ class NormalChat: # 清理可能存在的已完成任务引用 if self._chat_task and self._chat_task.done(): self._chat_task = None + if self._priority_chat_task and self._priority_chat_task.done(): + self._priority_chat_task = None try: logger.info(f"[{self.stream_name}] 创建新的聊天轮询任务,模式: {self.reply_mode}") + if self.reply_mode == "priority": - polling_task_send = asyncio.create_task(self._priority_chat_loop()) - polling_task_recv = asyncio.create_task(self._priority_chat_loop_add_message()) - print("555") - polling_task = asyncio.gather(polling_task_send, polling_task_recv) - print("666") + # Start producer loop + producer_task = asyncio.create_task(self._priority_message_producer_loop()) + self._chat_task = producer_task + self._chat_task.add_done_callback(lambda t: self._handle_task_completion(t, "priority_producer")) - else: # 默认或 "interest" 模式 - polling_task = asyncio.create_task(self._reply_interested_message()) + # Start consumer loop + consumer_task = asyncio.create_task(self._priority_chat_loop()) + self._priority_chat_task = consumer_task + self._priority_chat_task.add_done_callback( + lambda t: self._handle_task_completion(t, "priority_consumer") + ) + else: # Interest mode + polling_task = asyncio.create_task(self._interest_message_polling_loop()) + self._chat_task = polling_task + self._chat_task.add_done_callback(lambda t: self._handle_task_completion(t, "interest_polling")) - # 设置回调 - polling_task.add_done_callback(lambda t: self._handle_task_completion(t)) - - # 保存任务引用 - self._chat_task = polling_task + self.running = True logger.debug(f"[{self.stream_name}] 聊天任务启动完成") except Exception as e: logger.error(f"[{self.stream_name}] 启动聊天任务失败: {e}") self._chat_task = None + self._priority_chat_task = None raise - def _handle_task_completion(self, task: asyncio.Task): - """任务完成回调处理""" - try: - # 简化回调逻辑,避免复杂的异常处理 - logger.debug(f"[{self.stream_name}] 任务完成回调被调用") + # def _handle_task_completion(self, task: asyncio.Task, task_name: str = "unknown"): + # """任务完成回调处理""" + # try: + # logger.debug(f"[{self.stream_name}] 任务 '{task_name}' 完成回调被调用") - # 检查是否是我们管理的任务 - if task is not self._chat_task: - # 如果已经不是当前任务(可能在stop_chat中已被清空),直接返回 - logger.debug(f"[{self.stream_name}] 回调的任务不是当前管理的任务") - return + # if task is self._chat_task: + # self._chat_task = None + # elif task is self._priority_chat_task: + # self._priority_chat_task = None + # else: + # logger.debug(f"[{self.stream_name}] 回调的任务 '{task_name}' 不是当前管理的任务") + # return - # 清理任务引用 - self._chat_task = None - logger.debug(f"[{self.stream_name}] 任务引用已清理") + # logger.debug(f"[{self.stream_name}] 任务 '{task_name}' 引用已清理") - # 简单记录任务状态,不进行复杂处理 - if task.cancelled(): - logger.debug(f"[{self.stream_name}] 任务已取消") - elif task.done(): - try: - # 尝试获取异常,但不抛出 - exc = task.exception() - if exc: - logger.error(f"[{self.stream_name}] 任务异常: {type(exc).__name__}: {exc}", exc_info=exc) - else: - logger.debug(f"[{self.stream_name}] 任务正常完成") - except Exception as e: - # 获取异常时也可能出错,静默处理 - logger.debug(f"[{self.stream_name}] 获取任务异常时出错: {e}") + # if task.cancelled(): + # logger.debug(f"[{self.stream_name}] 任务 '{task_name}' 已取消") + # elif task.done(): + # exc = task.exception() + # if exc: + # logger.error(f"[{self.stream_name}] 任务 '{task_name}' 异常: {type(exc).__name__}: {exc}", exc_info=exc) + # else: + # logger.debug(f"[{self.stream_name}] 任务 '{task_name}' 正常完成") - except Exception as e: - # 回调函数中的任何异常都要捕获,避免影响系统 - logger.error(f"[{self.stream_name}] 任务完成回调处理出错: {e}") - # 确保任务引用被清理 - self._chat_task = None + # except Exception as e: + # logger.error(f"[{self.stream_name}] 任务完成回调处理出错: {e}") + # self._chat_task = None + # self._priority_chat_task = None # 改为实例方法, 移除 stream_id 参数 async def stop_chat(self): """停止当前实例的兴趣监控任务。""" logger.debug(f"[{self.stream_name}] 开始停止聊天任务") - # 立即设置停用标志,防止新任务启动 self._disabled = True - # 如果没有运行中的任务,直接返回 - if not self._chat_task or self._chat_task.done(): - logger.debug(f"[{self.stream_name}] 没有运行中的任务,直接完成停止") - self._chat_task = None - return + if self._chat_task and not self._chat_task.done(): + self._chat_task.cancel() + if self._priority_chat_task and not self._priority_chat_task.done(): + self._priority_chat_task.cancel() - # 保存任务引用并立即清空,避免回调中的循环引用 - task_to_cancel = self._chat_task self._chat_task = None + self._priority_chat_task = None - logger.debug(f"[{self.stream_name}] 取消聊天任务") + # def adjust_reply_frequency(self): + # """ + # 根据预设规则动态调整回复意愿(willing_amplifier)。 + # - 评估周期:10分钟 + # - 目标频率:由 global_config.chat.talk_frequency 定义(例如 1条/分钟) + # - 调整逻辑: + # - 0条回复 -> 5.0x 意愿 + # - 达到目标回复数 -> 1.0x 意愿(基准) + # - 达到目标2倍回复数 -> 0.2x 意愿 + # - 中间值线性变化 + # - 增益抑制:如果最近5分钟回复过快,则不增加意愿。 + # """ + # # --- 1. 定义参数 --- + # evaluation_minutes = 10.0 + # target_replies_per_min = global_config.chat.get_current_talk_frequency( + # self.stream_id + # ) # 目标频率:e.g. 1条/分钟 + # target_replies_in_window = target_replies_per_min * evaluation_minutes # 10分钟内的目标回复数 - # 尝试优雅取消任务 - task_to_cancel.cancel() + # if target_replies_in_window <= 0: + # logger.debug(f"[{self.stream_name}] 目标回复频率为0或负数,不调整意愿放大器。") + # return - # 异步清理思考消息,不阻塞当前流程 - asyncio.create_task(self._cleanup_thinking_messages_async()) + # # --- 2. 获取近期统计数据 --- + # stats_10_min = get_recent_message_stats(minutes=evaluation_minutes, chat_id=self.stream_id) + # bot_reply_count_10_min = stats_10_min["bot_reply_count"] - async def _cleanup_thinking_messages_async(self): - """异步清理思考消息,避免阻塞主流程""" - try: - # 添加短暂延迟,让任务有时间响应取消 - await asyncio.sleep(0.1) + # # --- 3. 计算新的意愿放大器 (willing_amplifier) --- + # # 基于回复数在 [0, target*2] 区间内进行分段线性映射 + # if bot_reply_count_10_min <= target_replies_in_window: + # # 在 [0, 目标数] 区间,意愿从 5.0 线性下降到 1.0 + # new_amplifier = 5.0 + (bot_reply_count_10_min - 0) * (1.0 - 5.0) / (target_replies_in_window - 0) + # elif bot_reply_count_10_min <= target_replies_in_window * 2: + # # 在 [目标数, 目标数*2] 区间,意愿从 1.0 线性下降到 0.2 + # over_target_cap = target_replies_in_window * 2 + # new_amplifier = 1.0 + (bot_reply_count_10_min - target_replies_in_window) * (0.2 - 1.0) / ( + # over_target_cap - target_replies_in_window + # ) + # else: + # # 超过目标数2倍,直接设为最小值 + # new_amplifier = 0.2 - container = await message_manager.get_container(self.stream_id) - if container: - # 查找并移除所有 MessageThinking 类型的消息 - thinking_messages = [msg for msg in container.messages[:] if isinstance(msg, MessageThinking)] - if thinking_messages: - for msg in thinking_messages: - container.messages.remove(msg) - logger.info(f"[{self.stream_name}] 清理了 {len(thinking_messages)} 条未处理的思考消息。") - except Exception as e: - logger.error(f"[{self.stream_name}] 异步清理思考消息时出错: {e}") - # 不打印完整栈跟踪,避免日志污染 + # # --- 4. 检查是否需要抑制增益 --- + # # "如果邻近5分钟内,回复数量 > 频率/2,就不再进行增益" + # suppress_gain = False + # if new_amplifier > self.willing_amplifier: # 仅在计算结果为增益时检查 + # suppression_minutes = 5.0 + # # 5分钟内目标回复数的一半 + # suppression_threshold = (target_replies_per_min / 2) * suppression_minutes # e.g., (1/2)*5 = 2.5 + # stats_5_min = get_recent_message_stats(minutes=suppression_minutes, chat_id=self.stream_id) + # bot_reply_count_5_min = stats_5_min["bot_reply_count"] - def adjust_reply_frequency(self): - """ - 根据预设规则动态调整回复意愿(willing_amplifier)。 - - 评估周期:10分钟 - - 目标频率:由 global_config.chat.talk_frequency 定义(例如 1条/分钟) - - 调整逻辑: - - 0条回复 -> 5.0x 意愿 - - 达到目标回复数 -> 1.0x 意愿(基准) - - 达到目标2倍回复数 -> 0.2x 意愿 - - 中间值线性变化 - - 增益抑制:如果最近5分钟回复过快,则不增加意愿。 - """ - # --- 1. 定义参数 --- - evaluation_minutes = 10.0 - target_replies_per_min = global_config.chat.get_current_talk_frequency( - self.stream_id - ) # 目标频率:e.g. 1条/分钟 - target_replies_in_window = target_replies_per_min * evaluation_minutes # 10分钟内的目标回复数 + # if bot_reply_count_5_min > suppression_threshold: + # suppress_gain = True - if target_replies_in_window <= 0: - logger.debug(f"[{self.stream_name}] 目标回复频率为0或负数,不调整意愿放大器。") - return + # # --- 5. 更新意愿放大器 --- + # if suppress_gain: + # logger.debug( + # f"[{self.stream_name}] 回复增益被抑制。最近5分钟内回复数 ({bot_reply_count_5_min}) " + # f"> 阈值 ({suppression_threshold:.1f})。意愿放大器保持在 {self.willing_amplifier:.2f}" + # ) + # # 不做任何改动 + # else: + # # 限制最终值在 [0.2, 5.0] 范围内 + # self.willing_amplifier = max(0.2, min(5.0, new_amplifier)) + # logger.debug( + # f"[{self.stream_name}] 调整回复意愿。10分钟内回复: {bot_reply_count_10_min} (目标: {target_replies_in_window:.0f}) -> " + # f"意愿放大器更新为: {self.willing_amplifier:.2f}" + # ) - # --- 2. 获取近期统计数据 --- - stats_10_min = get_recent_message_stats(minutes=evaluation_minutes, chat_id=self.stream_id) - bot_reply_count_10_min = stats_10_min["bot_reply_count"] + # async def _execute_action( + # self, action_type: str, action_data: dict, message_data: dict, thinking_id: str + # ) -> Optional[bool]: + # """执行具体的动作,只返回执行成功与否""" + # try: + # # 创建动作处理器实例 + # action_handler = self.action_manager.create_action( + # action_name=action_type, + # action_data=action_data, + # reasoning=action_data.get("reasoning", ""), + # cycle_timers={}, # normal_chat使用空的cycle_timers + # thinking_id=thinking_id, + # chat_stream=self.chat_stream, + # log_prefix=self.stream_name, + # shutting_down=self._disabled, + # ) - # --- 3. 计算新的意愿放大器 (willing_amplifier) --- - # 基于回复数在 [0, target*2] 区间内进行分段线性映射 - if bot_reply_count_10_min <= target_replies_in_window: - # 在 [0, 目标数] 区间,意愿从 5.0 线性下降到 1.0 - new_amplifier = 5.0 + (bot_reply_count_10_min - 0) * (1.0 - 5.0) / (target_replies_in_window - 0) - elif bot_reply_count_10_min <= target_replies_in_window * 2: - # 在 [目标数, 目标数*2] 区间,意愿从 1.0 线性下降到 0.2 - over_target_cap = target_replies_in_window * 2 - new_amplifier = 1.0 + (bot_reply_count_10_min - target_replies_in_window) * (0.2 - 1.0) / ( - over_target_cap - target_replies_in_window - ) - else: - # 超过目标数2倍,直接设为最小值 - new_amplifier = 0.2 + # if action_handler: + # # 执行动作 + # result = await action_handler.handle_action() + # success = False - # --- 4. 检查是否需要抑制增益 --- - # "如果邻近5分钟内,回复数量 > 频率/2,就不再进行增益" - suppress_gain = False - if new_amplifier > self.willing_amplifier: # 仅在计算结果为增益时检查 - suppression_minutes = 5.0 - # 5分钟内目标回复数的一半 - suppression_threshold = (target_replies_per_min / 2) * suppression_minutes # e.g., (1/2)*5 = 2.5 - stats_5_min = get_recent_message_stats(minutes=suppression_minutes, chat_id=self.stream_id) - bot_reply_count_5_min = stats_5_min["bot_reply_count"] + # if result and isinstance(result, tuple) and len(result) >= 2: + # # handle_action返回 (success: bool, message: str) + # success = result[0] + # elif result: + # # 如果返回了其他结果,假设成功 + # success = True - if bot_reply_count_5_min > suppression_threshold: - suppress_gain = True + # return success - # --- 5. 更新意愿放大器 --- - if suppress_gain: - logger.debug( - f"[{self.stream_name}] 回复增益被抑制。最近5分钟内回复数 ({bot_reply_count_5_min}) " - f"> 阈值 ({suppression_threshold:.1f})。意愿放大器保持在 {self.willing_amplifier:.2f}" - ) - # 不做任何改动 - else: - # 限制最终值在 [0.2, 5.0] 范围内 - self.willing_amplifier = max(0.2, min(5.0, new_amplifier)) - logger.debug( - f"[{self.stream_name}] 调整回复意愿。10分钟内回复: {bot_reply_count_10_min} (目标: {target_replies_in_window:.0f}) -> " - f"意愿放大器更新为: {self.willing_amplifier:.2f}" - ) + # except Exception as e: + # logger.error(f"[{self.stream_name}] 执行动作 {action_type} 失败: {e}") - async def _execute_action( - self, action_type: str, action_data: dict, message: MessageRecv, thinking_id: str - ) -> Optional[bool]: - """执行具体的动作,只返回执行成功与否""" - try: - # 创建动作处理器实例 - action_handler = self.action_manager.create_action( - action_name=action_type, - action_data=action_data, - reasoning=action_data.get("reasoning", ""), - cycle_timers={}, # normal_chat使用空的cycle_timers - thinking_id=thinking_id, - chat_stream=self.chat_stream, - log_prefix=self.stream_name, - shutting_down=self._disabled, - ) + # return False - if action_handler: - # 执行动作 - result = await action_handler.handle_action() - success = False + # def get_action_manager(self) -> ActionManager: + # """获取动作管理器实例""" + # return self.action_manager - if result and isinstance(result, tuple) and len(result) >= 2: - # handle_action返回 (success: bool, message: str) - success = result[0] - elif result: - # 如果返回了其他结果,假设成功 - success = True + # def _get_fatigue_reply_multiplier(self) -> float: + # """获取疲劳期回复频率调整系数 - return success + # Returns: + # float: 回复频率调整系数,范围0.5-1.0 + # """ + # if not self.get_cooldown_progress_callback: + # return 1.0 # 没有冷却进度回调,返回正常系数 - except Exception as e: - logger.error(f"[{self.stream_name}] 执行动作 {action_type} 失败: {e}") + # try: + # cooldown_progress = self.get_cooldown_progress_callback() - return False + # if cooldown_progress >= 1.0: + # return 1.0 # 冷却完成,正常回复频率 - def get_action_manager(self) -> ActionManager: - """获取动作管理器实例""" - return self.action_manager + # # 疲劳期间:从0.5逐渐恢复到1.0 + # # progress=0时系数为0.5,progress=1时系数为1.0 + # multiplier = 0.2 + (0.8 * cooldown_progress) - def _get_fatigue_reply_multiplier(self) -> float: - """获取疲劳期回复频率调整系数 - - Returns: - float: 回复频率调整系数,范围0.5-1.0 - """ - if not self.get_cooldown_progress_callback: - return 1.0 # 没有冷却进度回调,返回正常系数 - - try: - cooldown_progress = self.get_cooldown_progress_callback() - - if cooldown_progress >= 1.0: - return 1.0 # 冷却完成,正常回复频率 - - # 疲劳期间:从0.5逐渐恢复到1.0 - # progress=0时系数为0.5,progress=1时系数为1.0 - multiplier = 0.2 + (0.8 * cooldown_progress) - - return multiplier - except Exception as e: - logger.warning(f"[{self.stream_name}] 获取疲劳调整系数时出错: {e}") - return 1.0 # 出错时返回正常系数 + # return multiplier + # except Exception as e: + # logger.warning(f"[{self.stream_name}] 获取疲劳调整系数时出错: {e}") + # return 1.0 # 出错时返回正常系数 async def _check_should_switch_to_focus(self) -> bool: """ @@ -972,42 +879,42 @@ class NormalChat: return should_switch - async def _cleanup_thinking_message_by_id(self, thinking_id: str): - """根据ID清理思考消息""" - try: - container = await message_manager.get_container(self.stream_id) - if container: - for msg in container.messages[:]: - if isinstance(msg, MessageThinking) and msg.message_info.message_id == thinking_id: - container.messages.remove(msg) - logger.info(f"[{self.stream_name}] 已清理思考消息 {thinking_id}") - break - except Exception as e: - logger.error(f"[{self.stream_name}] 清理思考消息 {thinking_id} 时出错: {e}") + # async def _cleanup_thinking_message_by_id(self, thinking_id: str): + # """根据ID清理思考消息""" + # try: + # container = await message_manager.get_container(self.stream_id) + # if container: + # for msg in container.messages[:]: + # if isinstance(msg, MessageThinking) and msg.message_info.message_id == thinking_id: + # container.messages.remove(msg) + # logger.info(f"[{self.stream_name}] 已清理思考消息 {thinking_id}") + # break + # except Exception as e: + # logger.error(f"[{self.stream_name}] 清理思考消息 {thinking_id} 时出错: {e}") -def get_recent_message_stats(minutes: int = 30, chat_id: str = None) -> dict: - """ - Args: - minutes (int): 检索的分钟数,默认30分钟 - chat_id (str, optional): 指定的chat_id,仅统计该chat下的消息。为None时统计全部。 - Returns: - dict: {"bot_reply_count": int, "total_message_count": int} - """ +# def get_recent_message_stats(minutes: int = 30, chat_id: str = None) -> dict: +# """ +# Args: +# minutes (int): 检索的分钟数,默认30分钟 +# chat_id (str, optional): 指定的chat_id,仅统计该chat下的消息。为None时统计全部。 +# Returns: +# dict: {"bot_reply_count": int, "total_message_count": int} +# """ - now = time.time() - start_time = now - minutes * 60 - bot_id = global_config.bot.qq_account +# now = time.time() +# start_time = now - minutes * 60 +# bot_id = global_config.bot.qq_account - filter_base = {"time": {"$gte": start_time}} - if chat_id is not None: - filter_base["chat_id"] = chat_id +# filter_base = {"time": {"$gte": start_time}} +# if chat_id is not None: +# filter_base["chat_id"] = chat_id - # 总消息数 - total_message_count = count_messages(filter_base) - # bot自身回复数 - bot_filter = filter_base.copy() - bot_filter["user_id"] = bot_id - bot_reply_count = count_messages(bot_filter) +# # 总消息数 +# total_message_count = count_messages(filter_base) +# # bot自身回复数 +# bot_filter = filter_base.copy() +# bot_filter["user_id"] = bot_id +# bot_reply_count = count_messages(bot_filter) - return {"bot_reply_count": bot_reply_count, "total_message_count": total_message_count} +# return {"bot_reply_count": bot_reply_count, "total_message_count": total_message_count} diff --git a/src/chat/planner_actions/action_manager.py b/src/chat/planner_actions/action_manager.py index ed045436f..483ce9a3a 100644 --- a/src/chat/planner_actions/action_manager.py +++ b/src/chat/planner_actions/action_manager.py @@ -145,29 +145,6 @@ class ActionManager: """获取当前正在使用的动作集合""" return self._using_actions.copy() - def get_using_actions_for_mode(self, mode: ChatMode) -> Dict[str, ActionInfo]: - """ - 根据聊天模式获取可用的动作集合 - - Args: - mode: 聊天模式 (ChatMode.FOCUS, ChatMode.NORMAL, ChatMode.ALL) - - Returns: - Dict[str, ActionInfo]: 在指定模式下可用的动作集合 - """ - enabled_actions = {} - - for action_name, action_info in self._using_actions.items(): - action_mode = action_info.mode_enable - - # 检查动作是否在当前模式下启用 - if action_mode in [ChatMode.ALL, mode]: - enabled_actions[action_name] = action_info - logger.debug(f"动作 {action_name} 在模式 {mode} 下可用 (mode_enable: {action_mode})") - - logger.debug(f"模式 {mode} 下可用动作: {list(enabled_actions.keys())}") - return enabled_actions - def add_action_to_using(self, action_name: str) -> bool: """ 添加已注册的动作到当前使用的动作集 diff --git a/src/chat/planner_actions/action_modifier.py b/src/chat/planner_actions/action_modifier.py index 13d13a71f..7853a9b9d 100644 --- a/src/chat/planner_actions/action_modifier.py +++ b/src/chat/planner_actions/action_modifier.py @@ -7,7 +7,7 @@ from typing import List, Any, Dict, TYPE_CHECKING from src.common.logger import get_logger from src.config.config import global_config from src.llm_models.utils_model import LLMRequest -from src.chat.focus_chat.focus_loop_info import FocusLoopInfo +from src.chat.focus_chat.hfc_utils import CycleDetail from src.chat.message_receive.chat_stream import get_chat_manager, ChatMessageContext from src.chat.planner_actions.action_manager import ActionManager from src.chat.utils.chat_message_builder import get_raw_msg_before_timestamp_with_chat, build_readable_messages @@ -48,8 +48,7 @@ class ActionModifier: async def modify_actions( self, - loop_info=None, - mode: ChatMode = ChatMode.FOCUS, + history_loop=None, message_content: str = "", ): # sourcery skip: use-named-expression """ @@ -67,7 +66,7 @@ class ActionModifier: removals_s2 = [] self.action_manager.restore_actions() - all_actions = self.action_manager.get_using_actions_for_mode(mode) + all_actions = self.action_manager.get_using_actions() message_list_before_now_half = get_raw_msg_before_timestamp_with_chat( chat_id=self.chat_stream.stream_id, @@ -87,10 +86,10 @@ class ActionModifier: chat_content = chat_content + "\n" + f"现在,最新的消息是:{message_content}" # === 第一阶段:传统观察处理 === - if loop_info: - removals_from_loop = await self.analyze_loop_actions(loop_info) - if removals_from_loop: - removals_s1.extend(removals_from_loop) + # if history_loop: + # removals_from_loop = await self.analyze_loop_actions(history_loop) + # if removals_from_loop: + # removals_s1.extend(removals_from_loop) # 检查动作的关联类型 chat_context = self.chat_stream.context @@ -109,12 +108,11 @@ class ActionModifier: logger.debug(f"{self.log_prefix}开始激活类型判定阶段") # 获取当前使用的动作集(经过第一阶段处理) - current_using_actions = self.action_manager.get_using_actions_for_mode(mode) + current_using_actions = self.action_manager.get_using_actions() # 获取因激活类型判定而需要移除的动作 removals_s2 = await self._get_deactivated_actions_by_type( current_using_actions, - mode, chat_content, ) @@ -129,7 +127,7 @@ class ActionModifier: removals_summary = " | ".join([f"{name}({reason})" for name, reason in all_removals]) logger.info( - f"{self.log_prefix}{mode}模式动作修改流程结束,最终可用动作: {list(self.action_manager.get_using_actions_for_mode(mode).keys())}||移除记录: {removals_summary}" + f"{self.log_prefix} 动作修改流程结束,最终可用动作: {list(self.action_manager.get_using_actions().keys())}||移除记录: {removals_summary}" ) def _check_action_associated_types(self, all_actions: Dict[str, ActionInfo], chat_context: ChatMessageContext): @@ -144,8 +142,7 @@ class ActionModifier: async def _get_deactivated_actions_by_type( self, - actions_with_info: Dict[str, ActionInfo], - mode: ChatMode = ChatMode.FOCUS, + actions_with_info: Dict[str, Any], chat_content: str = "", ) -> List[tuple[str, str]]: """ @@ -167,9 +164,11 @@ class ActionModifier: random.shuffle(actions_to_check) for action_name, action_info in actions_to_check: - mode_activation_type = f"{mode}_activation_type" - activation_type = getattr(action_info, mode_activation_type, ActionActivationType.ALWAYS) - if activation_type == ActionActivationType.ALWAYS: + activation_type = action_info.get("activation_type", "") + if not activation_type: + activation_type = action_info.get("focus_activation_type", "") + + if activation_type == "always": continue # 总是激活,无需处理 elif activation_type == ActionActivationType.RANDOM: @@ -189,6 +188,11 @@ class ActionModifier: elif activation_type == ActionActivationType.LLM_JUDGE: llm_judge_actions[action_name] = action_info + elif activation_type == "never": + reason = "激活类型为never" + deactivated_actions.append((action_name, reason)) + logger.debug(f"{self.log_prefix}未激活动作: {action_name},原因: 激活类型为never") + else: logger.warning(f"{self.log_prefix}未知的激活类型: {activation_type},跳过处理") @@ -434,7 +438,7 @@ class ActionModifier: logger.debug(f"{self.log_prefix}动作 {action_name} 未匹配到任何关键词: {activation_keywords}") return False - async def analyze_loop_actions(self, obs: FocusLoopInfo) -> List[tuple[str, str]]: + async def analyze_loop_actions(self, history_loop: List[CycleDetail]) -> List[tuple[str, str]]: """分析最近的循环内容并决定动作的移除 Returns: @@ -444,7 +448,7 @@ class ActionModifier: removals = [] # 获取最近10次循环 - recent_cycles = obs.history_loop[-10:] if len(obs.history_loop) > 10 else obs.history_loop + recent_cycles = history_loop[-10:] if len(history_loop) > 10 else history_loop if not recent_cycles: return removals @@ -501,16 +505,24 @@ class ActionModifier: return removals - def get_available_actions_count(self) -> int: + def get_available_actions_count(self, mode: str = "focus") -> int: """获取当前可用动作数量(排除默认的no_action)""" - current_actions = self.action_manager.get_using_actions_for_mode(ChatMode.NORMAL) + current_actions = self.action_manager.get_using_actions_for_mode(mode) # 排除no_action(如果存在) filtered_actions = {k: v for k, v in current_actions.items() if k != "no_action"} return len(filtered_actions) - def should_skip_planning(self) -> bool: + def should_skip_planning_for_no_reply(self) -> bool: """判断是否应该跳过规划过程""" - available_count = self.get_available_actions_count() + current_actions = self.action_manager.get_using_actions_for_mode("focus") + # 排除no_action(如果存在) + if len(current_actions) == 1 and "no_reply" in current_actions: + return True + return False + + def should_skip_planning_for_no_action(self) -> bool: + """判断是否应该跳过规划过程""" + available_count = self.action_manager.get_using_actions_for_mode("normal") if available_count == 0: logger.debug(f"{self.log_prefix} 没有可用动作,跳过规划") return True diff --git a/src/chat/planner_actions/planner.py b/src/chat/planner_actions/planner.py index 537e00ba9..e6279d0c3 100644 --- a/src/chat/planner_actions/planner.py +++ b/src/chat/planner_actions/planner.py @@ -67,20 +67,19 @@ def init_prompt(): class ActionPlanner: - def __init__(self, chat_id: str, action_manager: ActionManager, mode: ChatMode = ChatMode.FOCUS): + def __init__(self, chat_id: str, action_manager: ActionManager): self.chat_id = chat_id self.log_prefix = f"[{get_chat_manager().get_stream_name(chat_id) or chat_id}]" - self.mode = mode self.action_manager = action_manager # LLM规划器配置 self.planner_llm = LLMRequest( model=global_config.model.planner, - request_type=f"{self.mode.value}.planner", # 用于动作规划 + request_type="planner", # 用于动作规划 ) self.last_obs_time_mark = 0.0 - async def plan(self) -> Dict[str, Any]: # sourcery skip: dict-comprehension + async def plan(self, mode: str = "focus") -> Dict[str, Any]: # sourcery skip: dict-comprehension """ 规划器 (Planner): 使用LLM根据上下文决定做出什么动作。 """ @@ -96,7 +95,7 @@ class ActionPlanner: is_group_chat, chat_target_info = get_chat_type_and_target_info(self.chat_id) logger.debug(f"{self.log_prefix}获取到聊天信息 - 群聊: {is_group_chat}, 目标信息: {chat_target_info}") - current_available_actions_dict = self.action_manager.get_using_actions_for_mode(self.mode) + current_available_actions_dict = self.action_manager.get_using_actions() # 获取完整的动作信息 all_registered_actions = self.action_manager.get_registered_actions() @@ -130,6 +129,7 @@ class ActionPlanner: is_group_chat=is_group_chat, # <-- Pass HFC state chat_target_info=chat_target_info, # <-- 传递获取到的聊天目标信息 current_available_actions=current_available_actions, # <-- Pass determined actions + mode=mode, ) # --- 调用 LLM (普通文本生成) --- @@ -178,7 +178,7 @@ class ActionPlanner: if action == "no_action": reasoning = "normal决定不使用额外动作" - elif action not in current_available_actions: + elif action != "no_reply" and action != "reply" and action not in current_available_actions: logger.warning( f"{self.log_prefix}LLM 返回了当前不可用或无效的动作: '{action}' (可用: {list(current_available_actions.keys())}),将强制使用 'no_reply'" ) @@ -219,6 +219,7 @@ class ActionPlanner: is_group_chat: bool, # Now passed as argument chat_target_info: Optional[dict], # Now passed as argument current_available_actions: Dict[str, ActionInfo], + mode: str = "focus", ) -> str: # sourcery skip: use-join """构建 Planner LLM 的提示词 (获取模板并填充数据)""" try: @@ -248,9 +249,25 @@ class ActionPlanner: self.last_obs_time_mark = time.time() - if self.mode == ChatMode.FOCUS: + if mode == "focus": by_what = "聊天内容" - no_action_block = "" + no_action_block = """重要说明1: +- 'no_reply' 表示只进行不进行回复,等待合适的回复时机 +- 当你刚刚发送了消息,没有人回复时,选择no_reply +- 当你一次发送了太多消息,为了避免打扰聊天节奏,选择no_reply + +动作:reply +动作描述:参与聊天回复,发送文本进行表达 +- 你想要闲聊或者随便附和 +- 有人提到你 +- 如果你刚刚进行了回复,不要对同一个话题重复回应 +{ + "action": "reply", + "reply_to":"你要回复的对方的发言内容,格式:(用户名:发言内容),可以为none" + "reason":"回复的原因" +} + +""" else: by_what = "聊天内容和用户的最新消息" no_action_block = """重要说明: diff --git a/src/chat/replyer/default_generator.py b/src/chat/replyer/default_generator.py index a9214a9af..082fafc62 100644 --- a/src/chat/replyer/default_generator.py +++ b/src/chat/replyer/default_generator.py @@ -18,7 +18,6 @@ from src.chat.utils.timer_calculator import Timer # <--- Import Timer from src.chat.utils.utils import get_chat_type_and_target_info from src.chat.utils.prompt_builder import Prompt, global_prompt_manager from src.chat.utils.chat_message_builder import build_readable_messages, get_raw_msg_before_timestamp_with_chat -from src.chat.focus_chat.hfc_utils import parse_thinking_id_to_timestamp from src.chat.express.expression_selector import expression_selector from src.chat.knowledge.knowledge_lib import qa_manager from src.chat.memory_system.memory_activator import MemoryActivator @@ -30,6 +29,8 @@ from src.plugin_system.base.component_types import ActionInfo logger = get_logger("replyer") +ENABLE_S2S_MODE = True + def init_prompt(): Prompt("你正在qq群里聊天,下面是群里在聊的内容:", "chat_target_group1") @@ -133,33 +134,6 @@ class DefaultReplyer: return random.choices(population=configs, weights=weights, k=1)[0] - async def _create_thinking_message(self, anchor_message: Optional[MessageRecv], thinking_id: str): - """创建思考消息 (尝试锚定到 anchor_message)""" - if not anchor_message or not anchor_message.chat_stream: - logger.error(f"{self.log_prefix} 无法创建思考消息,缺少有效的锚点消息或聊天流。") - return None - - chat = anchor_message.chat_stream - message_info = anchor_message.message_info - thinking_time_point = parse_thinking_id_to_timestamp(thinking_id) - bot_user_info = UserInfo( - user_id=global_config.bot.qq_account, - user_nickname=global_config.bot.nickname, - platform=message_info.platform, - ) - - thinking_message = MessageThinking( - message_id=thinking_id, - chat_stream=chat, - bot_user_info=bot_user_info, - reply=anchor_message, # 回复的是锚点消息 - thinking_start_time=thinking_time_point, - ) - # logger.debug(f"创建思考消息thinking_message:{thinking_message}") - - await self.heart_fc_sender.register_thinking(thinking_message) - return None - async def generate_reply_with_context( self, reply_data: Optional[Dict[str, Any]] = None, @@ -526,13 +500,13 @@ class DefaultReplyer: show_actions=True, ) - message_list_before_now_half = get_raw_msg_before_timestamp_with_chat( + message_list_before_short = get_raw_msg_before_timestamp_with_chat( chat_id=chat_id, timestamp=time.time(), - limit=int(global_config.chat.max_context_size * 0.5), + limit=int(global_config.chat.max_context_size * 0.33), ) - chat_talking_prompt_half = build_readable_messages( - message_list_before_now_half, + chat_talking_prompt_short = build_readable_messages( + message_list_before_short, replace_bot_name=True, merge_messages=False, timestamp_mode="relative", @@ -543,14 +517,14 @@ class DefaultReplyer: # 并行执行四个构建任务 task_results = await asyncio.gather( self._time_and_run_task( - self.build_expression_habits(chat_talking_prompt_half, target), "build_expression_habits" + self.build_expression_habits(chat_talking_prompt_short, target), "build_expression_habits" ), self._time_and_run_task( - self.build_relation_info(reply_data, chat_talking_prompt_half), "build_relation_info" + self.build_relation_info(reply_data, chat_talking_prompt_short), "build_relation_info" ), - self._time_and_run_task(self.build_memory_block(chat_talking_prompt_half, target), "build_memory_block"), + self._time_and_run_task(self.build_memory_block(chat_talking_prompt_short, target), "build_memory_block"), self._time_and_run_task( - self.build_tool_info(chat_talking_prompt_half, reply_data, enable_tool=enable_tool), "build_tool_info" + self.build_tool_info(chat_talking_prompt_short, reply_data, enable_tool=enable_tool), "build_tool_info" ), ) @@ -801,108 +775,6 @@ class DefaultReplyer: moderation_prompt=moderation_prompt_block, ) - async def send_response_messages( - self, - anchor_message: Optional[MessageRecv], - response_set: List[Tuple[str, str]], - thinking_id: str = "", - display_message: str = "", - ) -> Optional[List[Tuple[str, bool]]]: - # sourcery skip: assign-if-exp, boolean-if-exp-identity, remove-unnecessary-cast - """发送回复消息 (尝试锚定到 anchor_message),使用 HeartFCSender""" - chat = self.chat_stream - chat_id = self.chat_stream.stream_id - if chat is None: - logger.error(f"{self.log_prefix} 无法发送回复,chat_stream 为空。") - return None - if not anchor_message: - logger.error(f"{self.log_prefix} 无法发送回复,anchor_message 为空。") - return None - - stream_name = get_chat_manager().get_stream_name(chat_id) or chat_id # 获取流名称用于日志 - - # 检查思考过程是否仍在进行,并获取开始时间 - if thinking_id: - # print(f"thinking_id: {thinking_id}") - thinking_start_time = await self.heart_fc_sender.get_thinking_start_time(chat_id, thinking_id) - else: - print("thinking_id is None") - # thinking_id = "ds" + str(round(time.time(), 2)) - thinking_start_time = time.time() - - if thinking_start_time is None: - logger.error(f"[{stream_name}]replyer思考过程未找到或已结束,无法发送回复。") - return None - - mark_head = False - # first_bot_msg: Optional[MessageSending] = None - reply_message_ids = [] # 记录实际发送的消息ID - - sent_msg_list = [] - - for i, msg_text in enumerate(response_set): - # 为每个消息片段生成唯一ID - msg_type = msg_text[0] - data = msg_text[1] - - if global_config.debug.debug_show_chat_mode and msg_type == "text": - data += "ᶠ" - - part_message_id = f"{thinking_id}_{i}" - message_segment = Seg(type=msg_type, data=data) - - if msg_type == "emoji": - is_emoji = True - else: - is_emoji = False - reply_to = not mark_head - - bot_message: MessageSending = await self._build_single_sending_message( - anchor_message=anchor_message, - message_id=part_message_id, - message_segment=message_segment, - display_message=display_message, - reply_to=reply_to, - is_emoji=is_emoji, - thinking_start_time=thinking_start_time, - ) - - try: - if ( - bot_message.is_private_message() - or bot_message.reply.processed_plain_text != "[System Trigger Context]" # type: ignore - or mark_head - ): - set_reply = False - else: - set_reply = True - - if not mark_head: - mark_head = True - typing = False - else: - typing = True - - sent_msg = await self.heart_fc_sender.send_message(bot_message, typing=typing, set_reply=set_reply) - - reply_message_ids.append(part_message_id) # 记录我们生成的ID - - sent_msg_list.append((msg_type, sent_msg)) - - except Exception as e: - logger.error(f"{self.log_prefix}发送回复片段 {i} ({part_message_id}) 时失败: {e}") - traceback.print_exc() - # 这里可以选择是继续发送下一个片段还是中止 - - # 在尝试发送完所有片段后,完成原始的 thinking_id 状态 - try: - await self.heart_fc_sender.complete_thinking(chat_id, thinking_id) - - except Exception as e: - logger.error(f"{self.log_prefix}完成思考状态 {thinking_id} 时出错: {e}") - - return sent_msg_list - async def _build_single_sending_message( self, message_id: str, diff --git a/src/chat/utils/chat_message_builder.py b/src/chat/utils/chat_message_builder.py index 6bdf7f58d..b1c050223 100644 --- a/src/chat/utils/chat_message_builder.py +++ b/src/chat/utils/chat_message_builder.py @@ -30,7 +30,12 @@ def get_raw_msg_by_timestamp( def get_raw_msg_by_timestamp_with_chat( - chat_id: str, timestamp_start: float, timestamp_end: float, limit: int = 0, limit_mode: str = "latest" + chat_id: str, + timestamp_start: float, + timestamp_end: float, + limit: int = 0, + limit_mode: str = "latest", + fliter_bot=False, ) -> List[Dict[str, Any]]: """获取在特定聊天从指定时间戳到指定时间戳的消息,按时间升序排序,返回消息列表 limit: 限制返回的消息数量,0为不限制 @@ -40,11 +45,18 @@ def get_raw_msg_by_timestamp_with_chat( # 只有当 limit 为 0 时才应用外部 sort sort_order = [("time", 1)] if limit == 0 else None # 直接将 limit_mode 传递给 find_messages - return find_messages(message_filter=filter_query, sort=sort_order, limit=limit, limit_mode=limit_mode) + return find_messages( + message_filter=filter_query, sort=sort_order, limit=limit, limit_mode=limit_mode, fliter_bot=fliter_bot + ) def get_raw_msg_by_timestamp_with_chat_inclusive( - chat_id: str, timestamp_start: float, timestamp_end: float, limit: int = 0, limit_mode: str = "latest" + chat_id: str, + timestamp_start: float, + timestamp_end: float, + limit: int = 0, + limit_mode: str = "latest", + fliter_bot=False, ) -> List[Dict[str, Any]]: """获取在特定聊天从指定时间戳到指定时间戳的消息(包含边界),按时间升序排序,返回消息列表 limit: 限制返回的消息数量,0为不限制 @@ -54,7 +66,10 @@ def get_raw_msg_by_timestamp_with_chat_inclusive( # 只有当 limit 为 0 时才应用外部 sort sort_order = [("time", 1)] if limit == 0 else None # 直接将 limit_mode 传递给 find_messages - return find_messages(message_filter=filter_query, sort=sort_order, limit=limit, limit_mode=limit_mode) + + return find_messages( + message_filter=filter_query, sort=sort_order, limit=limit, limit_mode=limit_mode, fliter_bot=fliter_bot + ) def get_raw_msg_by_timestamp_with_chat_users( @@ -580,6 +595,9 @@ def build_readable_actions(actions: List[Dict[str, Any]]) -> str: for action in actions: action_time = action.get("time", current_time) action_name = action.get("action_name", "未知动作") + if action_name == "no_action" or action_name == "no_reply": + continue + action_prompt_display = action.get("action_prompt_display", "无具体内容") time_diff_seconds = current_time - action_time diff --git a/src/chat/normal_chat/willing/mode_classical.py b/src/chat/willing/mode_classical.py similarity index 100% rename from src/chat/normal_chat/willing/mode_classical.py rename to src/chat/willing/mode_classical.py diff --git a/src/chat/normal_chat/willing/mode_custom.py b/src/chat/willing/mode_custom.py similarity index 100% rename from src/chat/normal_chat/willing/mode_custom.py rename to src/chat/willing/mode_custom.py diff --git a/src/chat/normal_chat/willing/mode_mxp.py b/src/chat/willing/mode_mxp.py similarity index 100% rename from src/chat/normal_chat/willing/mode_mxp.py rename to src/chat/willing/mode_mxp.py diff --git a/src/chat/normal_chat/willing/willing_manager.py b/src/chat/willing/willing_manager.py similarity index 95% rename from src/chat/normal_chat/willing/willing_manager.py rename to src/chat/willing/willing_manager.py index f797bc3e0..9a2507586 100644 --- a/src/chat/normal_chat/willing/willing_manager.py +++ b/src/chat/willing/willing_manager.py @@ -93,7 +93,7 @@ class BaseWillingManager(ABC): self.lock = asyncio.Lock() self.logger = logger - def setup(self, message: MessageRecv, chat: ChatStream, is_mentioned_bot: bool, interested_rate: float): + def setup(self, message: dict, chat: ChatStream): person_id = PersonInfoManager.get_person_id(chat.platform, chat.user_info.user_id) # type: ignore self.ongoing_messages[message.message_info.message_id] = WillingInfo( # type: ignore message=message, @@ -102,10 +102,10 @@ class BaseWillingManager(ABC): chat_id=chat.stream_id, person_id=person_id, group_info=chat.group_info, - is_mentioned_bot=is_mentioned_bot, - is_emoji=message.is_emoji, - is_picid=message.is_picid, - interested_rate=interested_rate, + is_mentioned_bot=message.get("is_mentioned_bot", False), + is_emoji=message.get("is_emoji", False), + is_picid=message.get("is_picid", False), + interested_rate=message.get("interested_rate", 0), ) def delete(self, message_id: str): diff --git a/src/common/database/database_model.py b/src/common/database/database_model.py index b411e1b3a..f61c92905 100644 --- a/src/common/database/database_model.py +++ b/src/common/database/database_model.py @@ -130,6 +130,7 @@ class Messages(BaseModel): reply_to = TextField(null=True) interest_value = DoubleField(null=True) + is_mentioned = BooleanField(null=True) # 从 chat_info 扁平化而来的字段 chat_info_stream_id = TextField() @@ -155,6 +156,13 @@ class Messages(BaseModel): detailed_plain_text = TextField(null=True) # 详细的纯文本消息 memorized_times = IntegerField(default=0) # 被记忆的次数 + priority_mode = TextField(null=True) + priority_info = TextField(null=True) + + additional_config = TextField(null=True) + is_emoji = BooleanField(default=False) + is_picid = BooleanField(default=False) + class Meta: # database = db # 继承自 BaseModel table_name = "messages" diff --git a/src/common/message_repository.py b/src/common/message_repository.py index dc5d8b7df..c483c114f 100644 --- a/src/common/message_repository.py +++ b/src/common/message_repository.py @@ -2,6 +2,7 @@ import traceback from typing import List, Any, Optional from peewee import Model # 添加 Peewee Model 导入 +from src.config.config import global_config from src.common.database.database_model import Messages from src.common.logger import get_logger @@ -21,6 +22,7 @@ def find_messages( sort: Optional[List[tuple[str, int]]] = None, limit: int = 0, limit_mode: str = "latest", + fliter_bot=False, ) -> List[dict[str, Any]]: """ 根据提供的过滤器、排序和限制条件查找消息。 @@ -70,6 +72,9 @@ def find_messages( if conditions: query = query.where(*conditions) + if fliter_bot: + query = query.where(Messages.user_id != global_config.bot.qq_account) + if limit > 0: if limit_mode == "earliest": # 获取时间最早的 limit 条记录,已经是正序 diff --git a/src/config/config.py b/src/config/config.py index b61111ec3..6bf97d005 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -49,7 +49,7 @@ TEMPLATE_DIR = os.path.join(PROJECT_ROOT, "template") # 考虑到,实际上配置文件中的mai_version是不会自动更新的,所以采用硬编码 # 对该字段的更新,请严格参照语义化版本规范:https://semver.org/lang/zh-CN/ -MMC_VERSION = "0.8.2-snapshot.1" +MMC_VERSION = "0.9.0-snapshot.1" def update_config(): diff --git a/src/config/official_configs.py b/src/config/official_configs.py index 6838df1d1..3ff3f7b62 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -49,7 +49,7 @@ class IdentityConfig(ConfigBase): identity_detail: list[str] = field(default_factory=lambda: []) """身份特征""" - compress_indentity: bool = True + compress_identity: bool = True """是否压缩身份,压缩后会精简身份信息,节省token消耗并提高回复性能,但是会丢失一些信息,如果不长,可以关闭""" @@ -68,9 +68,6 @@ class RelationshipConfig(ConfigBase): class ChatConfig(ConfigBase): """聊天配置类""" - chat_mode: str = "normal" - """聊天模式""" - max_context_size: int = 18 """上下文长度""" @@ -281,17 +278,11 @@ class NormalChatConfig(ConfigBase): at_bot_inevitable_reply: bool = False """@bot 必然回复""" - enable_planner: bool = False - """是否启用动作规划器""" - @dataclass class FocusChatConfig(ConfigBase): """专注聊天配置类""" - think_interval: float = 1 - """思考间隔(秒)""" - consecutive_replies: float = 1 """连续回复能力,值越高,麦麦连续回复的概率越高""" @@ -530,9 +521,6 @@ class TelemetryConfig(ConfigBase): class DebugConfig(ConfigBase): """调试配置类""" - debug_show_chat_mode: bool = False - """是否在回复后显示当前聊天模式""" - show_prompt: bool = False """是否显示prompt""" diff --git a/src/experimental/PFC/conversation.py b/src/experimental/PFC/conversation.py index 9be055176..c333f3998 100644 --- a/src/experimental/PFC/conversation.py +++ b/src/experimental/PFC/conversation.py @@ -10,7 +10,6 @@ from typing import Dict, Any, Optional from src.chat.message_receive.message import Message from .pfc_types import ConversationState from .pfc import ChatObserver, GoalAnalyzer -from .message_sender import DirectMessageSender from src.common.logger import get_logger from .action_planner import ActionPlanner from .observation_info import ObservationInfo diff --git a/src/experimental/PFC/message_sender.py b/src/experimental/PFC/message_sender.py deleted file mode 100644 index d0816d8b5..000000000 --- a/src/experimental/PFC/message_sender.py +++ /dev/null @@ -1,81 +0,0 @@ -import time -from typing import Optional -from src.common.logger import get_logger -from src.chat.message_receive.chat_stream import ChatStream -from src.chat.message_receive.message import Message -from maim_message import UserInfo, Seg -from src.chat.message_receive.message import MessageSending, MessageSet -from src.chat.message_receive.normal_message_sender import message_manager -from src.chat.message_receive.storage import MessageStorage -from src.config.config import global_config -from rich.traceback import install - -install(extra_lines=3) - - -logger = get_logger("message_sender") - - -class DirectMessageSender: - """直接消息发送器""" - - def __init__(self, private_name: str): - self.private_name = private_name - self.storage = MessageStorage() - - async def send_message( - self, - chat_stream: ChatStream, - content: str, - reply_to_message: Optional[Message] = None, - ) -> None: - """发送消息到聊天流 - - Args: - chat_stream: 聊天流 - content: 消息内容 - reply_to_message: 要回复的消息(可选) - """ - try: - # 创建消息内容 - segments = Seg(type="seglist", data=[Seg(type="text", data=content)]) - - # 获取麦麦的信息 - bot_user_info = UserInfo( - user_id=global_config.bot.qq_account, - user_nickname=global_config.bot.nickname, - platform=chat_stream.platform, - ) - - # 用当前时间作为message_id,和之前那套sender一样 - message_id = f"dm{round(time.time(), 2)}" - - # 构建消息对象 - message = MessageSending( - message_id=message_id, - chat_stream=chat_stream, - bot_user_info=bot_user_info, - sender_info=reply_to_message.message_info.user_info if reply_to_message else None, - message_segment=segments, - reply=reply_to_message, - is_head=True, - is_emoji=False, - thinking_start_time=time.time(), - ) - - # 处理消息 - await message.process() - - # 不知道有什么用,先留下来了,和之前那套sender一样 - _message_json = message.to_dict() - - # 发送消息 - message_set = MessageSet(chat_stream, message_id) - message_set.add_message(message) - await message_manager.add_message(message_set) - await self.storage.store_message(message, chat_stream) - logger.info(f"[私聊][{self.private_name}]PFC消息已发送: {content}") - - except Exception as e: - logger.error(f"[私聊][{self.private_name}]PFC消息发送失败: {str(e)}") - raise diff --git a/src/individuality/individuality.py b/src/individuality/individuality.py index 532b203fd..47048a2bd 100644 --- a/src/individuality/individuality.py +++ b/src/individuality/individuality.py @@ -337,7 +337,7 @@ class Individuality: # 身份配置哈希 identity_config = { "identity_detail": sorted(identity_detail), - "compress_identity": global_config.identity.compress_indentity, + "compress_identity": global_config.identity.compress_identity, } identity_str = json.dumps(identity_config, sort_keys=True) identity_hash = hashlib.md5(identity_str.encode("utf-8")).hexdigest() @@ -507,7 +507,7 @@ class Individuality: """使用LLM创建压缩版本的impression""" logger.info("正在构建身份.........") - if global_config.identity.compress_indentity: + if global_config.identity.compress_identity: identity_to_compress = [] if identity_detail: identity_to_compress.append(f"身份背景: {'、'.join(identity_detail)}") diff --git a/src/main.py b/src/main.py index d481c7d03..0e85f6945 100644 --- a/src/main.py +++ b/src/main.py @@ -7,9 +7,8 @@ from src.common.remote import TelemetryHeartBeatTask from src.manager.async_task_manager import async_task_manager from src.chat.utils.statistic import OnlineTimeRecordTask, StatisticOutputTask from src.chat.emoji_system.emoji_manager import get_emoji_manager -from src.chat.normal_chat.willing.willing_manager import get_willing_manager +from src.chat.willing.willing_manager import get_willing_manager from src.chat.message_receive.chat_stream import get_chat_manager -from src.chat.message_receive.normal_message_sender import message_manager from src.chat.message_receive.storage import MessageStorage from src.config.config import global_config from src.chat.message_receive.bot import chat_bot @@ -23,9 +22,6 @@ from rich.traceback import install # 导入新的插件管理器 from src.plugin_system.core.plugin_manager import plugin_manager -# 导入HFC性能记录器用于日志清理 -from src.chat.focus_chat.hfc_performance_logger import HFCPerformanceLogger - # 导入消息API和traceback模块 from src.common.message import get_global_api @@ -69,11 +65,6 @@ class MainSystem: """初始化其他组件""" init_start_time = time.time() - # 清理HFC旧日志文件(保持目录大小在50MB以内) - logger.info("开始清理HFC旧日志文件...") - HFCPerformanceLogger.cleanup_old_logs(max_size_mb=50.0) - logger.info("HFC日志清理完成") - # 添加在线时间统计任务 await async_task_manager.add_task(OnlineTimeRecordTask()) @@ -134,10 +125,6 @@ class MainSystem: logger.info("个体特征初始化成功") try: - # 启动全局消息管理器 (负责消息发送/排队) - await message_manager.start() - logger.info("全局消息管理器启动成功") - init_time = int(1000 * (time.time() - init_start_time)) logger.info(f"初始化完成,神经元放电{init_time}次") except Exception as e: diff --git a/src/mais4u/mais4u_chat/s4u_mood_manager.py b/src/mais4u/mais4u_chat/s4u_mood_manager.py index f9846c9be..6b9704e94 100644 --- a/src/mais4u/mais4u_chat/s4u_mood_manager.py +++ b/src/mais4u/mais4u_chat/s4u_mood_manager.py @@ -38,7 +38,7 @@ def init_prompt(): 现在,发送了消息,引起了你的注意,你对其进行了阅读和思考,请你输出一句话描述你新的情绪状态,不要输出任何其他内容 请只输出情绪状态,不要输出其他内容: """, - "change_mood_prompt", + "change_mood_prompt_vtb", ) Prompt( """ @@ -51,7 +51,7 @@ def init_prompt(): 距离你上次关注直播间消息已经过去了一段时间,你冷静了下来,请你输出一句话描述你现在的情绪状态 请只输出情绪状态,不要输出其他内容: """, - "regress_mood_prompt", + "regress_mood_prompt_vtb", ) Prompt( """ @@ -183,7 +183,7 @@ class ChatMood: async def _update_text_mood(): prompt = await global_prompt_manager.format_prompt( - "change_mood_prompt", + "change_mood_prompt_vtb", chat_talking_prompt=chat_talking_prompt, indentify_block=indentify_block, mood_state=self.mood_state, @@ -257,7 +257,7 @@ class ChatMood: async def _regress_text_mood(): prompt = await global_prompt_manager.format_prompt( - "regress_mood_prompt", + "regress_mood_prompt_vtb", chat_talking_prompt=chat_talking_prompt, indentify_block=indentify_block, mood_state=self.mood_state, diff --git a/src/mood/mood_manager.py b/src/mood/mood_manager.py index e3a66370b..a577f2dd9 100644 --- a/src/mood/mood_manager.py +++ b/src/mood/mood_manager.py @@ -70,7 +70,7 @@ class ChatMood: else: interest_multiplier = 3 * math.pow(interested_rate, 0.25) - logger.info( + logger.debug( f"base_probability: {base_probability}, time_multiplier: {time_multiplier}, interest_multiplier: {interest_multiplier}" ) update_probability = min(1.0, base_probability * time_multiplier * interest_multiplier) @@ -78,6 +78,8 @@ class ChatMood: if random.random() > update_probability: return + logger.info(f"更新情绪状态,感兴趣度: {interested_rate}, 更新概率: {update_probability}") + message_time: float = message.message_info.time # type: ignore message_list_before_now = get_raw_msg_by_timestamp_with_chat_inclusive( chat_id=self.chat_id, diff --git a/src/plugin_system/apis/message_api.py b/src/plugin_system/apis/message_api.py index a4241ab53..e3847c55f 100644 --- a/src/plugin_system/apis/message_api.py +++ b/src/plugin_system/apis/message_api.py @@ -9,6 +9,7 @@ """ from typing import List, Dict, Any, Tuple, Optional +from src.config.config import global_config import time from src.chat.utils.chat_message_builder import ( get_raw_msg_by_timestamp, @@ -34,7 +35,7 @@ from src.chat.utils.chat_message_builder import ( def get_messages_by_time( - start_time: float, end_time: float, limit: int = 0, limit_mode: str = "latest" + start_time: float, end_time: float, limit: int = 0, limit_mode: str = "latest", filter_mai: bool = False ) -> List[Dict[str, Any]]: """ 获取指定时间范围内的消息 @@ -44,15 +45,23 @@ def get_messages_by_time( end_time: 结束时间戳 limit: 限制返回的消息数量,0为不限制 limit_mode: 当limit>0时生效,'earliest'表示获取最早的记录,'latest'表示获取最新的记录 + filter_mai: 是否过滤麦麦自身的消息,默认为False Returns: 消息列表 """ + if filter_mai: + return filter_mai_messages(get_raw_msg_by_timestamp(start_time, end_time, limit, limit_mode)) return get_raw_msg_by_timestamp(start_time, end_time, limit, limit_mode) def get_messages_by_time_in_chat( - chat_id: str, start_time: float, end_time: float, limit: int = 0, limit_mode: str = "latest" + chat_id: str, + start_time: float, + end_time: float, + limit: int = 0, + limit_mode: str = "latest", + filter_mai: bool = False, ) -> List[Dict[str, Any]]: """ 获取指定聊天中指定时间范围内的消息 @@ -63,15 +72,23 @@ def get_messages_by_time_in_chat( end_time: 结束时间戳 limit: 限制返回的消息数量,0为不限制 limit_mode: 当limit>0时生效,'earliest'表示获取最早的记录,'latest'表示获取最新的记录 + filter_mai: 是否过滤麦麦自身的消息,默认为False Returns: 消息列表 """ + if filter_mai: + return filter_mai_messages(get_raw_msg_by_timestamp_with_chat(chat_id, start_time, end_time, limit, limit_mode)) return get_raw_msg_by_timestamp_with_chat(chat_id, start_time, end_time, limit, limit_mode) def get_messages_by_time_in_chat_inclusive( - chat_id: str, start_time: float, end_time: float, limit: int = 0, limit_mode: str = "latest" + chat_id: str, + start_time: float, + end_time: float, + limit: int = 0, + limit_mode: str = "latest", + filter_mai: bool = False, ) -> List[Dict[str, Any]]: """ 获取指定聊天中指定时间范围内的消息(包含边界) @@ -82,10 +99,15 @@ def get_messages_by_time_in_chat_inclusive( end_time: 结束时间戳(包含) limit: 限制返回的消息数量,0为不限制 limit_mode: 当limit>0时生效,'earliest'表示获取最早的记录,'latest'表示获取最新的记录 + filter_mai: 是否过滤麦麦自身的消息,默认为False Returns: 消息列表 """ + if filter_mai: + return filter_mai_messages( + get_raw_msg_by_timestamp_with_chat_inclusive(chat_id, start_time, end_time, limit, limit_mode) + ) return get_raw_msg_by_timestamp_with_chat_inclusive(chat_id, start_time, end_time, limit, limit_mode) @@ -115,7 +137,7 @@ def get_messages_by_time_in_chat_for_users( def get_random_chat_messages( - start_time: float, end_time: float, limit: int = 0, limit_mode: str = "latest" + start_time: float, end_time: float, limit: int = 0, limit_mode: str = "latest", filter_mai: bool = False ) -> List[Dict[str, Any]]: """ 随机选择一个聊天,返回该聊天在指定时间范围内的消息 @@ -125,10 +147,13 @@ def get_random_chat_messages( end_time: 结束时间戳 limit: 限制返回的消息数量,0为不限制 limit_mode: 当limit>0时生效,'earliest'表示获取最早的记录,'latest'表示获取最新的记录 + filter_mai: 是否过滤麦麦自身的消息,默认为False Returns: 消息列表 """ + if filter_mai: + return filter_mai_messages(get_raw_msg_by_timestamp_random(start_time, end_time, limit, limit_mode)) return get_raw_msg_by_timestamp_random(start_time, end_time, limit, limit_mode) @@ -151,21 +176,26 @@ def get_messages_by_time_for_users( return get_raw_msg_by_timestamp_with_users(start_time, end_time, person_ids, limit, limit_mode) -def get_messages_before_time(timestamp: float, limit: int = 0) -> List[Dict[str, Any]]: +def get_messages_before_time(timestamp: float, limit: int = 0, filter_mai: bool = False) -> List[Dict[str, Any]]: """ 获取指定时间戳之前的消息 Args: timestamp: 时间戳 limit: 限制返回的消息数量,0为不限制 + filter_mai: 是否过滤麦麦自身的消息,默认为False Returns: 消息列表 """ + if filter_mai: + return filter_mai_messages(get_raw_msg_before_timestamp(timestamp, limit)) return get_raw_msg_before_timestamp(timestamp, limit) -def get_messages_before_time_in_chat(chat_id: str, timestamp: float, limit: int = 0) -> List[Dict[str, Any]]: +def get_messages_before_time_in_chat( + chat_id: str, timestamp: float, limit: int = 0, filter_mai: bool = False +) -> List[Dict[str, Any]]: """ 获取指定聊天中指定时间戳之前的消息 @@ -173,10 +203,13 @@ def get_messages_before_time_in_chat(chat_id: str, timestamp: float, limit: int chat_id: 聊天ID timestamp: 时间戳 limit: 限制返回的消息数量,0为不限制 + filter_mai: 是否过滤麦麦自身的消息,默认为False Returns: 消息列表 """ + if filter_mai: + return filter_mai_messages(get_raw_msg_before_timestamp_with_chat(chat_id, timestamp, limit)) return get_raw_msg_before_timestamp_with_chat(chat_id, timestamp, limit) @@ -196,7 +229,7 @@ def get_messages_before_time_for_users(timestamp: float, person_ids: list, limit def get_recent_messages( - chat_id: str, hours: float = 24.0, limit: int = 100, limit_mode: str = "latest" + chat_id: str, hours: float = 24.0, limit: int = 100, limit_mode: str = "latest", filter_mai: bool = False ) -> List[Dict[str, Any]]: """ 获取指定聊天中最近一段时间的消息 @@ -206,12 +239,15 @@ def get_recent_messages( hours: 最近多少小时,默认24小时 limit: 限制返回的消息数量,默认100条 limit_mode: 当limit>0时生效,'earliest'表示获取最早的记录,'latest'表示获取最新的记录 + filter_mai: 是否过滤麦麦自身的消息,默认为False Returns: 消息列表 """ now = time.time() start_time = now - hours * 3600 + if filter_mai: + return filter_mai_messages(get_raw_msg_by_timestamp_with_chat(chat_id, start_time, now, limit, limit_mode)) return get_raw_msg_by_timestamp_with_chat(chat_id, start_time, now, limit, limit_mode) @@ -319,3 +355,19 @@ async def get_person_ids_from_messages(messages: List[Dict[str, Any]]) -> List[s 用户ID列表 """ return await get_person_id_list(messages) + + +# ============================================================================= +# 消息过滤函数 +# ============================================================================= + + +def filter_mai_messages(messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]: + """ + 从消息列表中移除麦麦的消息 + Args: + messages: 消息列表,每个元素是消息字典 + Returns: + 过滤后的消息列表 + """ + return [msg for msg in messages if msg.get("user_id") != str(global_config.bot.qq_account)] diff --git a/src/plugin_system/base/base_action.py b/src/plugin_system/base/base_action.py index 73c883e0a..886d74b8b 100644 --- a/src/plugin_system/base/base_action.py +++ b/src/plugin_system/base/base_action.py @@ -34,7 +34,6 @@ class BaseAction(ABC): thinking_id: str, chat_stream: ChatStream, log_prefix: str = "", - shutting_down: bool = False, plugin_config: Optional[dict] = None, **kwargs, ): @@ -60,7 +59,6 @@ class BaseAction(ABC): self.cycle_timers = cycle_timers self.thinking_id = thinking_id self.log_prefix = log_prefix - self.shutting_down = shutting_down # 保存插件配置 self.plugin_config = plugin_config or {} diff --git a/src/plugins/built_in/core_actions/no_reply.py b/src/plugins/built_in/core_actions/no_reply.py index 99337e515..080c717f2 100644 --- a/src/plugins/built_in/core_actions/no_reply.py +++ b/src/plugins/built_in/core_actions/no_reply.py @@ -24,7 +24,7 @@ class NoReplyAction(BaseAction): 2. 累计新消息数量达到随机阈值 (默认5-10条) 则结束等待 """ - focus_activation_type = ActionActivationType.ALWAYS + focus_activation_type = ActionActivationType.NEVER normal_activation_type = ActionActivationType.NEVER mode_enable = ChatMode.FOCUS parallel_action = False @@ -61,8 +61,8 @@ class NoReplyAction(BaseAction): count = NoReplyAction._consecutive_count reason = self.action_data.get("reason", "") - start_time = time.time() - check_interval = 1.0 # 每秒检查一次 + start_time = self.action_data.get("loop_start_time", time.time()) + check_interval = 0.6 # 每秒检查一次 # 随机生成本次等待需要的新消息数量阈值 exit_message_count_threshold = random.randint(self._min_exit_message_count, self._max_exit_message_count) diff --git a/src/plugins/built_in/core_actions/plugin.py b/src/plugins/built_in/core_actions/plugin.py index c7efabed7..aea58bfd7 100644 --- a/src/plugins/built_in/core_actions/plugin.py +++ b/src/plugins/built_in/core_actions/plugin.py @@ -34,7 +34,7 @@ class ReplyAction(BaseAction): """回复动作 - 参与聊天回复""" # 激活设置 - focus_activation_type = ActionActivationType.ALWAYS + focus_activation_type = ActionActivationType.NEVER normal_activation_type = ActionActivationType.NEVER mode_enable = ChatMode.FOCUS parallel_action = False @@ -98,7 +98,7 @@ class ReplyAction(BaseAction): ) # 根据新消息数量决定是否使用reply_to - need_reply = new_message_count >= random.randint(2, 5) + need_reply = new_message_count >= random.randint(2, 4) logger.info( f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,{'使用' if need_reply else '不使用'}引用回复" ) diff --git a/src/plugins/built_in/tts_plugin/_manifest.json b/src/plugins/built_in/tts_plugin/_manifest.json index be9f61b0a..05a233757 100644 --- a/src/plugins/built_in/tts_plugin/_manifest.json +++ b/src/plugins/built_in/tts_plugin/_manifest.json @@ -10,8 +10,7 @@ "license": "GPL-v3.0-or-later", "host_application": { - "min_version": "0.8.0", - "max_version": "0.8.10" + "min_version": "0.8.0" }, "homepage_url": "https://github.com/MaiM-with-u/maibot", "repository_url": "https://github.com/MaiM-with-u/maibot", diff --git a/src/plugins/built_in/vtb_plugin/_manifest.json b/src/plugins/built_in/vtb_plugin/_manifest.json index 1cff37136..96f985abd 100644 --- a/src/plugins/built_in/vtb_plugin/_manifest.json +++ b/src/plugins/built_in/vtb_plugin/_manifest.json @@ -9,8 +9,7 @@ }, "license": "GPL-v3.0-or-later", "host_application": { - "min_version": "0.8.0", - "max_version": "0.8.10" + "min_version": "0.8.0" }, "keywords": ["vtb", "vtuber", "emotion", "expression", "virtual", "streamer"], "categories": ["Entertainment", "Virtual Assistant", "Emotion"], diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index d4c158f65..7ab5195d1 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -1,5 +1,5 @@ [inner] -version = "3.7.0" +version = "4.0.1" #----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读---- #如果你想要修改配置文件,请在修改后将version的值进行变更 @@ -40,7 +40,7 @@ identity_detail = [ "有橙色的短发", ] -compress_indentity = true # 是否压缩身份,压缩后会精简身份信息,节省token消耗并提高回复性能,但是会丢失一些信息,如果不长,可以关闭 +compress_identity = true # 是否压缩身份,压缩后会精简身份信息,节省token消耗并提高回复性能,但是会丢失一些信息,如果不长,可以关闭 [expression] # 表达方式 @@ -62,14 +62,13 @@ enable_relationship = true # 是否启用关系系统 relation_frequency = 1 # 关系频率,麦麦构建关系的频率 [chat] #麦麦的聊天通用设置 -chat_mode = "normal" # 聊天模式 —— 普通模式:normal,专注模式:focus,auto模式:在普通模式和专注模式之间自动切换 auto_focus_threshold = 1 # 自动切换到专注聊天的阈值,越低越容易进入专注聊天 exit_focus_threshold = 1 # 自动退出专注聊天的阈值,越低越容易退出专注聊天 # 普通模式下,麦麦会针对感兴趣的消息进行回复,token消耗量较低 # 专注模式下,麦麦会进行主动的观察,并给出回复,token消耗量略高,但是回复时机更准确 # 自动模式下,麦麦会根据消息内容自动切换到专注模式或普通模式 -max_context_size = 18 # 上下文长度 +max_context_size = 25 # 上下文长度 thinking_timeout = 20 # 麦麦一次回复最长思考规划时间,超过这个时间的思考会放弃(往往是api反应太慢) replyer_random_probability = 0.5 # 首要replyer模型被选择的概率 @@ -119,10 +118,8 @@ willing_mode = "classical" # 回复意愿模式 —— 经典模式:classical response_interested_rate_amplifier = 1 # 麦麦回复兴趣度放大系数 mentioned_bot_inevitable_reply = true # 提及 bot 必然回复 at_bot_inevitable_reply = true # @bot 必然回复(包含提及) -enable_planner = true # 是否启用动作规划器(与focus_chat共享actions) [focus_chat] #专注聊天 -think_interval = 3 # 思考间隔 单位秒,可以有效减少消耗 consecutive_replies = 1 # 连续回复能力,值越高,麦麦连续回复的概率越高 [tool] @@ -234,7 +231,6 @@ library_log_levels = { "aiohttp" = "WARNING"} # 设置特定库的日志级别 [debug] show_prompt = false # 是否显示prompt -debug_show_chat_mode = false # 是否在回复后显示当前聊天模式 [model]