feat:把CHAT状态判断交给LLM

This commit is contained in:
SengokuCola
2025-04-26 15:23:16 +08:00
parent 0e03c2e492
commit 3ddd55e387
8 changed files with 218 additions and 92 deletions

View File

@@ -247,7 +247,7 @@ class InterestMonitorApp:
self.stream_chat_states[stream_id] = subflow_entry.get("sub_chat_state", "N/A")
self.stream_threshold_status[stream_id] = subflow_entry.get("is_above_threshold", False)
self.stream_last_active[stream_id] = subflow_entry.get(
"last_changed_state_time"
"chat_state_changed_time"
) # 存储原始时间戳
self.stream_last_interaction[stream_id] = subflow_entry.get(
"last_interaction_time"

View File

@@ -105,7 +105,8 @@ c HeartFChatting工作方式
- 负责所有 `SubHeartflow` 实例的生命周期管理,包括:
- 创建和获取 (`get_or_create_subheartflow`)。
- 停止和清理 (`sleep_subheartflow`, `cleanup_inactive_subheartflows`)。
- 根据 `Heartflow` 的状态 (`self.mai_state_info`) 和限制条件,激活、停用或调整子心流的状态(例如 `enforce_subheartflow_limits`, `activate_random_subflows_to_chat`, `evaluate_interest_and_promote`)。
- 根据 `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` 状态的数量上限)。
- **清理机制**: 通过后台任务 (`BackgroundTaskManager`) 定期调用 `cleanup_inactive_subheartflows` 方法,此方法会识别并**删除**那些处于 `ABSENT` 状态超过一小时 (`INACTIVE_THRESHOLD_SECONDS`) 的子心流实例。
### 1.5. 消息处理与回复流程 (Message Processing vs. Replying Flow)
@@ -149,9 +150,9 @@ c HeartFChatting工作方式
* `ChatState.FOCUSED` (专注/认真水群): 专注聊天模式。激活 `HeartFlowChatInstance`
- **选择**: 子心流可以根据外部指令(来自 `SubHeartflowManager`)或内部逻辑(未来的扩展)选择进入 `ABSENT` 状态(不回复不观察),或进入 `CHAT` / `FOCUSED` 中的一种回复模式。
- **状态转换机制** (由 `SubHeartflowManager` 驱动):
- **激活 `CHAT`**: 当 `Heartflow` 状态从 `OFFLINE` 变为允许聊天的状态时,`SubHeartflowManager` 会根据限制(通过 `self.mai_state_info` 获取),选择部分 `ABSENT` 状态的子心流,**检查当前 CHAT 状态数量是否达到上限**,如果未达上限,则调用其 `change_chat_state` 方法将其转换为 `CHAT`
- **激活 `CHAT`**: 当 `Heartflow` 状态从 `OFFLINE` 变为允许聊天的状态时,`SubHeartflowManager` 会根据限制(通过 `self.mai_state_info` 获取),选择部分 `ABSENT` 状态的子心流,**检查当前 CHAT 状态数量是否达到上限**,如果未达上限,则调用其 `change_chat_state` 方法将其转换为 `CHAT`此外,`evaluate_and_transition_subflows_by_llm` 方法也会根据 LLM 的判断,在未达上限时将 `ABSENT` 状态的子心流激活为 `CHAT`
- **激活 `FOCUSED`**: `SubHeartflowManager` 会定期评估处于 `CHAT` 状态的子心流的兴趣度 (`InterestChatting.start_hfc_probability`),若满足条件且**检查当前 FOCUSED 状态数量未达上限**(通过 `self.mai_state_info` 获取限制),则调用 `change_chat_state` 将其提升为 `FOCUSED`
- **停用/回退**: `SubHeartflowManager` 可能因 `Heartflow` 状态变化、达到数量限制、长时间不活跃随机概率等原因,调用 `change_chat_state` 将子心流状态设置为 `ABSENT` 或从 `FOCUSED` 回退到 `CHAT`。当子心流进入 `ABSENT` 状态后,如果持续一小时不活跃,才会被后台清理任务删除。
- **停用/回退**: `SubHeartflowManager` 可能因 `Heartflow` 状态变化、达到数量限制、长时间不活跃随机概率 (`randomly_deactivate_subflows`) 或 LLM 评估 (`evaluate_and_transition_subflows_by_llm` 判断 `CHAT` 状态子心流应休眠) 等原因,调用 `change_chat_state` 将子心流状态设置为 `ABSENT` 或从 `FOCUSED` 回退到 `CHAT`。当子心流进入 `ABSENT` 状态后,如果持续一小时不活跃,才会被后台清理任务删除。
- **注意**: `change_chat_state` 方法本身只负责执行状态转换和管理内部聊天实例(`NormalChatInstance`/`HeartFlowChatInstance`),不再进行限额检查。限额检查的责任完全由调用方(即 `SubHeartflowManager` 中的相关方法,这些方法会使用内部存储的 `mai_state_info` 来获取限制)承担。
## 3. 聊天实例详解 (Chat Instances Explained)

View File

@@ -34,7 +34,6 @@ class BackgroundTaskManager:
update_interval: int,
cleanup_interval: int,
log_interval: int,
inactive_threshold: int,
# 新增兴趣评估间隔参数
interest_eval_interval: int = INTEREST_EVAL_INTERVAL_SECONDS,
# 新增随机停用间隔参数
@@ -58,6 +57,7 @@ class BackgroundTaskManager:
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._tasks: List[Optional[asyncio.Task]] = [] # Keep track of all tasks
async def start_tasks(self):
@@ -79,12 +79,20 @@ class BackgroundTaskManager:
f"聊天状态更新任务已启动 间隔:{self.update_interval}s",
"_state_update_task",
),
(
self._hf_judge_state_update_task,
lambda: self._run_hf_judge_state_update_cycle(300),
"hf_judge_state_update",
"debug",
f"状态评估任务已启动 间隔:{300}s",
"_hf_judge_state_update_task",
),
(
self._cleanup_task,
self._run_cleanup_cycle,
"hf_cleanup",
"info",
f"清理任务已启动 间隔:{self.cleanup_interval}s 阈值:{self.inactive_threshold}s",
f"清理任务已启动 间隔:{self.cleanup_interval}s",
"_cleanup_task",
),
(
@@ -203,21 +211,21 @@ class BackgroundTaskManager:
if state_changed:
current_state = self.mai_state_info.get_current_state()
await self.subheartflow_manager.enforce_subheartflow_limits(current_state)
await self.subheartflow_manager.enforce_subheartflow_limits()
# 状态转换处理
if (
previous_status == self.mai_state_info.mai_status.OFFLINE
and current_state != self.mai_state_info.mai_status.OFFLINE
):
logger.info("[后台任务] 主状态激活,触发子流激活")
await self.subheartflow_manager.activate_random_subflows_to_chat(current_state)
elif (
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_hf_judge_state_update_work(self):
"""调用llm检测是否转换ABSENT-CHAT状态"""
logger.info("[状态评估任务] 开始基于LLM评估子心流状态...")
await self.subheartflow_manager.evaluate_and_transition_subflows_by_llm()
async def _perform_cleanup_work(self):
"""执行子心流清理任务
@@ -252,7 +260,7 @@ class BackgroundTaskManager:
async def _perform_interest_eval_work(self):
"""执行一轮子心流兴趣评估与提升检查。"""
# 直接调用 subheartflow_manager 的方法,并传递当前状态信息
await self.subheartflow_manager.evaluate_interest_and_promote(self.mai_state_info)
await self.subheartflow_manager.evaluate_interest_and_promote()
# --- 结束新增 ---
@@ -268,6 +276,11 @@ class BackgroundTaskManager:
await self._run_periodic_loop(
task_name="State Update", interval=interval, task_func=self._perform_state_update_work
)
async def _run_hf_judge_state_update_cycle(self, interval: int):
await self._run_periodic_loop(
task_name="State Update", interval=interval, task_func=self._perform_hf_judge_state_update_work
)
async def _run_cleanup_cycle(self):
await self._run_periodic_loop(

View File

@@ -72,7 +72,6 @@ class Heartflow:
update_interval=STATE_UPDATE_INTERVAL_SECONDS,
cleanup_interval=CLEANUP_INTERVAL_SECONDS,
log_interval=3, # Example: Using value directly, ideally get from config
inactive_threshold=INACTIVE_THRESHOLD_SECONDS,
)
async def get_or_create_subheartflow(self, subheartflow_id: Any) -> Optional["SubHeartflow"]:

View File

@@ -58,7 +58,7 @@ class InterestLogger:
return results
for subheartflow in all_flows:
if self.subheartflow_manager.get_or_create_subheartflow(subheartflow.subheartflow_id):
if await self.subheartflow_manager.get_or_create_subheartflow(subheartflow.subheartflow_id):
tasks.append(
asyncio.create_task(subheartflow.get_full_state(), name=f"get_state_{subheartflow.subheartflow_id}")
)

View File

@@ -481,7 +481,7 @@ class SubHeartflow:
"interest_state": interest_state,
"current_mind": self.sub_mind.current_mind,
"chat_state": self.chat_state.chat_status.value,
"last_changed_state_time": self.last_changed_state_time,
"chat_state_changed_time": self.chat_state_changed_time,
}
async def shutdown(self):

View File

@@ -12,7 +12,11 @@ from src.plugins.chat.chat_stream import chat_manager
# 导入心流相关类
from src.heart_flow.sub_heartflow import SubHeartflow, ChatState
from src.heart_flow.mai_state_manager import MaiStateInfo
from .observation import ChattingObservation
from .observation import ChattingObservation, Observation
# 导入LLM请求工具
from src.plugins.models.utils_model import LLMRequest
from src.config.config import global_config
# 初始化日志记录器
@@ -34,6 +38,15 @@ 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" # 保留特定的请求类型
)
def get_all_subheartflows(self) -> List["SubHeartflow"]:
"""获取所有当前管理的 SubHeartflow 实例列表 (快照)。"""
return list(self.subheartflows.values())
@@ -103,24 +116,6 @@ class SubHeartflowManager:
except Exception as e:
logger.error(f"[子心流管理] 设置ABSENT状态失败: {e}")
# 停止子心流内部循环
subheartflow.should_stop = True
# 取消后台任务
task = subheartflow.task
if task and not task.done():
task.cancel()
logger.debug(f"[子心流管理] 已取消 {stream_name} 的后台任务")
# 从管理字典中移除
if subheartflow_id in self.subheartflows:
del self.subheartflows[subheartflow_id]
logger.debug(f"[子心流管理] 已移除 {stream_name}")
return True
else:
logger.warning(f"[子心流管理] {stream_name} 已被提前移除")
return False
def get_inactive_subheartflows(self, max_age_seconds=INACTIVE_THRESHOLD_SECONDS):
"""识别并返回需要清理的不活跃(处于ABSENT状态超过一小时)子心流(id, 原因)"""
current_time = time.time()
@@ -185,52 +180,6 @@ class SubHeartflowManager:
else:
logger.debug(f"[限制] 无需停止, 当前总数:{len(self.subheartflows)}")
async def activate_random_subflows_to_chat(self):
"""主状态激活时随机选择ABSENT子心流进入CHAT状态"""
# 使用 self.mai_state_info 获取当前状态和限制
current_mai_state = self.mai_state_info.get_current_state()
limit = current_mai_state.get_normal_chat_max_num()
if limit <= 0:
logger.info("[激活] 当前状态不允许CHAT子心流")
return
# 获取所有ABSENT状态的子心流
absent_flows = [flow for flow in self.subheartflows.values() if flow.chat_state.chat_status == ChatState.ABSENT]
num_to_activate = min(limit, len(absent_flows))
if num_to_activate <= 0:
logger.info(f"[激活] 无可用ABSENT子心流(限额:{limit}, 可用:{len(absent_flows)})")
return
logger.info(f"[激活] 随机选择{num_to_activate}个ABSENT子心流进入CHAT状态")
activated_count = 0
for flow in random.sample(absent_flows, num_to_activate):
flow_id = flow.subheartflow_id
stream_name = chat_manager.get_stream_name(flow_id) or flow_id
if flow_id not in self.subheartflows:
logger.warning(f"[激活] 跳过{stream_name}, 子心流已不存在")
continue
logger.debug(f"[激活] 正在激活子心流{stream_name}")
# --- 限额检查 --- #
current_chat_count = self.count_subflows_by_state(ChatState.CHAT)
if current_chat_count >= limit:
logger.warning(f"[激活] 跳过{stream_name}, 普通聊天已达上限 ({current_chat_count}/{limit})")
continue # 跳过此子心流,继续尝试激活下一个
# --- 结束限额检查 --- #
# 移除 states_num 参数
await flow.change_chat_state(ChatState.CHAT)
if flow.chat_state.chat_status == ChatState.CHAT:
activated_count += 1
else:
logger.warning(f"[激活] {stream_name}状态设置失败")
logger.info(f"[激活] 完成, 成功激活{activated_count}个子心流")
async def deactivate_all_subflows(self):
"""将所有子心流的状态更改为 ABSENT (例如主状态变为OFFLINE时调用)"""
@@ -394,15 +343,172 @@ class SubHeartflowManager:
else:
logger.debug(f"{log_prefix_manager} 随机停用周期结束, 未停用任何子心流。")
def count_subflows_by_state(self, state: ChatState) -> int:
"""统计指定状态的子心流数量
async def evaluate_and_transition_subflows_by_llm(self):
"""
使用LLM评估每个子心流的状态并根据LLM的判断执行状态转换ABSENT <-> CHAT
注意此函数包含对假设的LLM函数的调用。
"""
log_prefix = "[LLM状态评估]"
logger.info(f"{log_prefix} 开始基于LLM评估子心流状态...")
# 获取当前状态和限制用于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: # 在锁内获取快照并迭代
subflows_snapshot = list(self.subheartflows.values())
# 使用不上锁的版本,因为我们已经在锁内
current_chat_count = self.count_subflows_by_state_nolock(ChatState.CHAT)
if not subflows_snapshot:
logger.info(f"{log_prefix} 当前没有子心流需要评估。")
return
for sub_hf in subflows_snapshot:
flow_id = sub_hf.subheartflow_id
stream_name = chat_manager.get_stream_name(flow_id) or flow_id
current_subflow_state = sub_hf.chat_state.chat_status
# --- 获取观察内容 ---
# 从 sub_hf.observations 获取 ChattingObservation 并提取信息
observation_summary = "没有可用的观察信息。" # 默认值
try:
# 检查 observations 列表是否存在且不为空
# 假设第一个观察者是 ChattingObservation
first_observation = sub_hf.observations[0]
if isinstance(first_observation, ChattingObservation):
# 组合中期记忆和当前聊天内容
current_chat = first_observation.talking_message_str or "当前无聊天内容。"
combined_summary = f"当前聊天内容:\n{current_chat}"
else:
logger.warning(f"{log_prefix} [{stream_name}] 第一个观察者不是 ChattingObservation 类型。")
except Exception as e:
logger.warning(f"{log_prefix} [{stream_name}] 获取观察信息失败: {e}", exc_info=True)
# 保留默认值或错误信息
combined_summary = f"获取观察信息时出错: {e}"
# --- 获取麦麦状态 ---
mai_state_description = f"麦麦当前状态: {current_mai_state.value}"
# --- 针对 ABSENT 状态 ---
if current_subflow_state == ChatState.ABSENT:
# 构建Prompt
prompt = (
f"子心流 [{stream_name}] 当前处于非活跃(ABSENT)状态。\n"
f"{mai_state_description}\n"
f"最近观察到的内容摘要:\n---\n{combined_summary}\n---\n"
f"基于以上信息,该子心流是否表现出足够的活跃迹象或重要性,"
f"值得将其唤醒并进入常规聊天(CHAT)状态?"
f"请回答 ''''"
)
# 调用LLM评估
try:
# 使用 self._llm_evaluate_state_transition
should_activate = await self._llm_evaluate_state_transition(prompt)
if should_activate:
# 检查CHAT限额
if current_chat_count < chat_limit:
logger.info(f"{log_prefix} [{stream_name}] 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
current_chat_count += 1 # 更新计数器
else:
logger.warning(f"{log_prefix} [{stream_name}] 尝试激活到CHAT失败。")
else:
logger.info(f"{log_prefix} [{stream_name}] LLM建议激活到CHAT状态但已达到上限({current_chat_count}/{chat_limit})。跳过转换。")
except Exception as e:
logger.error(f"{log_prefix} [{stream_name}] LLM评估或状态转换(ABSENT->CHAT)时出错: {e}", exc_info=True)
# --- 针对 CHAT 状态 ---
elif current_subflow_state == ChatState.CHAT:
# 构建Prompt
prompt = (
f"子心流 [{stream_name}] 当前处于常规聊天(CHAT)状态。\n"
f"{mai_state_description}\n"
f"最近观察到的内容摘要:\n---\n{combined_summary}\n---\n"
f"基于以上信息,该子心流是否表现出不活跃、对话结束或不再需要关注的迹象,"
f"应该让其进入休眠(ABSENT)状态?"
f"请回答 ''''"
)
# 调用LLM评估
try:
# 使用 self._llm_evaluate_state_transition
should_deactivate = await self._llm_evaluate_state_transition(prompt)
if should_deactivate:
logger.info(f"{log_prefix} [{stream_name}] LLM建议进入ABSENT状态。正在尝试转换...")
await sub_hf.change_chat_state(ChatState.ABSENT)
if sub_hf.chat_state.chat_status == ChatState.ABSENT:
transitioned_to_absent += 1
current_chat_count -= 1 # 更新计数器
else:
logger.warning(f"{log_prefix} [{stream_name}] 尝试转换为ABSENT失败。")
except Exception as e:
logger.error(f"{log_prefix} [{stream_name}] LLM评估或状态转换(CHAT->ABSENT)时出错: {e}", exc_info=True)
# 可以选择性地为 FOCUSED 状态添加评估逻辑,例如判断是否降级回 CHAT 或 ABSENT
logger.info(
f"{log_prefix} LLM评估周期结束。"
f" 成功转换到CHAT: {transitioned_to_chat}."
f" 成功转换到ABSENT: {transitioned_to_absent}."
)
async def _llm_evaluate_state_transition(self, prompt: str) -> bool:
"""
使用 LLM 评估是否应进行状态转换。
Args:
state: 要统计的聊天状态枚举值
prompt: 提供给 LLM 的提示信息。
Returns:
int: 处于该状态的子心流数量
bool: True 表示应该转换False 表示不应该转换。
"""
log_prefix = "[LLM状态评估]"
try:
# --- 真实的 LLM 调用 ---
response_text, _, model_name = await self.llm_state_evaluator.generate_response_async(prompt)
logger.debug(f"{log_prefix} 使用模型 {model_name} 评估,原始响应: {response_text}")
# 解析响应 - 这里需要根据你的LLM的确切输出来调整逻辑
# 假设 LLM 会明确回答 "是" 或 "否"
if response_text and "" in response_text.strip():
logger.debug(f"{log_prefix} LLM评估结果: 建议转换 (响应包含 '')")
return True
elif response_text and "" in response_text.strip():
logger.debug(f"{log_prefix} LLM评估结果: 建议不转换 (响应包含 '')")
return False
else:
logger.warning(f"{log_prefix} LLM 未明确回答 '''',响应: {response_text}")
# 可以设定一个默认行为,例如默认不转换
return False
# --- 真实的 LLM 调用结束 ---
# # --- 占位符逻辑:随机返回 True/False ---
# # 请在接入真实 LLM 后移除此部分
# await asyncio.sleep(0.1) # 模拟LLM调用延迟
# result = random.choice([True, False])
# logger.debug(f"{log_prefix} (占位符) LLM评估结果: {'建议转换' if result else '建议不转换'}")
# return result
# # --- 占位符逻辑结束 ---
except Exception as e:
logger.error(f"{log_prefix} 调用 LLM 进行状态评估时出错: {e}", exc_info=True)
def count_subflows_by_state(self, state: ChatState) -> int:
"""统计指定状态的子心流数量"""
count = 0
# 遍历所有子心流实例
for subheartflow in self.subheartflows.values():
@@ -411,12 +517,19 @@ class SubHeartflowManager:
count += 1
return count
def get_active_subflow_minds(self) -> List[str]:
"""获取所有活跃(非ABSENT)子心流的当前想法
返回:
List[str]: 包含所有活跃子心流当前想法的列表
def count_subflows_by_state_nolock(self, state: ChatState) -> int:
"""
统计指定状态的子心流数量 (不上锁版本)。
警告:仅应在已持有 self._lock 的上下文中使用此方法。
"""
count = 0
for subheartflow in self.subheartflows.values():
if subheartflow.chat_state.chat_status == state:
count += 1
return count
def get_active_subflow_minds(self) -> List[str]:
"""获取所有活跃(非ABSENT)子心流的当前想法"""
minds = []
for subheartflow in self.subheartflows.values():
# 检查子心流是否活跃(非ABSENT状态)

View File

@@ -167,7 +167,7 @@ class HeartFCProcessor:
# 6. 兴趣度计算与更新
interested_rate, is_mentioned = await self._calculate_interest(message)
await subheartflow.interest_chatting.increase_interest(value=interested_rate)
await subheartflow.interest_chatting.add_interest_dict(message, interested_rate, is_mentioned)
subheartflow.interest_chatting.add_interest_dict(message, interested_rate, is_mentioned)
# 7. 日志记录
mes_name = chat.group_info.group_name if chat.group_info else "私聊"