better:整理config

This commit is contained in:
SengokuCola
2025-04-30 17:18:14 +08:00
parent 3ed5835937
commit b8736e4299
24 changed files with 484 additions and 462 deletions

View File

@@ -1,48 +0,0 @@
# 0.6.3 版本发布前待办事项
- [0.6.3]**统一化人格配置:**
- 检查代码中是否存在硬编码的人格相关配置。
- 将所有硬编码的人格配置替换为使用 `individual` 模块进行管理。
- [0.6.3]**在 Planner 中添加回复计数信息:**
- 修改 `HeartFlowChatInstance``Plan` 阶段逻辑。
- 将当前周期的回复计数(或其他相关统计信息)作为输入提供给 Planner。
- 目的是为 Planner 提供负反馈,减少连续回复或不当回复的可能性。
- [0.6.3]**恢复/检查被停止的功能:**
- 全面审查代码,特别是对比之前的版本或设计文档。
- 识别并重新启用那些暂时被禁用但应该恢复的功能。
- 确认没有核心功能意外丢失。
- [0.6.3]**参数提取与配置化:**
- 识别代码中散落的各种可调参数例如概率阈值、时间间隔、次数限制、LLM 模型名称等)。
- 将这些参数统一提取到模块或类的顶部。
- 最终将这些参数移至外部配置文件(如 YAML 或 JSON 文件),方便用户自定义。
- **[0.6.3]提供 HFC (HeartFlowChatInstance) 开启/关闭选项:**
- 增加一个全局或针对特定子心流的配置选项。
- 允许用户控制是否启用 `FOCUSED` 状态以及关联的 `HeartFlowChatInstance`
- 如果禁用 HFC子心流可能只会在 `ABSENT``CHAT` 状态间切换。
- [0.6.3]**添加防破线机制 (针对接收消息):**
- 在消息处理流程的早期阶段 (例如 `HeartHC_processor` 或类似模块),增加对接收到的消息文本长度的检查。
- 对超过预设长度阈值的*接收*消息进行截断处理。
- 目的是防止过长的输入(可能包含"破限"提示词影响后续的兴趣计算、LLM 回复生成等环节。
- [0.6.3]**NormalChat 模式下的记忆与 Prompt 优化:**
- 重点审视 `NormalChatInstance` (闲聊/推理模式) 中记忆调用 (例如 `HippocampusManager` 的使用) 的方式。
- 评估在该模式下引入工具调用 (Tool Calling) 机制以更结构化访问记忆的必要性。
- 优化 `NormalChatInstance` 中与记忆检索、应用相关的 Prompt。
- [0.6.3]**完善简易兴趣监控 GUI:**
- 改进现有的、用于监控聊天兴趣度 (`InterestChatting`?) 的简单 GUI 界面。
- 使其能更清晰地展示关键参数和状态,作为查看日志之外的更直观的监控方式。
- 作为完整外部 UI 开发完成前的临时替代方案。
- [0.6.3]**修复/完善中期记忆 (Midterm Memory):**
- 检查当前中期记忆模块的状态。
- 修复已知问题,使其能够稳定运行。
- (优先级视开发时间而定)
对于有些群频繁激活HFC却不回复需要处理一下

View File

@@ -106,8 +106,8 @@ c HeartFChatting工作方式
- 负责所有 `SubHeartflow` 实例的生命周期管理,包括:
- 创建和获取 (`get_or_create_subheartflow`)。
- 停止和清理 (`sleep_subheartflow`, `cleanup_inactive_subheartflows`)。
- 根据 `Heartflow` 的状态 (`self.mai_state_info`) 和限制条件,激活、停用或调整子心流的状态(例如 `enforce_subheartflow_limits`, `randomly_deactivate_subflows`, `evaluate_interest_and_promote`)。
- **新增**: 通过调用 `evaluate_and_transition_subflows_by_llm` 方法,使用 LLM (配置与 `Heartflow` 主 LLM 相同) 评估处于 `ABSENT``CHAT` 状态的子心流,根据观察到的活动摘要和 `Heartflow` 的当前状态,判断是否应在 `ABSENT``CHAT` 之间进行转换 (同样受限于 `CHAT` 状态的数量上限)。
- 根据 `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)
@@ -155,20 +155,20 @@ c HeartFChatting工作方式
- **初始状态**: 新创建的 `SubHeartflow` 默认为 `ABSENT` 状态。
- **`ABSENT` -> `CHAT` (激活闲聊)**:
- **触发条件**: `Heartflow` 的主状态 (`MaiState`) 允许 `CHAT` 模式,且当前 `CHAT` 状态的子心流数量未达上限。
- **判定机制**: `SubHeartflowManager` 中的 `evaluate_and_transition_subflows_by_llm` 方法调用大模型(LLM)。LLM 读取该群聊的近期内容和结合自身个性信息,判断是否"想"在该群开始聊天。
- **判定机制**: `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` 中的 `evaluate_interest_and_promote` 方法定期检查满足条件的 `CHAT` 子心流。
- **判定机制**: `SubHeartflowManager` 中的 `sbhf_absent_into_focus` 方法定期检查满足条件的 `CHAT` 子心流。
- **执行**: 若满足所有条件,`SubHeartflowManager` 调用 `change_chat_state(ChatState.FOCUSED)`
- **注意**: 无法从 `ABSENT` 直接跳到 `FOCUSED`,必须先经过 `CHAT`
- **`FOCUSED` -> `ABSENT` (退出专注)**:
- **主要途径 (内部驱动)**: 在 `FOCUSED` 状态下运行的 `HeartFlowChatInstance` 连续多次决策为 `no_reply` (例如达到 5 次,次数可配),它会通过回调函数 (`request_absent_transition`) 请求 `SubHeartflowManager` 将其状态**直接**设置为 `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` 中的 `evaluate_and_transition_subflows_by_llm` 方法调用 LLM。LLM 读取群聊内容和结合自身状态,判断是否"不想"继续在此群闲聊。
- **主要途径 (内部驱动)**: `SubHeartflowManager` 中的 `sbhf_absent_into_chat` 方法调用 LLM。LLM 读取群聊内容和结合自身状态,判断是否"不想"继续在此群闲聊。
- **执行**: 若 LLM 判断为是,`SubHeartflowManager` 调用 `change_chat_state(ChatState.ABSENT)`
- **其他途径 (外部驱动)**:
- `Heartflow` 主状态变为 `OFFLINE`

View File

@@ -12,11 +12,17 @@ from src.heart_flow.interest_logger import InterestLogger
logger = get_logger("background_tasks")
# 新增随机停用间隔 (5 分钟)
RANDOM_DEACTIVATION_INTERVAL_SECONDS = 300
# 新增兴趣评估间隔
INTEREST_EVAL_INTERVAL_SECONDS = 5
# 新增聊天超时检查间隔
NORMAL_CHAT_TIMEOUT_CHECK_INTERVAL_SECONDS = 60
# 新增状态评估间隔
HF_JUDGE_STATE_UPDATE_INTERVAL_SECONDS = 60
CLEANUP_INTERVAL_SECONDS = 1200
STATE_UPDATE_INTERVAL_SECONDS = 60
LOG_INTERVAL_SECONDS = 3
class BackgroundTaskManager:
"""管理 Heartflow 的后台周期性任务。"""
@@ -26,34 +32,21 @@ class BackgroundTaskManager:
mai_state_info: MaiStateInfo, # Needs current state info
mai_state_manager: MaiStateManager,
subheartflow_manager: SubHeartflowManager,
interest_logger: InterestLogger,
update_interval: int,
cleanup_interval: int,
log_interval: int,
# 新增兴趣评估间隔参数
interest_eval_interval: int = INTEREST_EVAL_INTERVAL_SECONDS,
# 新增随机停用间隔参数
random_deactivation_interval: int = RANDOM_DEACTIVATION_INTERVAL_SECONDS,
interest_logger: InterestLogger
):
self.mai_state_info = mai_state_info
self.mai_state_manager = mai_state_manager
self.subheartflow_manager = subheartflow_manager
self.interest_logger = interest_logger
# Intervals
self.update_interval = update_interval
self.cleanup_interval = cleanup_interval
self.log_interval = log_interval
self.interest_eval_interval = interest_eval_interval # 存储兴趣评估间隔
self.random_deactivation_interval = random_deactivation_interval # 存储随机停用间隔
# 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._interest_eval_task: Optional[asyncio.Task] = None # 新增兴趣评估任务引用
self._random_deactivation_task: Optional[asyncio.Task] = None # 新增随机停用任务引用
self._hf_judge_state_update_task: Optional[asyncio.Task] = None # 新增状态评估任务引用
self._normal_chat_timeout_check_task: Optional[asyncio.Task] = None # Nyaa~ 添加聊天超时检查任务引用
self._hf_judge_state_update_task: Optional[asyncio.Task] = None # Nyaa~ 添加状态评估任务引用
self._into_focus_task: Optional[asyncio.Task] = None # Nyaa~ 添加兴趣评估任务引用
self._tasks: List[Optional[asyncio.Task]] = [] # Keep track of all tasks
async def start_tasks(self):
@@ -65,57 +58,53 @@ class BackgroundTaskManager:
- 将任务引用保存到任务列表
"""
# 任务配置列表: (任务变量名, 任务函数, 任务名称, 日志级别, 额外日志信息, 任务对象引用属性名)
# 任务配置列表: (任务函数, 任务名称, 日志级别, 额外日志信息, 任务对象引用属性名)
task_configs = [
(
self._state_update_task,
lambda: self._run_state_update_cycle(self.update_interval),
"hf_state_update",
lambda: self._run_state_update_cycle(STATE_UPDATE_INTERVAL_SECONDS),
"debug",
f"聊天状态更新任务已启动 间隔:{self.update_interval}s",
f"聊天状态更新任务已启动 间隔:{STATE_UPDATE_INTERVAL_SECONDS}s",
"_state_update_task",
),
(
self._hf_judge_state_update_task,
lambda: self._run_hf_judge_state_update_cycle(60),
"hf_judge_state_update",
lambda: self._run_normal_chat_timeout_check_cycle(NORMAL_CHAT_TIMEOUT_CHECK_INTERVAL_SECONDS),
"debug",
f"状态评估任务已启动 间隔:{60}s",
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._cleanup_task,
self._run_cleanup_cycle,
"hf_cleanup",
"info",
f"清理任务已启动 间隔:{self.cleanup_interval}s",
f"清理任务已启动 间隔:{CLEANUP_INTERVAL_SECONDS}s",
"_cleanup_task",
),
(
self._logging_task,
self._run_logging_cycle,
"hf_logging",
"info",
f"日志任务已启动 间隔:{self.log_interval}s",
f"日志任务已启动 间隔:{LOG_INTERVAL_SECONDS}s",
"_logging_task",
),
# 新增兴趣评估任务配置
(
self._interest_eval_task,
self._run_interest_eval_cycle,
"hf_interest_eval",
self._run_into_focus_cycle,
"debug", # 设为debug避免过多日志
f"兴趣评估任务已启动 间隔:{self.interest_eval_interval}s",
"_interest_eval_task",
f"专注评估任务已启动 间隔:{INTEREST_EVAL_INTERVAL_SECONDS}s",
"_into_focus_task",
),
]
# 统一启动所有任务
for _task_var, task_func, task_name, log_level, log_msg, task_attr_name in task_configs:
for task_func,log_level, log_msg, task_attr_name in task_configs:
# 检查任务变量是否存在且未完成
current_task_var = getattr(self, task_attr_name)
if current_task_var is None or current_task_var.done():
new_task = asyncio.create_task(task_func(), name=task_name)
new_task = asyncio.create_task(task_func())
setattr(self, task_attr_name, new_task) # 更新任务变量
if new_task not in self._tasks: # 避免重复添加
self._tasks.append(new_task)
@@ -123,7 +112,7 @@ class BackgroundTaskManager:
# 根据配置记录不同级别的日志
getattr(logger, log_level)(log_msg)
else:
logger.warning(f"{task_name}任务已在运行")
logger.warning(f"{task_attr_name}任务已在运行")
async def stop_tasks(self):
"""停止所有后台任务。
@@ -209,10 +198,15 @@ class BackgroundTaskManager:
logger.info("检测到离线,停用所有子心流")
await self.subheartflow_manager.deactivate_all_subflows()
async def _perform_hf_judge_state_update_work(self):
async def _perform_absent_into_chat(self):
"""调用llm检测是否转换ABSENT-CHAT状态"""
logger.info("[状态评估任务] 开始基于LLM评估子心流状态...")
await self.subheartflow_manager.evaluate_and_transition_subflows_by_llm()
await self.subheartflow_manager.sbhf_absent_into_chat()
async def _normal_chat_timeout_check_work(self):
"""检查处于CHAT状态的子心流是否因长时间未发言而超时并将其转为ABSENT"""
logger.info("[聊天超时检查] 开始检查处于CHAT状态的子心流...")
await self.subheartflow_manager.sbhf_chat_into_absent()
async def _perform_cleanup_work(self):
"""执行子心流清理任务
@@ -244,10 +238,10 @@ class BackgroundTaskManager:
await self.interest_logger.log_all_states()
# --- 新增兴趣评估工作函数 ---
async def _perform_interest_eval_work(self):
async def _perform_into_focus_work(self):
"""执行一轮子心流兴趣评估与提升检查。"""
# 直接调用 subheartflow_manager 的方法,并传递当前状态信息
await self.subheartflow_manager.evaluate_interest_and_promote()
await self.subheartflow_manager.sbhf_absent_into_focus()
# --- 结束新增 ---
@@ -259,25 +253,30 @@ class BackgroundTaskManager:
task_name="State Update", interval=interval, task_func=self._perform_state_update_work
)
async def _run_hf_judge_state_update_cycle(self, interval: int):
async def _run_absent_into_chat(self, interval: int):
await self._run_periodic_loop(
task_name="State Update", interval=interval, task_func=self._perform_hf_judge_state_update_work
task_name="Into Chat", interval=interval, task_func=self._perform_absent_into_chat
)
async def _run_normal_chat_timeout_check_cycle(self, interval: int):
await self._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 self._run_periodic_loop(
task_name="Subflow Cleanup", interval=self.cleanup_interval, task_func=self._perform_cleanup_work
task_name="Subflow Cleanup", interval=CLEANUP_INTERVAL_SECONDS, task_func=self._perform_cleanup_work
)
async def _run_logging_cycle(self):
await self._run_periodic_loop(
task_name="State Logging", interval=self.log_interval, task_func=self._perform_logging_work
task_name="State Logging", interval=LOG_INTERVAL_SECONDS, task_func=self._perform_logging_work
)
# --- 新增兴趣评估任务运行器 ---
async def _run_interest_eval_cycle(self):
async def _run_into_focus_cycle(self):
await self._run_periodic_loop(
task_name="Interest Evaluation",
interval=self.interest_eval_interval,
task_func=self._perform_interest_eval_work,
task_name="Into Focus",
interval=INTEREST_EVAL_INTERVAL_SECONDS,
task_func=self._perform_into_focus_work,
)

View File

@@ -11,20 +11,9 @@ from src.heart_flow.subheartflow_manager import SubHeartflowManager
from src.heart_flow.mind import Mind
from src.heart_flow.interest_logger import InterestLogger # Import InterestLogger
from src.heart_flow.background_tasks import BackgroundTaskManager # Import BackgroundTaskManager
# --- End import ---
logger = get_logger("heartflow")
# Task Intervals (should be in BackgroundTaskManager or config)
CLEANUP_INTERVAL_SECONDS = 1200
STATE_UPDATE_INTERVAL_SECONDS = 60
# Thresholds (should be in SubHeartflowManager or config)
INACTIVE_THRESHOLD_SECONDS = 1200
# --- End Constants --- #
class Heartflow:
"""主心流协调器,负责初始化并协调各个子系统:
- 状态管理 (MaiState)
@@ -65,9 +54,6 @@ class Heartflow:
mai_state_manager=self.mai_state_manager,
subheartflow_manager=self.subheartflow_manager,
interest_logger=self.interest_logger,
update_interval=STATE_UPDATE_INTERVAL_SECONDS,
cleanup_interval=CLEANUP_INTERVAL_SECONDS,
log_interval=3, # Example: Using value directly, ideally get from config
)
async def get_or_create_subheartflow(self, subheartflow_id: Any) -> Optional["SubHeartflow"]:

View File

@@ -4,24 +4,30 @@ 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 加载) --
enable_unlimited_hfc_chat = True # 调试用:无限专注聊天
# enable_unlimited_hfc_chat = False
prevent_offline_state = True # 调试用:防止进入离线状态
# enable_unlimited_hfc_chat = True # 调试用:无限专注聊天
enable_unlimited_hfc_chat = False
prevent_offline_state = True
# 目前默认不启用OFFLINE状态
# 不同状态下普通聊天的最大消息数
MAX_NORMAL_CHAT_NUM_PEEKING = 30
MAX_NORMAL_CHAT_NUM_NORMAL = 40
MAX_NORMAL_CHAT_NUM_FOCUSED = 30
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 = 20
MAX_FOCUSED_CHAT_NUM_NORMAL = 30
MAX_FOCUSED_CHAT_NUM_FOCUSED = 40
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
# -- 状态定义 --
@@ -164,7 +170,7 @@ class MaiStateManager:
if random.random() < 0.03: # 3% 概率切换到 OFFLINE
potential_next = MaiState.OFFLINE
resolved_next = _resolve_offline(potential_next)
logger.debug(f"规则1概率触发下线resolve 为 {resolved_next.value}")
logger.debug(f"概率触发下线resolve 为 {resolved_next.value}")
# 只有当解析后的状态与当前状态不同时才设置 next_state
if resolved_next != current_status:
next_state = resolved_next

View File

@@ -146,7 +146,7 @@ class ChattingObservation(Observation):
self.talking_message_str = await build_readable_messages(
messages=self.talking_message,
timestamp_mode="normal",
timestamp_mode="lite",
read_mark=last_obs_time_mark,
)
self.talking_message_str_truncate = await build_readable_messages(

View File

@@ -15,20 +15,15 @@ from src.heart_flow.mai_state_manager import MaiStateInfo
from src.heart_flow.chat_state_info import ChatState, ChatStateInfo
from src.heart_flow.sub_mind import SubMind
# # --- REMOVE: Conditional import --- #
# if TYPE_CHECKING:
# from src.heart_flow.subheartflow_manager import SubHeartflowManager
# # --- END REMOVE --- #
# 定义常量 (从 interest.py 移动过来)
MAX_INTEREST = 15.0
logger = get_logger("subheartflow")
base_reply_probability = 0.05
probability_increase_rate_per_second = 0.08
max_reply_probability = 1
PROBABILITY_INCREASE_RATE_PER_SECOND = 0.1
PROBABILITY_DECREASE_RATE_PER_SECOND = 0.1
MAX_REPLY_PROBABILITY = 1
class InterestChatting:
@@ -37,24 +32,15 @@ class InterestChatting:
decay_rate=global_config.default_decay_rate_per_second,
max_interest=MAX_INTEREST,
trigger_threshold=global_config.reply_trigger_threshold,
base_reply_probability=base_reply_probability,
increase_rate=probability_increase_rate_per_second,
decay_factor=global_config.probability_decay_factor_per_second,
max_probability=max_reply_probability,
max_probability=MAX_REPLY_PROBABILITY,
):
# 基础属性初始化
self.interest_level: float = 0.0
self.last_update_time: float = time.time()
self.decay_rate_per_second: float = decay_rate
self.max_interest: float = max_interest
self.last_interaction_time: float = self.last_update_time
self.trigger_threshold: float = trigger_threshold
self.base_reply_probability: float = base_reply_probability
self.probability_increase_rate: float = increase_rate
self.probability_decay_factor: float = decay_factor
self.max_reply_probability: float = max_probability
self.current_reply_probability: float = 0.0
self.is_above_threshold: bool = False
# 任务相关属性初始化
@@ -100,7 +86,6 @@ class InterestChatting:
"""
# 添加新消息
self.interest_dict[message.message_info.message_id] = (message, interest_value, is_mentioned)
self.last_interaction_time = time.time()
# 如果字典长度超过10删除最旧的消息
if len(self.interest_dict) > 10:
@@ -144,10 +129,10 @@ class InterestChatting:
async def _update_reply_probability(self):
self.above_threshold = self.interest_level >= self.trigger_threshold
if self.above_threshold:
self.start_hfc_probability += 0.1
self.start_hfc_probability += PROBABILITY_INCREASE_RATE_PER_SECOND
else:
if self.start_hfc_probability > 0:
self.start_hfc_probability = max(0, self.start_hfc_probability - 0.1)
self.start_hfc_probability = max(0, self.start_hfc_probability - PROBABILITY_DECREASE_RATE_PER_SECOND)
async def increase_interest(self, value: float):
self.interest_level += value
@@ -168,12 +153,7 @@ class InterestChatting:
"above_threshold": self.above_threshold,
}
async def should_evaluate_reply(self) -> bool:
if self.current_reply_probability > 0:
trigger = random.random() < self.current_reply_probability
return trigger
else:
return False
# --- 新增后台更新任务相关方法 ---
async def _run_update_loop(self, update_interval: float = 1.0):
@@ -492,12 +472,11 @@ class SubHeartflow:
async def get_interest_state(self) -> dict:
return await self.interest_chatting.get_state()
async def get_interest_level(self) -> float:
return await self.interest_chatting.get_interest()
async def should_evaluate_reply(self) -> bool:
return await self.interest_chatting.should_evaluate_reply()
def get_normal_chat_last_speak_time(self) -> float:
if self.normal_chat_instance:
return self.normal_chat_instance.last_speak_time
return 0
def get_interest_dict(self) -> Dict[str, tuple[MessageRecv, float, bool]]:
return self.interest_chatting.interest_dict

View File

@@ -140,11 +140,11 @@ class SubMind:
individuality = Individuality.get_instance()
relation_prompt = ""
print(f"person_list: {person_list}")
# print(f"person_list: {person_list}")
for person in person_list:
relation_prompt += await relationship_manager.build_relationship_info(person, is_id=True)
print(f"relat22222ion_prompt: {relation_prompt}")
# print(f"relat22222ion_prompt: {relation_prompt}")
# 构建个性部分
prompt_personality = individuality.get_prompt(x_person=2, level=2)

View File

@@ -29,7 +29,7 @@ logger = get_logger("subheartflow_manager")
# 子心流管理相关常量
INACTIVE_THRESHOLD_SECONDS = 3600 # 子心流不活跃超时时间(秒)
NORMAL_CHAT_TIMEOUT_SECONDS = 30 * 60 # 30分钟
class SubHeartflowManager:
"""管理所有活跃的 SubHeartflow 实例。"""
@@ -256,7 +256,7 @@ class SubHeartflowManager:
f"{log_prefix} 完成,共处理 {processed_count} 个子心流,成功将 {changed_count} 个非 ABSENT 子心流的状态更改为 ABSENT。"
)
async def evaluate_interest_and_promote(self):
async def sbhf_absent_into_focus(self):
"""评估子心流兴趣度满足条件且未达上限则提升到FOCUSED状态基于start_hfc_probability"""
try:
log_prefix = "[兴趣评估]"
@@ -271,10 +271,7 @@ class SubHeartflowManager:
return # 如果不允许,直接返回
# --- 结束新增 ---
logger.debug(f"{log_prefix} 当前状态 ({current_state.value}) 开始尝试提升到FOCUSED状态")
if int(time.time()) % 20 == 0: # 每20秒输出一次
logger.debug(f"{log_prefix} 当前状态 ({current_state.value}) 可以在{focused_limit}个群激情聊天")
logger.debug(f"{log_prefix} 当前状态 ({current_state.value}) 可以在{focused_limit}个群激情聊天")
if focused_limit <= 0:
# logger.debug(f"{log_prefix} 当前状态 ({current_state.value}) 不允许 FOCUSED 子心流")
@@ -333,137 +330,187 @@ class SubHeartflowManager:
except Exception as e:
logger.error(f"启动HFC 兴趣评估失败: {e}", exc_info=True)
async def evaluate_and_transition_subflows_by_llm(self):
async def sbhf_absent_into_chat(self):
"""
使用LLM评估每个子心流的状态并根据LLM的判断执行状态转换ABSENT <-> CHAT
注意此函数包含对假设的LLM函数的调用
随机选一个 ABSENT 状态的子心流,评估是否应转换为 CHAT 状态
每次调用最多转换一个
"""
# 获取当前状态和限制用于CHAT激活检查
current_mai_state = self.mai_state_info.get_current_state()
chat_limit = current_mai_state.get_normal_chat_max_num()
transitioned_to_chat = 0
transitioned_to_absent = 0
async with self._lock:
# 1. 筛选出所有 ABSENT 状态的子心流
absent_subflows = [
hf for hf in self.subheartflows.values()
if hf.chat_state.chat_status == ChatState.ABSENT
]
async with self._lock: # 在锁内获取快照并迭代
subflows_snapshot = list(self.subheartflows.values())
# 使用不上锁的版本,因为我们已经在锁内
if not absent_subflows:
logger.debug("没有摸鱼的子心流可以评估。") # 日志太频繁,注释掉
return # 没有目标,直接返回
# 2. 随机选一个幸运儿
sub_hf_to_evaluate = random.choice(absent_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.debug(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"最近聊了这些:\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"你当前没在 [{stream_name}] 群聊天。\\n"
f"{mai_state_description}\\n"
f"{chat_status_prompt}\\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 = await self._llm_evaluate_state_transition(prompt)
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:
logger.info("当前没有子心流需要评估。")
# logger.debug(f"{log_prefix_task} 没有子心流需要检查超时。")
return
for sub_hf in subflows_snapshot:
# 只检查 CHAT 状态的子心流
if sub_hf.chat_state.chat_status != ChatState.CHAT:
continue
flow_id = sub_hf.subheartflow_id
stream_name = chat_manager.get_stream_name(flow_id) or flow_id
log_prefix = f"[{stream_name}]"
current_subflow_state = sub_hf.chat_state.chat_status
log_prefix = f"[{stream_name}]({log_prefix_task})"
_observation_summary = "没有可用的观察信息。" # 默认值
should_deactivate = False
reason = ""
first_observation = sub_hf.observations[0]
if isinstance(first_observation, ChattingObservation):
# 组合中期记忆和当前聊天内容
await first_observation.observe()
current_chat = first_observation.talking_message_str or "当前无聊天内容。"
combined_summary = f"当前聊天内容:\n{current_chat}"
else:
logger.warning(f"{log_prefix} [{stream_name}] 第一个观察者不是 ChattingObservation 类型。")
try:
# 使用变量名 last_bot_dong_zuo_time 替代 last_bot_activity_time
last_bot_dong_zuo_time = sub_hf.get_normal_chat_last_speak_time()
# --- 获取麦麦状态 ---
mai_state_description = f"你当前状态: {current_mai_state.value}"
if last_bot_dong_zuo_time > 0:
current_time = time.time()
# 使用变量名 time_since_last_bb 替代 time_since_last_reply
time_since_last_bb = current_time - last_bot_dong_zuo_time
# 获取个性化信息
individuality = Individuality.get_instance()
if time_since_last_bb > NORMAL_CHAT_TIMEOUT_SECONDS:
should_deactivate = True
reason = f"超过 {NORMAL_CHAT_TIMEOUT_SECONDS / 60:.0f} 分钟没 BB"
logger.info(f"{log_prefix} 检测到超时 ({reason}),准备转为 ABSENT。上次活动时间: {last_bot_dong_zuo_time:.0f}")
# else:
# logger.debug(f"{log_prefix} Bot活动时间未超时 ({time_since_last_bb:.0f}s < {NORMAL_CHAT_TIMEOUT_SECONDS}s),保持 CHAT 状态。")
# else:
# 如果没有记录到Bot的活动时间暂时不因为超时而转换状态
# logger.debug(f"{log_prefix} 未找到有效的 Bot 最后活动时间记录,不执行超时检查。")
# 构建个性部分
prompt_personality = f"你正在扮演名为{individuality.personality.bot_nickname}的人类,你"
prompt_personality += individuality.personality.personality_core
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 individuality.personality.personality_sides:
random_side = random.choice(individuality.personality.personality_sides)
prompt_personality += f"{random_side}"
# 随机添加身份细节
if individuality.identity.identity_detail:
random_detail = random.choice(individuality.identity.identity_detail)
prompt_personality += f"{random_detail}"
# --- 针对 ABSENT 状态 ---
if current_subflow_state == ChatState.ABSENT:
# 构建Prompt
prompt = (
f"{prompt_personality}\n"
f"你当前没有在: [{stream_name}] 群中聊天。\n"
f"{mai_state_description}\n"
f"这个群里最近的聊天内容是:\n---\n{combined_summary}\n---\n"
f"基于以上信息,请判断你是否愿意在这个群开始闲聊,"
f"进入常规聊天(CHAT)状态?\n"
f"给出你的判断,和理由,然后以 JSON 格式回答"
f"包含键 'decision',如果要开始聊天,值为 true ,否则为 false.\n"
f"包含键 'reason',其值为你的理由。\n"
f'例如:{{"decision": true, "reason": "因为我想聊天"}}\n'
f"请只输出有效的 JSON 对象。"
)
# 调用LLM评估
should_activate = await self._llm_evaluate_state_transition(prompt)
if should_activate is None: # 处理解析失败或意外情况
logger.warning(f"{log_prefix}LLM评估返回无效结果跳过。")
continue
if should_activate:
# 检查CHAT限额
# 使用不上锁的版本,因为我们已经在锁内
current_chat_count = self.count_subflows_by_state_nolock(ChatState.CHAT)
if current_chat_count < chat_limit:
logger.info(
f"{log_prefix}LLM建议激活到CHAT状态且未达上限({current_chat_count}/{chat_limit})。正在尝试转换..."
)
await sub_hf.change_chat_state(ChatState.CHAT)
if sub_hf.chat_state.chat_status == ChatState.CHAT:
transitioned_to_chat += 1
else:
logger.warning(f"{log_prefix}尝试激活到CHAT失败。")
else:
logger.info(
f"{log_prefix}LLM建议激活到CHAT状态但已达到上限({current_chat_count}/{chat_limit})。跳过转换。"
)
# --- 执行状态转换(如果超时) ---
if should_deactivate:
logger.info(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} 已成功转换为 ABSENT 状态。")
else:
logger.info(f"{log_prefix}LLM建议不激活到CHAT状态")
logger.warning(f"{log_prefix} 尝试因超时转换为 ABSENT 失败")
# --- 针对 CHAT 状态 ---
elif current_subflow_state == ChatState.CHAT:
# 构建Prompt
prompt = (
f"{prompt_personality}\n"
f"你正在在: [{stream_name}] 群中聊天。\n"
f"{mai_state_description}\n"
f"这个群里最近的聊天内容是:\n---\n{combined_summary}\n---\n"
f"基于以上信息,请判断你是否愿意在这个群继续闲聊,"
f"还是暂时离开聊天,进入休眠状态?\n"
f"给出你的判断,和理由,然后以 JSON 格式回答"
f"包含键 'decision',如果要离开聊天,值为 true ,否则为 false.\n"
f"包含键 'reason',其值为你的理由。\n"
f'例如:{{"decision": true, "reason": "因为我想休息"}}\n'
f"请只输出有效的 JSON 对象。"
)
# 调用LLM评估
should_deactivate = await self._llm_evaluate_state_transition(prompt)
if should_deactivate is None: # 处理解析失败或意外情况
logger.warning(f"{log_prefix}LLM评估返回无效结果跳过。")
continue
if should_deactivate:
logger.info(f"{log_prefix}LLM建议进入ABSENT状态。正在尝试转换...")
await sub_hf.change_chat_state(ChatState.ABSENT)
if sub_hf.chat_state.chat_status == ChatState.ABSENT:
transitioned_to_absent += 1
else:
logger.info(f"{log_prefix}LLM建议不进入ABSENT状态。")
if transitioned_to_absent > 0:
logger.info(f"{log_prefix_task} 完成,共检查 {checked_count} 个子心流,{transitioned_to_absent} 个因超时转为 ABSENT。")
# else:
# logger.debug(f"{log_prefix_task} 完成,共检查 {checked_count} 个子心流,无超时转换。")
# --- 结束新增 ---
async def _llm_evaluate_state_transition(self, prompt: str) -> Optional[bool]:
"""
@@ -579,14 +626,14 @@ class SubHeartflowManager:
# --- 新增:处理 HFC 无回复回调的专用方法 --- #
async def _handle_hfc_no_reply(self, subheartflow_id: Any):
"""处理来自 HeartFChatting 的连续无回复信号 (通过 partial 绑定 ID)"""
# 注意:这里不需要再获取锁,因为 request_absent_transition 内部会处理锁
# 注意:这里不需要再获取锁,因为 sbhf_focus_into_absent 内部会处理锁
logger.debug(f"[管理器 HFC 处理器] 接收到来自 {subheartflow_id} 的 HFC 无回复信号")
await self.request_absent_transition(subheartflow_id)
await self.sbhf_focus_into_absent(subheartflow_id)
# --- 结束新增 --- #
# --- 新增:处理来自 HeartFChatting 的状态转换请求 --- #
async def request_absent_transition(self, subflow_id: Any):
async def sbhf_focus_into_absent(self, subflow_id: Any):
"""
接收来自 HeartFChatting 的请求,将特定子心流的状态转换为 ABSENT。
通常在连续多次 "no_reply" 后被调用。
@@ -606,12 +653,42 @@ class SubHeartflowManager:
# 仅当子心流处于 FOCUSED 状态时才进行转换
# 因为 HeartFChatting 只在 FOCUSED 状态下运行
if current_state == ChatState.FOCUSED:
logger.info(f"[状态转换请求] 接收到请求,将 {stream_name} (当前: {current_state.value}) 转换为 ABSENT")
target_state = ChatState.ABSENT # 默认目标状态
log_reason = "默认转换"
# 决定是去 ABSENT 还是 CHAT
if random.random() < 0.5:
target_state = ChatState.ABSENT
log_reason = "随机选择 ABSENT"
logger.debug(f"[状态转换请求] {stream_name} ({current_state.value}) 随机决定进入 ABSENT")
else:
# 尝试进入 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})"
logger.debug(f"[状态转换请求] {stream_name} ({current_state.value}) 随机决定进入 CHAT未达上限 ({current_chat_count}/{chat_limit})")
else:
target_state = ChatState.ABSENT
log_reason = f"随机选择 CHAT 但已达上限 ({current_chat_count}/{chat_limit}),转为 ABSENT"
logger.debug(f"[状态转换请求] {stream_name} ({current_state.value}) 随机决定进入 CHAT但已达上限 ({current_chat_count}/{chat_limit}),改为进入 ABSENT")
#开始转换
logger.info(f"[状态转换请求] 接收到请求,将 {stream_name} (当前: {current_state.value}) 尝试转换为 {target_state.value} ({log_reason})")
try:
await subflow.change_chat_state(ChatState.ABSENT)
logger.info(f"[状态转换请求] {stream_name} 状态已成功转换为 ABSENT")
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}ABSENT 时出错: {e}", exc_info=True)
logger.error(f"[状态转换请求] 转换 {stream_name}{target_state.value} 时出错: {e}", exc_info=True)
elif current_state == ChatState.ABSENT:
logger.debug(f"[状态转换请求] {stream_name} 已处于 ABSENT 状态,无需转换")
else: