fix:更名和小bug修复

This commit is contained in:
SengokuCola
2025-04-22 21:59:23 +08:00
parent 1482133005
commit 8d50a381e4
21 changed files with 852 additions and 468 deletions

View File

@@ -171,7 +171,7 @@ class ToolUser:
# 如果有工具结果,返回结构化的信息 # 如果有工具结果,返回结构化的信息
if structured_info: if structured_info:
logger.info(f"工具调用收集到结构化信息: {json.dumps(structured_info, ensure_ascii=False)}") logger.debug(f"工具调用收集到结构化信息: {json.dumps(structured_info, ensure_ascii=False)}")
return {"used_tools": True, "structured_info": structured_info} return {"used_tools": True, "structured_info": structured_info}
else: else:
# 没有工具调用 # 没有工具调用

View File

@@ -1,7 +1,5 @@
# 心流系统 (Heart Flow System) # 心流系统 (Heart Flow System)
心流系统是一个模拟AI机器人内心思考和情感流动的核心系统。它通过多层次的心流结构使AI能够对外界信息进行观察、思考和情感反应从而产生更自然的对话和行为。
## 系统架构 ## 系统架构
### 1. 主心流 (Heartflow) ### 1. 主心流 (Heartflow)
@@ -24,22 +22,6 @@
- 支持多种观察类型(如聊天观察) - 支持多种观察类型(如聊天观察)
- 对信息进行实时总结和更新 - 对信息进行实时总结和更新
## 主要功能
### 思维系统
- 定期进行思维更新
- 维护短期记忆和思维连续性
- 支持多层次的思维处理
### 情感系统
- 情绪状态管理
- 回复意愿判断
- 情感因素影响决策
### 交互系统
- 群聊消息处理
- 多场景并行处理
- 智能回复生成
## 工作流程 ## 工作流程
@@ -63,11 +45,6 @@ observation = ChattingObservation(chat_id)
subheartflow.add_observation(observation) subheartflow.add_observation(observation)
``` ```
### 启动心流系统
```python
await heartflow.heartflow_start_working()
```
## 配置说明 ## 配置说明
系统的主要配置参数: 系统的主要配置参数:
@@ -81,14 +58,100 @@ await heartflow.heartflow_start_working()
2. 需要合理配置更新间隔以平衡性能和响应速度 2. 需要合理配置更新间隔以平衡性能和响应速度
3. 观察系统会限制消息处理数量以避免过载 3. 观察系统会限制消息处理数量以避免过载
# PFChatting 与主动回复流程说明 (V2)
更新: 本文档描述了 `PFChatting` 类及其在 `heartFC_controler` 模块中实现的主动、基于兴趣的回复流程。
把聊天控制移动到心流下吧
首先心流要根据日程以及当前状况判定总体状态MaiStateInfo
然后根据每个子心流的运行情况给子心流分配聊天资源ChatStateInfoABSENT CHAT 或者 FOCUS ## 1. `PFChatting` 类概述
子心流负责根据状态进行执行 * **目标**: 管理特定聊天流 (`stream_id`) 的主动回复逻辑,使其行为更像人类的自然交流。
* **创建时机**: 当 `HeartFC_Chat` 的兴趣监控任务 (`_interest_monitor_loop`) 检测到某个聊天流的兴趣度 (`InterestChatting`) 达到了触发回复评估的条件 (`should_evaluate_reply`) 时,会为该 `stream_id` 获取或创建唯一的 `PFChatting` 实例 (`_get_or_create_heartFC_chat`)。
* **持有**:
* 对应的 `sub_heartflow` 实例引用 (通过 `heartflow.get_subheartflow(stream_id)`)。
* 对应的 `chat_stream` 实例引用。
*`HeartFC_Chat` 单例的引用 (用于调用发送消息、处理表情等辅助方法)。
* **初始化**: `PFChatting` 实例在创建后会执行异步初始化 (`_initialize`),这可能包括加载必要的上下文或历史信息(*待确认是否实现了读取历史消息*)。
1.将interest.py进行拆分class InterestChatting 将会在 sub_heartflow中声明每个sub_heartflow都会所属一个InterestChatting ## 2. 核心回复流程 (由 `HeartFC_Chat` 触发)
class InterestManager 将会在heartflow中声明成为heartflow的一个组件伴随heartflow产生
`HeartFC_Chat` 调用 `PFChatting` 实例的方法 (例如 `add_time`) 时,会启动内部的回复决策与执行流程:
1. **规划 (Planner):**
* **输入**: 从关联的 `sub_heartflow` 获取观察结果、思考链、记忆片段等上下文信息。
* **决策**:
* 判断当前是否适合进行回复。
* 决定回复的形式(纯文本、带表情包等)。
* 选择合适的回复时机和策略。
* **实现**: *此部分逻辑待详细实现,可能利用 LLM 的工具调用能力来增强决策的灵活性和智能性。需要考虑机器人的个性化设定。*
2. **回复生成 (Replier):**
* **输入**: Planner 的决策结果和必要的上下文。
* **执行**:
* 调用 `ResponseGenerator` (`self.gpt`) 或类似组件生成具体的回复文本内容。
* 可能根据 Planner 的策略生成多个候选回复。
* **并发**: 系统支持同时存在多个思考/生成任务(上限由 `global_config.max_concurrent_thinking_messages` 控制)。
3. **检查 (Checker):**
* **时机**: 在回复生成过程中或生成后、发送前执行。
* **目的**:
* 检查自开始生成回复以来,聊天流中是否出现了新的消息。
* 评估已生成的候选回复在新的上下文下是否仍然合适、相关。
* *需要实现相似度比较逻辑,防止发送与近期消息内容相近或重复的回复。*
* **处理**: 如果检查结果认为回复不合适,则该回复将被**抛弃**。
4. **发送协调:**
* **执行**: 如果 Checker 通过,`PFChatting` 会调用 `HeartFC_Chat` 实例提供的发送接口:
* `_create_thinking_message`: 通知 `MessageManager` 显示"正在思考"状态。
* `_send_response_messages`: 将最终的回复文本交给 `MessageManager` 进行排队和发送。
* `_handle_emoji`: 如果需要发送表情包,调用此方法处理表情包的获取和发送。
* **细节**: 实际的消息发送、排队、间隔控制由 `MessageManager``MessageSender` 负责。
## 3. 与其他模块的交互
* **`HeartFC_Chat`**:
* 创建、管理和触发 `PFChatting` 实例。
* 提供发送消息 (`_send_response_messages`)、处理表情 (`_handle_emoji`)、创建思考消息 (`_create_thinking_message`) 的接口给 `PFChatting` 调用。
* 运行兴趣监控循环 (`_interest_monitor_loop`)。
* **`InterestManager` / `InterestChatting`**:
* `InterestManager` 存储每个 `stream_id``InterestChatting` 实例。
* `InterestChatting` 负责计算兴趣衰减和回复概率。
* `HeartFC_Chat` 查询 `InterestChatting.should_evaluate_reply()` 来决定是否触发 `PFChatting`
* **`heartflow` / `sub_heartflow`**:
* `PFChatting` 从对应的 `sub_heartflow` 获取进行规划所需的核心上下文信息 (观察、思考链等)。
* **`MessageManager` / `MessageSender`**:
* 接收来自 `HeartFC_Chat` 的发送请求 (思考消息、文本消息、表情包消息)。
* 管理消息队列 (`MessageContainer`),处理消息发送间隔和实际发送 (`MessageSender`)。
* **`ResponseGenerator` (`gpt`)**:
*`PFChatting` 的 Replier 部分调用,用于生成回复文本。
* **`MessageStorage`**:
* 存储所有接收和发送的消息。
* **`HippocampusManager`**:
* `HeartFC_Processor` 使用它计算传入消息的记忆激活率,作为兴趣度计算的输入之一。
## 4. 原有问题与状态更新
1. **每个 `pfchating` 是否对应一个 `chat_stream`,是否是唯一的?**
* **是**`HeartFC_Chat._get_or_create_heartFC_chat` 确保了每个 `stream_id` 只有一个 `PFChatting` 实例。 (已确认)
2. **`observe_text` 传入进来是纯 str是不是应该传进来 message 构成的 list?**
* **机制已改变**。当前的触发机制是基于 `InterestManager` 的概率判断。`PFChatting` 启动后,应从其关联的 `sub_heartflow` 获取更丰富的上下文信息,而非简单的 `observe_text`
3. **检查失败的回复应该怎么处理?**
* **暂定:抛弃**。这是当前 Checker 逻辑的基础设定。
4. **如何比较相似度?**
* **待实现**。Checker 需要具体的算法来比较候选回复与新消息的相似度。
5. **Planner 怎么写?**
* **待实现**。这是 `PFChatting` 的核心决策逻辑,需要结合 `sub_heartflow` 的输出、LLM 工具调用和个性化配置来设计。
## 6. 未来优化点
* 实现 Checker 中的相似度比较算法。
* 详细设计并实现 Planner 的决策逻辑,包括 LLM 工具调用和个性化。
* 确认并完善 `PFChatting._initialize()` 中的历史消息加载逻辑。
* 探索更优的检查失败回复处理策略(例如:重新规划、修改回复等)。
* 优化 `PFChatting``sub_heartflow` 的信息交互。
BUG:
2.复读可能是planner还未校准好
3.planner还未个性化需要加入bot个性信息且获取的聊天内容有问题

View File

@@ -1,11 +0,0 @@
更新:
把聊天控制移动到心流下吧
首先心流要根据日程以及当前状况判定总体状态MaiStateInfo
然后根据每个子心流的运行情况给子心流分配聊天资源ChatStateInfoABSENT CHAT 或者 FOCUS
子心流负责根据状态进行执行
1.将interest.py进行拆分class InterestChatting 将会在 sub_heartflow中声明每个sub_heartflow都会所属一个InterestChatting
class InterestManager 将会在heartflow中声明成为heartflow的一个组件伴随heartflow产生

View File

@@ -1,4 +1,4 @@
from .sub_heartflow import SubHeartflow, ChattingObservation from src.heart_flow.sub_heartflow import SubHeartflow, ChattingObservation, ChatState
from src.plugins.moods.moods import MoodManager from src.plugins.moods.moods import MoodManager
from src.plugins.models.utils_model import LLMRequest from src.plugins.models.utils_model import LLMRequest
from src.config.config import global_config from src.config.config import global_config
@@ -9,7 +9,7 @@ from src.common.logger import get_module_logger, LogConfig, HEARTFLOW_STYLE_CONF
from src.individuality.individuality import Individuality from src.individuality.individuality import Individuality
import time import time
import random import random
from typing import Dict, Any, Optional from typing import Dict, Any, Optional, TYPE_CHECKING
import traceback import traceback
import enum import enum
import os # 新增 import os # 新增
@@ -23,6 +23,9 @@ heartflow_config = LogConfig(
) )
logger = get_module_logger("heartflow", config=heartflow_config) logger = get_module_logger("heartflow", config=heartflow_config)
# Type hinting for circular dependency
if TYPE_CHECKING:
from src.heart_flow.sub_heartflow import SubHeartflow, ChatState # Keep SubHeartflow here too
def init_prompt(): def init_prompt():
prompt = "" prompt = ""
@@ -53,6 +56,13 @@ INACTIVE_THRESHOLD_SECONDS = 1200 # 不活跃时间阈值 (例如20分钟) -
LOG_INTERVAL_SECONDS = 3 # 日志记录间隔 (例如3秒) - 保持与 interest.py 一致 LOG_INTERVAL_SECONDS = 3 # 日志记录间隔 (例如3秒) - 保持与 interest.py 一致
# --- 结束新增常量 --- # --- 结束新增常量 ---
# --- 新增:状态更新常量 ---
STATE_UPDATE_INTERVAL_SECONDS = 30 # 状态更新检查间隔(秒)
FIVE_MINUTES = 3 * 60
FIFTEEN_MINUTES = 6 * 60
TWENTY_MINUTES = 9 * 60
# --- 结束新增常量 ---
# 新增 ChatStatus 枚举 # 新增 ChatStatus 枚举
class MaiState(enum.Enum): class MaiState(enum.Enum):
@@ -92,10 +102,12 @@ class MaiState(enum.Enum):
class MaiStateInfo: class MaiStateInfo:
def __init__(self): def __init__(self):
self.current_state_info = ""
# 使用枚举类型初始化状态,默认为不在线 # 使用枚举类型初始化状态,默认为不在线
self.mai_status: MaiState = MaiState.OFFLINE self.mai_status: MaiState = MaiState.OFFLINE
self.mai_status_history = [] # 历史状态,包含 状态,最后时间
self.last_status_change_time: float = time.time() # 新增:状态最后改变时间
self.last_5min_check_time: float = time.time() # 新增上次5分钟规则检查时间
self.normal_chatting = [] self.normal_chatting = []
self.focused_chatting = [] self.focused_chatting = []
@@ -103,17 +115,21 @@ class MaiStateInfo:
self.mood_manager = MoodManager() self.mood_manager = MoodManager()
self.mood = self.mood_manager.get_prompt() self.mood = self.mood_manager.get_prompt()
def update_current_state_info(self):
self.current_state_info = self.mood_manager.get_current_mood()
# 新增更新聊天状态的方法 # 新增更新聊天状态的方法
def update_mai_status(self, new_status: MaiState): def update_mai_status(self, new_status: MaiState):
"""更新聊天状态""" """更新聊天状态"""
if isinstance(new_status, MaiState): if isinstance(new_status, MaiState) and new_status != self.mai_status: # 只有状态实际改变时才更新
self.mai_status = new_status self.mai_status = new_status
current_time = time.time()
self.last_status_change_time = current_time # 更新状态改变时间
self.last_5min_check_time = current_time # 重置5分钟检查计时器
# 将新状态和时间戳添加到历史记录
self.mai_status_history.append((new_status, current_time))
logger.info(f"麦麦状态更新为: {self.mai_status.value}") logger.info(f"麦麦状态更新为: {self.mai_status.value}")
else: elif not isinstance(new_status, MaiState):
logger.warning(f"尝试设置无效的麦麦状态: {new_status}") logger.warning(f"尝试设置无效的麦麦状态: {new_status}")
# else: # 状态未改变,不处理
# pass
class Heartflow: class Heartflow:
@@ -125,13 +141,14 @@ class Heartflow:
model=global_config.llm_heartflow, temperature=0.6, max_tokens=1000, request_type="heart_flow" model=global_config.llm_heartflow, temperature=0.6, max_tokens=1000, request_type="heart_flow"
) )
self._subheartflows: Dict[Any, SubHeartflow] = {} self._subheartflows: Dict[Any, 'SubHeartflow'] = {} # Update type hint
# --- 新增:日志和清理相关属性 (从 InterestManager 移动) --- # --- 新增:日志和清理相关属性 (从 InterestManager 移动) ---
self._history_log_file_path = os.path.join(LOG_DIRECTORY, HISTORY_LOG_FILENAME) self._history_log_file_path = os.path.join(LOG_DIRECTORY, HISTORY_LOG_FILENAME)
self._ensure_log_directory() # 初始化时确保目录存在 self._ensure_log_directory() # 初始化时确保目录存在
self._cleanup_task: Optional[asyncio.Task] = None self._cleanup_task: Optional[asyncio.Task] = None
self._logging_task: Optional[asyncio.Task] = None self._logging_task: Optional[asyncio.Task] = None
self._state_update_task: Optional[asyncio.Task] = None # 新增:状态更新任务
# 注意:衰减任务 (_decay_task) 不再需要,衰减在 SubHeartflow 的 InterestChatting 内部处理 # 注意:衰减任务 (_decay_task) 不再需要,衰减在 SubHeartflow 的 InterestChatting 内部处理
# --- 结束新增属性 --- # --- 结束新增属性 ---
@@ -203,6 +220,107 @@ class Heartflow:
logger.error(f"[Heartflow] Unexpected error during periodic history logging: {e}") logger.error(f"[Heartflow] Unexpected error during periodic history logging: {e}")
logger.error(traceback.format_exc()) # 记录 traceback logger.error(traceback.format_exc()) # 记录 traceback
async def _periodic_state_update_task(self):
"""定期检查并更新 Mai 状态"""
while True:
await asyncio.sleep(STATE_UPDATE_INTERVAL_SECONDS)
try:
current_time = time.time()
# 获取更新前的状态
previous_status = self.current_state.mai_status
current_status = self.current_state.mai_status # 保持此行以进行后续逻辑
time_in_current_status = current_time - self.current_state.last_status_change_time
time_since_last_5min_check = current_time - self.current_state.last_5min_check_time
next_state = None # 预设下一状态为 None
# --- 状态转换逻辑 (保持不变) ---
# 1. 通用规则每5分钟检查 (对于非 OFFLINE 状态)
if time_since_last_5min_check >= FIVE_MINUTES:
self.current_state.last_5min_check_time = current_time # 重置5分钟检查计时器无论是否切换
if current_status != MaiState.OFFLINE:
if random.random() < 0.10: # 10% 概率切换到 OFFLINE
logger.debug(f"[Heartflow State] 触发5分钟规则{current_status.value} 切换到 OFFLINE")
next_state = MaiState.OFFLINE # 设置 next_state 而不是直接更新
# self.current_state.update_mai_status(MaiState.OFFLINE)
# continue # 状态已改变,进入下一轮循环
# 2. 状态持续时间规则 (仅在未被5分钟规则覆盖时执行)
if next_state is None: # 仅当5分钟规则未触发切换时检查持续时间
if current_status == MaiState.OFFLINE:
# OFFLINE 状态下检查是否已持续5分钟
if time_in_current_status >= FIVE_MINUTES:
weights = [35, 35, 30]
choices_list = [MaiState.PEEKING, MaiState.NORMAL_CHAT, MaiState.OFFLINE]
next_state_candidate = random.choices(choices_list, weights=weights, k=1)[0]
if next_state_candidate != MaiState.OFFLINE:
next_state = next_state_candidate
logger.debug(f"[Heartflow State] OFFLINE 持续时间达到,切换到 {next_state.value}")
else:
# 保持 OFFLINE重置计时器以开始新的5分钟计时
logger.debug(f"[Heartflow State] OFFLINE 持续时间达到,保持 OFFLINE重置计时器")
self.current_state.last_status_change_time = current_time
self.current_state.last_5min_check_time = current_time # 保持一致
# 显式将 next_state 设为 OFFLINE 以便后续处理
next_state = MaiState.OFFLINE
elif current_status == MaiState.PEEKING:
if time_in_current_status >= FIVE_MINUTES: # PEEKING 最多持续 5 分钟
weights = [50, 30, 20]
choices_list = [MaiState.OFFLINE, MaiState.NORMAL_CHAT, MaiState.FOCUSED_CHAT]
next_state = random.choices(choices_list, weights=weights, k=1)[0]
logger.debug(f"[Heartflow State] PEEKING 持续时间达到,切换到 {next_state.value}")
elif current_status == MaiState.NORMAL_CHAT:
if time_in_current_status >= FIFTEEN_MINUTES: # NORMAL_CHAT 最多持续 15 分钟
weights = [50, 50]
choices_list = [MaiState.OFFLINE, MaiState.FOCUSED_CHAT]
next_state = random.choices(choices_list, weights=weights, k=1)[0]
logger.debug(f"[Heartflow State] NORMAL_CHAT 持续时间达到,切换到 {next_state.value}")
elif current_status == MaiState.FOCUSED_CHAT:
if time_in_current_status >= TWENTY_MINUTES: # FOCUSED_CHAT 最多持续 20 分钟
weights = [80, 20]
choices_list = [MaiState.OFFLINE, MaiState.NORMAL_CHAT]
next_state = random.choices(choices_list, weights=weights, k=1)[0]
logger.debug(f"[Heartflow State] FOCUSED_CHAT 持续时间达到,切换到 {next_state.value}")
# --- 状态转换逻辑结束 ---
# --- 更新状态并执行相关操作 --- #
if next_state is not None:
# 检查状态是否真的改变了
if next_state != previous_status:
logger.info(f"[Heartflow] 准备从 {previous_status.value} 转换状态到 {next_state.value}")
self.current_state.update_mai_status(next_state)
# 在状态改变后,强制执行子心流数量限制 (保持)
await self._enforce_subheartflow_limits(next_state)
# --- 新增逻辑:根据状态转换调整子心流 --- #
if previous_status == MaiState.OFFLINE and next_state != MaiState.OFFLINE:
logger.info(f"[Heartflow] 主状态从 OFFLINE 激活,尝试激活子心流到 CHAT 状态。")
await self._activate_random_subflows_to_chat(next_state)
elif next_state == MaiState.OFFLINE and previous_status != MaiState.OFFLINE:
logger.info(f"[Heartflow] 主状态变为 OFFLINE停用所有子心流活动。")
await self._deactivate_all_subflows_on_offline()
# --- 结束新增逻辑 --- #
elif next_state == MaiState.OFFLINE and previous_status == MaiState.OFFLINE:
# 如果决定保持 OFFLINE 状态(例如,因为随机选择或持续时间规则),并且之前已经是 OFFLINE
# 确保计时器被重置 (这在上面的持续时间规则中已处理,但为了清晰再次确认)
if time_in_current_status >= FIVE_MINUTES:
# 确保计时器已在上面重置,这里无需操作,只记录日志
logger.debug(f"[Heartflow State] 保持 OFFLINE 状态,计时器已重置。")
pass # 无需状态转换,也无需调用激活/停用逻辑
# --- 如果没有确定 next_state (即没有触发任何切换规则) --- #
# logger.debug(f"[Heartflow State] 状态未改变,保持 {current_status.value}") # 减少日志噪音
except Exception as e:
logger.error(f"[Heartflow] 状态更新任务出错: {e}")
logger.error(traceback.format_exc())
logger.info(f"当前状态:{self.current_state.mai_status.value}")
def get_all_interest_states(self) -> Dict[str, Dict]: # 新增方法 def get_all_interest_states(self) -> Dict[str, Dict]: # 新增方法
"""获取所有活跃子心流的当前兴趣状态""" """获取所有活跃子心流的当前兴趣状态"""
states = {} states = {}
@@ -266,16 +384,6 @@ class Heartflow:
# logger.info(f"[Heartflow] 清理完成。没有流符合移除条件。当前数量: {initial_count}") # 减少日志噪音 # logger.info(f"[Heartflow] 清理完成。没有流符合移除条件。当前数量: {initial_count}") # 减少日志噪音
pass pass
async def _sub_heartflow_update(self): # 这个任务目前作用不大,可以考虑移除或赋予新职责
while True:
# 检查是否存在子心流
if not self._subheartflows:
# logger.info("当前没有子心流,等待新的子心流创建...")
await asyncio.sleep(30) # 短暂休眠
continue
# 当前无实际操作,只是等待
await asyncio.sleep(300)
async def heartflow_start_working(self): async def heartflow_start_working(self):
# 启动清理任务 (使用新的 periodic_cleanup_task) # 启动清理任务 (使用新的 periodic_cleanup_task)
@@ -299,8 +407,14 @@ class Heartflow:
else: else:
logger.warning("[Heartflow] 跳过创建日志任务: 任务已在运行或存在。") logger.warning("[Heartflow] 跳过创建日志任务: 任务已在运行或存在。")
# (可选) 启动旧的子心流更新任务,如果它还有用的话 # 新增:启动状态更新任务
# asyncio.create_task(self._sub_heartflow_update()) if self._state_update_task is None or self._state_update_task.done():
self._state_update_task = asyncio.create_task(self._periodic_state_update_task())
logger.info(f"[Heartflow] 已创建定期状态更新任务。间隔: {STATE_UPDATE_INTERVAL_SECONDS}s")
else:
logger.warning("[Heartflow] 跳过创建状态更新任务: 任务已在运行或存在。")
@staticmethod @staticmethod
async def _update_current_state(): async def _update_current_state():
@@ -308,7 +422,6 @@ class Heartflow:
async def do_a_thinking(self): async def do_a_thinking(self):
# logger.debug("麦麦大脑袋转起来了") # logger.debug("麦麦大脑袋转起来了")
self.current_state.update_current_state_info()
# 开始构建prompt # 开始构建prompt
prompt_personality = "" prompt_personality = ""
@@ -426,22 +539,50 @@ class Heartflow:
logger.error(traceback.format_exc()) logger.error(traceback.format_exc())
return "(想法汇总时发生错误...)" return "(想法汇总时发生错误...)"
async def create_subheartflow(self, subheartflow_id: Any) -> Optional[SubHeartflow]: # --- Add helper method to count subflows by state --- #
def count_subflows_by_state(self, target_state: 'ChatState') -> int:
"""Counts the number of subheartflows currently in the specified state."""
count = 0
# Use items() directly for read-only iteration if thread safety isn't a major concern here
# Or create snapshot if modification during iteration is possible elsewhere
items_snapshot = list(self._subheartflows.items())
for _, flow in items_snapshot:
# Check if flow still exists in the main dict in case it was removed concurrently
if flow.subheartflow_id in self._subheartflows and flow.chat_state.chat_status == target_state:
count += 1
return count
# --- End helper method --- #
async def create_subheartflow(self, subheartflow_id: Any) -> Optional['SubHeartflow']:
""" """
获取或创建一个新的SubHeartflow实例。 获取或创建一个新的SubHeartflow实例。
(主要逻辑不变InterestChatting 现在在 SubHeartflow 内部创建) 创建本身不受限因为初始状态是ABSENT。
限制将在状态转换时检查。
""" """
# --- 移除创建前的限制检查 --- #
# current_mai_state = self.current_state.mai_status
# normal_limit = current_mai_state.get_normal_chat_max_num()
# focused_limit = current_mai_state.get_focused_chat_max_num()
# total_limit = normal_limit + focused_limit
# current_active_count = 0
# items_snapshot = list(self._subheartflows.items())
# for _, flow in items_snapshot:
# if flow.chat_state.chat_status == ChatState.CHAT or flow.chat_state.chat_status == ChatState.FOCUSED:
# current_active_count += 1
# if current_active_count >= total_limit and total_limit > 0:
# stream_name = chat_manager.get_stream_name(subheartflow_id) or subheartflow_id
# logger.warning(f"[Heartflow Create] Skipped due to limit...")
# return None
# --- 结束移除 --- #
existing_subheartflow = self._subheartflows.get(subheartflow_id) existing_subheartflow = self._subheartflows.get(subheartflow_id)
if existing_subheartflow: if existing_subheartflow:
# 如果已存在,确保其 last_active_time 更新 (如果需要的话)
# existing_subheartflow.last_active_time = time.time() # 移除,活跃时间由实际操作更新
# logger.debug(f"[Heartflow] 返回已存在的 subheartflow: {subheartflow_id}")
return existing_subheartflow return existing_subheartflow
logger.info(f"[Heartflow] 尝试创建新的 subheartflow: {subheartflow_id}") logger.info(f"[Heartflow] 尝试创建新的 subheartflow: {subheartflow_id}")
try: try:
# 创建 SubHeartflow它内部会创建 InterestChatting # --- Pass 'self' (Heartflow instance) to SubHeartflow constructor --- #
subheartflow = SubHeartflow(subheartflow_id) subheartflow = SubHeartflow(subheartflow_id, self)
# 创建并初始化观察对象 # 创建并初始化观察对象
logger.debug(f"[Heartflow] 为 {subheartflow_id} 创建 observation") logger.debug(f"[Heartflow] 为 {subheartflow_id} 创建 observation")
@@ -464,7 +605,7 @@ class Heartflow:
logger.error(traceback.format_exc()) logger.error(traceback.format_exc())
return None return None
def get_subheartflow(self, observe_chat_id: Any) -> Optional[SubHeartflow]: def get_subheartflow(self, observe_chat_id: Any) -> Optional['SubHeartflow']:
"""获取指定ID的SubHeartflow实例""" """获取指定ID的SubHeartflow实例"""
return self._subheartflows.get(observe_chat_id) return self._subheartflows.get(observe_chat_id)
@@ -472,6 +613,182 @@ class Heartflow:
"""获取当前所有活跃的子心流的 ID 列表""" """获取当前所有活跃的子心流的 ID 列表"""
return list(self._subheartflows.keys()) return list(self._subheartflows.keys())
async def _stop_subheartflow(self, subheartflow_id: Any, reason: str):
"""停止并移除指定的子心流"""
if subheartflow_id in self._subheartflows:
subheartflow = self._subheartflows[subheartflow_id]
stream_name = chat_manager.get_stream_name(subheartflow_id) or subheartflow_id
logger.info(f"[Heartflow Limits] 停止子心流 {stream_name}. 原因: {reason}")
# 标记停止并取消任务
subheartflow.should_stop = True
task_to_cancel = subheartflow.task
if task_to_cancel and not task_to_cancel.done():
task_to_cancel.cancel()
logger.debug(f"[Heartflow Limits] 已取消子心流 {stream_name} 的后台任务")
# TODO: Ensure controller.stop_heartFC_chat is called if needed
from src.plugins.heartFC_chat.heartFC_controler import HeartFCController # Local import to avoid cycle
controller = HeartFCController.get_instance()
if controller and controller.is_heartFC_chat_active(subheartflow_id):
await controller.stop_heartFC_chat(subheartflow_id)
# 从字典移除
del self._subheartflows[subheartflow_id]
logger.debug(f"[Heartflow Limits] 已移除子心流: {stream_name}")
return True
return False
async def _enforce_subheartflow_limits(self, current_mai_state: MaiState):
"""根据当前的 MaiState 强制执行 SubHeartflow 数量限制"""
normal_limit = current_mai_state.get_normal_chat_max_num()
focused_limit = current_mai_state.get_focused_chat_max_num()
logger.debug(f"[Heartflow Limits] 执行限制检查。当前状态: {current_mai_state.value}, Normal上限: {normal_limit}, Focused上限: {focused_limit}")
# 分类并统计当前 subheartflows
normal_flows = []
focused_flows = []
other_flows = [] # e.g., ABSENT
# 创建快照以安全迭代
items_snapshot = list(self._subheartflows.items())
for flow_id, flow in items_snapshot:
# 确保 flow 实例仍然存在 (避免在迭代期间被其他任务移除)
if flow_id not in self._subheartflows:
continue
if flow.chat_state.chat_status == ChatState.CHAT:
normal_flows.append((flow_id, flow.last_active_time))
elif flow.chat_state.chat_status == ChatState.FOCUSED:
focused_flows.append((flow_id, flow.last_active_time))
else:
other_flows.append((flow_id, flow.last_active_time))
logger.debug(f"[Heartflow Limits] 当前计数 - Normal: {len(normal_flows)}, Focused: {len(focused_flows)}, Other: {len(other_flows)}")
stopped_count = 0
# 检查 Normal (CHAT) 限制
if len(normal_flows) > normal_limit:
excess_count = len(normal_flows) - normal_limit
logger.info(f"[Heartflow Limits] 检测到 Normal (CHAT) 状态超额 {excess_count} 个。上限: {normal_limit}")
# 按 last_active_time 升序排序 (最不活跃的在前)
normal_flows.sort(key=lambda item: item[1])
# 停止最不活跃的超额部分
for i in range(excess_count):
flow_id_to_stop = normal_flows[i][0]
if await self._stop_subheartflow(flow_id_to_stop, f"Normal (CHAT) 状态超出上限 ({normal_limit}),停止最不活跃的实例"):
stopped_count += 1
# 重新获取 focused_flows 列表,因为上面的停止操作可能已经改变了状态或移除了实例
focused_flows = []
items_snapshot_after_normal = list(self._subheartflows.items())
for flow_id, flow in items_snapshot_after_normal:
if flow_id not in self._subheartflows: continue # Double check
if flow.chat_state.chat_status == ChatState.FOCUSED:
focused_flows.append((flow_id, flow.last_active_time))
# 检查 Focused (FOCUSED) 限制
if len(focused_flows) > focused_limit:
excess_count = len(focused_flows) - focused_limit
logger.info(f"[Heartflow Limits] 检测到 Focused (FOCUSED) 状态超额 {excess_count} 个。上限: {focused_limit}")
# 按 last_active_time 升序排序
focused_flows.sort(key=lambda item: item[1])
# 停止最不活跃的超额部分
for i in range(excess_count):
flow_id_to_stop = focused_flows[i][0]
if await self._stop_subheartflow(flow_id_to_stop, f"Focused (FOCUSED) 状态超出上限 ({focused_limit}),停止最不活跃的实例"):
stopped_count += 1
if stopped_count > 0:
logger.info(f"[Heartflow Limits] 限制执行完成,共停止了 {stopped_count} 个子心流。当前总数: {len(self._subheartflows)}")
else:
logger.debug(f"[Heartflow Limits] 限制检查完成,无需停止子心流。当前总数: {len(self._subheartflows)}")
# --- 新增方法 --- #
async def _activate_random_subflows_to_chat(self, new_mai_state: MaiState):
"""当主状态从 OFFLINE 激活时,随机选择子心流进入 CHAT 状态"""
limit = new_mai_state.get_normal_chat_max_num()
if limit <= 0:
logger.info("[Heartflow Activate] 当前状态不允许 CHAT 子心流,跳过激活。")
return
# 使用快照进行迭代
all_flows_snapshot = list(self._subheartflows.values())
absent_flows = [flow for flow in all_flows_snapshot if flow.subheartflow_id in self._subheartflows and flow.chat_state.chat_status == ChatState.ABSENT]
num_to_activate = min(limit, len(absent_flows))
if num_to_activate <= 0:
logger.info(f"[Heartflow Activate] 没有处于 ABSENT 状态的子心流可供激活至 CHAT (上限: {limit})。")
return
logger.info(f"[Heartflow Activate] 将随机选择 {num_to_activate} 个 (上限 {limit}) ABSENT 子心流激活至 CHAT 状态。")
selected_flows = random.sample(absent_flows, num_to_activate)
activated_count = 0
for flow in selected_flows:
# 再次检查 flow 是否仍然存在且状态为 ABSENT (以防并发修改)
if flow.subheartflow_id in self._subheartflows and self._subheartflows[flow.subheartflow_id].chat_state.chat_status == ChatState.ABSENT:
stream_name = chat_manager.get_stream_name(flow.subheartflow_id) or flow.subheartflow_id
logger.debug(f"[Heartflow Activate] 正在将子心流 {stream_name} 状态设置为 CHAT。")
# 调用 set_chat_state它内部会处理日志记录
flow.set_chat_state(ChatState.CHAT)
activated_count += 1
else:
stream_name = chat_manager.get_stream_name(flow.subheartflow_id) or flow.subheartflow_id
logger.warning(f"[Heartflow Activate] 跳过激活子心流 {stream_name},因为它不再存在或状态已改变。")
logger.info(f"[Heartflow Activate] 完成激活,成功将 {activated_count} 个子心流设置为 CHAT 状态。")
async def _deactivate_all_subflows_on_offline(self):
"""当主状态变为 OFFLINE 时,停止所有子心流的活动并设置为 ABSENT"""
logger.info("[Heartflow Deactivate] 开始停用所有子心流...")
try:
from src.plugins.heartFC_chat.heartFC_controler import HeartFCController # 本地导入避免循环依赖
controller = HeartFCController.get_instance()
except ImportError:
logger.warning("[Heartflow Deactivate] 无法导入 HeartFCController将跳过停止 heartFC_chat。")
controller = None
except Exception as e:
logger.error(f"[Heartflow Deactivate] 获取 HeartFCController 实例时出错: {e}")
controller = None
# 使用 ID 快照进行迭代
flow_ids_snapshot = list(self._subheartflows.keys())
deactivated_count = 0
for flow_id in flow_ids_snapshot:
subflow = self._subheartflows.get(flow_id)
if not subflow:
continue # Subflow 可能在迭代过程中被清理
stream_name = chat_manager.get_stream_name(flow_id) or flow_id
try:
# 停止相关聊天进程 (例如 pf_chat)
if controller:
# TODO: 确认是否有 reason_chat 需要停止,并添加相应逻辑
if controller.is_heartFC_chat_active(flow_id):
logger.debug(f"[Heartflow Deactivate] 正在停止子心流 {stream_name} 的 heartFC_chat。")
await controller.stop_heartFC_chat(flow_id)
# 设置状态为 ABSENT
if subflow.chat_state.chat_status != ChatState.ABSENT:
logger.debug(f"[Heartflow Deactivate] 正在将子心流 {stream_name} 状态设置为 ABSENT。")
# 调用 set_chat_state它会处理日志和状态更新
subflow.set_chat_state(ChatState.ABSENT)
deactivated_count += 1
else:
# 如果已经是 ABSENT则无需再次设置但记录一下检查
logger.trace(f"[Heartflow Deactivate] 子心流 {stream_name} 已处于 ABSENT 状态。")
except Exception as e:
logger.error(f"[Heartflow Deactivate] 停用子心流 {stream_name} 时出错: {e}")
logger.error(traceback.format_exc())
logger.info(f"[Heartflow Deactivate] 完成停用,共将 {deactivated_count} 个子心流设置为 ABSENT 状态 (不包括已是 ABSENT 的)。")
# --- 结束新增方法 --- #
init_prompt() init_prompt()
# 创建一个全局的管理器实例 # 创建一个全局的管理器实例

View File

@@ -78,8 +78,6 @@ class ChattingObservation(Observation):
return self.talking_message_str return self.talking_message_str
async def observe(self): async def observe(self):
# 查找新消息,最多获取 self.max_now_obs_len 条
print("2222222222222222221111111111111111开始观察")
new_messages_list = get_raw_msg_by_timestamp_with_chat( new_messages_list = get_raw_msg_by_timestamp_with_chat(
chat_id=self.chat_id, chat_id=self.chat_id,
timestamp_start=self.last_observe_time, timestamp_start=self.last_observe_time,
@@ -87,8 +85,8 @@ class ChattingObservation(Observation):
limit=self.max_now_obs_len, limit=self.max_now_obs_len,
limit_mode="latest", limit_mode="latest",
) )
print(f"2222222222222222221111111111111111获取到新消息{len(new_messages_list)}")
if new_messages_list: # 检查列表是否为空 if new_messages_list: # 检查列表是否为空
last_obs_time_mark = self.last_observe_time
self.last_observe_time = new_messages_list[-1]["time"] self.last_observe_time = new_messages_list[-1]["time"]
self.talking_message.extend(new_messages_list) self.talking_message.extend(new_messages_list)
@@ -98,7 +96,11 @@ class ChattingObservation(Observation):
oldest_messages = self.talking_message[:messages_to_remove_count] oldest_messages = self.talking_message[:messages_to_remove_count]
self.talking_message = self.talking_message[messages_to_remove_count:] # 保留后半部分,即最新的 self.talking_message = self.talking_message[messages_to_remove_count:] # 保留后半部分,即最新的
oldest_messages_str = await build_readable_messages(oldest_messages) oldest_messages_str = await build_readable_messages(
messages=oldest_messages,
timestamp_mode="normal",
read_mark=last_obs_time_mark,
)
# 调用 LLM 总结主题 # 调用 LLM 总结主题
prompt = ( prompt = (
@@ -134,10 +136,7 @@ class ChattingObservation(Observation):
f"距离现在{time_diff}分钟前(聊天记录id:{mid_memory_item['id']}){mid_memory_item['theme']}\n" f"距离现在{time_diff}分钟前(聊天记录id:{mid_memory_item['id']}){mid_memory_item['theme']}\n"
) )
self.mid_memory_info = mid_memory_str self.mid_memory_info = mid_memory_str
# except Exception as e: # 将异常处理移至此处以覆盖整个总结过程
# logger.error(f"处理和总结旧消息时出错 for chat {self.chat_id}: {e}")
# traceback.print_exc() # 记录详细堆栈
# print(f"处理后self.talking_message{self.talking_message}")
self.talking_message_str = await build_readable_messages(messages=self.talking_message, timestamp_mode="normal") self.talking_message_str = await build_readable_messages(messages=self.talking_message, timestamp_mode="normal")

View File

