From d4ee8d36186b591e81dce4565aaadc4a9dc68718 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Sun, 18 May 2025 20:30:00 +0800 Subject: [PATCH] =?UTF-8?q?update=EF=BC=9A0.6.3=20fix4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- changelogs/changelog.md | 8 + changelogs/changelog_config.md | 51 -- src/api/config_api.py | 10 +- src/common/logger.py | 33 +- src/common/logger_manager.py | 4 +- src/config/config.py | 32 +- src/do_tool/not_used/get_current_task.py | 60 -- src/heart_flow/README.md | 241 ------- src/heart_flow/background_tasks.py | 172 +---- src/heart_flow/chat_state_info.py | 3 +- src/heart_flow/heartflow.py | 12 - src/heart_flow/interest_chatting.py | 2 +- src/heart_flow/mai_state_manager.py | 200 +----- src/heart_flow/mind.py | 4 +- src/heart_flow/sub_heartflow.py | 40 +- src/heart_flow/subheartflow_manager.py | 658 ++---------------- src/main.py | 10 +- src/plugins/__init__.py | 2 - src/plugins/heartFC_chat/heartFC_chat.py | 4 +- .../heartFC_chat/heartFC_chatting_logic.md | 4 +- .../heartFC_chat/heartflow_prompt_builder.py | 13 - src/plugins/heartFC_chat/normal_chat.py | 14 +- src/plugins/models/utils_model.py | 2 +- src/plugins/schedule/schedule_generator.py | 307 -------- template/bot_config_meta.toml | 134 ---- template/bot_config_template.toml | 28 +- 27 files changed, 172 insertions(+), 1878 deletions(-) delete mode 100644 changelogs/changelog_config.md delete mode 100644 src/do_tool/not_used/get_current_task.py delete mode 100644 src/heart_flow/README.md delete mode 100644 src/plugins/schedule/schedule_generator.py delete mode 100644 template/bot_config_meta.toml diff --git a/README.md b/README.md index 41f9ea1a..042b7f35 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ | 模块 | 主要功能 | 特点 | |----------|------------------------------------------------------------------|-------| -| 💬 聊天系统 | • **统一调控不同回复逻辑**
• 智能交互模式 (普通聊天/专注聊天)
• 关键词主动发言
• 多模型支持
• 动态prompt构建
• 私聊功能(PFC)增强 | 拟人化交互 | +| 💬 聊天系统 | • **统一调控不同回复逻辑**
• 智能交互模式 (普通聊天/专注水群)
• 关键词主动发言
• 多模型支持
• 动态prompt构建
• 私聊功能(PFC)增强 | 拟人化交互 | | 🧠 心流系统 | • 实时思考生成
• **智能状态管理**
• **概率回复机制**
• 自动启停机制
• 日程系统联动
• **上下文感知工具调用** | 智能化决策 | | 🧠 记忆系统 | • **记忆整合与提取**
• 海马体记忆机制
• 聊天记录概括 | 持久化记忆 | | 😊 表情系统 | • **全新表情包系统**
• **优化选择逻辑**
• 情绪匹配发送
• GIF支持
• 自动收集与审查 | 丰富表达 | diff --git a/changelogs/changelog.md b/changelogs/changelog.md index 0d6608b0..d521b876 100644 --- a/changelogs/changelog.md +++ b/changelogs/changelog.md @@ -1,5 +1,13 @@ # Changelog +## [0.6.3-fix-4] - 2025-5-18 +- 0.6.3 的最后一个修复版 + +### fix1-fix4修复日志 +- + + + ## [0.6.3] - 2025-4-15 ### 摘要 diff --git a/changelogs/changelog_config.md b/changelogs/changelog_config.md deleted file mode 100644 index 5aa5fb92..00000000 --- a/changelogs/changelog_config.md +++ /dev/null @@ -1,51 +0,0 @@ -# Changelog - -## [1.0.3] - 2025-3-31 -### Added -- 新增了心流相关配置项: - - `heartflow` 配置项,用于控制心流功能 - -### Removed -- 移除了 `response` 配置项中的 `model_r1_probability` 和 `model_v3_probability` 选项 -- 移除了次级推理模型相关配置 - -## [1.0.1] - 2025-3-30 -### Added -- 增加了流式输出控制项 `stream` -- 修复 `LLM_Request` 不会自动为 `payload` 增加流式输出标志的问题 - -## [1.0.0] - 2025-3-30 -### Added -- 修复了错误的版本命名 -- 杀掉了所有无关文件 - -## [0.0.11] - 2025-3-12 -### Added -- 新增了 `schedule` 配置项,用于配置日程表生成功能 -- 新增了 `response_splitter` 配置项,用于控制回复分割 -- 新增了 `experimental` 配置项,用于实验性功能开关 -- 新增了 `llm_observation` 和 `llm_sub_heartflow` 模型配置 -- 新增了 `llm_heartflow` 模型配置 -- 在 `personality` 配置项中新增了 `prompt_schedule_gen` 参数 - -### Changed -- 优化了模型配置的组织结构 -- 调整了部分配置项的默认值 -- 调整了配置项的顺序,将 `groups` 配置项移到了更靠前的位置 -- 在 `message` 配置项中: - - 新增了 `model_max_output_length` 参数 -- 在 `willing` 配置项中新增了 `emoji_response_penalty` 参数 -- 将 `personality` 配置项中的 `prompt_schedule` 重命名为 `prompt_schedule_gen` - -### Removed -- 移除了 `min_text_length` 配置项 -- 移除了 `cq_code` 配置项 -- 移除了 `others` 配置项(其功能已整合到 `experimental` 中) - -## [0.0.5] - 2025-3-11 -### Added -- 新增了 `alias_names` 配置项,用于指定麦麦的别名。 - -## [0.0.4] - 2025-3-9 -### Added -- 新增了 `memory_ban_words` 配置项,用于指定不希望记忆的词汇。 \ No newline at end of file diff --git a/src/api/config_api.py b/src/api/config_api.py index 581c05a0..a09b6674 100644 --- a/src/api/config_api.py +++ b/src/api/config_api.py @@ -33,13 +33,7 @@ class APIBotConfig: gender: str # 性别 appearance: str # 外貌特征描述 - # schedule - ENABLE_SCHEDULE_GEN: bool # 是否启用日程生成 - ENABLE_SCHEDULE_INTERACTION: bool # 是否启用日程交互 - PROMPT_SCHEDULE_GEN: str # 日程生成提示词 - SCHEDULE_DOING_UPDATE_INTERVAL: int # 日程进行中更新间隔 - SCHEDULE_TEMPERATURE: float # 日程生成温度 - TIME_ZONE: str # 时区 + # platforms platforms: Dict[str, str] # 平台信息 @@ -47,7 +41,7 @@ class APIBotConfig: # chat allow_focus_mode: bool # 是否允许专注模式 base_normal_chat_num: int # 基础普通聊天次数 - base_focused_chat_num: int # 基础专注聊天次数 + base_focused_chat_num: int # 基础专注水群次数 observation_context_size: int # 观察上下文大小 message_buffer: bool # 是否启用消息缓冲 ban_words: List[str] # 禁止词列表 diff --git a/src/common/logger.py b/src/common/logger.py index f7d6fb28..6e6ade97 100644 --- a/src/common/logger.py +++ b/src/common/logger.py @@ -277,6 +277,22 @@ CHAT_STYLE_CONFIG = { }, } +NORMAL_CHAT_STYLE_CONFIG = { + "advanced": { + "console_format": ( + "{time:YYYY-MM-DD HH:mm:ss} | " + "{level: <8} | " + "普通水群 | " + "{message}" + ), + "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 普通水群 | {message}", + }, + "simple": { + "console_format": "{time:MM-DD HH:mm} | 普通水群 | {message}", + "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 普通水群 | {message}", + }, +} + REMOTE_STYLE_CONFIG = { "advanced": { "console_format": ( @@ -347,14 +363,14 @@ SUBHEARTFLOW_MANAGER_STYLE_CONFIG = { "console_format": ( "{time:YYYY-MM-DD HH:mm:ss} | " "{level: <8} | " - "麦麦水群[管理] | " + "聊天管理 | " "{message}" ), - "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 麦麦水群[管理] | {message}", + "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 聊天管理 | {message}", }, "simple": { - "console_format": "{time:MM-DD HH:mm} | 麦麦水群[管理] | {message}", # noqa: E501 - "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 麦麦水群[管理] | {message}", + "console_format": "{time:MM-DD HH:mm} | 聊天管理 | {message}", # noqa: E501 + "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 聊天管理 | {message}", }, } @@ -595,14 +611,14 @@ HFC_STYLE_CONFIG = { "console_format": ( "{time:YYYY-MM-DD HH:mm:ss} | " "{level: <8} | " - "专注聊天 | " + "专注水群 | " "{message}" ), - "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 专注聊天 | {message}", + "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 专注水群 | {message}", }, "simple": { - "console_format": "{time:MM-DD HH:mm} | 专注聊天 | {message}", - "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 专注聊天 | {message}", + "console_format": "{time:MM-DD HH:mm} | 专注水群 | {message}", + "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 专注水群 | {message}", }, } @@ -866,6 +882,7 @@ API_SERVER_STYLE_CONFIG = API_SERVER_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT els INTEREST_CHAT_STYLE_CONFIG = ( INTEREST_CHAT_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else INTEREST_CHAT_STYLE_CONFIG["advanced"] ) +NORMAL_CHAT_STYLE_CONFIG = NORMAL_CHAT_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else NORMAL_CHAT_STYLE_CONFIG["advanced"] def is_registered_module(record: dict) -> bool: diff --git a/src/common/logger_manager.py b/src/common/logger_manager.py index 8aae71e1..2f5bcae4 100644 --- a/src/common/logger_manager.py +++ b/src/common/logger_manager.py @@ -25,6 +25,7 @@ from src.common.logger import ( LPMM_STYLE_CONFIG, HFC_STYLE_CONFIG, TIANYI_STYLE_CONFIG, + NORMAL_CHAT_STYLE_CONFIG, REMOTE_STYLE_CONFIG, TOPIC_STYLE_CONFIG, SENDER_STYLE_CONFIG, @@ -62,7 +63,7 @@ MODULE_LOGGER_CONFIGS = { "emoji": EMOJI_STYLE_CONFIG, # 表情包 "sub_heartflow": SUB_HEARTFLOW_STYLE_CONFIG, # 麦麦水群 "sub_heartflow_mind": SUB_HEARTFLOW_MIND_STYLE_CONFIG, # 麦麦小脑袋 - "subheartflow_manager": SUBHEARTFLOW_MANAGER_STYLE_CONFIG, # 麦麦水群[管理] + "subheartflow_manager": SUBHEARTFLOW_MANAGER_STYLE_CONFIG, # 聊天管理 "base_tool": BASE_TOOL_STYLE_CONFIG, # 工具使用 "chat_stream": CHAT_STREAM_STYLE_CONFIG, # 聊天流 "person_info": PERSON_INFO_STYLE_CONFIG, # 人物信息 @@ -73,6 +74,7 @@ MODULE_LOGGER_CONFIGS = { "hfc": HFC_STYLE_CONFIG, # HFC "tianyi": TIANYI_STYLE_CONFIG, # 天依 "remote": REMOTE_STYLE_CONFIG, # 远程 + "normal_chat": NORMAL_CHAT_STYLE_CONFIG, # 普通水群 "topic": TOPIC_STYLE_CONFIG, # 话题 "sender": SENDER_STYLE_CONFIG, # 消息发送 "confirm": CONFIRM_STYLE_CONFIG, # EULA与PRIVACY确认 diff --git a/src/config/config.py b/src/config/config.py index 5c2bdcc2..5f022941 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -25,7 +25,7 @@ logger = get_logger("config") # 考虑到,实际上配置文件中的mai_version是不会自动更新的,所以采用硬编码 is_test = False mai_version_main = "0.6.3" -mai_version_fix = "fix-3" +mai_version_fix = "fix-4" if mai_version_fix: if is_test: @@ -166,18 +166,13 @@ class BotConfig: gender: str = "男" # 性别 appearance: str = "用几句话描述外貌特征" # 外貌特征 - # schedule - ENABLE_SCHEDULE_GEN: bool = False # 是否启用日程生成 - PROMPT_SCHEDULE_GEN = "无日程" - SCHEDULE_DOING_UPDATE_INTERVAL: int = 300 # 日程表更新间隔 单位秒 - SCHEDULE_TEMPERATURE: float = 0.5 # 日程表温度,建议0.5-1.0 - TIME_ZONE: str = "Asia/Shanghai" # 时区 + # chat - allow_focus_mode: bool = True # 是否允许专注聊天状态 + allow_focus_mode: bool = True # 是否允许专注水群状态 base_normal_chat_num: int = 3 # 最多允许多少个群进行普通聊天 - base_focused_chat_num: int = 2 # 最多允许多少个群进行专注聊天 + base_focused_chat_num: int = 2 # 最多允许多少个群进行专注水群 observation_context_size: int = 12 # 心流观察到的最长上下文大小,超过这个值的上下文会被压缩 @@ -371,23 +366,7 @@ class BotConfig: config.gender = identity_config.get("gender", config.gender) config.appearance = identity_config.get("appearance", config.appearance) - def schedule(parent: dict): - schedule_config = parent["schedule"] - config.ENABLE_SCHEDULE_GEN = schedule_config.get("enable_schedule_gen", config.ENABLE_SCHEDULE_GEN) - config.PROMPT_SCHEDULE_GEN = schedule_config.get("prompt_schedule_gen", config.PROMPT_SCHEDULE_GEN) - config.SCHEDULE_DOING_UPDATE_INTERVAL = schedule_config.get( - "schedule_doing_update_interval", config.SCHEDULE_DOING_UPDATE_INTERVAL - ) - logger.info( - f"载入自定义日程prompt:{schedule_config.get('prompt_schedule_gen', config.PROMPT_SCHEDULE_GEN)}" - ) - if config.INNER_VERSION in SpecifierSet(">=1.0.2"): - config.SCHEDULE_TEMPERATURE = schedule_config.get("schedule_temperature", config.SCHEDULE_TEMPERATURE) - time_zone = schedule_config.get("time_zone", config.TIME_ZONE) - if tz.gettz(time_zone) is None: - logger.error(f"无效的时区: {time_zone},使用默认值: {config.TIME_ZONE}") - else: - config.TIME_ZONE = time_zone + def emoji(parent: dict): emoji_config = parent["emoji"] @@ -678,7 +657,6 @@ class BotConfig: "groups": {"func": groups, "support": ">=0.0.0"}, "personality": {"func": personality, "support": ">=0.0.0"}, "identity": {"func": identity, "support": ">=1.2.4"}, - "schedule": {"func": schedule, "support": ">=0.0.11", "necessary": False}, "emoji": {"func": emoji, "support": ">=0.0.0"}, "model": {"func": model, "support": ">=0.0.0"}, "memory": {"func": memory, "support": ">=0.0.0", "necessary": False}, diff --git a/src/do_tool/not_used/get_current_task.py b/src/do_tool/not_used/get_current_task.py deleted file mode 100644 index 30184d67..00000000 --- a/src/do_tool/not_used/get_current_task.py +++ /dev/null @@ -1,60 +0,0 @@ -from src.do_tool.tool_can_use.base_tool import BaseTool -from src.plugins.schedule.schedule_generator import bot_schedule -from src.common.logger import get_module_logger -from typing import Any -from datetime import datetime - -logger = get_module_logger("get_current_task_tool") - - -class GetCurrentTaskTool(BaseTool): - """获取当前正在做的事情/最近的任务工具""" - - name = "get_schedule" - description = "获取当前正在做的事情,或者某个时间点/时间段的日程信息" - parameters = { - "type": "object", - "properties": { - "start_time": {"type": "string", "description": "开始时间,格式为'HH:MM',填写current则获取当前任务"}, - "end_time": {"type": "string", "description": "结束时间,格式为'HH:MM',填写current则获取当前任务"}, - }, - "required": ["start_time", "end_time"], - } - - async def execute(self, function_args: dict[str, Any], message_txt: str = "") -> dict[str, Any]: - """执行获取当前任务或指定时间段的日程信息 - - Args: - function_args: 工具参数 - message_txt: 原始消息文本,此工具不使用 - - Returns: - dict: 工具执行结果 - """ - start_time = function_args.get("start_time") - end_time = function_args.get("end_time") - - # 如果 start_time 或 end_time 为 "current",则获取当前任务 - if start_time == "current" or end_time == "current": - current_task = bot_schedule.get_current_num_task(num=1, time_info=True) - current_time = datetime.now().strftime("%H:%M:%S") - current_date = datetime.now().strftime("%Y-%m-%d") - if current_task: - task_info = f"{current_date} {current_time},你在{current_task}" - else: - task_info = f"{current_time} {current_date},没在做任何事情" - # 如果提供了时间范围,则获取该时间段的日程信息 - elif start_time and end_time: - tasks = await bot_schedule.get_task_from_time_to_time(start_time, end_time) - if tasks: - task_list = [] - for task in tasks: - task_time = task[0].strftime("%H:%M") - task_content = task[1] - task_list.append(f"{task_time}时,{task_content}") - task_info = "\n".join(task_list) - else: - task_info = f"在 {start_time} 到 {end_time} 之间没有找到日程信息" - else: - task_info = "请提供有效的开始时间和结束时间" - return {"name": "get_current_task", "content": f"日程信息: {task_info}"} diff --git a/src/heart_flow/README.md b/src/heart_flow/README.md deleted file mode 100644 index a55f1c97..00000000 --- a/src/heart_flow/README.md +++ /dev/null @@ -1,241 +0,0 @@ -# 心流系统 (Heart Flow System) - -## 一条消息是怎么到最终回复的?简明易懂的介绍 - -1 接受消息,由HeartHC_processor处理消息,存储消息 - - 1.1 process_message()函数,接受消息 - - 1.2 创建消息对应的聊天流(chat_stream)和子心流(sub_heartflow) - - 1.3 进行常规消息处理 - - 1.4 存储消息 store_message() - - 1.5 计算兴趣度Interest - - 1.6 将消息连同兴趣度,存储到内存中的interest_dict(SubHeartflow的属性) - -2 根据 sub_heartflow 的聊天状态,决定后续处理流程 - - 2a ABSENT状态:不做任何处理 - - 2b CHAT状态:送入NormalChat 实例 - - 2c FOCUS状态:送入HeartFChatting 实例 - -b NormalChat工作方式 - - b.1 启动后台任务 _reply_interested_message,持续运行。 - b.2 该任务轮询 InterestChatting 提供的 interest_dict - b.3 对每条消息,结合兴趣度、是否被提及(@)、意愿管理器(WillingManager)计算回复概率。(这部分要改,目前还是用willing计算的,之后要和Interest合并) - b.4 若概率通过: - b.4.1 创建"思考中"消息 (MessageThinking)。 - b.4.2 调用 NormalChatGenerator 生成文本回复。 - b.4.3 通过 message_manager 发送回复 (MessageSending)。 - b.4.4 可能根据配置和文本内容,额外发送一个匹配的表情包。 - b.4.5 更新关系值和全局情绪。 - b.5 处理完成后,从 interest_dict 中移除该消息。 - -c HeartFChatting工作方式 - - c.1 启动主循环 _hfc_loop - c.2 每个循环称为一个周期 (Cycle),执行 think_plan_execute 流程。 - c.3 Think (思考) 阶段: - c.3.1 观察 (Observe): 通过 ChattingObservation,使用 observe() 获取最新的聊天消息。 - c.3.2 思考 (Think): 调用 SubMind 的 do_thinking_before_reply 方法。 - c.3.2.1 SubMind 结合观察到的内容、个性、情绪、上周期动作等信息,生成当前的内心想法 (current_mind)。 - c.3.2.2 在此过程中 SubMind 的LLM可能请求调用工具 (ToolUser) 来获取额外信息或执行操作,结果存储在 structured_info 中。 - c.4 Plan (规划/决策) 阶段: - c.4.1 结合观察到的消息文本、`SubMind` 生成的 `current_mind` 和 `structured_info`、以及 `ActionManager` 提供的可用动作,决定本次周期的行动 (`text_reply`/`emoji_reply`/`no_reply`) 和理由。 - c.4.2 重新规划检查 (Re-plan Check): 如果在 c.3.1 到 c.4.1 期间检测到新消息,可能(有概率)触发重新执行 c.4.1 决策步骤。 - c.5 Execute (执行/回复) 阶段: - c.5.1 如果决策是 text_reply: - c.5.1.1 获取锚点消息。 - c.5.1.2 通过 HeartFCSender 注册"思考中"状态。 - c.5.1.3 调用 HeartFCGenerator (gpt_instance) 生成回复文本。 - c.5.1.4 通过 HeartFCSender 发送回复 - c.5.1.5 如果规划时指定了表情查询 (emoji_query),随后发送表情。 - c.5.2 如果决策是 emoji_reply: - c.5.2.1 获取锚点消息。 - c.5.2.2 通过 HeartFCSender 直接发送匹配查询 (emoji_query) 的表情。 - c.5.3 如果决策是 no_reply: - c.5.3.1 进入等待状态,直到检测到新消息或超时。 - c.5.3.2 同时,增加内部连续不回复计数器。如果该计数器达到预设阈值(例如 5 次),则调用初始化时由 `SubHeartflowManager` 提供的回调函数。此回调函数会通知 `SubHeartflowManager` 请求将对应的 `SubHeartflow` 状态转换为 `ABSENT`。如果执行了其他动作(如 `text_reply` 或 `emoji_reply`),则此计数器会被重置。 - c.6 循环结束后,记录周期信息 (CycleInfo),并根据情况进行短暂休眠,防止CPU空转。 - - - -## 1. 一条消息是怎么到最终回复的?复杂细致的介绍 - -### 1.1. 主心流 (Heartflow) -- **文件**: `heartflow.py` -- **职责**: - - 作为整个系统的主控制器。 - - 持有并管理 `SubHeartflowManager`,用于管理所有子心流。 - - 持有并管理自身状态 `self.current_state: MaiStateInfo`,该状态控制系统的整体行为模式。 - - 统筹管理系统后台任务(如消息存储、资源分配等)。 - - **注意**: 主心流自身不进行周期性的全局思考更新。 - -### 1.2. 子心流 (SubHeartflow) -- **文件**: `sub_heartflow.py` -- **职责**: - - 处理具体的交互场景,例如:群聊、私聊、与虚拟主播(vtb)互动、桌面宠物交互等。 - - 维护特定场景下的思维状态和聊天流状态 (`ChatState`)。 - - 通过关联的 `Observation` 实例接收和处理信息。 - - 拥有独立的思考 (`SubMind`) 和回复判断能力。 -- **观察者**: 每个子心流可以拥有一个或多个 `Observation` 实例(目前每个子心流仅使用一个 `ChattingObservation`)。 -- **内部结构**: - - **聊天流状态 (`ChatState`)**: 标记当前子心流的参与模式 (`ABSENT`, `CHAT`, `FOCUSED`),决定是否观察、回复以及使用何种回复模式。 - - **聊天实例 (`NormalChatInstance` / `HeartFlowChatInstance`)**: 根据 `ChatState` 激活对应的实例来处理聊天逻辑。同一时间只有一个实例处于活动状态。 - -### 1.3. 观察系统 (Observation) -- **文件**: `observation.py` -- **职责**: - - 定义信息输入的来源和格式。 - - 为子心流提供其所处环境的信息。 -- **当前实现**: - - 目前仅有 `ChattingObservation` 一种观察类型。 - - `ChattingObservation` 负责从数据库拉取指定聊天的最新消息,并将其格式化为可读内容,供 `SubHeartflow` 使用。 - -### 1.4. 子心流管理器 (SubHeartflowManager) -- **文件**: `subheartflow_manager.py` -- **职责**: - - 作为 `Heartflow` 的成员变量存在。 - - **在初始化时接收并持有 `Heartflow` 的 `MaiStateInfo` 实例。** - - 负责所有 `SubHeartflow` 实例的生命周期管理,包括: - - 创建和获取 (`get_or_create_subheartflow`)。 - - 停止和清理 (`sleep_subheartflow`, `cleanup_inactive_subheartflows`)。 - - 根据 `Heartflow` 的状态 (`self.mai_state_info`) 和限制条件,激活、停用或调整子心流的状态(例如 `enforce_subheartflow_limits`, `randomly_deactivate_subflows`, `sbhf_absent_into_focus`)。 - - **新增**: 通过调用 `sbhf_absent_into_chat` 方法,使用 LLM (配置与 `Heartflow` 主 LLM 相同) 评估处于 `ABSENT` 或 `CHAT` 状态的子心流,根据观察到的活动摘要和 `Heartflow` 的当前状态,判断是否应在 `ABSENT` 和 `CHAT` 之间进行转换 (同样受限于 `CHAT` 状态的数量上限)。 - - **清理机制**: 通过后台任务 (`BackgroundTaskManager`) 定期调用 `cleanup_inactive_subheartflows` 方法,此方法会识别并**删除**那些处于 `ABSENT` 状态超过一小时 (`INACTIVE_THRESHOLD_SECONDS`) 的子心流实例。 - -### 1.5. 消息处理与回复流程 (Message Processing vs. Replying Flow) -- **关注点分离**: 系统严格区分了接收和处理传入消息的流程与决定和生成回复的流程。 - - **消息处理 (Processing)**: - - 由一个独立的处理器(例如 `HeartFCProcessor`)负责接收原始消息数据。 - - 职责包括:消息解析 (`MessageRecv`)、过滤(屏蔽词、正则表达式)、基于记忆系统的初步兴趣计算 (`HippocampusManager`)、消息存储 (`MessageStorage`) 以及用户关系更新 (`RelationshipManager`)。 - - 处理后的消息信息(如计算出的兴趣度)会传递给对应的 `SubHeartflow`。 - - **回复决策与生成 (Replying)**: - - 由 `SubHeartflow` 及其当前激活的聊天实例 (`NormalChatInstance` 或 `HeartFlowChatInstance`) 负责。 - - 基于其内部状态 (`ChatState`、`SubMind` 的思考结果)、观察到的信息 (`Observation` 提供的内容) 以及 `InterestChatting` 的状态来决定是否回复、何时回复以及如何回复。 -- **消息缓冲 (Message Caching)**: - - `message_buffer` 模块会对某些传入消息进行临时缓存,尤其是在处理连续的多部分消息(如多张图片)时。 - - 这个缓冲机制发生在 `HeartFCProcessor` 处理流程中,确保消息的完整性,然后才进行后续的存储和兴趣计算。 - - 缓存的消息最终仍会流向对应的 `ChatStream`(与 `SubHeartflow` 关联),但核心的消息处理与回复决策仍然是分离的步骤。 - -## 2. 核心控制与状态管理 (Core Control and State Management) - -### 2.1. Heart Flow 整体控制 -- **控制者**: 主心流 (`Heartflow`) -- **核心职责**: - - 通过其成员 `SubHeartflowManager` 创建和管理子心流(**在创建 `SubHeartflowManager` 时会传入自身的 `MaiStateInfo`**)。 - - 通过其成员 `self.current_state: MaiStateInfo` 控制整体行为模式。 - - 管理系统级后台任务。 - - **注意**: 不再提供直接获取所有子心流 ID (`get_all_subheartflows_streams_ids`) 的公共方法。 - -### 2.2. Heart Flow 状态 (`MaiStateInfo`) -- **定义与管理**: `Heartflow` 持有 `MaiStateInfo` 的实例 (`self.current_state`) 来管理其状态。状态的枚举定义在 `my_state_manager.py` 中的 `MaiState`。 -- **状态及含义**: - - `MaiState.OFFLINE` (不在线): 不观察任何群消息,不进行主动交互,仅存储消息。当主状态变为 `OFFLINE` 时,`SubHeartflowManager` 会将所有子心流的状态设置为 `ChatState.ABSENT`。 - - `MaiState.PEEKING` (看一眼手机): 有限度地参与聊天(由 `MaiStateInfo` 定义具体的普通/专注群数量限制)。 - - `MaiState.NORMAL_CHAT` (正常看手机): 正常参与聊天,允许 `SubHeartflow` 进入 `CHAT` 或 `FOCUSED` 状态(数量受限)。 - * `MaiState.FOCUSED_CHAT` (专心看手机): 更积极地参与聊天,通常允许更多或更高优先级的 `FOCUSED` 状态子心流。 -- **当前转换逻辑**: 目前,`MaiState` 之间的转换由 `MaiStateManager` 管理,主要基于状态持续时间和随机概率。这是一种临时的实现方式,未来计划进行改进。 -- **作用**: `Heartflow` 的状态直接影响 `SubHeartflowManager` 如何管理子心流(如激活数量、允许的状态等)。 - -### 2.3. 聊天流状态 (`ChatState`) 与转换 -- **管理对象**: 每个 `SubHeartflow` 实例内部维护其 `ChatStateInfo`,包含当前的 `ChatState`。 -- **状态及含义**: - - `ChatState.ABSENT` (不参与/没在看): 初始或停用状态。子心流不观察新信息,不进行思考,也不回复。 - - `ChatState.CHAT` (随便看看/水群): 普通聊天模式。激活 `NormalChatInstance`。 - * `ChatState.FOCUSED` (专注/认真水群): 专注聊天模式。激活 `HeartFlowChatInstance`。 -- **选择**: 子心流可以根据外部指令(来自 `SubHeartflowManager`)或内部逻辑(未来的扩展)选择进入 `ABSENT` 状态(不回复不观察),或进入 `CHAT` / `FOCUSED` 中的一种回复模式。 -- **状态转换机制** (由 `SubHeartflowManager` 驱动,更细致的说明): - - **初始状态**: 新创建的 `SubHeartflow` 默认为 `ABSENT` 状态。 - - **`ABSENT` -> `CHAT` (激活闲聊)**: - - **触发条件**: `Heartflow` 的主状态 (`MaiState`) 允许 `CHAT` 模式,且当前 `CHAT` 状态的子心流数量未达上限。 - - **判定机制**: `SubHeartflowManager` 中的 `sbhf_absent_into_chat` 方法调用大模型(LLM)。LLM 读取该群聊的近期内容和结合自身个性信息,判断是否"想"在该群开始聊天。 - - **执行**: 若 LLM 判断为是,且名额未满,`SubHeartflowManager` 调用 `change_chat_state(ChatState.CHAT)`。 - - **`CHAT` -> `FOCUSED` (激活专注)**: - - **触发条件**: 子心流处于 `CHAT` 状态,其内部维护的"开屎热聊"概率 (`InterestChatting.start_hfc_probability`) 达到预设阈值(表示对当前聊天兴趣浓厚),同时 `Heartflow` 的主状态允许 `FOCUSED` 模式,且 `FOCUSED` 名额未满。 - - **判定机制**: `SubHeartflowManager` 中的 `sbhf_absent_into_focus` 方法定期检查满足条件的 `CHAT` 子心流。 - - **执行**: 若满足所有条件,`SubHeartflowManager` 调用 `change_chat_state(ChatState.FOCUSED)`。 - - **注意**: 无法从 `ABSENT` 直接跳到 `FOCUSED`,必须先经过 `CHAT`。 - - **`FOCUSED` -> `ABSENT` (退出专注)**: - - **主要途径 (内部驱动)**: 在 `FOCUSED` 状态下运行的 `HeartFlowChatInstance` 连续多次决策为 `no_reply` (例如达到 5 次,次数可配),它会通过回调函数 (`sbhf_focus_into_absent`) 请求 `SubHeartflowManager` 将其状态**直接**设置为 `ABSENT`。 - - **其他途径 (外部驱动)**: - - `Heartflow` 主状态变为 `OFFLINE`,`SubHeartflowManager` 强制所有子心流变为 `ABSENT`。 - - `SubHeartflowManager` 因 `FOCUSED` 名额超限 (`enforce_subheartflow_limits`) 或随机停用 (`randomly_deactivate_subflows`) 而将其设置为 `ABSENT`。 - - **`CHAT` -> `ABSENT` (退出闲聊)**: - - **主要途径 (内部驱动)**: `SubHeartflowManager` 中的 `sbhf_absent_into_chat` 方法调用 LLM。LLM 读取群聊内容和结合自身状态,判断是否"不想"继续在此群闲聊。 - - **执行**: 若 LLM 判断为是,`SubHeartflowManager` 调用 `change_chat_state(ChatState.ABSENT)`。 - - **其他途径 (外部驱动)**: - - `Heartflow` 主状态变为 `OFFLINE`。 - - `SubHeartflowManager` 因 `CHAT` 名额超限或随机停用。 - - **全局强制 `ABSENT`**: 当 `Heartflow` 的 `MaiState` 变为 `OFFLINE` 时,`SubHeartflowManager` 会调用所有子心流的 `change_chat_state(ChatState.ABSENT)`,强制它们全部停止活动。 - - **状态变更执行者**: `change_chat_state` 方法仅负责执行状态的切换和对应聊天实例的启停,不进行名额检查。名额检查的责任由 `SubHeartflowManager` 中的各个决策方法承担。 - - **最终清理**: 进入 `ABSENT` 状态的子心流不会立即被删除,只有在 `ABSENT` 状态持续一小时 (`INACTIVE_THRESHOLD_SECONDS`) 后,才会被后台清理任务 (`cleanup_inactive_subheartflows`) 删除。 - -## 3. 聊天实例详解 (Chat Instances Explained) - -### 3.1. NormalChatInstance -- **激活条件**: 对应 `SubHeartflow` 的 `ChatState` 为 `CHAT`。 -- **工作流程**: - - 当 `SubHeartflow` 进入 `CHAT` 状态时,`NormalChatInstance` 会被激活。 - - 实例启动后,会创建一个后台任务 (`_reply_interested_message`)。 - - 该任务持续监控由 `InterestChatting` 传入的、具有一定兴趣度的消息列表 (`interest_dict`)。 - - 对列表中的每条消息,结合是否被提及 (`@`)、消息本身的兴趣度以及当前的回复意愿 (`WillingManager`),计算出一个回复概率。 - - 根据计算出的概率随机决定是否对该消息进行回复。 - - 如果决定回复,则调用 `NormalChatGenerator` 生成回复内容,并可能附带表情包。 -- **行为特点**: - - 回复相对常规、简单。 - - 不投入过多计算资源。 - - 侧重于维持基本的交流氛围。 - - 示例:对问候语、日常分享等进行简单回应。 - -### 3.2. HeartFlowChatInstance (继承自原 PFC 逻辑) -- **激活条件**: 对应 `SubHeartflow` 的 `ChatState` 为 `FOCUSED`。 -- **工作流程**: - - 基于更复杂的规则(原 PFC 模式)进行深度处理。 - - 对群内话题进行深入分析。 - - 可能主动发起相关话题或引导交流。 -- **行为特点**: - - 回复更积极、深入。 - - 投入更多资源参与聊天。 - - 回复内容可能更详细、有针对性。 - - 对话题参与度高,能带动交流。 - - 示例:对复杂或有争议话题阐述观点,并与人互动。 - -## 4. 工作流程示例 (Example Workflow) - -1. **启动**: `Heartflow` 启动,初始化 `MaiStateInfo` (例如 `OFFLINE`) 和 `SubHeartflowManager`。 -2. **状态变化**: 用户操作或内部逻辑使 `Heartflow` 的 `current_state` 变为 `NORMAL_CHAT`。 -3. **管理器响应**: `SubHeartflowManager` 检测到状态变化,根据 `NORMAL_CHAT` 的限制,调用 `get_or_create_subheartflow` 获取或创建子心流,并通过 `change_chat_state` 将部分子心流状态从 `ABSENT` 激活为 `CHAT`。 -4. **子心流激活**: 被激活的 `SubHeartflow` 启动其 `NormalChatInstance`。 -5. **信息接收**: 该 `SubHeartflow` 的 `ChattingObservation` 开始从数据库拉取新消息。 -6. **普通回复**: `NormalChatInstance` 处理观察到的信息,执行普通回复逻辑。 -7. **兴趣评估**: `SubHeartflowManager` 定期评估该子心流的 `InterestChatting` 状态。 -8. **提升状态**: 若兴趣度达标且 `Heartflow` 状态允许,`SubHeartflowManager` 调用该子心流的 `change_chat_state` 将其状态提升为 `FOCUSED`。 -9. **子心流切换**: `SubHeartflow` 内部停止 `NormalChatInstance`,启动 `HeartFlowChatInstance`。 -10. **专注回复**: `HeartFlowChatInstance` 开始根据其逻辑进行更深入的交互。 -11. **状态回落/停用**: 若 `Heartflow` 状态变为 `OFFLINE`,`SubHeartflowManager` 会调用所有活跃子心流的 `change_chat_state(ChatState.ABSENT)`,使其进入 `ABSENT` 状态(它们不会立即被删除,只有在 `ABSENT` 状态持续1小时后才会被清理)。 - -## 5. 使用与配置 (Usage and Configuration) - -### 5.1. 使用说明 (Code Examples) -- **(内部)创建/获取子心流** (由 `SubHeartflowManager` 调用, 示例): - ```python - # subheartflow_manager.py (get_or_create_subheartflow 内部) - # 注意:mai_states 现在是 self.mai_state_info - new_subflow = SubHeartflow(subheartflow_id, self.mai_state_info) - await new_subflow.initialize() - observation = ChattingObservation(chat_id=subheartflow_id) - new_subflow.add_observation(observation) - ``` -- **(内部)添加观察者** (由 `SubHeartflowManager` 或 `SubHeartflow` 内部调用): - ```python - # sub_heartflow.py - self.observations.append(observation) - ``` - diff --git a/src/heart_flow/background_tasks.py b/src/heart_flow/background_tasks.py index 5ed664e0..35245c08 100644 --- a/src/heart_flow/background_tasks.py +++ b/src/heart_flow/background_tasks.py @@ -13,17 +13,11 @@ from src.heart_flow.interest_logger import InterestLogger logger = get_logger("background_tasks") -# 新增兴趣评估间隔 +# 兴趣评估间隔 INTEREST_EVAL_INTERVAL_SECONDS = 5 -# 新增聊天超时检查间隔 -NORMAL_CHAT_TIMEOUT_CHECK_INTERVAL_SECONDS = 60 -# 新增状态评估间隔 -HF_JUDGE_STATE_UPDATE_INTERVAL_SECONDS = 20 -# 新增私聊激活检查间隔 -PRIVATE_CHAT_ACTIVATION_CHECK_INTERVAL_SECONDS = 5 # 与兴趣评估类似,设为5秒 - -CLEANUP_INTERVAL_SECONDS = 1200 -STATE_UPDATE_INTERVAL_SECONDS = 60 +# 专注水群检查间隔 +FOCUS_CHAT_CHECK_INTERVAL_SECONDS = 60 +# 日志记录间隔 LOG_INTERVAL_SECONDS = 3 @@ -70,21 +64,16 @@ class BackgroundTaskManager: self.interest_logger = interest_logger # Task references - self._state_update_task: Optional[asyncio.Task] = None - self._cleanup_task: Optional[asyncio.Task] = None self._logging_task: Optional[asyncio.Task] = None - self._normal_chat_timeout_check_task: Optional[asyncio.Task] = None - self._hf_judge_state_update_task: Optional[asyncio.Task] = None + self._focus_chat_check_task: Optional[asyncio.Task] = None self._into_focus_task: Optional[asyncio.Task] = None - self._private_chat_activation_task: Optional[asyncio.Task] = None # 新增私聊激活任务引用 self._tasks: List[Optional[asyncio.Task]] = [] # Keep track of all tasks - self._detect_command_from_gui_task: Optional[asyncio.Task] = None # 新增GUI命令检测任务引用 async def start_tasks(self): """启动所有后台任务 功能说明: - - 启动核心后台任务: 状态更新、清理、日志记录、兴趣评估和随机停用 + - 启动核心后台任务: 日志记录、兴趣评估和专注水群监测 - 每个任务启动前检查是否已在运行 - 将任务引用保存到任务列表 """ @@ -92,28 +81,10 @@ class BackgroundTaskManager: # 任务配置列表: (任务函数, 任务名称, 日志级别, 额外日志信息, 任务对象引用属性名) task_configs = [ ( - lambda: self._run_state_update_cycle(STATE_UPDATE_INTERVAL_SECONDS), + lambda: self._run_focus_chat_check_cycle(FOCUS_CHAT_CHECK_INTERVAL_SECONDS), "debug", - f"聊天状态更新任务已启动 间隔:{STATE_UPDATE_INTERVAL_SECONDS}s", - "_state_update_task", - ), - ( - lambda: self._run_normal_chat_timeout_check_cycle(NORMAL_CHAT_TIMEOUT_CHECK_INTERVAL_SECONDS), - "debug", - f"聊天超时检查任务已启动 间隔:{NORMAL_CHAT_TIMEOUT_CHECK_INTERVAL_SECONDS}s", - "_normal_chat_timeout_check_task", - ), - ( - lambda: self._run_absent_into_chat(HF_JUDGE_STATE_UPDATE_INTERVAL_SECONDS), - "debug", - f"状态评估任务已启动 间隔:{HF_JUDGE_STATE_UPDATE_INTERVAL_SECONDS}s", - "_hf_judge_state_update_task", - ), - ( - self._run_cleanup_cycle, - "info", - f"清理任务已启动 间隔:{CLEANUP_INTERVAL_SECONDS}s", - "_cleanup_task", + f"专注水群检查任务已启动 间隔:{FOCUS_CHAT_CHECK_INTERVAL_SECONDS}s", + "_focus_chat_check_task", ), ( self._run_logging_cycle, @@ -121,28 +92,13 @@ class BackgroundTaskManager: f"日志任务已启动 间隔:{LOG_INTERVAL_SECONDS}s", "_logging_task", ), - # 新增兴趣评估任务配置 + # 兴趣评估任务配置 ( self._run_into_focus_cycle, "debug", # 设为debug,避免过多日志 f"专注评估任务已启动 间隔:{INTEREST_EVAL_INTERVAL_SECONDS}s", "_into_focus_task", ), - # 新增私聊激活任务配置 - ( - # Use lambda to pass the interval to the runner function - lambda: self._run_private_chat_activation_cycle(PRIVATE_CHAT_ACTIVATION_CHECK_INTERVAL_SECONDS), - "debug", - f"私聊激活检查任务已启动 间隔:{PRIVATE_CHAT_ACTIVATION_CHECK_INTERVAL_SECONDS}s", - "_private_chat_activation_task", - ), - # 新增GUI命令检测任务配置 - # ( - # lambda: self._run_detect_command_from_gui_cycle(3), - # "debug", - # f"GUI命令检测任务已启动 间隔:{3}s", - # "_detect_command_from_gui_task", - # ), ] # 统一启动所有任务 @@ -189,99 +145,24 @@ class BackgroundTaskManager: # 第三步:清空任务列表 self._tasks = [] # 重置任务列表 - async def _perform_state_update_work(self): - """执行状态更新工作""" - previous_status = self.mai_state_info.get_current_state() - next_state = self.mai_state_manager.check_and_decide_next_state(self.mai_state_info) - - state_changed = False - - if next_state is not None: - state_changed = self.mai_state_info.update_mai_status(next_state) - - # 处理保持离线状态的特殊情况 - if not state_changed and next_state == previous_status == self.mai_state_info.mai_status.OFFLINE: - self.mai_state_info.reset_state_timer() - logger.debug("[后台任务] 保持离线状态并重置计时器") - state_changed = True # 触发后续处理 - - if state_changed: - current_state = self.mai_state_info.get_current_state() - await self.subheartflow_manager.enforce_subheartflow_limits() - - # 状态转换处理 - - if ( - current_state == self.mai_state_info.mai_status.OFFLINE - and previous_status != self.mai_state_info.mai_status.OFFLINE - ): - logger.info("检测到离线,停用所有子心流") - await self.subheartflow_manager.deactivate_all_subflows() - - async def _perform_absent_into_chat(self): - """调用llm检测是否转换ABSENT-CHAT状态""" - logger.debug("[状态评估任务] 开始基于LLM评估子心流状态...") - await self.subheartflow_manager.sbhf_absent_into_chat() - - async def _normal_chat_timeout_check_work(self): - """检查处于CHAT状态的子心流是否因长时间未发言而超时,并将其转为ABSENT""" - logger.debug("[聊天超时检查] 开始检查处于CHAT状态的子心流...") - await self.subheartflow_manager.sbhf_chat_into_absent() - - async def _perform_cleanup_work(self): - """执行子心流清理任务 - 1. 获取需要清理的不活跃子心流列表 - 2. 逐个停止这些子心流 - 3. 记录清理结果 - """ - # 获取需要清理的子心流列表(包含ID和原因) - flows_to_stop = self.subheartflow_manager.get_inactive_subheartflows() - - if not flows_to_stop: - return # 没有需要清理的子心流直接返回 - - logger.info(f"准备删除 {len(flows_to_stop)} 个不活跃(1h)子心流") - stopped_count = 0 - - # 逐个停止子心流 - for flow_id in flows_to_stop: - success = await self.subheartflow_manager.delete_subflow(flow_id) - if success: - stopped_count += 1 - logger.debug(f"[清理任务] 已停止子心流 {flow_id}") - - # 记录最终清理结果 - logger.info(f"[清理任务] 清理完成, 共停止 {stopped_count}/{len(flows_to_stop)} 个子心流") + async def _focus_chat_check_work(self): + """检查FOCUSED状态的子心流是否需要转回CHAT状态""" + logger.debug("[专注水群检查] 开始检查专注水群状态的子心流...") + await self.subheartflow_manager.sbhf_focus_into_chat() async def _perform_logging_work(self): """执行一轮状态日志记录。""" await self.interest_logger.log_all_states() - # --- 新增兴趣评估工作函数 --- async def _perform_into_focus_work(self): """执行一轮子心流兴趣评估与提升检查。""" - # 直接调用 subheartflow_manager 的方法,并传递当前状态信息 - await self.subheartflow_manager.sbhf_absent_into_focus() - - # --- 结束新增 --- - - # --- 结束新增 --- + # 直接调用 subheartflow_manager 的方法进行CHAT到FOCUSED的转换 + await self.subheartflow_manager.sbhf_chat_into_focus() # --- Specific Task Runners --- # - async def _run_state_update_cycle(self, interval: int): - await _run_periodic_loop(task_name="State Update", interval=interval, task_func=self._perform_state_update_work) - - async def _run_absent_into_chat(self, interval: int): - await _run_periodic_loop(task_name="Into Chat", interval=interval, task_func=self._perform_absent_into_chat) - - async def _run_normal_chat_timeout_check_cycle(self, interval: int): + async def _run_focus_chat_check_cycle(self, interval: int): await _run_periodic_loop( - task_name="Normal Chat Timeout Check", interval=interval, task_func=self._normal_chat_timeout_check_work - ) - - async def _run_cleanup_cycle(self): - await _run_periodic_loop( - task_name="Subflow Cleanup", interval=CLEANUP_INTERVAL_SECONDS, task_func=self._perform_cleanup_work + task_name="Focus Chat Check", interval=interval, task_func=self._focus_chat_check_work ) async def _run_logging_cycle(self): @@ -289,26 +170,9 @@ class BackgroundTaskManager: task_name="State Logging", interval=LOG_INTERVAL_SECONDS, task_func=self._perform_logging_work ) - # --- 新增兴趣评估任务运行器 --- async def _run_into_focus_cycle(self): await _run_periodic_loop( task_name="Into Focus", interval=INTEREST_EVAL_INTERVAL_SECONDS, task_func=self._perform_into_focus_work, ) - - # 新增私聊激活任务运行器 - async def _run_private_chat_activation_cycle(self, interval: int): - await _run_periodic_loop( - task_name="Private Chat Activation Check", - interval=interval, - task_func=self.subheartflow_manager.sbhf_absent_private_into_focus, - ) - - # # 有api之后删除 - # async def _run_detect_command_from_gui_cycle(self, interval: int): - # await _run_periodic_loop( - # task_name="Detect Command from GUI", - # interval=interval, - # task_func=self.subheartflow_manager.detect_command_from_gui, - # ) diff --git a/src/heart_flow/chat_state_info.py b/src/heart_flow/chat_state_info.py index 619f372f..622f543c 100644 --- a/src/heart_flow/chat_state_info.py +++ b/src/heart_flow/chat_state_info.py @@ -3,14 +3,13 @@ import enum class ChatState(enum.Enum): - ABSENT = "没在看群" CHAT = "随便水群" FOCUSED = "认真水群" class ChatStateInfo: def __init__(self): - self.chat_status: ChatState = ChatState.ABSENT + self.chat_status: ChatState = ChatState.CHAT self.current_state_time = 120 self.mood_manager = MoodManager() diff --git a/src/heart_flow/heartflow.py b/src/heart_flow/heartflow.py index 2cf7d365..5934d02e 100644 --- a/src/heart_flow/heartflow.py +++ b/src/heart_flow/heartflow.py @@ -1,7 +1,6 @@ from src.heart_flow.sub_heartflow import SubHeartflow, ChatState from src.plugins.models.utils_model import LLMRequest from src.config.config import global_config -from src.plugins.schedule.schedule_generator import bot_schedule from src.common.logger_manager import get_logger from typing import Any, Optional from src.do_tool.tool_use import ToolUser @@ -97,16 +96,5 @@ class Heartflow: await self.subheartflow_manager.deactivate_all_subflows() logger.info("[Heartflow] 所有任务和子心流已停止") - async def do_a_thinking(self): - """执行一次主心流思考过程""" - schedule_info = bot_schedule.get_current_num_task(num=4, time_info=True) - new_mind = await self.mind.do_a_thinking( - current_main_mind=self.current_mind, mai_state_info=self.current_state, schedule_info=schedule_info - ) - self.past_mind.append(self.current_mind) - self.current_mind = new_mind - logger.info(f"麦麦的总体脑内状态更新为:{self.current_mind[:100]}...") - self.mind.update_subflows_with_main_mind(new_mind) - heartflow = Heartflow() diff --git a/src/heart_flow/interest_chatting.py b/src/heart_flow/interest_chatting.py index 4525d09d..46d9d07d 100644 --- a/src/heart_flow/interest_chatting.py +++ b/src/heart_flow/interest_chatting.py @@ -145,7 +145,7 @@ class InterestChatting: } # --- 新增后台更新任务相关方法 --- - async def _run_update_loop(self, update_interval: float = 1.0): + async def _run_update_loop(self, update_interval: float = 0.5): """后台循环,定期更新兴趣和回复概率。""" try: while not self._stop_event.is_set(): diff --git a/src/heart_flow/mai_state_manager.py b/src/heart_flow/mai_state_manager.py index d289a94a..60768437 100644 --- a/src/heart_flow/mai_state_manager.py +++ b/src/heart_flow/mai_state_manager.py @@ -1,125 +1,45 @@ import enum import time -import random from typing import List, Tuple, Optional from src.common.logger_manager import get_logger from src.plugins.moods.moods import MoodManager -from src.config.config import global_config logger = get_logger("mai_state") -# -- 状态相关的可配置参数 (可以从 glocal_config 加载) -- -# The line `enable_unlimited_hfc_chat = False` is setting a configuration parameter that controls -# whether a specific debugging feature is enabled or not. When `enable_unlimited_hfc_chat` is set to -# `False`, it means that the debugging feature for unlimited focused chatting is disabled. -# enable_unlimited_hfc_chat = True # 调试用:无限专注聊天 -enable_unlimited_hfc_chat = False -prevent_offline_state = True -# 目前默认不启用OFFLINE状态 - -# 不同状态下普通聊天的最大消息数 -base_normal_chat_num = global_config.base_normal_chat_num -base_focused_chat_num = global_config.base_focused_chat_num - - -MAX_NORMAL_CHAT_NUM_PEEKING = int(base_normal_chat_num / 2) -MAX_NORMAL_CHAT_NUM_NORMAL = base_normal_chat_num -MAX_NORMAL_CHAT_NUM_FOCUSED = base_normal_chat_num + 1 - -# 不同状态下专注聊天的最大消息数 -MAX_FOCUSED_CHAT_NUM_PEEKING = int(base_focused_chat_num / 2) -MAX_FOCUSED_CHAT_NUM_NORMAL = base_focused_chat_num -MAX_FOCUSED_CHAT_NUM_FOCUSED = base_focused_chat_num + 2 - # -- 状态定义 -- - - class MaiState(enum.Enum): """ 聊天状态: - OFFLINE: 不在线:回复概率极低,不会进行任何聊天 - PEEKING: 看一眼手机:回复概率较低,会进行一些普通聊天 - NORMAL_CHAT: 正常看手机:回复概率较高,会进行一些普通聊天和少量的专注聊天 - FOCUSED_CHAT: 专注聊天:回复概率极高,会进行专注聊天和少量的普通聊天 + NORMAL_CHAT: 正常看手机:回复概率较高,会进行一些普通聊天和少量的专注水群 + FOCUSED_CHAT: 专注水群:回复概率极高,会进行专注水群和少量的普通聊天 """ - - OFFLINE = "不在线" - PEEKING = "看一眼手机" NORMAL_CHAT = "正常看手机" FOCUSED_CHAT = "专心看手机" - def get_normal_chat_max_num(self): - # 调试用 - if enable_unlimited_hfc_chat: - return 1000 - - if self == MaiState.OFFLINE: - return 0 - elif self == MaiState.PEEKING: - return MAX_NORMAL_CHAT_NUM_PEEKING - elif self == MaiState.NORMAL_CHAT: - return MAX_NORMAL_CHAT_NUM_NORMAL - elif self == MaiState.FOCUSED_CHAT: - return MAX_NORMAL_CHAT_NUM_FOCUSED - return None - - def get_focused_chat_max_num(self): - # 调试用 - if enable_unlimited_hfc_chat: - return 1000 - - if self == MaiState.OFFLINE: - return 0 - elif self == MaiState.PEEKING: - return MAX_FOCUSED_CHAT_NUM_PEEKING - elif self == MaiState.NORMAL_CHAT: - return MAX_FOCUSED_CHAT_NUM_NORMAL - elif self == MaiState.FOCUSED_CHAT: - return MAX_FOCUSED_CHAT_NUM_FOCUSED - return None - class MaiStateInfo: def __init__(self): - self.mai_status: MaiState = MaiState.OFFLINE + self.mai_status: MaiState = MaiState.NORMAL_CHAT self.mai_status_history: List[Tuple[MaiState, float]] = [] # 历史状态,包含 状态,时间戳 self.last_status_change_time: float = time.time() # 状态最后改变时间 - self.last_min_check_time: float = time.time() # 上次1分钟规则检查时间 # Mood management is now part of MaiStateInfo self.mood_manager = MoodManager.get_instance() # Use singleton instance def update_mai_status(self, new_status: MaiState) -> bool: """ - 更新聊天状态。 + 更新聊天状态。始终返回False,因为状态永远固定为NORMAL_CHAT Args: new_status: 新的 MaiState 状态。 Returns: - bool: 如果状态实际发生了改变则返回 True,否则返回 False。 + bool: 始终返回False,表示状态不会改变。 """ - if new_status != self.mai_status: - self.mai_status = new_status - current_time = time.time() - self.last_status_change_time = current_time - self.last_min_check_time = current_time # Reset 1-min check on any state change - self.mai_status_history.append((new_status, current_time)) - logger.info(f"麦麦状态更新为: {self.mai_status.value}") - return True - else: - return False - - def reset_state_timer(self): - """ - 重置状态持续时间计时器和一分钟规则检查计时器。 - 通常在状态保持不变但需要重新开始计时的情况下调用(例如,保持 OFFLINE)。 - """ - current_time = time.time() - self.last_status_change_time = current_time - self.last_min_check_time = current_time # Also reset the 1-min check timer - logger.debug("MaiStateInfo 状态计时器已重置。") + # 不再允许状态变化,始终保持NORMAL_CHAT + logger.debug(f"尝试将状态更新为: {new_status.value},但状态已固定为 {self.mai_status.value}") + return False def get_mood_prompt(self) -> str: """获取当前的心情提示词""" @@ -128,7 +48,7 @@ class MaiStateInfo: def get_current_state(self) -> MaiState: """获取当前的 MaiState""" - return self.mai_status + return MaiState.NORMAL_CHAT # 始终返回NORMAL_CHAT class MaiStateManager: @@ -140,106 +60,14 @@ class MaiStateManager: @staticmethod def check_and_decide_next_state(current_state_info: MaiStateInfo) -> Optional[MaiState]: """ - 根据当前状态和规则检查是否需要转换状态,并决定下一个状态。 + 状态检查函数。由于状态已固定为NORMAL_CHAT,此函数始终返回None。 Args: current_state_info: 当前的 MaiStateInfo 实例。 Returns: - Optional[MaiState]: 如果需要转换,返回目标 MaiState;否则返回 None。 + Optional[MaiState]: 始终返回None,表示没有状态变化。 """ - current_time = time.time() - current_status = current_state_info.mai_status - time_in_current_status = current_time - current_state_info.last_status_change_time - time_since_last_min_check = current_time - current_state_info.last_min_check_time - next_state: Optional[MaiState] = None - - # 辅助函数:根据 prevent_offline_state 标志调整目标状态 - def _resolve_offline(candidate_state: MaiState) -> MaiState: - if prevent_offline_state and candidate_state == MaiState.OFFLINE: - logger.debug("阻止进入 OFFLINE,改为 PEEKING") - return MaiState.PEEKING - return candidate_state - - if current_status == MaiState.OFFLINE: - logger.info("当前[离线],没看手机,思考要不要上线看看......") - elif current_status == MaiState.PEEKING: - logger.info("当前[看一眼手机],思考要不要继续聊下去......") - elif current_status == MaiState.NORMAL_CHAT: - logger.info("当前在[正常看手机]思考要不要继续聊下去......") - elif current_status == MaiState.FOCUSED_CHAT: - logger.info("当前在[专心看手机]思考要不要继续聊下去......") - - # 1. 麦麦每分钟都有概率离线 - if time_since_last_min_check >= 60: - if current_status != MaiState.OFFLINE: - if random.random() < 0.03: # 3% 概率切换到 OFFLINE - potential_next = MaiState.OFFLINE - resolved_next = _resolve_offline(potential_next) - logger.debug(f"概率触发下线,resolve 为 {resolved_next.value}") - # 只有当解析后的状态与当前状态不同时才设置 next_state - if resolved_next != current_status: - next_state = resolved_next - - # 2. 状态持续时间规则 (只有在规则1没有触发状态改变时才检查) - if next_state is None: - time_limit_exceeded = False - choices_list = [] - weights = [] - rule_id = "" - - if current_status == MaiState.OFFLINE: - # 注意:即使 prevent_offline_state=True,也可能从初始的 OFFLINE 状态启动 - if time_in_current_status >= 60: - time_limit_exceeded = True - rule_id = "2.1 (From OFFLINE)" - weights = [30, 30, 20, 20] - choices_list = [MaiState.PEEKING, MaiState.NORMAL_CHAT, MaiState.FOCUSED_CHAT, MaiState.OFFLINE] - elif current_status == MaiState.PEEKING: - if time_in_current_status >= 600: # PEEKING 最多持续 600 秒 - time_limit_exceeded = True - rule_id = "2.2 (From PEEKING)" - weights = [70, 20, 10] - choices_list = [MaiState.OFFLINE, MaiState.NORMAL_CHAT, MaiState.FOCUSED_CHAT] - elif current_status == MaiState.NORMAL_CHAT: - if time_in_current_status >= 300: # NORMAL_CHAT 最多持续 300 秒 - time_limit_exceeded = True - rule_id = "2.3 (From NORMAL_CHAT)" - weights = [50, 50] - choices_list = [MaiState.OFFLINE, MaiState.FOCUSED_CHAT] - elif current_status == MaiState.FOCUSED_CHAT: - if time_in_current_status >= 600: # FOCUSED_CHAT 最多持续 600 秒 - time_limit_exceeded = True - rule_id = "2.4 (From FOCUSED_CHAT)" - weights = [80, 20] - choices_list = [MaiState.OFFLINE, MaiState.NORMAL_CHAT] - - if time_limit_exceeded: - next_state_candidate = random.choices(choices_list, weights=weights, k=1)[0] - resolved_candidate = _resolve_offline(next_state_candidate) - logger.debug( - f"规则{rule_id}:时间到,随机选择 {next_state_candidate.value},resolve 为 {resolved_candidate.value}" - ) - next_state = resolved_candidate # 直接使用解析后的状态 - - # 注意:enable_unlimited_hfc_chat 优先级高于 prevent_offline_state - # 如果触发了这个,它会覆盖上面规则2设置的 next_state - if enable_unlimited_hfc_chat: - logger.debug("调试用:开挂了,强制切换到专注聊天") - next_state = MaiState.FOCUSED_CHAT - - # --- 最终决策 --- # - # 如果决定了下一个状态,且这个状态与当前状态不同,则返回下一个状态 - if next_state is not None and next_state != current_status: - return next_state - # 如果决定保持 OFFLINE (next_state == MaiState.OFFLINE) 且当前也是 OFFLINE, - # 并且是由于持续时间规则触发的,返回 OFFLINE 以便调用者可以重置计时器。 - # 注意:这个分支只有在 prevent_offline_state = False 时才可能被触发。 - elif next_state == MaiState.OFFLINE and current_status == MaiState.OFFLINE and time_in_current_status >= 60: - logger.debug("决定保持 OFFLINE (持续时间规则),返回 OFFLINE 以提示重置计时器。") - return MaiState.OFFLINE # Return OFFLINE to signal caller that timer reset might be needed - else: - # 1. next_state is None (没有触发任何转换规则) - # 2. next_state is not None 但等于 current_status (例如规则1想切OFFLINE但被resolve成PEEKING,而当前已经是PEEKING) - # 3. next_state is OFFLINE, current is OFFLINE, 但不是因为时间规则触发 (例如初始状态还没到60秒) - return None # 没有状态转换发生或无需重置计时器 + # 不再需要检查或决定状态变化,状态已固定为NORMAL_CHAT + logger.debug("当前在[正常看手机]状态,状态已固定,不会变化") + return None # 没有状态转换发生 diff --git a/src/heart_flow/mind.py b/src/heart_flow/mind.py index 89ffc6a3..ecad1ea3 100644 --- a/src/heart_flow/mind.py +++ b/src/heart_flow/mind.py @@ -24,14 +24,13 @@ class Mind: self.llm_model = llm_model self.individuality = Individuality.get_instance() - async def do_a_thinking(self, current_main_mind: str, mai_state_info: "MaiStateInfo", schedule_info: str): + async def do_a_thinking(self, current_main_mind: str, mai_state_info: "MaiStateInfo"): """ 执行一次主心流思考过程,生成新的内心独白。 Args: current_main_mind: 当前的主心流想法。 mai_state_info: 当前的 Mai 状态信息 (用于获取 mood)。 - schedule_info: 当前的日程信息。 Returns: str: 生成的新的内心独白,如果出错则返回提示信息。 @@ -58,7 +57,6 @@ class Mind: # Format prompt try: prompt = (await global_prompt_manager.get_prompt_async("thinking_prompt")).format( - schedule_info=schedule_info, personality_info=personality_info, related_memory_info=related_memory_info, current_thinking_info=current_main_mind, # Use passed current mind diff --git a/src/heart_flow/sub_heartflow.py b/src/heart_flow/sub_heartflow.py index e2a36dbd..8c89513b 100644 --- a/src/heart_flow/sub_heartflow.py +++ b/src/heart_flow/sub_heartflow.py @@ -94,6 +94,9 @@ class SubHeartflow: await self.interest_chatting.initialize() logger.debug(f"{self.log_prefix} InterestChatting 实例已初始化。") + # 确保普通聊天已启动 + await self._start_normal_chat(rewind=True) + def update_last_chat_state_time(self): self.chat_state_last_time = time.time() - self.chat_state_changed_time @@ -103,7 +106,7 @@ class SubHeartflow: 切出 CHAT 状态时使用 """ if self.normal_chat_instance: - logger.info(f"{self.log_prefix} 离开CHAT模式,结束 随便水群") + logger.info(f"{self.log_prefix} 结束随便水群") try: await self.normal_chat_instance.stop_chat() # 调用 stop_chat except Exception as e: @@ -116,7 +119,7 @@ class SubHeartflow: 进入 CHAT 状态时使用。 确保 HeartFChatting 已停止。 """ - await self._stop_heart_fc_chat() # 确保 专注聊天已停止 + await self._stop_heart_fc_chat() # 确保 专注水群已停止 log_prefix = self.log_prefix try: @@ -128,7 +131,8 @@ class SubHeartflow: if rewind: self.normal_chat_instance = NormalChat(chat_stream=chat_stream, interest_dict=self.get_interest_dict()) else: - self.normal_chat_instance = NormalChat(chat_stream=chat_stream) + self.clear_interest_dict() + self.normal_chat_instance = NormalChat(chat_stream=chat_stream, interest_dict=self.get_interest_dict()) # 进行异步初始化 await self.normal_chat_instance.initialize() @@ -146,7 +150,7 @@ class SubHeartflow: async def _stop_heart_fc_chat(self): """停止并清理 HeartFChatting 实例""" if self.heart_fc_instance: - logger.debug(f"{self.log_prefix} 结束专注聊天...") + logger.debug(f"{self.log_prefix} 结束专注水群...") try: await self.heart_fc_instance.shutdown() except Exception as e: @@ -159,7 +163,7 @@ class SubHeartflow: async def _start_heart_fc_chat(self) -> bool: """启动 HeartFChatting 实例,确保 NormalChat 已停止""" await self._stop_normal_chat() # 确保普通聊天监控已停止 - self.clear_interest_dict() # 清理兴趣字典,准备专注聊天 + self.clear_interest_dict() # 清理兴趣字典,准备专注水群 log_prefix = self.log_prefix # 如果实例已存在,检查其循环任务状态 @@ -181,7 +185,7 @@ class SubHeartflow: return True # 已经在运行 # 如果实例不存在,则创建并启动 - logger.info(f"{log_prefix} 麦麦准备开始专注聊天...") + logger.info(f"{log_prefix} 麦麦准备开始专注水群...") try: # 创建 HeartFChatting 实例,并传递 从构造函数传入的 回调函数 self.heart_fc_instance = HeartFChatting( @@ -194,7 +198,7 @@ class SubHeartflow: # 初始化并启动 HeartFChatting if await self.heart_fc_instance._initialize(): await self.heart_fc_instance.start() - logger.debug(f"{log_prefix} 麦麦已成功进入专注聊天模式 (新实例已启动)。") + logger.debug(f"{log_prefix} 麦麦已成功进入专注水群模式 (新实例已启动)。") return True else: logger.error(f"{log_prefix} HeartFChatting 初始化失败,无法进入专注模式。") @@ -218,44 +222,30 @@ class SubHeartflow: # --- 状态转换逻辑 --- if new_state == ChatState.CHAT: - # 移除限额检查逻辑 logger.debug(f"{log_prefix} 准备进入或保持 聊天 状态") if current_state == ChatState.FOCUSED: if await self._start_normal_chat(rewind=False): - # logger.info(f"{log_prefix} 成功进入或保持 NormalChat 状态。") state_changed = True else: logger.error(f"{log_prefix} 从FOCUSED状态启动 NormalChat 失败,无法进入 CHAT 状态。") - # 考虑是否需要回滚状态或采取其他措施 return # 启动失败,不改变状态 else: + # 这个分支不应该再被进入,因为已没有ABSENT状态 if await self._start_normal_chat(rewind=True): - # logger.info(f"{log_prefix} 成功进入或保持 NormalChat 状态。") state_changed = True else: - logger.error(f"{log_prefix} 从ABSENT状态启动 NormalChat 失败,无法进入 CHAT 状态。") - # 考虑是否需要回滚状态或采取其他措施 + logger.error(f"{log_prefix} 启动 NormalChat 失败,无法进入 CHAT 状态。") return # 启动失败,不改变状态 elif new_state == ChatState.FOCUSED: - # 移除限额检查逻辑 - logger.debug(f"{log_prefix} 准备进入或保持 专注聊天 状态") + logger.debug(f"{log_prefix} 准备进入或保持 专注水群 状态") if await self._start_heart_fc_chat(): logger.debug(f"{log_prefix} 成功进入或保持 HeartFChatting 状态。") state_changed = True else: logger.error(f"{log_prefix} 启动 HeartFChatting 失败,无法进入 FOCUSED 状态。") - # 启动失败,状态回滚到之前的状态或ABSENT?这里保持不改变 return # 启动失败,不改变状态 - elif new_state == ChatState.ABSENT: - logger.info(f"{log_prefix} 进入 ABSENT 状态,停止所有聊天活动...") - await self.clear_interest_dict() - - await self._stop_normal_chat() - await self._stop_heart_fc_chat() - state_changed = True # 总是可以成功转换到 ABSENT - # --- 更新状态和最后活动时间 --- if state_changed: self.update_last_chat_state_time() @@ -369,6 +359,6 @@ class SubHeartflow: logger.error(f"{self.log_prefix} 等待子心流主任务取消时发生错误 (Shutdown): {e}") self.task = None # 清理任务引用 - self.chat_state.chat_status = ChatState.ABSENT # 状态重置为不参与 + self.chat_state.chat_status = ChatState.CHAT # 状态重置为随便水群而不是不参与 logger.info(f"{self.log_prefix} 子心流关闭完成。") diff --git a/src/heart_flow/subheartflow_manager.py b/src/heart_flow/subheartflow_manager.py index c074d29a..0b18350f 100644 --- a/src/heart_flow/subheartflow_manager.py +++ b/src/heart_flow/subheartflow_manager.py @@ -28,7 +28,6 @@ import traceback logger = get_logger("subheartflow_manager") # 子心流管理相关常量 -INACTIVE_THRESHOLD_SECONDS = 3600 # 子心流不活跃超时时间(秒) NORMAL_CHAT_TIMEOUT_SECONDS = 30 * 60 # 30分钟 @@ -74,15 +73,6 @@ class SubHeartflowManager: self._lock = asyncio.Lock() # 用于保护 self.subheartflows 的访问 self.mai_state_info: MaiStateInfo = mai_state_info # 存储传入的 MaiStateInfo 实例 - # 为 LLM 状态评估创建一个 LLMRequest 实例 - # 使用与 Heartflow 相同的模型和参数 - self.llm_state_evaluator = LLMRequest( - model=global_config.llm_heartflow, # 与 Heartflow 一致 - temperature=0.6, # 与 Heartflow 一致 - max_tokens=1000, # 与 Heartflow 一致 (虽然可能不需要这么多) - request_type="subheartflow_state_eval", # 保留特定的请求类型 - ) - async def force_change_state(self, subflow_id: Any, target_state: ChatState) -> bool: """强制改变指定子心流的状态""" async with self._lock: @@ -155,128 +145,9 @@ class SubHeartflowManager: logger.error(f"创建子心流 {subheartflow_id} 失败: {e}", exc_info=True) return None - # --- 新增:内部方法,用于尝试将单个子心流设置为 ABSENT --- - - # --- 结束新增 --- - - async def sleep_subheartflow(self, subheartflow_id: Any, reason: str) -> bool: - """停止指定的子心流并将其状态设置为 ABSENT""" - log_prefix = "[子心流管理]" - async with self._lock: # 加锁以安全访问字典 - subheartflow = self.subheartflows.get(subheartflow_id) - - stream_name = chat_manager.get_stream_name(subheartflow_id) or subheartflow_id - logger.info(f"{log_prefix} 正在停止 {stream_name}, 原因: {reason}") - - # 调用内部方法处理状态变更 - success = await _try_set_subflow_absent_internal(subheartflow, log_prefix) - - return success - # 锁在此处自动释放 - - def get_inactive_subheartflows(self, max_age_seconds=INACTIVE_THRESHOLD_SECONDS): - """识别并返回需要清理的不活跃(处于ABSENT状态超过一小时)子心流(id, 原因)""" - _current_time = time.time() - flows_to_stop = [] - - for subheartflow_id, subheartflow in list(self.subheartflows.items()): - state = subheartflow.chat_state.chat_status - if state != ChatState.ABSENT: - continue - subheartflow.update_last_chat_state_time() - _absent_last_time = subheartflow.chat_state_last_time - flows_to_stop.append(subheartflow_id) - - return flows_to_stop - - async def enforce_subheartflow_limits(self): - """根据主状态限制停止超额子心流(优先停不活跃的)""" - # 使用 self.mai_state_info 获取当前状态和限制 - current_mai_state = self.mai_state_info.get_current_state() - normal_limit = current_mai_state.get_normal_chat_max_num() - focused_limit = current_mai_state.get_focused_chat_max_num() - logger.debug(f"[限制] 状态:{current_mai_state.value}, 普通限:{normal_limit}, 专注限:{focused_limit}") - - # 分类统计当前子心流 - normal_flows = [] - focused_flows = [] - for flow_id, flow in list(self.subheartflows.items()): - if flow.chat_state.chat_status == ChatState.CHAT: - normal_flows.append((flow_id, getattr(flow, "last_active_time", 0))) - elif flow.chat_state.chat_status == ChatState.FOCUSED: - focused_flows.append((flow_id, getattr(flow, "last_active_time", 0))) - - logger.debug(f"[限制] 当前数量 - 普通:{len(normal_flows)}, 专注:{len(focused_flows)}") - stopped = 0 - - # 处理普通聊天超额 - if len(normal_flows) > normal_limit: - excess = len(normal_flows) - normal_limit - logger.info(f"[限制] 普通聊天超额({len(normal_flows)}>{normal_limit}), 停止{excess}个") - normal_flows.sort(key=lambda x: x[1]) - for flow_id, _ in normal_flows[:excess]: - if await self.sleep_subheartflow(flow_id, f"普通聊天超额(限{normal_limit})"): - stopped += 1 - - # 处理专注聊天超额(需重新统计) - focused_flows = [ - (fid, t) - for fid, f in list(self.subheartflows.items()) - if (t := getattr(f, "last_active_time", 0)) and f.chat_state.chat_status == ChatState.FOCUSED - ] - if len(focused_flows) > focused_limit: - excess = len(focused_flows) - focused_limit - logger.info(f"[限制] 专注聊天超额({len(focused_flows)}>{focused_limit}), 停止{excess}个") - focused_flows.sort(key=lambda x: x[1]) - for flow_id, _ in focused_flows[:excess]: - if await self.sleep_subheartflow(flow_id, f"专注聊天超额(限{focused_limit})"): - stopped += 1 - - if stopped: - logger.info(f"[限制] 已停止{stopped}个子心流, 剩余:{len(self.subheartflows)}") - else: - logger.debug(f"[限制] 无需停止, 当前总数:{len(self.subheartflows)}") - - async def deactivate_all_subflows(self): - """将所有子心流的状态更改为 ABSENT (例如主状态变为OFFLINE时调用)""" - log_prefix = "[停用]" - changed_count = 0 - processed_count = 0 - - async with self._lock: # 获取锁以安全迭代 - # 使用 list() 创建一个当前值的快照,防止在迭代时修改字典 - flows_to_update = list(self.subheartflows.values()) - processed_count = len(flows_to_update) - if not flows_to_update: - logger.debug(f"{log_prefix} 无活跃子心流,无需操作") - return - - for subflow in flows_to_update: - # 记录原始状态,以便统计实际改变的数量 - original_state_was_absent = subflow.chat_state.chat_status == ChatState.ABSENT - - success = await _try_set_subflow_absent_internal(subflow, log_prefix) - - # 如果成功设置为 ABSENT 且原始状态不是 ABSENT,则计数 - if success and not original_state_was_absent: - if subflow.chat_state.chat_status == ChatState.ABSENT: - changed_count += 1 - else: - # 这种情况理论上不应发生,如果内部方法返回 True 的话 - stream_name = chat_manager.get_stream_name(subflow.subheartflow_id) or subflow.subheartflow_id - logger.warning(f"{log_prefix} 内部方法声称成功但 {stream_name} 状态未变为 ABSENT。") - # 锁在此处自动释放 - - logger.info( - f"{log_prefix} 完成,共处理 {processed_count} 个子心流,成功将 {changed_count} 个非 ABSENT 子心流的状态更改为 ABSENT。" - ) - - async def sbhf_absent_into_focus(self): - """评估子心流兴趣度,满足条件且未达上限则提升到FOCUSED状态(基于start_hfc_probability)""" + async def sbhf_chat_into_focus(self): + """评估子心流兴趣度,满足条件则提升到FOCUSED状态(基于start_hfc_probability)""" try: - current_state = self.mai_state_info.get_current_state() - focused_limit = current_state.get_focused_chat_max_num() - # --- 新增:检查是否允许进入 FOCUS 模式 --- # if not global_config.allow_focus_mode: if int(time.time()) % 60 == 0: # 每60秒输出一次日志避免刷屏 @@ -284,22 +155,11 @@ class SubHeartflowManager: return # 如果不允许,直接返回 # --- 结束新增 --- - logger.info(f"当前状态 ({current_state.value}) 可以在{focused_limit}个群 专注聊天") - - if focused_limit <= 0: - # logger.debug(f"{log_prefix} 当前状态 ({current_state.value}) 不允许 FOCUSED 子心流") - return - - current_focused_count = self.count_subflows_by_state(ChatState.FOCUSED) - if current_focused_count >= focused_limit: - logger.debug(f"已达专注上限 ({current_focused_count}/{focused_limit})") - return - for sub_hf in list(self.subheartflows.values()): flow_id = sub_hf.subheartflow_id stream_name = chat_manager.get_stream_name(flow_id) or flow_id - # 跳过非CHAT状态或已经是FOCUSED状态的子心流 + # 跳过已经是FOCUSED状态的子心流 if sub_hf.chat_state.chat_status == ChatState.FOCUSED: continue @@ -310,21 +170,12 @@ class SubHeartflowManager: f"{stream_name},现在状态: {sub_hf.chat_state.chat_status.value},进入专注概率: {sub_hf.interest_chatting.start_hfc_probability}" ) - # 调试用 - from .mai_state_manager import enable_unlimited_hfc_chat - - if not enable_unlimited_hfc_chat: - if sub_hf.chat_state.chat_status != ChatState.CHAT: - continue + if sub_hf.chat_state.chat_status != ChatState.CHAT: + continue if random.random() >= sub_hf.interest_chatting.start_hfc_probability: continue - # 再次检查是否达到上限 - if current_focused_count >= focused_limit: - logger.debug(f"{stream_name} 已达专注上限") - break - # 获取最新状态并执行提升 current_subflow = self.subheartflows.get(flow_id) if not current_subflow: @@ -336,284 +187,74 @@ class SubHeartflowManager: # 执行状态提升 await current_subflow.change_chat_state(ChatState.FOCUSED) - - # 验证提升结果 - if ( - final_subflow := self.subheartflows.get(flow_id) - ) and final_subflow.chat_state.chat_status == ChatState.FOCUSED: - current_focused_count += 1 except Exception as e: logger.error(f"启动HFC 兴趣评估失败: {e}", exc_info=True) - async def sbhf_absent_into_chat(self): - """ - 随机选一个 ABSENT 状态的 *群聊* 子心流,评估是否应转换为 CHAT 状态。 - 每次调用最多转换一个。 - 私聊会被忽略。 - """ - current_mai_state = self.mai_state_info.get_current_state() - chat_limit = current_mai_state.get_normal_chat_max_num() + async def sbhf_focus_into_chat(self): + """检查FOCUSED状态的子心流是否需要转回CHAT状态""" + focus_time_limit = 600 # 专注状态最多持续10分钟 async with self._lock: - # 1. 筛选出所有 ABSENT 状态的 *群聊* 子心流 - absent_group_subflows = [ - hf - for hf in self.subheartflows.values() - if hf.chat_state.chat_status == ChatState.ABSENT and hf.is_group_chat + # 筛选出所有FOCUSED状态的子心流 + focused_subflows = [ + hf for hf in self.subheartflows.values() + if hf.chat_state.chat_status == ChatState.FOCUSED ] - if not absent_group_subflows: - # logger.debug("没有摸鱼的群聊子心流可以评估。") # 日志太频繁 - return # 没有目标,直接返回 - - # 2. 随机选一个幸运儿 - sub_hf_to_evaluate = random.choice(absent_group_subflows) - flow_id = sub_hf_to_evaluate.subheartflow_id - stream_name = chat_manager.get_stream_name(flow_id) or flow_id - log_prefix = f"[{stream_name}]" - - # 3. 检查 CHAT 上限 - current_chat_count = self.count_subflows_by_state_nolock(ChatState.CHAT) - if current_chat_count >= chat_limit: - logger.info(f"{log_prefix} 想看看能不能聊,但是聊天太多了, ({current_chat_count}/{chat_limit}) 满了。") - return # 满了,这次就算了 - - # --- 获取 FOCUSED 计数 --- - current_focused_count = self.count_subflows_by_state_nolock(ChatState.FOCUSED) - focused_limit = current_mai_state.get_focused_chat_max_num() - - # --- 新增:获取聊天和专注群名 --- - chatting_group_names = [] - focused_group_names = [] - for flow_id, hf in self.subheartflows.items(): - stream_name = chat_manager.get_stream_name(flow_id) or str(flow_id) # 保证有名字 - if hf.chat_state.chat_status == ChatState.CHAT: - chatting_group_names.append(stream_name) - elif hf.chat_state.chat_status == ChatState.FOCUSED: - focused_group_names.append(stream_name) - # --- 结束新增 --- - - # --- 获取观察信息和构建 Prompt --- - first_observation = sub_hf_to_evaluate.observations[0] # 喵~第一个观察者肯定存在的说 - await first_observation.observe() - current_chat_log = first_observation.talking_message_str or "当前没啥聊天内容。" - _observation_summary = f"在[{stream_name}]这个群中,你最近看群友聊了这些:\n{current_chat_log}" - - _mai_state_description = f"你当前状态: {current_mai_state.value}。" - individuality = Individuality.get_instance() - personality_prompt = individuality.get_prompt(x_person=2, level=2) - prompt_personality = f"你正在扮演名为{individuality.name}的人类,{personality_prompt}" - - # --- 修改:在 prompt 中加入当前聊天计数和群名信息 (条件显示) --- - chat_status_lines = [] - if chatting_group_names: - chat_status_lines.append( - f"正在这些群闲聊 ({current_chat_count}/{chat_limit}): {', '.join(chatting_group_names)}" - ) - if focused_group_names: - chat_status_lines.append( - f"正在这些群专注的聊天 ({current_focused_count}/{focused_limit}): {', '.join(focused_group_names)}" - ) - - chat_status_prompt = "当前没有在任何群聊中。" # 默认消息喵~ - if chat_status_lines: - chat_status_prompt = "当前聊天情况,你已经参与了下面这几个群的聊天:\n" + "\n".join( - chat_status_lines - ) # 拼接状态信息 - - prompt = ( - f"{prompt_personality}\n" - f"{chat_status_prompt}\n" # <-- 喵!用了新的状态信息~ - f"你当前尚未加入 [{stream_name}] 群聊天。\n" - f"{_observation_summary}\n---\n" - f"基于以上信息,你想不想开始在这个群闲聊?\n" - f"请说明理由,并以 JSON 格式回答,包含 'decision' (布尔值) 和 'reason' (字符串)。\n" - f'例如:{{"decision": true, "reason": "看起来挺热闹的,插个话"}}\n' - f'例如:{{"decision": false, "reason": "已经聊了好多,休息一下"}}\n' - f"请只输出有效的 JSON 对象。" - ) - # --- 结束修改 --- - - # --- 4. LLM 评估是否想聊 --- - yao_kai_shi_liao_ma, reason = await self._llm_evaluate_state_transition(prompt) - - if reason: - if yao_kai_shi_liao_ma: - logger.info(f"{log_prefix} 打算开始聊,原因是: {reason}") - else: - logger.info(f"{log_prefix} 不打算聊,原因是: {reason}") - else: - logger.info(f"{log_prefix} 结果: {yao_kai_shi_liao_ma}") - - if yao_kai_shi_liao_ma is None: - logger.debug(f"{log_prefix} 问AI想不想聊失败了,这次算了。") - return # 评估失败,结束 - - if not yao_kai_shi_liao_ma: - # logger.info(f"{log_prefix} 现在不想聊这个群。") - return # 不想聊,结束 - - # --- 5. AI想聊,再次检查额度并尝试转换 --- - # 再次检查以防万一 - current_chat_count_before_change = self.count_subflows_by_state_nolock(ChatState.CHAT) - if current_chat_count_before_change < chat_limit: - logger.info( - f"{log_prefix} 想聊,而且还有精力 ({current_chat_count_before_change}/{chat_limit}),这就去聊!" - ) - await sub_hf_to_evaluate.change_chat_state(ChatState.CHAT) - # 确认转换成功 - if sub_hf_to_evaluate.chat_state.chat_status == ChatState.CHAT: - logger.debug(f"{log_prefix} 成功进入聊天状态!本次评估圆满结束。") - else: - logger.warning( - f"{log_prefix} 奇怪,尝试进入聊天状态失败了。当前状态: {sub_hf_to_evaluate.chat_state.chat_status.value}" - ) - else: - logger.warning( - f"{log_prefix} AI说想聊,但是刚问完就没空位了 ({current_chat_count_before_change}/{chat_limit})。真不巧,下次再说吧。" - ) - # 无论转换成功与否,本次评估都结束了 - - # 锁在这里自动释放 - - # --- 新增:单独检查 CHAT 状态超时的任务 --- - async def sbhf_chat_into_absent(self): - """定期检查处于 CHAT 状态的子心流是否因长时间未发言而超时,并将其转为 ABSENT。""" - log_prefix_task = "[聊天超时检查]" - transitioned_to_absent = 0 - checked_count = 0 - - async with self._lock: - subflows_snapshot = list(self.subheartflows.values()) - checked_count = len(subflows_snapshot) - - if not subflows_snapshot: + if not focused_subflows: return - for sub_hf in subflows_snapshot: - # 只检查 CHAT 状态的子心流 - if sub_hf.chat_state.chat_status != ChatState.CHAT: - continue - + for sub_hf in focused_subflows: flow_id = sub_hf.subheartflow_id stream_name = chat_manager.get_stream_name(flow_id) or flow_id - log_prefix = f"[{stream_name}]({log_prefix_task})" + log_prefix = f"[{stream_name}]" - should_deactivate = False - reason = "" + # 检查持续时间 + sub_hf.update_last_chat_state_time() + time_in_state = sub_hf.chat_state_last_time + # 10%概率随机转回CHAT,或者超过时间限制 + if time_in_state > focus_time_limit or random.random() < 0.2: + logger.info(f"{log_prefix} {'超过时间限制' if time_in_state > focus_time_limit else '随机'}从专注水群转为普通聊天") + await sub_hf.change_chat_state(ChatState.CHAT) + + # --- 新增:处理 HFC 无回复回调的专用方法 --- # + async def _handle_hfc_no_reply(self, subheartflow_id: Any): + """处理来自 HeartFChatting 的连续无回复信号 (通过 partial 绑定 ID)""" + logger.debug(f"[管理器 HFC 处理器] 接收到来自 {subheartflow_id} 的 HFC 无回复信号") + await self.sbhf_focus_into_chat_single(subheartflow_id) + + # --- 专用于处理单个子心流从FOCUSED转为CHAT的方法 --- # + async def sbhf_focus_into_chat_single(self, subflow_id: Any): + """将特定的FOCUSED状态子心流转为CHAT状态""" + async with self._lock: + subflow = self.subheartflows.get(subflow_id) + if not subflow: + logger.warning(f"[状态转换请求] 尝试转换不存在的子心流 {subflow_id}") + return + + stream_name = chat_manager.get_stream_name(subflow_id) or subflow_id + current_state = subflow.chat_state.chat_status + + if current_state == ChatState.FOCUSED: + logger.info(f"[状态转换请求] 将 {stream_name} (当前: {current_state.value}) 转换为 CHAT") try: - last_bot_dong_zuo_time = sub_hf.get_normal_chat_last_speak_time() - - if last_bot_dong_zuo_time > 0: - current_time = time.time() - time_since_last_bb = current_time - last_bot_dong_zuo_time - minutes_since_last_bb = time_since_last_bb / 60 - - # 60分钟强制退出 - if minutes_since_last_bb >= 60: - should_deactivate = True - reason = "超过60分钟未发言,强制退出" - else: - # 根据时间区间确定退出概率 - exit_probability = 0 - if minutes_since_last_bb < 5: - exit_probability = 0.01 # 1% - elif minutes_since_last_bb < 15: - exit_probability = 0.02 # 2% - elif minutes_since_last_bb < 30: - exit_probability = 0.04 # 4% - else: - exit_probability = 0.08 # 8% - - # 随机判断是否退出 - if random.random() < exit_probability: - should_deactivate = True - reason = f"已{minutes_since_last_bb:.1f}分钟未发言,触发{exit_probability * 100:.0f}%退出概率" - - except AttributeError: - logger.error( - f"{log_prefix} 无法获取 Bot 最后 BB 时间,请确保 SubHeartflow 相关实现正确。跳过超时检查。" - ) - except Exception as e: - logger.error(f"{log_prefix} 检查 Bot 超时状态时出错: {e}", exc_info=True) - - # 执行状态转换(如果超时) - if should_deactivate: - logger.debug(f"{log_prefix} 因超时 ({reason}),尝试转换为 ABSENT 状态。") - await sub_hf.change_chat_state(ChatState.ABSENT) - # 再次检查确保状态已改变 - if sub_hf.chat_state.chat_status == ChatState.ABSENT: - transitioned_to_absent += 1 - logger.info(f"{log_prefix} 不看了。") + # 从HFC到CHAT时,清空兴趣字典 + subflow.clear_interest_dict() + await subflow.change_chat_state(ChatState.CHAT) + final_state = subflow.chat_state.chat_status + if final_state == ChatState.CHAT: + logger.debug(f"[状态转换请求] {stream_name} 状态已成功转换为 {final_state.value}") else: - logger.warning(f"{log_prefix} 尝试因超时转换为 ABSENT 失败。") - - if transitioned_to_absent > 0: - logger.debug( - f"{log_prefix_task} 完成,共检查 {checked_count} 个子心流,{transitioned_to_absent} 个因超时转为 ABSENT。" - ) - - # --- 结束新增 --- - - async def _llm_evaluate_state_transition(self, prompt: str) -> Tuple[Optional[bool], Optional[str]]: - """ - 使用 LLM 评估是否应进行状态转换,期望 LLM 返回 JSON 格式。 - - Args: - prompt: 提供给 LLM 的提示信息,要求返回 {"decision": true/false}。 - - Returns: - Optional[bool]: 如果成功解析 LLM 的 JSON 响应并提取了 'decision' 键的值,则返回该布尔值。 - 如果 LLM 调用失败、返回无效 JSON 或 JSON 中缺少 'decision' 键或其值不是布尔型,则返回 None。 - """ - log_prefix = "[LLM状态评估]" - try: - # --- 真实的 LLM 调用 --- - response_text, _ = await self.llm_state_evaluator.generate_response_async(prompt) - # logger.debug(f"{log_prefix} 使用模型 {self.llm_state_evaluator.model_name} 评估") - logger.debug(f"{log_prefix} 原始输入: {prompt}") - logger.debug(f"{log_prefix} 原始评估结果: {response_text}") - - # --- 解析 JSON 响应 --- - try: - # 尝试去除可能的Markdown代码块标记 - cleaned_response = response_text.strip().strip("`").strip() - if cleaned_response.startswith("json"): - cleaned_response = cleaned_response[4:].strip() - - data = json.loads(cleaned_response) - decision = data.get("decision") # 使用 .get() 避免 KeyError - reason = data.get("reason") - - if isinstance(decision, bool): - logger.debug(f"{log_prefix} LLM评估结果 (来自JSON): {'建议转换' if decision else '建议不转换'}") - - return decision, reason - else: - logger.warning( - f"{log_prefix} LLM 返回的 JSON 中 'decision' 键的值不是布尔型: {decision}。响应: {response_text}" + logger.warning( + f"[状态转换请求] 尝试将 {stream_name} 转换为 CHAT 后,状态实际为 {final_state.value}" + ) + except Exception as e: + logger.error( + f"[状态转换请求] 转换 {stream_name} 到 CHAT 时出错: {e}", exc_info=True ) - return None, None # 值类型不正确 - - except json.JSONDecodeError as json_err: - logger.warning(f"{log_prefix} LLM 返回的响应不是有效的 JSON: {json_err}。响应: {response_text}") - # 尝试在非JSON响应中查找关键词作为后备方案 (可选) - if "true" in response_text.lower(): - logger.debug(f"{log_prefix} 在非JSON响应中找到 'true',解释为建议转换") - return True, None - if "false" in response_text.lower(): - logger.debug(f"{log_prefix} 在非JSON响应中找到 'false',解释为建议不转换") - return False, None - return None, None # JSON 解析失败,也未找到关键词 - except Exception as parse_err: # 捕获其他可能的解析错误 - logger.warning(f"{log_prefix} 解析 LLM JSON 响应时发生意外错误: {parse_err}。响应: {response_text}") - return None, None - - except Exception as e: - logger.error(f"{log_prefix} 调用 LLM 或处理其响应时出错: {e}", exc_info=True) - traceback.print_exc() - return None, None # LLM 调用或处理失败 + else: + logger.debug(f"[状态转换请求] {stream_name} 已处于 CHAT 状态,无需转换") def count_subflows_by_state(self, state: ChatState) -> int: """统计指定状态的子心流数量""" @@ -637,12 +278,10 @@ class SubHeartflowManager: return count def get_active_subflow_minds(self) -> List[str]: - """获取所有活跃(非ABSENT)子心流的当前想法""" + """获取所有子心流的当前想法""" minds = [] for subheartflow in self.subheartflows.values(): - # 检查子心流是否活跃(非ABSENT状态) - if subheartflow.chat_state.chat_status != ChatState.ABSENT: - minds.append(subheartflow.sub_mind.current_mind) + minds.append(subheartflow.sub_mind.current_mind) return minds def update_main_mind_in_subflows(self, main_mind: str): @@ -668,182 +307,3 @@ class SubHeartflowManager: logger.error(f"删除 SubHeartflow {subheartflow_id} 时出错: {e}", exc_info=True) else: logger.warning(f"尝试删除不存在的 SubHeartflow: {subheartflow_id}") - - # --- 新增:处理 HFC 无回复回调的专用方法 --- # - async def _handle_hfc_no_reply(self, subheartflow_id: Any): - """处理来自 HeartFChatting 的连续无回复信号 (通过 partial 绑定 ID)""" - # 注意:这里不需要再获取锁,因为 sbhf_focus_into_absent 内部会处理锁 - logger.debug(f"[管理器 HFC 处理器] 接收到来自 {subheartflow_id} 的 HFC 无回复信号") - await self.sbhf_focus_into_absent_or_chat(subheartflow_id) - - # --- 结束新增 --- # - - # --- 新增:处理来自 HeartFChatting 的状态转换请求 --- # - async def sbhf_focus_into_absent_or_chat(self, subflow_id: Any): - """ - 接收来自 HeartFChatting 的请求,将特定子心流的状态转换为 ABSENT 或 CHAT。 - 通常在连续多次 "no_reply" 后被调用。 - 对于私聊,总是转换为 ABSENT。 - 对于群聊,随机决定转换为 ABSENT 或 CHAT (如果 CHAT 未达上限)。 - - Args: - subflow_id: 需要转换状态的子心流 ID。 - """ - async with self._lock: - subflow = self.subheartflows.get(subflow_id) - if not subflow: - logger.warning(f"[状态转换请求] 尝试转换不存在的子心流 {subflow_id} 到 ABSENT/CHAT") - return - - stream_name = chat_manager.get_stream_name(subflow_id) or subflow_id - current_state = subflow.chat_state.chat_status - - if current_state == ChatState.FOCUSED: - target_state = ChatState.ABSENT # Default target - log_reason = "默认转换 (私聊或群聊)" - - # --- Modify logic based on chat type --- # - if subflow.is_group_chat: - # Group chat: Decide between ABSENT or CHAT - if random.random() < 0.5: # 50% chance to try CHAT - current_mai_state = self.mai_state_info.get_current_state() - chat_limit = current_mai_state.get_normal_chat_max_num() - current_chat_count = self.count_subflows_by_state_nolock(ChatState.CHAT) - - if current_chat_count < chat_limit: - target_state = ChatState.CHAT - log_reason = f"群聊随机选择 CHAT (当前 {current_chat_count}/{chat_limit})" - else: - target_state = ChatState.ABSENT # Fallback to ABSENT if CHAT limit reached - log_reason = ( - f"群聊随机选择 CHAT 但已达上限 ({current_chat_count}/{chat_limit}),转为 ABSENT" - ) - else: # 50% chance to go directly to ABSENT - target_state = ChatState.ABSENT - log_reason = "群聊随机选择 ABSENT" - else: - # Private chat: Always go to ABSENT - target_state = ChatState.ABSENT - log_reason = "私聊退出 FOCUSED,转为 ABSENT" - # --- End modification --- # - - logger.info( - f"[状态转换请求] 接收到请求,将 {stream_name} (当前: {current_state.value}) 尝试转换为 {target_state.value} ({log_reason})" - ) - try: - # 从HFC到CHAT时,清空兴趣字典 - subflow.clear_interest_dict() - await subflow.change_chat_state(target_state) - final_state = subflow.chat_state.chat_status - if final_state == target_state: - logger.debug(f"[状态转换请求] {stream_name} 状态已成功转换为 {final_state.value}") - else: - logger.warning( - f"[状态转换请求] 尝试将 {stream_name} 转换为 {target_state.value} 后,状态实际为 {final_state.value}" - ) - except Exception as e: - logger.error( - f"[状态转换请求] 转换 {stream_name} 到 {target_state.value} 时出错: {e}", exc_info=True - ) - elif current_state == ChatState.ABSENT: - logger.debug(f"[状态转换请求] {stream_name} 已处于 ABSENT 状态,无需转换") - else: - logger.warning( - f"[状态转换请求] 收到对 {stream_name} 的请求,但其状态为 {current_state.value} (非 FOCUSED),不执行转换" - ) - - # --- 结束新增 --- # - - # --- 新增:处理私聊从 ABSENT 直接到 FOCUSED 的逻辑 --- # - async def sbhf_absent_private_into_focus(self): - """检查 ABSENT 状态的私聊子心流是否有新活动,若有且未达 FOCUSED 上限,则直接转换为 FOCUSED。""" - log_prefix_task = "[私聊激活检查]" - transitioned_count = 0 - checked_count = 0 - - # --- 获取当前状态和 FOCUSED 上限 --- # - current_mai_state = self.mai_state_info.get_current_state() - focused_limit = current_mai_state.get_focused_chat_max_num() - - # --- 检查是否允许 FOCUS 模式 --- # - if not global_config.allow_focus_mode: - # Log less frequently to avoid spam - # if int(time.time()) % 60 == 0: - # logger.debug(f"{log_prefix_task} 配置不允许进入 FOCUSED 状态") - return - - if focused_limit <= 0: - # logger.debug(f"{log_prefix_task} 当前状态 ({current_mai_state.value}) 不允许 FOCUSED 子心流") - return - - async with self._lock: - # --- 获取当前 FOCUSED 计数 (不上锁版本) --- # - current_focused_count = self.count_subflows_by_state_nolock(ChatState.FOCUSED) - - # --- 筛选出所有 ABSENT 状态的私聊子心流 --- # - eligible_subflows = [ - hf - for hf in self.subheartflows.values() - if hf.chat_state.chat_status == ChatState.ABSENT and not hf.is_group_chat - ] - checked_count = len(eligible_subflows) - - if not eligible_subflows: - # logger.debug(f"{log_prefix_task} 没有 ABSENT 状态的私聊子心流可以评估。") - return - - # --- 遍历评估每个符合条件的私聊 --- # - for sub_hf in eligible_subflows: - # --- 再次检查 FOCUSED 上限,因为可能有多个同时激活 --- # - if current_focused_count >= focused_limit: - logger.debug( - f"{log_prefix_task} 已达专注上限 ({current_focused_count}/{focused_limit}),停止检查后续私聊。" - ) - break # 已满,无需再检查其他私聊 - - flow_id = sub_hf.subheartflow_id - stream_name = chat_manager.get_stream_name(flow_id) or flow_id - log_prefix = f"[{stream_name}]({log_prefix_task})" - - try: - # --- 检查是否有新活动 --- # - observation = sub_hf._get_primary_observation() # 获取主要观察者 - is_active = False - if observation: - # 检查自上次状态变为 ABSENT 后是否有新消息 - # 使用 chat_state_changed_time 可能更精确 - # 加一点点缓冲时间(例如 1 秒)以防时间戳完全相等 - timestamp_to_check = sub_hf.chat_state_changed_time - 1 - has_new = await observation.has_new_messages_since(timestamp_to_check) - if has_new: - is_active = True - logger.debug(f"{log_prefix} 检测到新消息,标记为活跃。") - else: - logger.warning(f"{log_prefix} 无法获取主要观察者来检查活动状态。") - - # --- 如果活跃且未达上限,则尝试转换 --- # - if is_active: - logger.info( - f"{log_prefix} 检测到活跃且未达专注上限 ({current_focused_count}/{focused_limit}),尝试转换为 FOCUSED。" - ) - await sub_hf.change_chat_state(ChatState.FOCUSED) - # 确认转换成功 - if sub_hf.chat_state.chat_status == ChatState.FOCUSED: - transitioned_count += 1 - current_focused_count += 1 # 更新计数器以供本轮后续检查 - logger.info(f"{log_prefix} 成功进入 FOCUSED 状态。") - else: - logger.warning( - f"{log_prefix} 尝试进入 FOCUSED 状态失败。当前状态: {sub_hf.chat_state.chat_status.value}" - ) - # else: # 不活跃,无需操作 - # logger.debug(f"{log_prefix} 未检测到新活动,保持 ABSENT。") - - except Exception as e: - logger.error(f"{log_prefix} 检查私聊活动或转换状态时出错: {e}", exc_info=True) - - # --- 循环结束后记录总结日志 --- # - if transitioned_count > 0: - logger.debug( - f"{log_prefix_task} 完成,共检查 {checked_count} 个私聊,{transitioned_count} 个转换为 FOCUSED。" - ) diff --git a/src/main.py b/src/main.py index be71524e..02d061c2 100644 --- a/src/main.py +++ b/src/main.py @@ -2,7 +2,6 @@ import asyncio import time from .plugins.utils.statistic import LLMStatistics from .plugins.moods.moods import MoodManager -from .plugins.schedule.schedule_generator import bot_schedule from .plugins.emoji_system.emoji_manager import emoji_manager from .plugins.person_info.person_info import person_info_manager from .plugins.willing.willing_manager import willing_manager @@ -81,14 +80,7 @@ class MainSystem: self.hippocampus_manager.initialize(global_config=global_config) # await asyncio.sleep(0.5) #防止logger输出飞了 - # 初始化日程 - bot_schedule.initialize( - name=global_config.BOT_NICKNAME, - personality=global_config.personality_core, - behavior=global_config.PROMPT_SCHEDULE_GEN, - interval=global_config.SCHEDULE_DOING_UPDATE_INTERVAL, - ) - asyncio.create_task(bot_schedule.mai_schedule_start()) + # 将bot.py中的chat_bot.message_process消息处理函数注册到api.py的消息处理基类中 self.app.register_message_handler(chat_bot.message_process) diff --git a/src/plugins/__init__.py b/src/plugins/__init__.py index 2e057e6f..d8f141d6 100644 --- a/src/plugins/__init__.py +++ b/src/plugins/__init__.py @@ -8,7 +8,6 @@ from .emoji_system.emoji_manager import emoji_manager from .person_info.relationship_manager import relationship_manager from .moods.moods import MoodManager from .willing.willing_manager import willing_manager -from .schedule.schedule_generator import bot_schedule # 导出主要组件供外部使用 __all__ = [ @@ -17,5 +16,4 @@ __all__ = [ "relationship_manager", "MoodManager", "willing_manager", - "bot_schedule", ] diff --git a/src/plugins/heartFC_chat/heartFC_chat.py b/src/plugins/heartFC_chat/heartFC_chat.py index b594bf02..ff34a719 100644 --- a/src/plugins/heartFC_chat/heartFC_chat.py +++ b/src/plugins/heartFC_chat/heartFC_chat.py @@ -32,7 +32,7 @@ from rich.traceback import install install(extra_lines=3) -WAITING_TIME_THRESHOLD = 300 # 等待新消息时间阈值,单位秒 +WAITING_TIME_THRESHOLD = 200 # 等待新消息时间阈值,单位秒 EMOJI_SEND_PRO = 0.3 # 设置一个概率,比如 30% 才真的发 @@ -667,7 +667,7 @@ class HeartFChatting: ) # 检查是否同时达到次数和时间阈值 - time_threshold = 0.66 * WAITING_TIME_THRESHOLD * CONSECUTIVE_NO_REPLY_THRESHOLD + time_threshold = 0.5 * WAITING_TIME_THRESHOLD * CONSECUTIVE_NO_REPLY_THRESHOLD if ( self._lian_xu_bu_hui_fu_ci_shu >= CONSECUTIVE_NO_REPLY_THRESHOLD and self._lian_xu_deng_dai_shi_jian >= time_threshold diff --git a/src/plugins/heartFC_chat/heartFC_chatting_logic.md b/src/plugins/heartFC_chat/heartFC_chatting_logic.md index 6d51c978..181680ff 100644 --- a/src/plugins/heartFC_chat/heartFC_chatting_logic.md +++ b/src/plugins/heartFC_chat/heartFC_chatting_logic.md @@ -1,6 +1,6 @@ # HeartFChatting 逻辑详解 -`HeartFChatting` 类是心流系统(Heart Flow System)中实现**专注聊天**(`ChatState.FOCUSED`)功能的核心。顾名思义,其职责乃是在特定聊天流(`stream_id`)中,模拟更为连贯深入之对话。此非凭空臆造,而是依赖一个持续不断的 **思考(Think)-规划(Plan)-执行(Execute)** 循环。当其所系的 `SubHeartflow` 进入 `FOCUSED` 状态时,便会创建并启动 `HeartFChatting` 实例;若状态转为他途(譬如 `CHAT` 或 `ABSENT`),则会将其关闭。 +`HeartFChatting` 类是心流系统(Heart Flow System)中实现**专注水群**(`ChatState.FOCUSED`)功能的核心。顾名思义,其职责乃是在特定聊天流(`stream_id`)中,模拟更为连贯深入之对话。此非凭空臆造,而是依赖一个持续不断的 **思考(Think)-规划(Plan)-执行(Execute)** 循环。当其所系的 `SubHeartflow` 进入 `FOCUSED` 状态时,便会创建并启动 `HeartFChatting` 实例;若状态转为他途(譬如 `CHAT` 或 `ABSENT`),则会将其关闭。 ## 1. 初始化简述 (`__init__`, `_initialize`) @@ -89,4 +89,4 @@ ## 总结 -`HeartFChatting` 通过 **观察 -> 思考(含工具)-> 规划 -> 执行** 的闭环,并利用 `CycleInfo` 进行上下文传递,实现了更加智能和连贯的专注聊天行为。其核心在于利用 `SubMind` 进行深度思考和信息收集,再通过 LLM 规划器进行决策,最后由 `HeartFCSender` 可靠地执行消息发送任务。 +`HeartFChatting` 通过 **观察 -> 思考(含工具)-> 规划 -> 执行** 的闭环,并利用 `CycleInfo` 进行上下文传递,实现了更加智能和连贯的专注水群行为。其核心在于利用 `SubMind` 进行深度思考和信息收集,再通过 LLM 规划器进行决策,最后由 `HeartFCSender` 可靠地执行消息发送任务。 diff --git a/src/plugins/heartFC_chat/heartflow_prompt_builder.py b/src/plugins/heartFC_chat/heartflow_prompt_builder.py index c59168a7..24aaf704 100644 --- a/src/plugins/heartFC_chat/heartflow_prompt_builder.py +++ b/src/plugins/heartFC_chat/heartflow_prompt_builder.py @@ -12,7 +12,6 @@ from ...common.database import db from ..chat.utils import get_recent_group_speaker from ..moods.moods import MoodManager from ..memory_system.Hippocampus import HippocampusManager -from ..schedule.schedule_generator import bot_schedule from ..knowledge.knowledge_lib import qa_manager import traceback from .heartFC_Cycleinfo import CycleInfo @@ -117,7 +116,6 @@ JSON 结构如下,包含三个字段 "action", "reasoning", "emoji_query": {memory_prompt} {relation_prompt} {prompt_info} -{schedule_prompt} {chat_target} {chat_talking_prompt} 现在"{sender_name}"说的:{message_txt}。引起了你的注意,你想要在群里发言或者回复这条消息。\n @@ -135,7 +133,6 @@ JSON 结构如下,包含三个字段 "action", "reasoning", "emoji_query": "你回忆起:{related_memory_info}。\n以上是你的回忆,不一定是目前聊天里的人说的,也不一定是现在发生的事情,请记住。\n", "memory_prompt", ) - Prompt("你现在正在做的事情是:{schedule_info}", "schedule_prompt") Prompt("\n你有以下这些**知识**:\n{prompt_info}\n请你**记住上面的知识**,之后可能会用到。\n", "knowledge_prompt") # --- Template for HeartFChatting (FOCUSED mode) --- @@ -166,7 +163,6 @@ JSON 结构如下,包含三个字段 "action", "reasoning", "emoji_query": {memory_prompt} {relation_prompt} {prompt_info} -{schedule_prompt} 你正在和 {sender_name} 私聊。 聊天记录如下: {chat_talking_prompt} @@ -426,13 +422,6 @@ class PromptBuilder: end_time = time.time() logger.debug(f"知识检索耗时: {(end_time - start_time):.3f}秒") - if global_config.ENABLE_SCHEDULE_GEN: - schedule_prompt = await global_prompt_manager.format_prompt( - "schedule_prompt", schedule_info=bot_schedule.get_current_num_task(num=1, time_info=False) - ) - else: - schedule_prompt = "" - logger.debug("开始构建 normal prompt") # --- Choose template and format based on chat type --- @@ -448,7 +437,6 @@ class PromptBuilder: sender_name=effective_sender_name, memory_prompt=memory_prompt, prompt_info=prompt_info, - schedule_prompt=schedule_prompt, chat_target=chat_target_1, chat_target_2=chat_target_2, chat_talking_prompt=chat_talking_prompt, @@ -473,7 +461,6 @@ class PromptBuilder: sender_name=effective_sender_name, memory_prompt=memory_prompt, prompt_info=prompt_info, - schedule_prompt=schedule_prompt, chat_talking_prompt=chat_talking_prompt, message_txt=message_txt, bot_name=global_config.BOT_NICKNAME, diff --git a/src/plugins/heartFC_chat/normal_chat.py b/src/plugins/heartFC_chat/normal_chat.py index 7e156942..a72d1b55 100644 --- a/src/plugins/heartFC_chat/normal_chat.py +++ b/src/plugins/heartFC_chat/normal_chat.py @@ -22,7 +22,7 @@ from src.plugins.utils.timer_calculator import Timer from src.heart_flow.utils_chat import get_chat_type_and_target_info -logger = get_logger("chat") +logger = get_logger("normal_chat") class NormalChat: @@ -201,12 +201,10 @@ class NormalChat: break # 获取待处理消息列表 - if self.interest_dict: - items_to_process = list(self.interest_dict.items()) - if not items_to_process: - continue - else: - return + + items_to_process = list(self.interest_dict.items()) + if not items_to_process: + continue # 处理每条兴趣消息 for msg_id, (message, interest_value, is_mentioned) in items_to_process: @@ -489,7 +487,7 @@ class NormalChat: finally: if self._chat_task is task: self._chat_task = None - logger.debug(f"[{self.stream_name}] 任务清理完成") + logger.debug(f"[{self.stream_name}] Normal_chat 结束") # 改为实例方法, 移除 stream_id 参数 async def stop_chat(self): diff --git a/src/plugins/models/utils_model.py b/src/plugins/models/utils_model.py index 8ee21956..18c9c737 100644 --- a/src/plugins/models/utils_model.py +++ b/src/plugins/models/utils_model.py @@ -157,7 +157,7 @@ class LLMRequest: completion_tokens: 输出token数 total_tokens: 总token数 user_id: 用户ID,默认为system - request_type: 请求类型(chat/embedding/image/topic/schedule) + request_type: 请求类型 endpoint: API端点 """ # 如果 request_type 为 None,则使用实例变量中的值 diff --git a/src/plugins/schedule/schedule_generator.py b/src/plugins/schedule/schedule_generator.py deleted file mode 100644 index 6bd2e587..00000000 --- a/src/plugins/schedule/schedule_generator.py +++ /dev/null @@ -1,307 +0,0 @@ -import datetime -import os -import sys -import asyncio -from dateutil import tz - -# 添加项目根目录到 Python 路径 -root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../..")) -sys.path.append(root_path) - -from src.common.database import db # noqa: E402 -from src.common.logger import get_module_logger, SCHEDULE_STYLE_CONFIG, LogConfig # noqa: E402 -from src.plugins.models.utils_model import LLMRequest # noqa: E402 -from src.config.config import global_config # noqa: E402 - -TIME_ZONE = tz.gettz(global_config.TIME_ZONE) # 设置时区 - - -schedule_config = LogConfig( - # 使用海马体专用样式 - console_format=SCHEDULE_STYLE_CONFIG["console_format"], - file_format=SCHEDULE_STYLE_CONFIG["file_format"], -) -logger = get_module_logger("scheduler", config=schedule_config) - - -class ScheduleGenerator: - # enable_output: bool = True - - def __init__(self): - # 使用离线LLM模型 - self.enable_output = None - self.llm_scheduler_all = LLMRequest( - model=global_config.llm_reasoning, - temperature=global_config.SCHEDULE_TEMPERATURE + 0.3, - max_tokens=7000, - request_type="schedule", - ) - self.llm_scheduler_doing = LLMRequest( - model=global_config.llm_normal, - temperature=global_config.SCHEDULE_TEMPERATURE, - max_tokens=2048, - request_type="schedule", - ) - - self.today_schedule_text = "" - self.today_done_list = [] - - self.yesterday_schedule_text = "" - self.yesterday_done_list = [] - - self.name = "" - self.personality = "" - self.behavior = "" - - self.start_time = datetime.datetime.now(TIME_ZONE) - - self.schedule_doing_update_interval = 300 # 最好大于60 - - def initialize( - self, - name: str = "bot_name", - personality: str = "你是一个爱国爱党的新时代青年", - behavior: str = "你非常外向,喜欢尝试新事物和人交流", - interval: int = 60, - ): - """初始化日程系统""" - self.name = name - self.behavior = behavior - self.schedule_doing_update_interval = interval - self.personality = personality - - async def mai_schedule_start(self): - """启动日程系统,每5分钟执行一次move_doing,并在日期变化时重新检查日程""" - try: - if global_config.ENABLE_SCHEDULE_GEN: - logger.info(f"日程系统启动/刷新时间: {self.start_time.strftime('%Y-%m-%d %H:%M:%S')}") - # 初始化日程 - await self.check_and_create_today_schedule() - # self.print_schedule() - - while True: - # print(self.get_current_num_task(1, True)) - - current_time = datetime.datetime.now(TIME_ZONE) - - # 检查是否需要重新生成日程(日期变化) - if current_time.date() != self.start_time.date(): - logger.info("检测到日期变化,重新生成日程") - self.start_time = current_time - await self.check_and_create_today_schedule() - # self.print_schedule() - - # 执行当前活动 - # mind_thinking = heartflow.current_state.current_mind - - await self.move_doing() - - await asyncio.sleep(self.schedule_doing_update_interval) - else: - logger.info("日程系统未启用") - - except Exception as e: - logger.error(f"日程系统运行时出错: {str(e)}") - logger.exception("详细错误信息:") - - async def check_and_create_today_schedule(self): - """检查昨天的日程,并确保今天有日程安排 - - Returns: - tuple: (today_schedule_text, today_schedule) 今天的日程文本和解析后的日程字典 - """ - today = datetime.datetime.now(TIME_ZONE) - yesterday = today - datetime.timedelta(days=1) - - # 先检查昨天的日程 - self.yesterday_schedule_text, self.yesterday_done_list = self.load_schedule_from_db(yesterday) - if self.yesterday_schedule_text: - logger.debug(f"已加载{yesterday.strftime('%Y-%m-%d')}的日程") - - # 检查今天的日程 - self.today_schedule_text, self.today_done_list = self.load_schedule_from_db(today) - if not self.today_done_list: - self.today_done_list = [] - if not self.today_schedule_text: - logger.info(f"{today.strftime('%Y-%m-%d')}的日程不存在,准备生成新的日程") - try: - self.today_schedule_text = await self.generate_daily_schedule(target_date=today) - except Exception as e: - logger.error(f"生成日程时发生错误: {str(e)}") - self.today_schedule_text = "" - - self.save_today_schedule_to_db() - - def construct_daytime_prompt(self, target_date: datetime.datetime): - date_str = target_date.strftime("%Y-%m-%d") - weekday = target_date.strftime("%A") - - prompt = f"你是{self.name},{self.personality},{self.behavior}" - prompt += f"你昨天的日程是:{self.yesterday_schedule_text}\n" - prompt += f"请为你生成{date_str}({weekday}),也就是今天的日程安排,结合你的个人特点和行为习惯以及昨天的安排\n" - prompt += "推测你的日程安排,包括你一天都在做什么,从起床到睡眠,有什么发现和思考,具体一些,详细一些,需要1500字以上,精确到每半个小时,记得写明时间\n" # noqa: E501 - prompt += "直接返回你的日程,现实一点,不要浮夸,从起床到睡觉,不要输出其他内容:" - return prompt - - def construct_doing_prompt(self, time: datetime.datetime, mind_thinking: str = ""): - now_time = time.strftime("%H:%M") - previous_doings = self.get_current_num_task(5, True) - - prompt = f"你是{self.name},{self.personality},{self.behavior}" - prompt += f"你今天的日程是:{self.today_schedule_text}\n" - if previous_doings: - prompt += f"你之前做了的事情是:{previous_doings},从之前到现在已经过去了{self.schedule_doing_update_interval / 60}分钟了\n" # noqa: E501 - if mind_thinking: - prompt += f"你脑子里在想:{mind_thinking}\n" - prompt += f"现在是{now_time},结合你的个人特点和行为习惯,注意关注你今天的日程安排和想法安排你接下来做什么,现实一点,不要浮夸" - prompt += "安排你接下来做什么,具体一些,详细一些\n" - prompt += "直接返回你在做的事情,注意是当前时间,不要输出其他内容:" - return prompt - - async def generate_daily_schedule( - self, - target_date: datetime.datetime = None, - ) -> dict[str, str]: - daytime_prompt = self.construct_daytime_prompt(target_date) - daytime_response, _ = await self.llm_scheduler_all.generate_response_async(daytime_prompt) - return daytime_response - - def print_schedule(self): - """打印完整的日程安排""" - if not self.today_schedule_text: - logger.warning("今日日程有误,将在下次运行时重新生成") - db.schedule.delete_one({"date": datetime.datetime.now(TIME_ZONE).strftime("%Y-%m-%d")}) - else: - logger.info("=== 今日日程安排 ===") - logger.info(self.today_schedule_text) - logger.info("==================") - self.enable_output = False - - async def update_today_done_list(self): - # 更新数据库中的 today_done_list - today_str = datetime.datetime.now(TIME_ZONE).strftime("%Y-%m-%d") - existing_schedule = db.schedule.find_one({"date": today_str}) - - if existing_schedule: - # 更新数据库中的 today_done_list - db.schedule.update_one({"date": today_str}, {"$set": {"today_done_list": self.today_done_list}}) - logger.debug(f"已更新{today_str}的已完成活动列表") - else: - logger.warning(f"未找到{today_str}的日程记录") - - async def move_doing(self, mind_thinking: str = ""): - try: - current_time = datetime.datetime.now(TIME_ZONE) - if mind_thinking: - doing_prompt = self.construct_doing_prompt(current_time, mind_thinking) - else: - doing_prompt = self.construct_doing_prompt(current_time) - - doing_response, _ = await self.llm_scheduler_doing.generate_response_async(doing_prompt) - self.today_done_list.append((current_time, doing_response)) - - await self.update_today_done_list() - - logger.info(f"当前活动: {doing_response}") - - return doing_response - except GeneratorExit: - logger.warning("日程生成被中断") - return "日程生成被中断" - except Exception as e: - logger.error(f"生成日程时发生错误: {str(e)}") - return "生成日程时发生错误" - - async def get_task_from_time_to_time(self, start_time: str, end_time: str): - """获取指定时间范围内的任务列表 - - Args: - start_time (str): 开始时间,格式为"HH:MM" - end_time (str): 结束时间,格式为"HH:MM" - - Returns: - list: 时间范围内的任务列表 - """ - result = [] - for task in self.today_done_list: - task_time = task[0] # 获取任务的时间戳 - task_time_str = task_time.strftime("%H:%M") - - # 检查任务时间是否在指定范围内 - if self._time_diff(start_time, task_time_str) >= 0 and self._time_diff(task_time_str, end_time) >= 0: - result.append(task) - - return result - - def get_current_num_task(self, num=1, time_info=False): - """获取最新加入的指定数量的日程 - - Args: - num (int): 需要获取的日程数量,默认为1 - time_info (bool): 是否包含时间信息,默认为False - - Returns: - list: 最新加入的日程列表 - """ - if not self.today_done_list: - return [] - - # 确保num不超过列表长度 - num = min(num, len(self.today_done_list)) - pre_doings = "" - for doing in self.today_done_list[-num:]: - if time_info: - time_str = doing[0].strftime("%H:%M") - pre_doings += time_str + "时," + doing[1] + "\n" - else: - pre_doings += doing[1] + "\n" - - # 返回最新的num条日程 - return pre_doings - - def save_today_schedule_to_db(self): - """保存日程到数据库,同时初始化 today_done_list""" - date_str = datetime.datetime.now(TIME_ZONE).strftime("%Y-%m-%d") - schedule_data = { - "date": date_str, - "schedule": self.today_schedule_text, - "today_done_list": self.today_done_list if hasattr(self, "today_done_list") else [], - } - # 使用 upsert 操作,如果存在则更新,不存在则插入 - db.schedule.update_one({"date": date_str}, {"$set": schedule_data}, upsert=True) - logger.debug(f"已保存{date_str}的日程到数据库") - - @staticmethod - def load_schedule_from_db(date: datetime.datetime): - """从数据库加载日程,同时加载 today_done_list""" - date_str = date.strftime("%Y-%m-%d") - existing_schedule = db.schedule.find_one({"date": date_str}) - - if existing_schedule: - schedule_text = existing_schedule["schedule"] - return schedule_text, existing_schedule.get("today_done_list", []) - else: - logger.debug(f"{date_str}的日程不存在") - return None, None - - -async def main(): - # 使用示例 - scheduler = ScheduleGenerator() - scheduler.initialize( - name="麦麦", - personality="你叫麦麦,你19岁,是一个大二的女大学生,你有一头黑色短发,你会刷贴吧,你现在在学习心理学", - behavior="你比较内向,一般熬夜比较晚,然后第二天早上10点起床吃早午饭", - interval=60, - ) - await scheduler.mai_schedule_start() - - -# 当作为组件导入时使用的实例 -bot_schedule = ScheduleGenerator() - -if __name__ == "__main__": - import asyncio - - # 当直接运行此文件时执行 - asyncio.run(main()) diff --git a/template/bot_config_meta.toml b/template/bot_config_meta.toml deleted file mode 100644 index 459b7026..00000000 --- a/template/bot_config_meta.toml +++ /dev/null @@ -1,134 +0,0 @@ -[inner.version] -describe = "版本号" -important = true -can_edit = false - -[bot.qq] -describe = "机器人的QQ号" -important = true -can_edit = true - -[bot.nickname] -describe = "机器人的昵称" -important = true -can_edit = true - -[bot.alias_names] -describe = "机器人的别名列表,该选项还在调试中,暂时未生效" -important = false -can_edit = true - -[groups.talk_allowed] -describe = "可以回复消息的群号码列表" -important = true -can_edit = true - -[groups.talk_frequency_down] -describe = "降低回复频率的群号码列表" -important = false -can_edit = true - -[groups.ban_user_id] -describe = "禁止回复和读取消息的QQ号列表" -important = false -can_edit = true - -[personality.personality_core] -describe = "用一句话或几句话描述人格的核心特点,建议20字以内" -important = true -can_edit = true - -[personality.personality_sides] -describe = "用一句话或几句话描述人格的一些细节,条数任意,不能为0,该选项还在调试中" -important = false -can_edit = true - -[identity.identity_detail] -describe = "身份特点列表,条数任意,不能为0,该选项还在调试中" -important = false -can_edit = true - -[identity.age] -describe = "年龄,单位岁" -important = false -can_edit = true - -[identity.gender] -describe = "性别" -important = false -can_edit = true - -[identity.appearance] -describe = "外貌特征描述,该选项还在调试中,暂时未生效" -important = false -can_edit = true - -[schedule.enable_schedule_gen] -describe = "是否启用日程表" -important = false -can_edit = true - -[schedule.enable_schedule_interaction] -describe = "日程表是否影响回复模式" -important = false -can_edit = true - -[schedule.prompt_schedule_gen] -describe = "用几句话描述描述性格特点或行动规律,这个特征会用来生成日程表" -important = false -can_edit = true - -[schedule.schedule_doing_update_interval] -describe = "日程表更新间隔,单位秒" -important = false -can_edit = true - -[schedule.schedule_temperature] -describe = "日程表温度,建议0.1-0.5" -important = false -can_edit = true - -[schedule.time_zone] -describe = "时区设置,可以解决运行电脑时区和国内时区不同的情况,或者模拟国外留学生日程" -important = false -can_edit = true - -[platforms.nonebot-qq] -describe = "nonebot-qq适配器提供的链接" -important = true -can_edit = true - -[chat.allow_focus_mode] -describe = "是否允许专注聊天状态" -important = false -can_edit = true - -[chat.base_normal_chat_num] -describe = "最多允许多少个群进行普通聊天" -important = false -can_edit = true - -[chat.base_focused_chat_num] -describe = "最多允许多少个群进行专注聊天" -important = false -can_edit = true - -[chat.observation_context_size] -describe = "观察到的最长上下文大小,建议15,太短太长都会导致脑袋尖尖" -important = false -can_edit = true - -[chat.message_buffer] -describe = "启用消息缓冲器,启用此项以解决消息的拆分问题,但会使麦麦的回复延迟" -important = false -can_edit = true - -[chat.ban_words] -describe = "需要过滤的消息列表" -important = false -can_edit = true - -[chat.ban_msgs_regex] -describe = "需要过滤的消息(原始消息)匹配的正则表达式,匹配到的消息将被过滤(支持CQ码)" -important = false -can_edit = true \ No newline at end of file diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index 8eab299c..633275b7 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -1,5 +1,5 @@ [inner] -version = "1.6.1" +version = "1.6.2" #----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读---- #如果你想要修改配置文件,请在修改后将version的值进行变更 @@ -54,26 +54,16 @@ age = 20 # 年龄 单位岁 gender = "男" # 性别 appearance = "用几句话描述外貌特征" # 外貌特征 该选项还在调试中,暂时未生效 -[schedule] -enable_schedule_gen = true # 是否启用日程表 -enable_schedule_interaction = true # 日程表是否影响回复模式 -prompt_schedule_gen = "用几句话描述描述性格特点或行动规律,这个特征会用来生成日程表" -schedule_doing_update_interval = 900 # 日程表更新间隔 单位秒 -schedule_temperature = 0.1 # 日程表温度,建议0.1-0.5 -time_zone = "Asia/Shanghai" # 给你的机器人设置时区,可以解决运行电脑时区和国内时区不同的情况,或者模拟国外留学生日程 - [platforms] # 必填项目,填写每个平台适配器提供的链接 nonebot-qq="http://127.0.0.1:18002/api/message" [chat] #麦麦的聊天通用设置 -allow_focus_mode = true # 是否允许专注聊天状态 +allow_focus_mode = false # 是否允许专注水群状态 # 是否启用heart_flowC(HFC)模式 # 启用后麦麦会自主选择进入heart_flowC模式(持续一段时间),进行主动的观察和回复,并给出回复,比较消耗token -base_normal_chat_num = 8 # 最多允许多少个群进行普通聊天 -base_focused_chat_num = 5 # 最多允许多少个群进行专注聊天 observation_context_size = 15 # 观察到的最长上下文大小,建议15,太短太长都会导致脑袋尖尖 -message_buffer = true # 启用消息缓冲器?启用此项以解决消息的拆分问题,但会使麦麦的回复延迟 +message_buffer = false # 启用消息缓冲器?启用此项以解决消息的拆分问题,但会使麦麦的回复延迟 # 以下是消息过滤,可以根据规则过滤特定消息,将不会读取这些消息 ban_words = [ @@ -103,14 +93,10 @@ emoji_response_penalty = 0 # 表情包回复惩罚系数,设为0为不回复 mentioned_bot_inevitable_reply = false # 提及 bot 必然回复 at_bot_inevitable_reply = false # @bot 必然回复 -[focus_chat] #专注聊天 -reply_trigger_threshold = 3.6 # 专注聊天触发阈值,越低越容易进入专注聊天 -default_decay_rate_per_second = 0.95 # 默认衰减率,越大衰减越快,越高越难进入专注聊天 -consecutive_no_reply_threshold = 3 # 连续不回复的阈值,越低越容易结束专注聊天 - -# 以下选项暂时无效 -compressed_length = 5 # 不能大于observation_context_size,心流上下文压缩的最短压缩长度,超过心流观察到的上下文长度,会压缩,最短压缩长度为5 -compress_length_limit = 5 #最多压缩份数,超过该数值的压缩上下文会被删除 +[focus_chat] #专注水群 +reply_trigger_threshold = 4 # 专注水群触发阈值,越低越容易进入专注水群 +default_decay_rate_per_second = 0.9 # 默认衰减率,越大衰减越快,越高越难进入专注水群 +consecutive_no_reply_threshold = 3 # 连续不回复的阈值,越低越容易结束专注水群 [emoji]