@@ -4,7 +4,7 @@ from src.plugins.moods.moods import MoodManager
from src.plugins.models.utils_model import LLMRequest from src.plugins.models.utils_model import LLMRequest
from src.config.config import global_config from src.config.config import global_config
import time import time
from typing import Optional, List, Dict from typing import Optional, List, Dict, Callable, TYPE_CHECKING
import traceback import traceback
from src.plugins.chat.utils import parse_text_timestamps from src.plugins.chat.utils import parse_text_timestamps
import enum import enum
@@ -14,8 +14,14 @@ import random
from src.plugins.person_info.relationship_manager import relationship_manager from src.plugins.person_info.relationship_manager import relationship_manager
from ..plugins.utils.prompt_builder import Prompt, global_prompt_manager from ..plugins.utils.prompt_builder import Prompt, global_prompt_manager
from src.plugins.chat.message import MessageRecv from src.plugins.chat.message import MessageRecv
from src.plugins.chat.chat_stream import chat_manager
import math import math
# Type hinting for circular dependency
if TYPE_CHECKING:
from .heartflow import Heartflow, MaiState # Import Heartflow for type hinting
from .sub_heartflow import ChatState # Keep ChatState here too?
# 定义常量 (从 interest.py 移动过来) # 定义常量 (从 interest.py 移动过来)
MAX_INTEREST = 15.0 MAX_INTEREST = 15.0
@@ -68,9 +74,6 @@ class ChatStateInfo:
self.mood_manager = MoodManager() self.mood_manager = MoodManager()
self.mood = self.mood_manager.get_prompt() self.mood = self.mood_manager.get_prompt()
def update_chat_state_info(self):
self.chat_state_info = self.mood_manager.get_current_mood()
base_reply_probability = 0.05 base_reply_probability = 0.05
probability_increase_rate_per_second = 0.08 probability_increase_rate_per_second = 0.08
@@ -87,6 +90,7 @@ class InterestChatting:
increase_rate=probability_increase_rate_per_second, increase_rate=probability_increase_rate_per_second,
decay_factor=global_config.probability_decay_factor_per_second, decay_factor=global_config.probability_decay_factor_per_second,
max_probability=max_reply_probability, max_probability=max_reply_probability,
state_change_callback: Optional[Callable[[ChatState], None]] = None
): ):
self.interest_level: float = 0.0 self.interest_level: float = 0.0
self.last_update_time: float = time.time() self.last_update_time: float = time.time()
@@ -101,6 +105,7 @@ class InterestChatting:
self.max_reply_probability: float = max_probability self.max_reply_probability: float = max_probability
self.current_reply_probability: float = 0.0 self.current_reply_probability: float = 0.0
self.is_above_threshold: bool = False self.is_above_threshold: bool = False
self.state_change_callback = state_change_callback
self.interest_dict: Dict[str, tuple[MessageRecv, float, bool]] = {} self.interest_dict: Dict[str, tuple[MessageRecv, float, bool]] = {}
@@ -144,6 +149,7 @@ class InterestChatting:
return return
currently_above = self.interest_level >= self.trigger_threshold currently_above = self.interest_level >= self.trigger_threshold
previous_is_above = self.is_above_threshold
if currently_above: if currently_above:
if not self.is_above_threshold: if not self.is_above_threshold:
@@ -158,6 +164,13 @@ class InterestChatting:
self.current_reply_probability = min(self.current_reply_probability, self.max_reply_probability) self.current_reply_probability = min(self.current_reply_probability, self.max_reply_probability)
else: else:
if previous_is_above:
if self.state_change_callback:
try:
self.state_change_callback(ChatState.ABSENT)
except Exception as e:
interest_logger.error(f"Error calling state_change_callback for ABSENT: {e}")
if 0 < self.probability_decay_factor < 1: if 0 < self.probability_decay_factor < 1:
decay_multiplier = math.pow(self.probability_decay_factor, time_delta) decay_multiplier = math.pow(self.probability_decay_factor, time_delta)
self.current_reply_probability *= decay_multiplier self.current_reply_probability *= decay_multiplier
@@ -216,14 +229,15 @@ class InterestChatting:
class SubHeartflow: class SubHeartflow:
def __init__(self, subheartflow_id): def __init__(self, subheartflow_id, parent_heartflow: 'Heartflow'):
self.subheartflow_id = subheartflow_id self.subheartflow_id = subheartflow_id
self.parent_heartflow = parent_heartflow
self.current_mind = "你什么也没想" self.current_mind = "你什么也没想"
self.past_mind = [] self.past_mind = []
self.chat_state: ChatStateInfo = ChatStateInfo() self.chat_state: ChatStateInfo = ChatStateInfo()
self.interest_chatting = InterestChatting() self.interest_chatting = InterestChatting(state_change_callback=self.set_chat_state)
self.llm_model = LLMRequest( self.llm_model = LLMRequest(
model=global_config.llm_sub_heartflow, model=global_config.llm_sub_heartflow,
@@ -234,33 +248,58 @@ class SubHeartflow:
self.main_heartflow_info = "" self.main_heartflow_info = ""
self.last_active_time = time.time() # 添加最后激活时间 self.last_active_time = time.time()
self.should_stop = False # 添加停止标志 self.should_stop = False
self.task: Optional[asyncio.Task] = None # 添加 task 属性 self.task: Optional[asyncio.Task] = None
self.is_active = False self.is_active = False
self.observations: List[ChattingObservation] = [] # 使用 List 类型提示 self.observations: List[ChattingObservation] = []
self.running_knowledges = [] self.running_knowledges = []
self.bot_name = global_config.BOT_NICKNAME self.bot_name = global_config.BOT_NICKNAME
logger.info(f"SubHeartflow {self.subheartflow_id} created with initial state: {self.chat_state.chat_status.value}")
def set_chat_state(self, new_state: 'ChatState'):
"""更新sub_heartflow的聊天状态"""
current_state = self.chat_state.chat_status
if current_state == new_state:
return # No change needed
log_prefix = f"[{chat_manager.get_stream_name(self.subheartflow_id) or self.subheartflow_id}]"
# --- Limit Check before entering CHAT state --- #
if new_state == ChatState.CHAT:
current_mai_state = self.parent_heartflow.current_state.mai_status
normal_limit = current_mai_state.get_normal_chat_max_num()
current_chat_count = self.parent_heartflow.count_subflows_by_state(ChatState.CHAT)
if current_chat_count >= normal_limit:
logger.debug(f"{log_prefix} 拒绝从 {current_state.value} 转换到 CHAT。原因CHAT 状态已达上限 ({normal_limit})。当前数量: {current_chat_count}")
return # Block the state transition
else:
logger.debug(f"{log_prefix} 允许从 {current_state.value} 转换到 CHAT (上限: {normal_limit}, 当前: {current_chat_count})" )
# 如果检查通过或目标状态不是CHAT则进行状态变更
self.chat_state.chat_status = new_state
# 状态变更时更新最后活跃时间
self.last_active_time = time.time()
logger.info(f"{log_prefix} 聊天状态从 {current_state.value} 变更为 {new_state.value}")
# TODO: 考虑从FOCUSED状态转出时是否需要停止PFChatting
# 这部分逻辑可能更适合放在Heartflow的_stop_subheartflow或HeartFCController的循环中处理
async def subheartflow_start_working(self): async def subheartflow_start_working(self):
while True: while True:
# --- 调整后台任务逻辑 --- #
# 这个后台循环现在主要负责检查是否需要自我销毁
# 不再主动进行思考或状态更新,这些由 HeartFC_Chat 驱动
# 检查是否被主心流标记为停止
if self.should_stop: if self.should_stop:
logger.info(f"子心流 {self.subheartflow_id} 被标记为停止,正在退出后台任务...") logger.info(f"子心流 {self.subheartflow_id} 被标记为停止,正在退出后台任务...")
break # 退出循环以停止任务 break
await asyncio.sleep(global_config.sub_heart_flow_update_interval) # 定期检查销毁条件 await asyncio.sleep(global_config.sub_heart_flow_update_interval)
async def ensure_observed(self): async def ensure_observed(self):
"""确保在思考前执行了观察"""
observation = self._get_primary_observation() observation = self._get_primary_observation()
if observation: if observation:
try: try:
@@ -273,18 +312,14 @@ class SubHeartflow:
async def do_thinking_before_reply( async def do_thinking_before_reply(
self, self,
extra_info: str, extra_info: str,
obs_id: list[str] = None, # 修改 obs_id 类型为 list[str] obs_id: list[str] = None,
): ):
# --- 在思考前确保观察已执行 --- # self.last_active_time = time.time()
# await self.ensure_observed()
self.last_active_time = time.time() # 更新最后激活时间戳
current_thinking_info = self.current_mind current_thinking_info = self.current_mind
mood_info = self.chat_state.mood mood_info = self.chat_state.mood
observation = self._get_primary_observation() observation = self._get_primary_observation()
# --- 获取观察信息 --- #
chat_observe_info = "" chat_observe_info = ""
if obs_id: if obs_id:
try: try:
@@ -294,12 +329,11 @@ class SubHeartflow:
logger.error( logger.error(
f"[{self.subheartflow_id}] Error getting observe info with IDs {obs_id}: {e}. Falling back." f"[{self.subheartflow_id}] Error getting observe info with IDs {obs_id}: {e}. Falling back."
) )
chat_observe_info = observation.get_observe_info() # 出错时回退到默认观察 chat_observe_info = observation.get_observe_info()
else: else:
chat_observe_info = observation.get_observe_info() chat_observe_info = observation.get_observe_info()
logger.debug(f"[{self.subheartflow_id}] Using default observation info.") # logger.debug(f"[{self.subheartflow_id}] Using default observation info.")
# --- 构建 Prompt (基本逻辑不变) --- #
extra_info_prompt = "" extra_info_prompt = ""
if extra_info: if extra_info:
for tool_name, tool_data in extra_info.items(): for tool_name, tool_data in extra_info.items():
@@ -307,28 +341,25 @@ class SubHeartflow:
for item in tool_data: for item in tool_data:
extra_info_prompt += f"- {item['name']}: {item['content']}\n" extra_info_prompt += f"- {item['name']}: {item['content']}\n"
else: else:
extra_info_prompt = "无工具信息。\n" # 提供默认值 extra_info_prompt = "无工具信息。\n"
individuality = Individuality.get_instance() individuality = Individuality.get_instance()
prompt_personality = f"你的名字是{self.bot_name},你" prompt_personality = f"你的名字是{self.bot_name},你"
prompt_personality += individuality.personality.personality_core prompt_personality += individuality.personality.personality_core
# 添加随机性格侧面
if individuality.personality.personality_sides: if individuality.personality.personality_sides:
random_side = random.choice(individuality.personality.personality_sides) random_side = random.choice(individuality.personality.personality_sides)
prompt_personality += f"{random_side}" prompt_personality += f"{random_side}"
# 添加随机身份细节
if individuality.identity.identity_detail: if individuality.identity.identity_detail:
random_detail = random.choice(individuality.identity.identity_detail) random_detail = random.choice(individuality.identity.identity_detail)
prompt_personality += f"{random_detail}" prompt_personality += f"{random_detail}"
time_now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) time_now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
# 创建局部Random对象避免影响全局随机状态
local_random = random.Random() local_random = random.Random()
current_minute = int(time.strftime("%M")) current_minute = int(time.strftime("%M"))
local_random.seed(current_minute) # 用分钟作为种子确保每分钟内选择一致 local_random.seed(current_minute)
hf_options = [ hf_options = [
("继续生成你在这个聊天中的想法,在原来想法的基础上继续思考", 0.7), ("继续生成你在这个聊天中的想法,在原来想法的基础上继续思考", 0.7),
@@ -343,7 +374,6 @@ class SubHeartflow:
prompt = (await global_prompt_manager.get_prompt_async("sub_heartflow_prompt_before")).format( prompt = (await global_prompt_manager.get_prompt_async("sub_heartflow_prompt_before")).format(
extra_info=extra_info_prompt, extra_info=extra_info_prompt,
# relation_prompt_all=relation_prompt_all,
prompt_personality=prompt_personality, prompt_personality=prompt_personality,
bot_name=self.bot_name, bot_name=self.bot_name,
current_thinking_info=current_thinking_info, current_thinking_info=current_thinking_info,
@@ -351,8 +381,6 @@ class SubHeartflow:
chat_observe_info=chat_observe_info, chat_observe_info=chat_observe_info,
mood_info=mood_info, mood_info=mood_info,
hf_do_next=hf_do_next, hf_do_next=hf_do_next,
# sender_name=sender_name_sign,
# message_txt=message_txt,
) )
prompt = await relationship_manager.convert_all_person_sign_to_person_name(prompt) prompt = await relationship_manager.convert_all_person_sign_to_person_name(prompt)
@@ -365,18 +393,15 @@ class SubHeartflow:
logger.debug(f"[{self.subheartflow_id}] 心流思考结果:\n{response}\n") logger.debug(f"[{self.subheartflow_id}] 心流思考结果:\n{response}\n")
if not response: # 如果 LLM 返回空,给一个默认想法 if not response:
response = "(不知道该想些什么...)" response = "(不知道该想些什么...)"
logger.warning(f"[{self.subheartflow_id}] LLM 返回空结果,思考失败。") logger.warning(f"[{self.subheartflow_id}] LLM 返回空结果,思考失败。")
except Exception as e: except Exception as e:
logger.error(f"[{self.subheartflow_id}] 内心独白获取失败: {e}") logger.error(f"[{self.subheartflow_id}] 内心独白获取失败: {e}")
response = "(思考时发生错误...)" # 错误时的默认想法 response = "(思考时发生错误...)"
self.update_current_mind(response) self.update_current_mind(response)
# self.current_mind 已经在 update_current_mind 中更新
# logger.info(f"[{self.subheartflow_id}] 思考前脑内状态:{self.current_mind}")
return self.current_mind, self.past_mind return self.current_mind, self.past_mind
def update_current_mind(self, response): def update_current_mind(self, response):
@@ -384,55 +409,41 @@ class SubHeartflow:
self.current_mind = response self.current_mind = response
def add_observation(self, observation: Observation): def add_observation(self, observation: Observation):
"""添加一个新的observation对象到列表中如果已存在相同id的observation则不添加"""
# 查找是否存在相同id的observation
for existing_obs in self.observations: for existing_obs in self.observations:
if existing_obs.observe_id == observation.observe_id: if existing_obs.observe_id == observation.observe_id:
# 如果找到相同id的observation直接返回
return return
# 如果没有找到相同id的observation则添加新的
self.observations.append(observation) self.observations.append(observation)
def remove_observation(self, observation: Observation): def remove_observation(self, observation: Observation):
"""从列表中移除一个observation对象"""
if observation in self.observations: if observation in self.observations:
self.observations.remove(observation) self.observations.remove(observation)
def get_all_observations(self) -> list[Observation]: def get_all_observations(self) -> list[Observation]:
"""获取所有observation对象"""
return self.observations return self.observations
def clear_observations(self): def clear_observations(self):
"""清空所有observation对象"""
self.observations.clear() self.observations.clear()
def _get_primary_observation(self) -> Optional[ChattingObservation]: def _get_primary_observation(self) -> Optional[ChattingObservation]:
"""获取主要的通常是第一个ChattingObservation实例"""
if self.observations and isinstance(self.observations[0], ChattingObservation): if self.observations and isinstance(self.observations[0], ChattingObservation):
return self.observations[0] return self.observations[0]
logger.warning(f"SubHeartflow {self.subheartflow_id} 没有找到有效的 ChattingObservation") logger.warning(f"SubHeartflow {self.subheartflow_id} 没有找到有效的 ChattingObservation")
return None return None
def get_interest_state(self) -> dict: def get_interest_state(self) -> dict:
"""获取当前兴趣状态"""
return self.interest_chatting.get_state() return self.interest_chatting.get_state()
def get_interest_level(self) -> float: def get_interest_level(self) -> float:
"""获取当前兴趣等级"""
return self.interest_chatting.get_interest() return self.interest_chatting.get_interest()
def should_evaluate_reply(self) -> bool: def should_evaluate_reply(self) -> bool:
"""判断是否应该评估回复"""
return self.interest_chatting.should_evaluate_reply() return self.interest_chatting.should_evaluate_reply()
def add_interest_dict_entry(self, message: MessageRecv, interest_value: float, is_mentioned: bool): def add_interest_dict_entry(self, message: MessageRecv, interest_value: float, is_mentioned: bool):
"""添加兴趣字典条目"""
self.interest_chatting.add_interest_dict(message, interest_value, is_mentioned) self.interest_chatting.add_interest_dict(message, interest_value, is_mentioned)
def get_interest_dict(self) -> Dict[str, tuple[MessageRecv, float, bool]]: def get_interest_dict(self) -> Dict[str, tuple[MessageRecv, float, bool]]:
"""获取兴趣字典"""
return self.interest_chatting.interest_dict return self.interest_chatting.interest_dict
init_prompt() init_prompt()
# subheartflow = SubHeartflow()

View File

@@ -112,7 +112,6 @@ class MainSystem:
logger.success("心流系统启动成功") logger.success("心流系统启动成功")
# 初始化并独立启动 HeartFCController # 初始化并独立启动 HeartFCController
HeartFCController()
heartfc_chat_instance = HeartFCController.get_instance() heartfc_chat_instance = HeartFCController.get_instance()
if heartfc_chat_instance: if heartfc_chat_instance:
await heartfc_chat_instance.start() await heartfc_chat_instance.start()

View File

@@ -6,7 +6,7 @@ from .chat_stream import chat_manager
from ..chat_module.only_process.only_message_process import MessageProcessor from ..chat_module.only_process.only_message_process import MessageProcessor
from src.common.logger import get_module_logger, CHAT_STYLE_CONFIG, LogConfig from src.common.logger import get_module_logger, CHAT_STYLE_CONFIG, LogConfig
from ..heartFC_chat.heartFC_processor import HeartFCProcessor from ..heartFC_chat.heartflow_processor import HeartFCProcessor
from ..utils.prompt_builder import Prompt, global_prompt_manager from ..utils.prompt_builder import Prompt, global_prompt_manager
import traceback import traceback
@@ -26,7 +26,7 @@ class ChatBot:
self.bot = None # bot 实例引用 self.bot = None # bot 实例引用
self._started = False self._started = False
self.mood_manager = MoodManager.get_instance() # 获取情绪管理器单例 self.mood_manager = MoodManager.get_instance() # 获取情绪管理器单例
self.heartFC_processor = HeartFCProcessor() # 新增 self.heartflow_processor = HeartFCProcessor() # 新增
# 创建初始化PFC管理器的任务会在_ensure_started时执行 # 创建初始化PFC管理器的任务会在_ensure_started时执行
self.only_process_chat = MessageProcessor() self.only_process_chat = MessageProcessor()
@@ -109,9 +109,9 @@ class ChatBot:
await self.only_process_chat.process_message(message) await self.only_process_chat.process_message(message)
await self._create_pfc_chat(message) await self._create_pfc_chat(message)
else: else:
await self.heartFC_processor.process_message(message_data) await self.heartflow_processor.process_message(message_data)
else: else:
await self.heartFC_processor.process_message(message_data) await self.heartflow_processor.process_message(message_data)
if template_group_name: if template_group_name:
async with global_prompt_manager.async_message_scope(template_group_name): async with global_prompt_manager.async_message_scope(template_group_name):

View File

@@ -218,7 +218,7 @@ class ImageManager:
"timestamp": timestamp, "timestamp": timestamp,
} }
db.images.update_one({"hash": image_hash}, {"$set": image_doc}, upsert=True) db.images.update_one({"hash": image_hash}, {"$set": image_doc}, upsert=True)
logger.success(f"保存图片: {file_path}") logger.trace(f"保存图片: {file_path}")
except Exception as e: except Exception as e:
logger.error(f"保存图片文件失败: {str(e)}") logger.error(f"保存图片文件失败: {str(e)}")

View File

@@ -203,7 +203,7 @@ class PFChatting:
self._processing_lock.release() self._processing_lock.release()
# Remove instance from controller's dict? Only if it's truly done. # Remove instance from controller's dict? Only if it's truly done.
# Consider if loop can be restarted vs instance destroyed. # Consider if loop can be restarted vs instance destroyed.
# asyncio.create_task(self.heartfc_controller._remove_pf_chatting_instance(self.stream_id)) # Example cleanup # asyncio.create_task(self.heartfc_controller._remove_heartFC_chat_instance(self.stream_id)) # Example cleanup
async def _run_pf_loop(self): async def _run_pf_loop(self):
""" """
@@ -268,7 +268,7 @@ class PFChatting:
# Continue to timer decrement and sleep # Continue to timer decrement and sleep
elif action == "text_reply": elif action == "text_reply":
logger.info(f"{log_prefix} PFChatting: 麦麦决定回复文本. 理由: {reasoning}") logger.debug(f"{log_prefix} PFChatting: 麦麦决定回复文本. 理由: {reasoning}")
action_taken_this_cycle = True action_taken_this_cycle = True
anchor_message = await self._get_anchor_message(observed_messages) anchor_message = await self._get_anchor_message(observed_messages)
if not anchor_message: if not anchor_message:
@@ -387,7 +387,7 @@ class PFChatting:
if timer_strings: # 如果有有效计时器数据才打印 if timer_strings: # 如果有有效计时器数据才打印
logger.debug( logger.debug(
f"{log_prefix} test testtesttesttesttesttesttesttesttesttest Cycle Timers: {'; '.join(timer_strings)}" f"{log_prefix} 该次决策耗时: {'; '.join(timer_strings)}"
) )
# --- Timer Decrement --- # # --- Timer Decrement --- #
@@ -580,30 +580,28 @@ class PFChatting:
""" """
try: try:
last_msg_dict = None # last_msg_dict = None
if observed_messages: # if observed_messages:
last_msg_dict = observed_messages[-1] # last_msg_dict = observed_messages[-1]
# if last_msg_dict:
# try:
# anchor_message = MessageRecv(last_msg_dict) # 移除 chat_stream 参数
# anchor_message.update_chat_stream(self.chat_stream) # 添加 update_chat_stream 调用
# if not (
# anchor_message
# and anchor_message.message_info
# and anchor_message.message_info.message_id
# and anchor_message.message_info.user_info
# ):
# raise ValueError("重构的 MessageRecv 缺少必要信息.")
# # logger.debug(f"{self._get_log_prefix()} 重构的锚点消息: ID={anchor_message.message_info.message_id}")
# return anchor_message
# except Exception as e_reconstruct:
# logger.warning(
# f"{self._get_log_prefix()} 从观察到的消息重构 MessageRecv 失败: {e_reconstruct}. 创建占位符."
# )
if last_msg_dict:
try:
# anchor_message = MessageRecv(last_msg_dict, chat_stream=self.chat_stream)
anchor_message = MessageRecv(last_msg_dict) # 移除 chat_stream 参数
anchor_message.update_chat_stream(self.chat_stream) # 添加 update_chat_stream 调用
if not (
anchor_message
and anchor_message.message_info
and anchor_message.message_info.message_id
and anchor_message.message_info.user_info
):
raise ValueError("重构的 MessageRecv 缺少必要信息.")
# logger.debug(f"{self._get_log_prefix()} 重构的锚点消息: ID={anchor_message.message_info.message_id}")
return anchor_message
except Exception as e_reconstruct:
logger.warning(
f"{self._get_log_prefix()} 从观察到的消息重构 MessageRecv 失败: {e_reconstruct}. 创建占位符."
)
# else:
# logger.warning(f"{self._get_log_prefix()} observed_messages 为空. 创建占位符锚点消息.")
# --- Create Placeholder --- # # --- Create Placeholder --- #
placeholder_id = f"mid_pf_{int(time.time() * 1000)}" placeholder_id = f"mid_pf_{int(time.time() * 1000)}"

View File

@@ -2,17 +2,17 @@ import traceback
from typing import Optional, Dict from typing import Optional, Dict
import asyncio import asyncio
import threading # 导入 threading import threading # 导入 threading
from ...moods.moods import MoodManager from ..moods.moods import MoodManager
from ...chat.emoji_manager import emoji_manager from ..chat.emoji_manager import emoji_manager
from .heartFC_generator import ResponseGenerator from .heartFC_generator import ResponseGenerator
from .messagesender import MessageManager from .heartflow_message_sender import MessageManager
from src.heart_flow.heartflow import heartflow from src.heart_flow.heartflow import heartflow, MaiState
from src.heart_flow.sub_heartflow import SubHeartflow, ChatState from src.heart_flow.sub_heartflow import SubHeartflow, ChatState
from src.common.logger import get_module_logger, CHAT_STYLE_CONFIG, LogConfig from src.common.logger import get_module_logger, CHAT_STYLE_CONFIG, LogConfig
from src.plugins.person_info.relationship_manager import relationship_manager from src.plugins.person_info.relationship_manager import relationship_manager
from src.do_tool.tool_use import ToolUser from src.do_tool.tool_use import ToolUser
from src.plugins.chat.chat_stream import chat_manager from src.plugins.chat.chat_stream import chat_manager
from .pf_chatting import PFChatting from .heartFC_chat import PFChatting
# 定义日志配置 # 定义日志配置
@@ -27,26 +27,21 @@ logger = get_module_logger("HeartFCController", config=chat_config)
INTEREST_MONITOR_INTERVAL_SECONDS = 1 INTEREST_MONITOR_INTERVAL_SECONDS = 1
# 合并后的版本:使用 __new__ + threading.Lock 实现线程安全单例,类名为 HeartFCController
class HeartFCController: class HeartFCController:
_instance = None _instance: Optional['HeartFCController'] = None
_lock = threading.Lock() # 使用 threading.Lock 保证 __new__ 线程安全 _lock = threading.Lock() # 用于保证 get_instance 线程安全
_initialized = False
def __new__(cls, *args, **kwargs):
if cls._instance is None:
with cls._lock:
# Double-checked locking
if cls._instance is None:
logger.debug("创建 HeartFCController 单例实例...")
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self): def __init__(self):
# 使用 _initialized 标志确保 __init__ 只执行一次 # __init__ 现在只会在 get_instance 首次创建实例时调用一次
if self._initialized: # 因此不再需要 _initialized 标志
# 检查是否已被初始化,防止意外重入 (虽然理论上不太可能)
# hasattr 检查通常比标志位稍慢,但在这里作为额外的安全措施
if hasattr(self, 'gpt'):
logger.warning("HeartFCController __init__ 被意外再次调用。")
return return
logger.debug("初始化 HeartFCController 单例实例...") # 更新日志信息
self.gpt = ResponseGenerator() self.gpt = ResponseGenerator()
self.mood_manager = MoodManager.get_instance() self.mood_manager = MoodManager.get_instance()
self.tool_user = ToolUser() self.tool_user = ToolUser()
@@ -54,37 +49,36 @@ class HeartFCController:
self.heartflow = heartflow self.heartflow = heartflow
self.pf_chatting_instances: Dict[str, PFChatting] = {} self.heartFC_chat_instances: Dict[str, PFChatting] = {}
self._pf_chatting_lock = asyncio.Lock() # 这个是 asyncio.Lock用于异步上下文 self._heartFC_chat_lock = asyncio.Lock()
self.emoji_manager = emoji_manager # 假设是全局或已初始化的实例 self.emoji_manager = emoji_manager
self.relationship_manager = relationship_manager # 假设是全局或已初始化的实例 self.relationship_manager = relationship_manager
self.MessageManager = MessageManager self.MessageManager = MessageManager
self._initialized = True
logger.info("HeartFCController 单例初始化完成。") logger.info("HeartFCController 单例初始化完成。")
@classmethod @classmethod
def get_instance(cls): def get_instance(cls):
"""获取 HeartFCController 的单例实例。""" """获取 HeartFCController 的单例实例。线程安全。"""
# 如果实例尚未创建,调用构造函数(这将触发 __new__ 和 __init__ # Double-checked locking
if cls._instance is None: if cls._instance is None:
# 在首次调用 get_instance 时创建实例。 with cls._lock:
# __new__ 中的锁会确保线程安全。 if cls._instance is None:
cls() logger.info("HeartFCController 实例不存在,正在创建...")
# 添加日志记录,说明实例是在 get_instance 调用时创建的 # 创建实例,这将自动调用 __init__ 一次
logger.info("HeartFCController 实例在首次 get_instance 时创建。") cls._instance = cls()
elif not cls._initialized: logger.info("HeartFCController 实例已创建并初始化。")
# 实例已创建但可能未初始化完成(理论上不太可能发生,除非 __init__ 异常) # else: # 不需要这个 else 日志,否则每次获取都会打印
logger.warning("HeartFCController 实例存在但尚未完成初始化。") # logger.debug("返回已存在的 HeartFCController 实例。")
return cls._instance return cls._instance
# --- 新增:检查 PFChatting 状态的方法 --- # # --- 新增:检查 PFChatting 状态的方法 --- #
def is_pf_chatting_active(self, stream_id: str) -> bool: def is_heartFC_chat_active(self, stream_id: str) -> bool:
"""检查指定 stream_id 的 PFChatting 循环是否处于活动状态。""" """检查指定 stream_id 的 PFChatting 循环是否处于活动状态。"""
# 注意:这里直接访问字典,不加锁,因为读取通常是安全的, # 注意:这里直接访问字典,不加锁,因为读取通常是安全的,
# 并且 PFChatting 实例的 _loop_active 状态由其自身的异步循环管理。 # 并且 PFChatting 实例的 _loop_active 状态由其自身的异步循环管理。
# 如果需要更强的保证,可以在访问 pf_instance 前获取 _pf_chatting_lock # 如果需要更强的保证,可以在访问 pf_instance 前获取 _heartFC_chat_lock
pf_instance = self.pf_chatting_instances.get(stream_id) pf_instance = self.heartFC_chat_instances.get(stream_id)
if pf_instance and pf_instance._loop_active: # 直接检查 PFChatting 实例的 _loop_active 属性 if pf_instance and pf_instance._loop_active: # 直接检查 PFChatting 实例的 _loop_active 属性
return True return True
return False return False
@@ -110,10 +104,10 @@ class HeartFCController:
logger.warning("跳过兴趣监控任务创建:任务已存在或正在运行。") logger.warning("跳过兴趣监控任务创建:任务已存在或正在运行。")
# --- Added PFChatting Instance Manager --- # --- Added PFChatting Instance Manager ---
async def _get_or_create_pf_chatting(self, stream_id: str) -> Optional[PFChatting]: async def _get_or_create_heartFC_chat(self, stream_id: str) -> Optional[PFChatting]:
"""获取现有PFChatting实例或创建新实例。""" """获取现有PFChatting实例或创建新实例。"""
async with self._pf_chatting_lock: async with self._heartFC_chat_lock:
if stream_id not in self.pf_chatting_instances: if stream_id not in self.heartFC_chat_instances:
logger.info(f"为流 {stream_id} 创建新的PFChatting实例") logger.info(f"为流 {stream_id} 创建新的PFChatting实例")
# 传递 self (HeartFCController 实例) 进行依赖注入 # 传递 self (HeartFCController 实例) 进行依赖注入
instance = PFChatting(stream_id, self) instance = PFChatting(stream_id, self)
@@ -121,8 +115,23 @@ class HeartFCController:
if not await instance._initialize(): if not await instance._initialize():
logger.error(f"为流 {stream_id} 初始化PFChatting失败") logger.error(f"为流 {stream_id} 初始化PFChatting失败")
return None return None
self.pf_chatting_instances[stream_id] = instance self.heartFC_chat_instances[stream_id] = instance
return self.pf_chatting_instances[stream_id] return self.heartFC_chat_instances[stream_id]
async def stop_heartFC_chat(self, stream_id: str):
"""尝试停止并清理指定 stream_id 的 PFChatting 实例。"""
async with self._heartFC_chat_lock:
pf_instance = self.heartFC_chat_instances.pop(stream_id, None) # 从字典中移除
if pf_instance:
stream_name = chat_manager.get_stream_name(stream_id) or stream_id
logger.info(f"[{stream_name}] 正在停止 PFChatting 实例...")
try:
await pf_instance.shutdown() # 调用实例的 shutdown 方法
logger.info(f"[{stream_name}] PFChatting 实例已停止。")
except Exception as e:
logger.error(f"[{stream_name}] 停止 PFChatting 实例时出错: {e}")
# else:
# logger.debug(f"[{stream_name}] 没有找到需要停止的 PFChatting 实例。")
async def _response_control_loop(self): async def _response_control_loop(self):
"""后台任务,定期检查兴趣度变化并触发回复""" """后台任务,定期检查兴趣度变化并触发回复"""
@@ -131,26 +140,48 @@ class HeartFCController:
await asyncio.sleep(INTEREST_MONITOR_INTERVAL_SECONDS) await asyncio.sleep(INTEREST_MONITOR_INTERVAL_SECONDS)
try: try:
# 从心流中获取活跃流 global_mai_state = self.heartflow.current_state.mai_status
active_stream_ids = list(self.heartflow.get_all_subheartflows_streams_ids()) active_stream_ids = list(self.heartflow.get_all_subheartflows_streams_ids())
for stream_id in active_stream_ids: for stream_id in active_stream_ids:
stream_name = chat_manager.get_stream_name(stream_id) or stream_id # 获取流名称 stream_name = chat_manager.get_stream_name(stream_id) or stream_id
sub_hf = self.heartflow.get_subheartflow(stream_id) sub_hf = self.heartflow.get_subheartflow(stream_id)
if not sub_hf: if not sub_hf:
logger.warning(f"监控循环: 无法获取活跃流 {stream_name} 的 sub_hf")
continue continue
should_trigger_hfc = False current_chat_state = sub_hf.chat_state.chat_status
try: log_prefix = f"[{stream_name}]"
interest_chatting = sub_hf.interest_chatting
should_trigger_hfc = interest_chatting.should_evaluate_reply()
except Exception as e: if global_mai_state == MaiState.OFFLINE:
logger.error(f"检查兴趣触发器时出错 流 {stream_name}: {e}") if current_chat_state == ChatState.FOCUSED:
logger.error(traceback.format_exc()) logger.warning(f"{log_prefix} Global state is OFFLINE, but SubHeartflow is FOCUSED. Stopping PFChatting.")
await self.stop_heartFC_chat(stream_id)
continue
if should_trigger_hfc: # --- 只有在全局状态允许时才执行以下逻辑 --- #
# 启动一次麦麦聊天 if current_chat_state == ChatState.CHAT:
should_evaluate = False
try:
should_evaluate = sub_hf.should_evaluate_reply()
except Exception as e:
logger.error(f"检查回复概率时出错 流 {stream_name}: {e}")
logger.error(traceback.format_exc())
if should_evaluate:
# --- Limit Check before entering FOCUSED state --- #
focused_limit = global_mai_state.get_focused_chat_max_num()
current_focused_count = self.heartflow.count_subflows_by_state(ChatState.FOCUSED)
if current_focused_count >= focused_limit:
logger.debug(f"{log_prefix} 拒绝从 CHAT 转换到 FOCUSED。原因FOCUSED 状态已达上限 ({focused_limit})。当前数量: {current_focused_count}")
# Do not change state, continue to next stream or cycle
else:
logger.info(f"{log_prefix} 兴趣概率触发,将状态从 CHAT 提升到 FOCUSED (全局状态: {global_mai_state.value}, 上限: {focused_limit}, 当前: {current_focused_count})")
sub_hf.set_chat_state(ChatState.FOCUSED)
# --- End Limit Check --- #
elif current_chat_state == ChatState.FOCUSED:
# logger.debug(f"[{stream_name}] State FOCUSED, triggering HFC (全局状态: {global_mai_state.value})...")
await self._trigger_hfc(sub_hf) await self._trigger_hfc(sub_hf)
except asyncio.CancelledError: except asyncio.CancelledError:
@@ -159,18 +190,29 @@ class HeartFCController:
except Exception as e: except Exception as e:
logger.error(f"兴趣监控循环错误: {e}") logger.error(f"兴趣监控循环错误: {e}")
logger.error(traceback.format_exc()) logger.error(traceback.format_exc())
await asyncio.sleep(5) # 发生错误时等待 await asyncio.sleep(5)
async def _trigger_hfc(self, sub_hf: SubHeartflow): async def _trigger_hfc(self, sub_hf: SubHeartflow):
chat_state = sub_hf.chat_state """仅当 SubHeartflow 状态为 FOCUSED 时,触发 PFChatting 的激活或时间增加。"""
if chat_state == ChatState.ABSENT: stream_id = sub_hf.subheartflow_id
chat_state = ChatState.CHAT stream_name = chat_manager.get_stream_name(stream_id) or stream_id # 获取流名称
elif chat_state == ChatState.CHAT:
chat_state = ChatState.FOCUSED
# 从 sub_hf 获取 stream_id # 首先检查状态
if chat_state == ChatState.FOCUSED: if sub_hf.chat_state.chat_status != ChatState.FOCUSED:
stream_id = sub_hf.subheartflow_id logger.warning(f"[{stream_name}] 尝试在非 FOCUSED 状态 ({sub_hf.chat_state.chat_status.value}) 下触发 HFC。已跳过。")
pf_instance = await self._get_or_create_pf_chatting(stream_id) return
if pf_instance: # 确保实例成功获取或创建
asyncio.create_task(pf_instance.add_time()) # 移除内部状态修改逻辑
# chat_state = sub_hf.chat_state
# if chat_state == ChatState.ABSENT:
# chat_state = ChatState.CHAT
# elif chat_state == ChatState.CHAT:
# chat_state = ChatState.FOCUSED
# 状态已经是 FOCUSED直接获取或创建 PFChatting 并添加时间
# logger.debug(f"[{stream_name}] Triggering PFChatting add_time in FOCUSED state.") # Debug log
pf_instance = await self._get_or_create_heartFC_chat(stream_id)
if pf_instance: # 确保实例成功获取或创建
await pf_instance.add_time() # 注意:这里不再需要 create_task因为 add_time 内部会处理任务创建
else:
logger.error(f"[{stream_name}] 无法获取或创建 PFChatting 实例以触发 HFC。")

View File

@@ -1,14 +1,14 @@
from typing import List, Optional from typing import List, Optional
from ...models.utils_model import LLMRequest from ..models.utils_model import LLMRequest
from ....config.config import global_config from ...config.config import global_config
from ...chat.message import MessageRecv from ..chat.message import MessageRecv
from .heartFC_prompt_builder import prompt_builder from .heartflow_prompt_builder import prompt_builder
from ...chat.utils import process_llm_response from ..chat.utils import process_llm_response
from src.common.logger import get_module_logger, LogConfig, LLM_STYLE_CONFIG from src.common.logger import get_module_logger, LogConfig, LLM_STYLE_CONFIG
from src.plugins.respon_info_catcher.info_catcher import info_catcher_manager from src.plugins.respon_info_catcher.info_catcher import info_catcher_manager
from ...utils.timer_calculater import Timer from ..utils.timer_calculater import Timer
from src.plugins.moods.moods import MoodManager from src.plugins.moods.moods import MoodManager

View File

@@ -3,11 +3,11 @@ import time
from typing import Dict, List, Optional, Union from typing import Dict, List, Optional, Union
from src.common.logger import get_module_logger from src.common.logger import get_module_logger
from ...message.api import global_api from ..message.api import global_api
from ...chat.message import MessageSending, MessageThinking, MessageSet from ..chat.message import MessageSending, MessageThinking, MessageSet
from ...storage.storage import MessageStorage from ..storage.storage import MessageStorage
from ....config.config import global_config from ...config.config import global_config
from ...chat.utils import truncate_message, calculate_typing_time, count_messages_between from ..chat.utils import truncate_message, calculate_typing_time, count_messages_between
from src.common.logger import LogConfig, SENDER_STYLE_CONFIG from src.common.logger import LogConfig, SENDER_STYLE_CONFIG
@@ -216,9 +216,7 @@ class MessageManager:
thinking_start_time=message_earliest.thinking_start_time, thinking_start_time=message_earliest.thinking_start_time,
is_emoji=message_earliest.is_emoji, is_emoji=message_earliest.is_emoji,
) )
logger.trace(f"\n{message_earliest.processed_plain_text},{typing_time},计算输入时间结束\n")
await asyncio.sleep(typing_time) await asyncio.sleep(typing_time)
logger.debug(f"\n{message_earliest.processed_plain_text},{typing_time},等待输入时间结束\n")
await MessageSender().send_message(message_earliest) await MessageSender().send_message(message_earliest)
await self.storage.store_message(message_earliest, message_earliest.chat_stream) await self.storage.store_message(message_earliest, message_earliest.chat_stream)

View File

@@ -1,31 +1,31 @@
import time import time
import traceback import traceback
from ...memory_system.Hippocampus import HippocampusManager from ..memory_system.Hippocampus import HippocampusManager
from ....config.config import global_config from ...config.config import global_config
from ...chat.message import MessageRecv from ..chat.message import MessageRecv
from ...storage.storage import MessageStorage from ..storage.storage import MessageStorage
from ...chat.utils import is_mentioned_bot_in_message from ..chat.utils import is_mentioned_bot_in_message
from ...message import Seg from ..message import Seg
from src.heart_flow.heartflow import heartflow from src.heart_flow.heartflow import heartflow
from src.common.logger import get_module_logger, CHAT_STYLE_CONFIG, LogConfig from src.common.logger import get_module_logger, CHAT_STYLE_CONFIG, LogConfig
from ...chat.chat_stream import chat_manager from ..chat.chat_stream import chat_manager
from ...chat.message_buffer import message_buffer from ..chat.message_buffer import message_buffer
from ...utils.timer_calculater import Timer from ..utils.timer_calculater import Timer
from src.plugins.person_info.relationship_manager import relationship_manager from src.plugins.person_info.relationship_manager import relationship_manager
from .reasoning_chat import ReasoningChat from .normal_chat import ReasoningChat
# 定义日志配置 # 定义日志配置
processor_config = LogConfig( processor_config = LogConfig(
console_format=CHAT_STYLE_CONFIG["console_format"], console_format=CHAT_STYLE_CONFIG["console_format"],
file_format=CHAT_STYLE_CONFIG["file_format"], file_format=CHAT_STYLE_CONFIG["file_format"],
) )
logger = get_module_logger("heartFC_processor", config=processor_config) logger = get_module_logger("heartflow_processor", config=processor_config)
class HeartFCProcessor: class HeartFCProcessor:
def __init__(self): def __init__(self):
self.storage = MessageStorage() self.storage = MessageStorage()
self.reasoning_chat = ReasoningChat.get_instance() self.normal_chat = ReasoningChat.get_instance()
async def process_message(self, message_data: str) -> None: async def process_message(self, message_data: str) -> None:
"""处理接收到的原始消息数据,完成消息解析、缓冲、过滤、存储、兴趣度计算与更新等核心流程。 """处理接收到的原始消息数据,完成消息解析、缓冲、过滤、存储、兴趣度计算与更新等核心流程。
@@ -77,7 +77,7 @@ class HeartFCProcessor:
# --- 添加兴趣追踪启动 (现在移动到这里,确保 subheartflow 存在后启动) --- # --- 添加兴趣追踪启动 (现在移动到这里,确保 subheartflow 存在后启动) ---
# 在获取到 chat 对象和确认 subheartflow 后,启动对该聊天流的兴趣监控 # 在获取到 chat 对象和确认 subheartflow 后,启动对该聊天流的兴趣监控
await self.reasoning_chat.start_monitoring_interest(chat) # start_monitoring_interest 内部需要修改以适应 await self.normal_chat.start_monitoring_interest(chat) # start_monitoring_interest 内部需要修改以适应
# --- 结束添加 --- # --- 结束添加 ---
message.update_chat_stream(chat) message.update_chat_stream(chat)
@@ -196,7 +196,7 @@ class HeartFCProcessor:
"", "",
) )
else: else:
logger.debug(f"已认识用户: {message.message_info.user_info.user_nickname}") # logger.debug(f"已认识用户: {message.message_info.user_info.user_nickname}")
if not await relationship_manager.is_qved_name( if not await relationship_manager.is_qved_name(
message.message_info.platform, message.message_info.user_info.user_id message.message_info.platform, message.message_info.user_info.user_id
): ):

View File

@@ -1,21 +1,22 @@
import random import random
from typing import Optional from typing import Optional
from ....config.config import global_config from ...config.config import global_config
from ...chat.utils import get_recent_group_detailed_plain_text from ..chat.utils import get_recent_group_detailed_plain_text
from ...chat.chat_stream import chat_manager from ..chat.chat_stream import chat_manager
from src.common.logger import get_module_logger from src.common.logger import get_module_logger
from ....individuality.individuality import Individuality from ...individuality.individuality import Individuality
from src.heart_flow.heartflow import heartflow from src.heart_flow.heartflow import heartflow
from src.plugins.utils.prompt_builder import Prompt, global_prompt_manager from src.plugins.utils.prompt_builder import Prompt, global_prompt_manager
from src.plugins.utils.chat_message_builder import build_readable_messages, get_raw_msg_before_timestamp_with_chat
from src.plugins.person_info.relationship_manager import relationship_manager from src.plugins.person_info.relationship_manager import relationship_manager
from src.plugins.chat.utils import parse_text_timestamps from src.plugins.chat.utils import parse_text_timestamps
import time import time
from typing import Union from typing import Union
from ....common.database import db from ...common.database import db
from ...chat.utils import get_embedding, get_recent_group_speaker from ..chat.utils import get_embedding, get_recent_group_speaker
from ...moods.moods import MoodManager from ..moods.moods import MoodManager
from ...memory_system.Hippocampus import HippocampusManager from ..memory_system.Hippocampus import HippocampusManager
from ...schedule.schedule_generator import bot_schedule from ..schedule.schedule_generator import bot_schedule
logger = get_module_logger("prompt") logger = get_module_logger("prompt")
@@ -105,21 +106,25 @@ class PromptBuilder:
# 日程构建 # 日程构建
# schedule_prompt = f'''你现在正在做的事情是:{bot_schedule.get_current_num_task(num = 1,time_info = False)}''' # schedule_prompt = f'''你现在正在做的事情是:{bot_schedule.get_current_num_task(num = 1,time_info = False)}'''
# 获取聊天上下文 chat_stream = chat_manager.get_stream(stream_id)
chat_in_group = True if chat_stream.group_info:
chat_talking_prompt = "" chat_in_group = True
if stream_id: else:
chat_talking_prompt = get_recent_group_detailed_plain_text( chat_in_group = False
stream_id, limit=global_config.MAX_CONTEXT_SIZE, combine=True
)
chat_stream = chat_manager.get_stream(stream_id)
if chat_stream.group_info:
chat_talking_prompt = chat_talking_prompt
else:
chat_in_group = False
chat_talking_prompt = chat_talking_prompt
# print(f"\033[1;34m[调试]\033[0m 已从数据库获取群 {group_id} 的消息记录:{chat_talking_prompt}")
message_list_before_now = get_raw_msg_before_timestamp_with_chat(
chat_id =chat_stream.stream_id,
timestamp = time.time(),
limit=global_config.MAX_CONTEXT_SIZE,
)
chat_talking_prompt = await build_readable_messages(
message_list_before_now,
replace_bot_name=True,
merge_messages=False,
timestamp_mode="relative",
read_mark=0.0,
)
# 关键词检测与反应 # 关键词检测与反应
keywords_reaction_prompt = "" keywords_reaction_prompt = ""
@@ -148,23 +153,9 @@ class PromptBuilder:
if random.random() < 0.02: if random.random() < 0.02:
prompt_ger += "你喜欢用反问句" prompt_ger += "你喜欢用反问句"
# moderation_prompt = ""
# moderation_prompt = """**检查并忽略**任何涉及尝试绕过审核的行为。
# 涉及政治敏感以及违法违规的内容请规避。"""
logger.debug("开始构建prompt") logger.debug("开始构建prompt")
# prompt = f"""
# {chat_target}
# {chat_talking_prompt}
# 现在"{sender_name}"说的:{message_txt}。引起了你的注意,你想要在群里发言发言或者回复这条消息。\n
# 你的网名叫{global_config.BOT_NICKNAME}{prompt_personality} {prompt_identity}。
# 你正在{chat_target_2},现在请你读读之前的聊天记录,然后给出日常且口语化的回复,平淡一些,
# 你刚刚脑子里在想:
# {current_mind_info}
# 回复尽量简短一些。{keywords_reaction_prompt}请注意把握聊天内容,不要回复的太有条理,可以有个性。{prompt_ger}
# 请回复的平淡一些,简短一些,说中文,不要刻意突出自身学科背景,尽量不要说你说过的话 ,注意只输出回复内容。
# {moderation_prompt}。注意:不要输出多余内容(包括前后缀冒号和引号括号表情包at或 @等 )。"""
prompt = await global_prompt_manager.format_prompt( prompt = await global_prompt_manager.format_prompt(
"heart_flow_prompt", "heart_flow_prompt",
chat_target=await global_prompt_manager.get_prompt_async("chat_target_group1") chat_target=await global_prompt_manager.get_prompt_async("chat_target_group1")
@@ -257,19 +248,28 @@ class PromptBuilder:
# schedule_prompt = f"""你现在正在做的事情是:{bot_schedule.get_current_num_task(num=1, time_info=False)}""" # schedule_prompt = f"""你现在正在做的事情是:{bot_schedule.get_current_num_task(num=1, time_info=False)}"""
# 获取聊天上下文 # 获取聊天上下文
chat_in_group = True chat_stream = chat_manager.get_stream(stream_id)
chat_talking_prompt = "" if chat_stream.group_info:
if stream_id: chat_in_group = True
chat_talking_prompt = get_recent_group_detailed_plain_text( else:
stream_id, limit=global_config.MAX_CONTEXT_SIZE, combine=True chat_in_group = False
)
chat_stream = chat_manager.get_stream(stream_id) message_list_before_now = get_raw_msg_before_timestamp_with_chat(
if chat_stream.group_info: chat_id =chat_stream.stream_id,
chat_talking_prompt = chat_talking_prompt timestamp = time.time(),
else: limit=global_config.MAX_CONTEXT_SIZE,
chat_in_group = False )
chat_talking_prompt = chat_talking_prompt
# print(f"\033[1;34m[调试]\033[0m 已从数据库获取群 {group_id} 的消息记录:{chat_talking_prompt}") chat_talking_prompt = await build_readable_messages(
message_list_before_now,
replace_bot_name=True,
merge_messages=False,
timestamp_mode="relative",
read_mark=0.0,
)
# 关键词检测与反应 # 关键词检测与反应
keywords_reaction_prompt = "" keywords_reaction_prompt = ""
for rule in global_config.keywords_reaction_rules: for rule in global_config.keywords_reaction_rules:
@@ -311,6 +311,10 @@ class PromptBuilder:
logger.debug("开始构建prompt") logger.debug("开始构建prompt")
schedule_prompt=await global_prompt_manager.format_prompt(
"schedule_prompt", schedule_info=bot_schedule.get_current_num_task(num=1, time_info=False)
)
prompt = await global_prompt_manager.format_prompt( prompt = await global_prompt_manager.format_prompt(
"reasoning_prompt_main", "reasoning_prompt_main",
relation_prompt_all=await global_prompt_manager.get_prompt_async("relationship_prompt"), relation_prompt_all=await global_prompt_manager.get_prompt_async("relationship_prompt"),
@@ -318,9 +322,7 @@ class PromptBuilder:
sender_name=sender_name, sender_name=sender_name,
memory_prompt=memory_prompt, memory_prompt=memory_prompt,
prompt_info=prompt_info, prompt_info=prompt_info,
schedule_prompt=await global_prompt_manager.format_prompt( schedule_prompt=schedule_prompt,
"schedule_prompt", schedule_info=bot_schedule.get_current_num_task(num=1, time_info=False)
),
chat_target=await global_prompt_manager.get_prompt_async("chat_target_group1") chat_target=await global_prompt_manager.get_prompt_async("chat_target_group1")
if chat_in_group if chat_in_group
else await global_prompt_manager.get_prompt_async("chat_target_private1"), else await global_prompt_manager.get_prompt_async("chat_target_private1"),

View File

@@ -4,23 +4,24 @@ from random import random
import traceback import traceback
import asyncio import asyncio
from typing import List, Dict from typing import List, Dict
from ...moods.moods import MoodManager from ..moods.moods import MoodManager
from ....config.config import global_config from ...config.config import global_config
from ...chat.emoji_manager import emoji_manager from ..chat.emoji_manager import emoji_manager
from .reasoning_generator import ResponseGenerator from .normal_chat_generator import ResponseGenerator
from ...chat.message import MessageSending, MessageRecv, MessageThinking, MessageSet from ..chat.message import MessageSending, MessageRecv, MessageThinking, MessageSet
from ...chat.messagesender import message_manager from ..chat.messagesender import message_manager
from ...storage.storage import MessageStorage from ..storage.storage import MessageStorage
from ...chat.utils import is_mentioned_bot_in_message from ..chat.utils import is_mentioned_bot_in_message
from ...chat.utils_image import image_path_to_base64 from ..chat.utils_image import image_path_to_base64
from ...willing.willing_manager import willing_manager from ..willing.willing_manager import willing_manager
from ...message import UserInfo, Seg from ..message import UserInfo, Seg
from src.common.logger import get_module_logger, CHAT_STYLE_CONFIG, LogConfig from src.common.logger import get_module_logger, CHAT_STYLE_CONFIG, LogConfig
from src.plugins.chat.chat_stream import ChatStream from src.plugins.chat.chat_stream import ChatStream, chat_manager
from src.plugins.person_info.relationship_manager import relationship_manager from src.plugins.person_info.relationship_manager import relationship_manager
from src.plugins.respon_info_catcher.info_catcher import info_catcher_manager from src.plugins.respon_info_catcher.info_catcher import info_catcher_manager
from src.plugins.utils.timer_calculater import Timer from src.plugins.utils.timer_calculater import Timer
from src.heart_flow.heartflow import heartflow from src.heart_flow.heartflow import heartflow
from src.heart_flow.sub_heartflow import ChatState
from .heartFC_controler import HeartFCController from .heartFC_controler import HeartFCController
# 定义日志配置 # 定义日志配置
@@ -29,7 +30,7 @@ chat_config = LogConfig(
file_format=CHAT_STYLE_CONFIG["file_format"], file_format=CHAT_STYLE_CONFIG["file_format"],
) )
logger = get_module_logger("reasoning_chat", config=chat_config) logger = get_module_logger("normal_chat", config=chat_config)
class ReasoningChat: class ReasoningChat:
@@ -187,69 +188,95 @@ class ReasoningChat:
# 在没有控制器的情况下可能需要决定是继续处理还是完全停止?这里暂时假设继续 # 在没有控制器的情况下可能需要决定是继续处理还是完全停止?这里暂时假设继续
pass # 或者 return? pass # 或者 return?
logger.info(f"[{stream_id}] 兴趣消息监控任务启动。") # 增加启动日志 # logger.info(f"[{stream_id}] 兴趣消息监控任务启动。") # 减少日志
while True: while True:
await asyncio.sleep(1) # 每秒检查一次 await asyncio.sleep(1) # 每秒检查一次
# --- 修改:通过 heartflow 获取 subheartflow 和 interest_dict --- #
subheartflow = heartflow.get_subheartflow(stream_id) subheartflow = heartflow.get_subheartflow(stream_id)
# 检查 subheartflow 是否存在以及是否被标记停止
if not subheartflow or subheartflow.should_stop: if not subheartflow or subheartflow.should_stop:
logger.info(f"[{stream_id}] SubHeartflow 不存在或已停止,兴趣消息监控任务退出。") # logger.info(f"[{stream_id}] SubHeartflow 不存在或已停止,兴趣消息监控任务退出。") # 减少日志
break # 退出循环,任务结束 break
# 从 subheartflow 获取 interest_dict
interest_dict = subheartflow.get_interest_dict() interest_dict = subheartflow.get_interest_dict()
# --- 结束修改 --- #
# 创建 items 快照进行迭代,避免在迭代时修改字典
items_to_process = list(interest_dict.items()) items_to_process = list(interest_dict.items())
if not items_to_process: if not items_to_process:
continue # 没有需要处理的消息,继续等待 continue
# logger.debug(f"[{stream_id}] 发现 {len(items_to_process)} 条待处理兴趣消息。") # 调试日志
for msg_id, (message, interest_value, is_mentioned) in items_to_process: for msg_id, (message, interest_value, is_mentioned) in items_to_process:
# --- 检查 PFChatting 是否活跃 --- # # --- 在处理前检查 SubHeartflow 的状态 --- #
current_chat_state = subheartflow.chat_state.chat_status
stream_name = chat_manager.get_stream_name(stream_id) or stream_id
if current_chat_state != ChatState.CHAT:
# 如果不是闲聊状态 (可能是 ABSENT 或 FOCUSED),则跳过推理聊天
# logger.debug(f"[{stream_name}] 跳过处理兴趣消息 {msg_id},因为当前状态为 {current_chat_state.value}")
# 移除消息并继续下一个
removed_item = interest_dict.pop(msg_id, None)
if removed_item:
# logger.debug(f"[{stream_name}] 已从兴趣字典中移除消息 {msg_id} (因状态跳过)") # 减少日志
pass
continue # 处理下一条消息
# --- 结束状态检查 --- #
# --- 检查 PFChatting 是否活跃 (保持原有逻辑) --- #
pf_active = False pf_active = False
if controller: if controller:
pf_active = controller.is_pf_chatting_active(stream_id) pf_active = controller.is_heartFC_chat_active(stream_id)
if pf_active: if pf_active:
# 如果 PFChatting 活跃,则跳过处理,直接移除消息
removed_item = interest_dict.pop(msg_id, None) removed_item = interest_dict.pop(msg_id, None)
if removed_item: if removed_item:
logger.debug(f"[{stream_id}] PFChatting 活跃,已跳过并移除兴趣消息 {msg_id}") logger.debug(f"[{stream_name}] PFChatting 活跃,已跳过并移除兴趣消息 {msg_id}")
continue # 处理下一条消息 continue
# --- 结束检查 --- # # --- 结束检查 --- #
# 只有当 PFChatting 不活跃时才执行以下处理逻辑 # 只有当状态为 CHAT 且 PFChatting 不活跃时才执行以下处理逻辑
try: try:
# logger.debug(f"[{stream_id}] 正在处理兴趣消息 {msg_id} (兴趣值: {interest_value:.2f})" ) await self.normal_normal_chat(
await self.normal_reasoning_chat(
message=message, message=message,
chat=chat, # chat 对象仍然有效 chat=chat,
is_mentioned=is_mentioned, is_mentioned=is_mentioned,
interested_rate=interest_value, # 使用从字典获取的原始兴趣值 interested_rate=interest_value,
) )
# logger.debug(f"[{stream_id}] 处理完成消息 {msg_id}")
except Exception as e: except Exception as e:
logger.error(f"[{stream_id}] 处理兴趣消息 {msg_id} 时出错: {e}\n{traceback.format_exc()}") logger.error(f"[{stream_name}] 处理兴趣消息 {msg_id} 时出错: {e}\n{traceback.format_exc()}")
finally: finally:
# 无论处理成功与否且PFChatting不活跃都尝试从原始字典中移除该消息
# 使用 pop(key, None) 避免 Key Error
removed_item = interest_dict.pop(msg_id, None) removed_item = interest_dict.pop(msg_id, None)
if removed_item: if removed_item:
logger.debug(f"[{stream_id}] 已从兴趣字典中移除消息 {msg_id}") # logger.debug(f"[{stream_name}] 已从兴趣字典中移除消息 {msg_id}") # 减少日志
pass
async def normal_reasoning_chat( async def normal_normal_chat(
self, message: MessageRecv, chat: ChatStream, is_mentioned: bool, interested_rate: float self, message: MessageRecv, chat: ChatStream, is_mentioned: bool, interested_rate: float
) -> None: ) -> None:
timing_results = {} timing_results = {}
userinfo = message.message_info.user_info userinfo = message.message_info.user_info
messageinfo = message.message_info messageinfo = message.message_info
stream_id = chat.stream_id
stream_name = chat_manager.get_stream_name(stream_id) or stream_id
# --- 在开始时检查 SubHeartflow 状态 --- #
sub_hf = heartflow.get_subheartflow(stream_id)
if not sub_hf:
logger.warning(f"[{stream_name}] 无法获取 SubHeartflow无法执行 normal_normal_chat。")
return
current_chat_state = sub_hf.chat_state.chat_status
if current_chat_state != ChatState.CHAT:
logger.debug(
f"[{stream_name}] 跳过 normal_normal_chat因为 SubHeartflow 状态为 {current_chat_state.value} (需要 CHAT)。"
)
# 可以在这里添加 not_reply_handle 逻辑吗? 如果不回复,也需要清理意愿。
# 注意willing_manager.setup 尚未调用
willing_manager.setup(message, chat, is_mentioned, interested_rate) # 先 setup
await willing_manager.not_reply_handle(message.message_info.message_id)
willing_manager.delete(message.message_info.message_id)
return
# --- 结束状态检查 --- #
# --- 接下来的逻辑只在 ChatState.CHAT 状态下执行 --- #
is_mentioned, reply_probability = is_mentioned_bot_in_message(message) is_mentioned, reply_probability = is_mentioned_bot_in_message(message)
# 意愿管理器设置当前message信息 # 意愿管理器设置当前message信息

View File

@@ -1,10 +1,9 @@
from typing import List, Optional, Tuple, Union from typing import List, Optional, Tuple, Union
import random import random
from ..models.utils_model import LLMRequest from ..models.utils_model import LLMRequest
from ...config.config import global_config from ...config.config import global_config
from ..chat.message import MessageThinking from ..chat.message import MessageThinking
from .heartFC_prompt_builder import prompt_builder from .heartflow_prompt_builder import prompt_builder
from ..chat.utils import process_llm_response from ..chat.utils import process_llm_response
from ..utils.timer_calculater import Timer from ..utils.timer_calculater import Timer
from src.common.logger import get_module_logger, LogConfig, LLM_STYLE_CONFIG from src.common.logger import get_module_logger, LogConfig, LLM_STYLE_CONFIG
@@ -86,7 +85,7 @@ class ResponseGenerator:
with Timer() as t_build_prompt: with Timer() as t_build_prompt:
prompt = await prompt_builder.build_prompt( prompt = await prompt_builder.build_prompt(
build_mode="normal", build_mode="normal",
reason=message.reason, reason= "",
chat_stream=message.chat_stream, chat_stream=message.chat_stream,
message_txt=message.processed_plain_text, message_txt=message.processed_plain_text,
sender_name=sender_name, sender_name=sender_name,
@@ -97,6 +96,8 @@ class ResponseGenerator:
try: try:
content, reasoning_content, self.current_model_name = await model.generate_response(prompt) content, reasoning_content, self.current_model_name = await model.generate_response(prompt)
logger.info(f"prompt:{prompt}\n生成回复:{content}")
info_catcher.catch_after_llm_generated( info_catcher.catch_after_llm_generated(
prompt=prompt, response=content, reasoning_content=reasoning_content, model_name=self.current_model_name prompt=prompt, response=content, reasoning_content=reasoning_content, model_name=self.current_model_name
) )

View File

@@ -1,97 +0,0 @@
# PFChatting 与主动回复流程说明 (V2)
本文档描述了 `PFChatting` 类及其在 `heartFC_controler` 模块中实现的主动、基于兴趣的回复流程。
## 1. `PFChatting` 类概述
* **目标**: 管理特定聊天流 (`stream_id`) 的主动回复逻辑,使其行为更像人类的自然交流。
* **创建时机**: 当 `HeartFC_Chat` 的兴趣监控任务 (`_interest_monitor_loop`) 检测到某个聊天流的兴趣度 (`InterestChatting`) 达到了触发回复评估的条件 (`should_evaluate_reply`) 时,会为该 `stream_id` 获取或创建唯一的 `PFChatting` 实例 (`_get_or_create_pf_chatting`)。
* **持有**:
* 对应的 `sub_heartflow` 实例引用 (通过 `heartflow.get_subheartflow(stream_id)`)。
* 对应的 `chat_stream` 实例引用。
*`HeartFC_Chat` 单例的引用 (用于调用发送消息、处理表情等辅助方法)。
* **初始化**: `PFChatting` 实例在创建后会执行异步初始化 (`_initialize`),这可能包括加载必要的上下文或历史信息(*待确认是否实现了读取历史消息*)。
## 2. 核心回复流程 (由 `HeartFC_Chat` 触发)
`HeartFC_Chat` 调用 `PFChatting` 实例的方法 (例如 `add_time`) 时,会启动内部的回复决策与执行流程:
1. **规划 (Planner):**
* **输入**: 从关联的 `sub_heartflow` 获取观察结果、思考链、记忆片段等上下文信息。
* **决策**:
* 判断当前是否适合进行回复。
* 决定回复的形式(纯文本、带表情包等)。
* 选择合适的回复时机和策略。
* **实现**: *此部分逻辑待详细实现,可能利用 LLM 的工具调用能力来增强决策的灵活性和智能性。需要考虑机器人的个性化设定。*
2. **回复生成 (Replier):**
* **输入**: Planner 的决策结果和必要的上下文。
* **执行**:
* 调用 `ResponseGenerator` (`self.gpt`) 或类似组件生成具体的回复文本内容。
* 可能根据 Planner 的策略生成多个候选回复。
* **并发**: 系统支持同时存在多个思考/生成任务(上限由 `global_config.max_concurrent_thinking_messages` 控制)。
3. **检查 (Checker):**
* **时机**: 在回复生成过程中或生成后、发送前执行。
* **目的**:
* 检查自开始生成回复以来,聊天流中是否出现了新的消息。
* 评估已生成的候选回复在新的上下文下是否仍然合适、相关。
* *需要实现相似度比较逻辑,防止发送与近期消息内容相近或重复的回复。*
* **处理**: 如果检查结果认为回复不合适,则该回复将被**抛弃**。
4. **发送协调:**
* **执行**: 如果 Checker 通过,`PFChatting` 会调用 `HeartFC_Chat` 实例提供的发送接口:
* `_create_thinking_message`: 通知 `MessageManager` 显示"正在思考"状态。
* `_send_response_messages`: 将最终的回复文本交给 `MessageManager` 进行排队和发送。
* `_handle_emoji`: 如果需要发送表情包,调用此方法处理表情包的获取和发送。
* **细节**: 实际的消息发送、排队、间隔控制由 `MessageManager``MessageSender` 负责。
## 3. 与其他模块的交互
* **`HeartFC_Chat`**:
* 创建、管理和触发 `PFChatting` 实例。
* 提供发送消息 (`_send_response_messages`)、处理表情 (`_handle_emoji`)、创建思考消息 (`_create_thinking_message`) 的接口给 `PFChatting` 调用。
* 运行兴趣监控循环 (`_interest_monitor_loop`)。
* **`InterestManager` / `InterestChatting`**:
* `InterestManager` 存储每个 `stream_id``InterestChatting` 实例。
* `InterestChatting` 负责计算兴趣衰减和回复概率。
* `HeartFC_Chat` 查询 `InterestChatting.should_evaluate_reply()` 来决定是否触发 `PFChatting`
* **`heartflow` / `sub_heartflow`**:
* `PFChatting` 从对应的 `sub_heartflow` 获取进行规划所需的核心上下文信息 (观察、思考链等)。
* **`MessageManager` / `MessageSender`**:
* 接收来自 `HeartFC_Chat` 的发送请求 (思考消息、文本消息、表情包消息)。
* 管理消息队列 (`MessageContainer`),处理消息发送间隔和实际发送 (`MessageSender`)。
* **`ResponseGenerator` (`gpt`)**:
*`PFChatting` 的 Replier 部分调用,用于生成回复文本。
* **`MessageStorage`**:
* 存储所有接收和发送的消息。
* **`HippocampusManager`**:
* `HeartFC_Processor` 使用它计算传入消息的记忆激活率,作为兴趣度计算的输入之一。
## 4. 原有问题与状态更新
1. **每个 `pfchating` 是否对应一个 `chat_stream`,是否是唯一的?**
* **是**`HeartFC_Chat._get_or_create_pf_chatting` 确保了每个 `stream_id` 只有一个 `PFChatting` 实例。 (已确认)
2. **`observe_text` 传入进来是纯 str是不是应该传进来 message 构成的 list?**
* **机制已改变**。当前的触发机制是基于 `InterestManager` 的概率判断。`PFChatting` 启动后,应从其关联的 `sub_heartflow` 获取更丰富的上下文信息,而非简单的 `observe_text`
3. **检查失败的回复应该怎么处理?**
* **暂定:抛弃**。这是当前 Checker 逻辑的基础设定。
4. **如何比较相似度?**
* **待实现**。Checker 需要具体的算法来比较候选回复与新消息的相似度。
5. **Planner 怎么写?**
* **待实现**。这是 `PFChatting` 的核心决策逻辑,需要结合 `sub_heartflow` 的输出、LLM 工具调用和个性化配置来设计。
## 6. 未来优化点
* 实现 Checker 中的相似度比较算法。
* 详细设计并实现 Planner 的决策逻辑,包括 LLM 工具调用和个性化。
* 确认并完善 `PFChatting._initialize()` 中的历史消息加载逻辑。
* 探索更优的检查失败回复处理策略(例如:重新规划、修改回复等)。
* 优化 `PFChatting``sub_heartflow` 的信息交互。
BUG:
2.复读可能是planner还未校准好
3.planner还未个性化需要加入bot个性信息且获取的聊天内容有问题

View File

@@ -66,11 +66,12 @@ def send_heartbeat(server_url, client_id):
logger.debug(f"心跳发送成功。服务器响应: {data}") logger.debug(f"心跳发送成功。服务器响应: {data}")
return True return True
else: else:
logger.error(f"心跳发送失败。状态码: {response.status_code}, 响应内容: {response.text}") logger.debug(f"心跳发送失败。状态码: {response.status_code}, 响应内容: {response.text}")
return False return False
except requests.RequestException as e: except requests.RequestException as e:
logger.error(f"发送心跳时出错: {e}") # 如果请求异常,可能是网络问题,不记录错误
logger.debug(f"发送心跳时出错: {e}")
return False return False

View File

@@ -76,7 +76,7 @@ class ScheduleGenerator:
logger.info(f"日程系统启动/刷新时间: {self.start_time.strftime('%Y-%m-%d %H:%M:%S')}") logger.info(f"日程系统启动/刷新时间: {self.start_time.strftime('%Y-%m-%d %H:%M:%S')}")
# 初始化日程 # 初始化日程
await self.check_and_create_today_schedule() await self.check_and_create_today_schedule()
self.print_schedule() # self.print_schedule()
while True: while True:
# print(self.get_current_num_task(1, True)) # print(self.get_current_num_task(1, True))
@@ -88,7 +88,7 @@ class ScheduleGenerator:
logger.info("检测到日期变化,重新生成日程") logger.info("检测到日期变化,重新生成日程")
self.start_time = current_time self.start_time = current_time
await self.check_and_create_today_schedule() await self.check_and_create_today_schedule()
self.print_schedule() # self.print_schedule()
# 执行当前活动 # 执行当前活动
# mind_thinking = heartflow.current_state.current_mind # mind_thinking = heartflow.current_state.current_mind

View File

@@ -232,7 +232,7 @@ async def _build_readable_messages_internal(
# 4 & 5: 格式化为字符串 # 4 & 5: 格式化为字符串
output_lines = [] output_lines = []
for merged in merged_messages: for _i, merged in enumerate(merged_messages):
# 使用指定的 timestamp_mode 格式化时间 # 使用指定的 timestamp_mode 格式化时间
readable_time = translate_timestamp_to_human_readable(merged["start_time"], mode=timestamp_mode) readable_time = translate_timestamp_to_human_readable(merged["start_time"], mode=timestamp_mode)
@@ -242,11 +242,14 @@ async def _build_readable_messages_internal(
for line in merged["content"]: for line in merged["content"]:
stripped_line = line.strip() stripped_line = line.strip()
if stripped_line: # 过滤空行 if stripped_line: # 过滤空行
# 移除末尾句号,添加分号
if stripped_line.endswith(""): if stripped_line.endswith(""):
stripped_line = stripped_line.rstrip("") stripped_line = stripped_line[:-1]
output_lines.append(f"{stripped_line};") output_lines.append(f"{stripped_line};")
output_lines += "\n" output_lines.append("\n") # 在每个消息块后添加换行,保持可读性
formatted_string = "".join(output_lines)
# 移除可能的多余换行,然后合并
formatted_string = "".join(output_lines).strip()
# 返回格式化后的字符串和原始的 message_details 列表 # 返回格式化后的字符串和原始的 message_details 列表
return formatted_string, message_details return formatted_string, message_details
@@ -273,12 +276,43 @@ async def build_readable_messages(
replace_bot_name: bool = True, replace_bot_name: bool = True,
merge_messages: bool = False, merge_messages: bool = False,
timestamp_mode: str = "relative", timestamp_mode: str = "relative",
read_mark: float = 0.0,
) -> str: ) -> str:
""" """
将消息列表转换为可读的文本格式。 将消息列表转换为可读的文本格式。
如果提供了 read_mark则在相应位置插入已读标记。
允许通过参数控制格式化行为。 允许通过参数控制格式化行为。
""" """
formatted_string, _ = await _build_readable_messages_internal( if read_mark <= 0:
messages, replace_bot_name, merge_messages, timestamp_mode # 没有有效的 read_mark直接格式化所有消息
) formatted_string, _ = await _build_readable_messages_internal(
return formatted_string messages, replace_bot_name, merge_messages, timestamp_mode
)
return formatted_string
else:
# 按 read_mark 分割消息
messages_before_mark = [msg for msg in messages if msg.get("time", 0) <= read_mark]
messages_after_mark = [msg for msg in messages if msg.get("time", 0) > read_mark]
# 分别格式化
formatted_before, _ = await _build_readable_messages_internal(
messages_before_mark, replace_bot_name, merge_messages, timestamp_mode
)
formatted_after, _ = await _build_readable_messages_internal(
messages_after_mark, replace_bot_name, merge_messages, timestamp_mode
)
readable_read_mark = translate_timestamp_to_human_readable(read_mark, mode=timestamp_mode)
read_mark_line = f"\n--- 以上消息已读 (标记时间: {readable_read_mark}) ---\n"
# 组合结果,确保空部分不引入多余的标记或换行
if formatted_before and formatted_after:
return f"{formatted_before}{read_mark_line}{formatted_after}"
elif formatted_before:
return f"{formatted_before}{read_mark_line}"
elif formatted_after:
return f"{read_mark_line}{formatted_after}"
else:
# 理论上不应该发生,但作为保险
return read_mark_line.strip() # 如果前后都无消息,只返回标记行