pull/1001/head
SnowindMe 2025-04-24 01:02:27 +08:00
commit 4f73319cbd
26 changed files with 1418 additions and 650 deletions

9
bot.py
View File

@ -8,11 +8,16 @@ import time
import platform import platform
import traceback import traceback
from dotenv import load_dotenv from dotenv import load_dotenv
from src.common.logger import get_module_logger, LogConfig, CONFIRM_STYLE_CONFIG from src.common.logger import get_module_logger, LogConfig, CONFIRM_STYLE_CONFIG, MAIN_STYLE_CONFIG
from src.common.crash_logger import install_crash_handler from src.common.crash_logger import install_crash_handler
from src.main import MainSystem from src.main import MainSystem
logger = get_module_logger("main_bot")
main_log_config = LogConfig(
console_format=MAIN_STYLE_CONFIG["console_format"],
file_format=MAIN_STYLE_CONFIG["file_format"],
)
logger = get_module_logger("main_bot", config=main_log_config)
confirm_logger_config = LogConfig( confirm_logger_config = LogConfig(
console_format=CONFIRM_STYLE_CONFIG["console_format"], console_format=CONFIRM_STYLE_CONFIG["console_format"],
file_format=CONFIRM_STYLE_CONFIG["file_format"], file_format=CONFIRM_STYLE_CONFIG["file_format"],

View File

@ -37,24 +37,55 @@ class InterestMonitorApp:
# 使用 deque 来存储有限的历史数据点 # 使用 deque 来存储有限的历史数据点
# key: stream_id, value: deque([(timestamp, interest_level), ...]) # key: stream_id, value: deque([(timestamp, interest_level), ...])
self.stream_history = {} self.stream_history = {}
# key: stream_id, value: deque([(timestamp, reply_probability), ...]) # <--- 新增:存储概率历史 # key: stream_id, value: deque([(timestamp, reply_probability), ...])
self.probability_history = {} self.probability_history = {}
self.stream_colors = {} # 为每个 stream 分配颜色 self.stream_colors = {} # 为每个 stream 分配颜色
self.stream_display_names = {} # *** New: Store display names (group_name) *** self.stream_display_names = {} # 存储显示名称 (group_name)
self.selected_stream_id = tk.StringVar() # 用于 Combobox 绑定 self.selected_stream_id = tk.StringVar() # 用于 Combobox 绑定
# --- 新增:存储其他参数 ---
# 顶层信息
self.latest_main_mind = tk.StringVar(value="N/A")
self.latest_mai_state = tk.StringVar(value="N/A")
self.latest_subflow_count = tk.IntVar(value=0)
# 子流最新状态 (key: stream_id)
self.stream_sub_minds = {}
self.stream_chat_states = {}
self.stream_threshold_status = {}
self.stream_last_active = {}
self.stream_last_interaction = {}
# 用于显示单个流详情的 StringVar
self.single_stream_sub_mind = tk.StringVar(value="想法: N/A")
self.single_stream_chat_state = tk.StringVar(value="状态: N/A")
self.single_stream_threshold = tk.StringVar(value="阈值: N/A")
self.single_stream_last_active = tk.StringVar(value="活跃: N/A")
self.single_stream_last_interaction = tk.StringVar(value="交互: N/A")
# --- UI 元素 --- # --- UI 元素 ---
# --- 新增:顶部全局信息框架 ---
self.global_info_frame = ttk.Frame(root, padding="5 0 5 5") # 顶部内边距调整
self.global_info_frame.pack(side=tk.TOP, fill=tk.X, pady=(5, 0)) # 底部外边距为0
ttk.Label(self.global_info_frame, text="全局状态:").pack(side=tk.LEFT, padx=(0, 10))
ttk.Label(self.global_info_frame, textvariable=self.latest_mai_state).pack(side=tk.LEFT, padx=5)
ttk.Label(self.global_info_frame, text="想法:").pack(side=tk.LEFT, padx=(10, 0))
ttk.Label(self.global_info_frame, textvariable=self.latest_main_mind).pack(side=tk.LEFT, padx=5)
ttk.Label(self.global_info_frame, text="子流数:").pack(side=tk.LEFT, padx=(10, 0))
ttk.Label(self.global_info_frame, textvariable=self.latest_subflow_count).pack(side=tk.LEFT, padx=5)
# 创建 Notebook (选项卡控件) # 创建 Notebook (选项卡控件)
self.notebook = ttk.Notebook(root) self.notebook = ttk.Notebook(root)
self.notebook.pack(pady=10, padx=10, fill=tk.BOTH, expand=1) # 修改fill 和 expand让 notebook 填充剩余空间
self.notebook.pack(pady=(5, 0), padx=10, fill=tk.BOTH, expand=1) # 顶部外边距改小
# --- 第一个选项卡:所有流 --- # --- 第一个选项卡:所有流 ---
self.frame_all = ttk.Frame(self.notebook, padding="5 5 5 5") self.frame_all = ttk.Frame(self.notebook, padding="5 5 5 5")
self.notebook.add(self.frame_all, text="所有聊天流") self.notebook.add(self.frame_all, text="所有聊天流")
# 状态标签 # 状态标签 (移动到最底部)
self.status_label = tk.Label(root, text="Initializing...", anchor="w", fg="grey") self.status_label = tk.Label(root, text="Initializing...", anchor="w", fg="grey")
self.status_label.pack(side=tk.BOTTOM, fill=tk.X, padx=5, pady=2) self.status_label.pack(side=tk.BOTTOM, fill=tk.X, padx=10, pady=(0, 5)) # 调整边距
# Matplotlib 图表设置 (用于第一个选项卡) # Matplotlib 图表设置 (用于第一个选项卡)
self.fig = Figure(figsize=(5, 4), dpi=100) self.fig = Figure(figsize=(5, 4), dpi=100)
@ -81,6 +112,24 @@ class InterestMonitorApp:
self.stream_selector.pack(side=tk.LEFT, fill=tk.X, expand=True) self.stream_selector.pack(side=tk.LEFT, fill=tk.X, expand=True)
self.stream_selector.bind("<<ComboboxSelected>>", self.on_stream_selected) self.stream_selector.bind("<<ComboboxSelected>>", self.on_stream_selected)
# --- 新增:单个流详情显示区域 ---
self.single_stream_details_frame = ttk.Frame(self.frame_single, padding="5 5 5 0")
self.single_stream_details_frame.pack(side=tk.TOP, fill=tk.X, pady=(0, 5))
ttk.Label(self.single_stream_details_frame, textvariable=self.single_stream_sub_mind).pack(side=tk.LEFT, padx=5)
ttk.Label(self.single_stream_details_frame, textvariable=self.single_stream_chat_state).pack(
side=tk.LEFT, padx=5
)
ttk.Label(self.single_stream_details_frame, textvariable=self.single_stream_threshold).pack(
side=tk.LEFT, padx=5
)
ttk.Label(self.single_stream_details_frame, textvariable=self.single_stream_last_active).pack(
side=tk.LEFT, padx=5
)
ttk.Label(self.single_stream_details_frame, textvariable=self.single_stream_last_interaction).pack(
side=tk.LEFT, padx=5
)
# Matplotlib 图表设置 (用于第二个选项卡) # Matplotlib 图表设置 (用于第二个选项卡)
self.fig_single = Figure(figsize=(5, 4), dpi=100) self.fig_single = Figure(figsize=(5, 4), dpi=100)
# 修改:创建两个子图,一个显示兴趣度,一个显示概率 # 修改:创建两个子图,一个显示兴趣度,一个显示概率
@ -116,6 +165,11 @@ class InterestMonitorApp:
new_stream_history = {} new_stream_history = {}
new_stream_display_names = {} new_stream_display_names = {}
new_probability_history = {} # <--- 重置概率历史 new_probability_history = {} # <--- 重置概率历史
# --- 新增:重置其他子流状态 --- (如果需要的话,但通常覆盖即可)
# self.stream_sub_minds = {}
# self.stream_chat_states = {}
# ... 等等 ...
read_count = 0 read_count = 0
error_count = 0 error_count = 0
# *** Calculate the timestamp threshold for the last 30 minutes *** # *** Calculate the timestamp threshold for the last 30 minutes ***
@ -128,58 +182,114 @@ class InterestMonitorApp:
read_count += 1 read_count += 1
try: try:
log_entry = json.loads(line.strip()) log_entry = json.loads(line.strip())
timestamp = log_entry.get("timestamp") timestamp = log_entry.get("timestamp") # 获取顶层时间戳
# *** Add time filtering *** # *** 时间过滤 ***
if timestamp is None or float(timestamp) < time_threshold: if timestamp is None:
continue # Skip old or invalid entries
stream_id = log_entry.get("stream_id")
interest_level = log_entry.get("interest_level")
group_name = log_entry.get(
"group_name", stream_id
) # *** Get group_name, fallback to stream_id ***
reply_probability = log_entry.get("reply_probability") # <--- 获取概率值
# *** Check other required fields AFTER time filtering ***
if stream_id is None or interest_level is None:
error_count += 1 error_count += 1
continue # 跳过无效行 continue # 跳过没有时间戳的行
try:
entry_timestamp = float(timestamp)
if entry_timestamp < time_threshold:
continue # 跳过时间过早的条目
except (ValueError, TypeError):
error_count += 1
continue # 跳过时间戳格式错误的行
# 如果是第一次读到这个 stream_id则创建 deque # --- 新增:更新顶层信息 (使用最后一个有效行的数据) ---
if stream_id not in new_stream_history: self.latest_main_mind.set(
new_stream_history[stream_id] = deque(maxlen=MAX_HISTORY_POINTS) log_entry.get("main_mind", self.latest_main_mind.get())
new_probability_history[stream_id] = deque(maxlen=MAX_HISTORY_POINTS) # <--- 创建概率 deque ) # 保留旧值如果缺失
# 检查是否已有颜色,没有则分配 self.latest_mai_state.set(log_entry.get("mai_state", self.latest_mai_state.get()))
if stream_id not in self.stream_colors: self.latest_subflow_count.set(log_entry.get("subflow_count", self.latest_subflow_count.get()))
self.stream_colors[stream_id] = self.get_random_color()
# *** Store the latest display name found for this stream_id *** # --- 修改开始:迭代 subflows ---
new_stream_display_names[stream_id] = group_name subflows = log_entry.get("subflows")
if not isinstance(subflows, list): # 检查 subflows 是否存在且为列表
error_count += 1
continue # 跳过没有 subflows 或格式无效的行
# 添加数据点 for subflow_entry in subflows:
new_stream_history[stream_id].append((float(timestamp), float(interest_level))) stream_id = subflow_entry.get("stream_id")
# 添加概率数据点 (如果存在) interest_level = subflow_entry.get("interest_level")
if reply_probability is not None: # 获取 group_name如果不存在则回退到 stream_id
group_name = subflow_entry.get("group_name", stream_id)
# reply_probability = subflow_entry.get("reply_probability") # 获取概率值 # <-- 注释掉旧行
start_hfc_probability = subflow_entry.get(
"start_hfc_probability"
) # <-- 添加新行,读取新字段
# *** 检查必要的字段 ***
# 注意:时间戳已在顶层检查过
if stream_id is None or interest_level is None:
# 这里可以选择记录子流错误,但暂时跳过
continue # 跳过无效的 subflow 条目
# 确保 interest_level 可以转换为浮点数
try: try:
new_probability_history[stream_id].append((float(timestamp), float(reply_probability))) interest_level_float = float(interest_level)
except (TypeError, ValueError): except (ValueError, TypeError):
# 如果概率值无效,可以跳过或记录一个默认值,这里跳过 continue # 跳过 interest_level 无效的 subflow
pass
# 如果是第一次读到这个 stream_id则创建 deque
if stream_id not in new_stream_history:
new_stream_history[stream_id] = deque(maxlen=MAX_HISTORY_POINTS)
new_probability_history[stream_id] = deque(maxlen=MAX_HISTORY_POINTS) # 创建概率 deque
# 检查是否已有颜色,没有则分配
if stream_id not in self.stream_colors:
self.stream_colors[stream_id] = self.get_random_color()
# *** 存储此 stream_id 最新的显示名称 ***
new_stream_display_names[stream_id] = group_name
# --- 新增:存储其他子流信息 ---
self.stream_sub_minds[stream_id] = subflow_entry.get("sub_mind", "N/A")
self.stream_chat_states[stream_id] = subflow_entry.get("sub_chat_state", "N/A")
self.stream_threshold_status[stream_id] = subflow_entry.get("is_above_threshold", False)
self.stream_last_active[stream_id] = subflow_entry.get("last_active_time") # 存储原始时间戳
self.stream_last_interaction[stream_id] = subflow_entry.get(
"last_interaction_time"
) # 存储原始时间戳
# 添加数据点 (使用顶层时间戳)
new_stream_history[stream_id].append((entry_timestamp, interest_level_float))
# 添加概率数据点 (如果存在且有效)
# if reply_probability is not None: # <-- 注释掉旧判断
if start_hfc_probability is not None: # <-- 修改判断条件
try:
# 尝试将概率转换为浮点数
# probability_float = float(reply_probability) # <-- 注释掉旧转换
probability_float = float(start_hfc_probability) # <-- 使用新变量
new_probability_history[stream_id].append((entry_timestamp, probability_float))
except (TypeError, ValueError):
# 如果概率值无效,可以跳过或记录一个默认值,这里跳过
pass
# --- 修改结束 ---
except json.JSONDecodeError: except json.JSONDecodeError:
error_count += 1 error_count += 1
# logger.warning(f"Skipping invalid JSON line: {line.strip()}") # logger.warning(f"Skipping invalid JSON line: {line.strip()}")
continue # 跳过无法解析的行 continue # 跳过无法解析的行
except (TypeError, ValueError): # except (TypeError, ValueError) as e: # 这个外层 catch 可能不再需要,因为类型错误在内部处理了
error_count += 1 # error_count += 1
# logger.warning(f"Skipping line due to data type error ({e}): {line.strip()}") # # logger.warning(f"Skipping line due to data type error ({e}): {line.strip()}")
continue # 跳过数据类型错误的行 # continue # 跳过数据类型错误的行
# 读取完成后,用新数据替换旧数据 # 读取完成后,用新数据替换旧数据
self.stream_history = new_stream_history self.stream_history = new_stream_history
self.stream_display_names = new_stream_display_names # *** Update display names *** self.stream_display_names = new_stream_display_names # *** Update display names ***
self.probability_history = new_probability_history # <--- 更新概率历史 self.probability_history = new_probability_history # <--- 更新概率历史
# 清理不再存在的 stream_id 的附加信息 (可选,但保持一致性)
streams_to_remove = set(self.stream_sub_minds.keys()) - set(new_stream_history.keys())
for sid in streams_to_remove:
self.stream_sub_minds.pop(sid, None)
self.stream_chat_states.pop(sid, None)
self.stream_threshold_status.pop(sid, None)
self.stream_last_active.pop(sid, None)
self.stream_last_interaction.pop(sid, None)
# 颜色和显示名称也应该清理,但当前逻辑是保留旧颜色
# self.stream_colors.pop(sid, None)
status_msg = f"Data loaded at {datetime.now().strftime('%H:%M:%S')}. Lines read: {read_count}." status_msg = f"Data loaded at {datetime.now().strftime('%H:%M:%S')}. Lines read: {read_count}."
if error_count > 0: if error_count > 0:
status_msg += f" Skipped {error_count} invalid lines." status_msg += f" Skipped {error_count} invalid lines."
@ -305,13 +415,13 @@ class InterestMonitorApp:
# 设置子图标题和标签 # 设置子图标题和标签
self.ax_single_interest.set_title("兴趣度") self.ax_single_interest.set_title("兴趣度")
self.ax_single_interest.set_ylabel("兴趣度")
self.ax_single_interest.grid(True)
self.ax_single_interest.set_ylim(0, 10) # 固定 Y 轴范围 0-10 self.ax_single_interest.set_ylim(0, 10) # 固定 Y 轴范围 0-10
self.ax_single_probability.set_title("回复评估概率") # self.ax_single_probability.set_title("回复评估概率") # <-- 注释掉旧标题
self.ax_single_probability.set_title("HFC 启动概率") # <-- 修改标题
self.ax_single_probability.set_xlabel("时间") self.ax_single_probability.set_xlabel("时间")
self.ax_single_probability.set_ylabel("概率") # self.ax_single_probability.set_ylabel("概率") # <-- 注释掉旧标签
self.ax_single_probability.set_ylabel("HFC 概率") # <-- 修改 Y 轴标签
self.ax_single_probability.grid(True) self.ax_single_probability.grid(True)
self.ax_single_probability.set_ylim(0, 1.05) # 固定 Y 轴范围 0-1 self.ax_single_probability.set_ylim(0, 1.05) # 固定 Y 轴范围 0-1
self.ax_single_probability.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M:%S")) self.ax_single_probability.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M:%S"))
@ -383,9 +493,45 @@ class InterestMonitorApp:
self.ax_single_interest.set_xlim(one_hour_ago, now) self.ax_single_interest.set_xlim(one_hour_ago, now)
# self.ax_single_probability.set_xlim(one_hour_ago, now) # sharex 会自动同步 # self.ax_single_probability.set_xlim(one_hour_ago, now) # sharex 会自动同步
# --- 新增:更新单个流的详细信息标签 ---
self.update_single_stream_details(selected_sid)
# --- 新增:重新绘制画布 --- # --- 新增:重新绘制画布 ---
self.canvas_single.draw() self.canvas_single.draw()
def format_timestamp(self, ts):
"""辅助函数:格式化时间戳,处理 None 或无效值"""
if ts is None:
return "N/A"
try:
# 假设 ts 是 float 类型的时间戳
dt_object = datetime.fromtimestamp(float(ts))
return dt_object.strftime("%Y-%m-%d %H:%M:%S")
except (ValueError, TypeError):
return "Invalid Time"
def update_single_stream_details(self, stream_id):
"""更新单个流详情区域的标签内容"""
if stream_id:
sub_mind = self.stream_sub_minds.get(stream_id, "N/A")
chat_state = self.stream_chat_states.get(stream_id, "N/A")
threshold = self.stream_threshold_status.get(stream_id, False)
last_active_ts = self.stream_last_active.get(stream_id)
last_interaction_ts = self.stream_last_interaction.get(stream_id)
self.single_stream_sub_mind.set(f"想法: {sub_mind}")
self.single_stream_chat_state.set(f"状态: {chat_state}")
self.single_stream_threshold.set(f"阈值以上: {'' if threshold else ''}")
self.single_stream_last_active.set(f"最后活跃: {self.format_timestamp(last_active_ts)}")
self.single_stream_last_interaction.set(f"最后交互: {self.format_timestamp(last_interaction_ts)}")
else:
# 如果没有选择流,则清空详情
self.single_stream_sub_mind.set("想法: N/A")
self.single_stream_chat_state.set("状态: N/A")
self.single_stream_threshold.set("阈值: N/A")
self.single_stream_last_active.set("活跃: N/A")
self.single_stream_last_interaction.set("交互: N/A")
def update_display(self): def update_display(self):
"""主更新循环""" """主更新循环"""
try: try:

View File

@ -0,0 +1,107 @@
import functools
import inspect
from typing import Callable, Any
from .logger import logger, add_custom_style_handler
def use_log_style(
style_name: str,
console_format: str,
console_level: str = "INFO",
# file_format: Optional[str] = None, # 暂未支持文件输出
# file_level: str = "DEBUG",
) -> Callable:
"""装饰器:为函数内的日志启用特定的自定义样式。
Args:
style_name (str): 自定义样式的唯一名称
console_format (str): 控制台输出的格式字符串
console_level (str, optional): 控制台日志级别. Defaults to "INFO".
# file_format (Optional[str], optional): 文件输出格式 (暂未支持). Defaults to None.
# file_level (str, optional): 文件日志级别 (暂未支持). Defaults to "DEBUG".
Returns:
Callable: 返回装饰器本身
"""
def decorator(func: Callable) -> Callable:
# 获取被装饰函数所在的模块名
module = inspect.getmodule(func)
if module is None:
# 如果无法获取模块(例如,在交互式解释器中定义函数),则使用默认名称
module_name = "unknown_module"
logger.warning(f"无法确定函数 {func.__name__} 的模块,将使用 '{module_name}'")
else:
module_name = module.__name__
# 在函数首次被调用(或模块加载时)确保自定义处理器已添加
# 注意:这会在模块加载时执行,而不是每次函数调用时
# print(f"Setting up custom style '{style_name}' for module '{module_name}' in decorator definition")
add_custom_style_handler(
module_name=module_name,
style_name=style_name,
console_format=console_format,
console_level=console_level,
# file_format=file_format,
# file_level=file_level,
)
@functools.wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Any:
# 创建绑定了模块名和自定义样式标记的 logger 实例
custom_logger = logger.bind(module=module_name, custom_style=style_name)
# print(f"Executing {func.__name__} with custom logger for style '{style_name}'")
# 将自定义 logger 作为第一个参数传递给原函数
# 注意:这要求被装饰的函数第一个参数用于接收 logger
try:
return func(custom_logger, *args, **kwargs)
except TypeError as e:
# 捕获可能的类型错误,比如原函数不接受 logger 参数
logger.error(
f"调用 {func.__name__} 时出错:请确保该函数接受一个 logger 实例作为其第一个参数。错误:{e}"
)
# 可以选择重新抛出异常或返回特定值
raise e
return wrapper
return decorator
# --- 示例用法 (可以在其他模块中这样使用) ---
# # 假设这是你的模块 my_module.py
# from src.common.log_decorators import use_log_style
# from src.common.logger import get_module_logger, LoguruLogger
# # 获取模块的标准 logger
# standard_logger = get_module_logger(__name__)
# # 定义一个自定义样式
# MY_SPECIAL_STYLE = "special"
# MY_SPECIAL_FORMAT = "<bg yellow><black> SPECIAL [{time:HH:mm:ss}] </black></bg yellow> | <level>{message}</level>"
# @use_log_style(style_name=MY_SPECIAL_STYLE, console_format=MY_SPECIAL_FORMAT)
# def my_function_with_special_logs(custom_logger: LoguruLogger, x: int, y: int):
# standard_logger.info("这是一条使用标准格式的日志")
# custom_logger.info(f"开始执行特殊操作,参数: x={x}, y={y}")
# result = x + y
# custom_logger.success(f"特殊操作完成,结果: {result}")
# standard_logger.info("标准格式日志:函数即将结束")
# return result
# @use_log_style(style_name="another_style", console_format="<cyan>任务:</cyan> {message}")
# def another_task(task_logger: LoguruLogger, task_name: str):
# standard_logger.debug("准备执行另一个任务")
# task_logger.info(f"正在处理任务 '{task_name}'")
# # ... 执行任务 ...
# task_logger.warning("任务处理中遇到一个警告")
# standard_logger.info("另一个任务的标准日志")
# if __name__ == "__main__":
# print("\n--- 调用 my_function_with_special_logs ---")
# my_function_with_special_logs(10, 5)
# print("\n--- 调用 another_task ---")
# another_task("数据清理")
# print("\n--- 单独使用标准 logger ---")
# standard_logger.info("这是一条完全独立的标准日志")

View File

@ -1,5 +1,5 @@
from loguru import logger from loguru import logger
from typing import Dict, Optional, Union, List from typing import Dict, Optional, Union, List, Tuple
import sys import sys
import os import os
from types import ModuleType from types import ModuleType
@ -26,12 +26,17 @@ LoguruLogger = logger.__class__
# 全局注册表记录模块与处理器ID的映射 # 全局注册表记录模块与处理器ID的映射
_handler_registry: Dict[str, List[int]] = {} _handler_registry: Dict[str, List[int]] = {}
_custom_style_handlers: Dict[Tuple[str, str], List[int]] = {} # 记录自定义样式处理器ID
# 获取日志存储根地址 # 获取日志存储根地址
current_file_path = Path(__file__).resolve() current_file_path = Path(__file__).resolve()
LOG_ROOT = "logs" LOG_ROOT = "logs"
SIMPLE_OUTPUT = os.getenv("SIMPLE_OUTPUT", "false") SIMPLE_OUTPUT = os.getenv("SIMPLE_OUTPUT", "false").strip().lower()
if SIMPLE_OUTPUT == "true":
SIMPLE_OUTPUT = True
else:
SIMPLE_OUTPUT = False
print(f"SIMPLE_OUTPUT: {SIMPLE_OUTPUT}") print(f"SIMPLE_OUTPUT: {SIMPLE_OUTPUT}")
if not SIMPLE_OUTPUT: if not SIMPLE_OUTPUT:
@ -42,10 +47,7 @@ if not SIMPLE_OUTPUT:
"file_level": "DEBUG", "file_level": "DEBUG",
# 格式配置 # 格式配置
"console_format": ( "console_format": (
"<green>{time:YYYY-MM-DD HH:mm:ss}</green> | " "<level>{time:YYYY-MM-DD HH:mm:ss}</level> | <cyan>{extra[module]: <12}</cyan> | <level>{message}</level>"
"<level>{level: <8}</level> | "
"<cyan>{extra[module]: <12}</cyan> | "
"<level>{message}</level>"
), ),
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | {message}", "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | {message}",
"log_dir": LOG_ROOT, "log_dir": LOG_ROOT,
@ -59,7 +61,7 @@ else:
"console_level": "INFO", "console_level": "INFO",
"file_level": "DEBUG", "file_level": "DEBUG",
# 格式配置 # 格式配置
"console_format": "<green>{time:MM-DD HH:mm}</green> | <cyan>{extra[module]}</cyan> | {message}", "console_format": "<level>{time:MM-DD HH:mm}</level> | <cyan>{extra[module]}</cyan> | {message}",
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | {message}", "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | {message}",
"log_dir": LOG_ROOT, "log_dir": LOG_ROOT,
"rotation": "00:00", "rotation": "00:00",
@ -68,13 +70,30 @@ else:
} }
MAIN_STYLE_CONFIG = {
"advanced": {
"console_format": (
"<white>{time:YYYY-MM-DD HH:mm:ss}</white> | "
"<level>{level: <8}</level> | "
"<light-yellow>主程序</light-yellow> | "
"<level>{message}</level>"
),
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 主程序 | {message}",
},
"simple": {
"console_format": (
"<level>{time:MM-DD HH:mm}</level> | <light-yellow>主程序</light-yellow> | <light-yellow>{message}</light-yellow>"
),
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 主程序 | {message}",
},
}
# 海马体日志样式配置 # 海马体日志样式配置
MEMORY_STYLE_CONFIG = { MEMORY_STYLE_CONFIG = {
"advanced": { "advanced": {
"console_format": ( "console_format": (
"<green>{time:YYYY-MM-DD HH:mm:ss}</green> | " "<white>{time:YYYY-MM-DD HH:mm:ss}</white> | "
"<level>{level: <8}</level> | " "<level>{level: <8}</level> | "
"<cyan>{extra[module]: <12}</cyan> | "
"<light-yellow>海马体</light-yellow> | " "<light-yellow>海马体</light-yellow> | "
"<level>{message}</level>" "<level>{message}</level>"
), ),
@ -82,7 +101,7 @@ MEMORY_STYLE_CONFIG = {
}, },
"simple": { "simple": {
"console_format": ( "console_format": (
"<green>{time:MM-DD HH:mm}</green> | <light-yellow>海马体</light-yellow> | <light-yellow>{message}</light-yellow>" "<level>{time:MM-DD HH:mm}</level> | <light-yellow>海马体</light-yellow> | <light-yellow>{message}</light-yellow>"
), ),
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 海马体 | {message}", "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 海马体 | {message}",
}, },
@ -92,9 +111,8 @@ MEMORY_STYLE_CONFIG = {
PFC_STYLE_CONFIG = { PFC_STYLE_CONFIG = {
"advanced": { "advanced": {
"console_format": ( "console_format": (
"<green>{time:YYYY-MM-DD HH:mm:ss}</green> | " "<white>{time:YYYY-MM-DD HH:mm:ss}</white> | "
"<level>{level: <8}</level> | " "<level>{level: <8}</level> | "
"<cyan>{extra[module]: <12}</cyan> | "
"<light-yellow>PFC</light-yellow> | " "<light-yellow>PFC</light-yellow> | "
"<level>{message}</level>" "<level>{message}</level>"
), ),
@ -102,7 +120,7 @@ PFC_STYLE_CONFIG = {
}, },
"simple": { "simple": {
"console_format": ( "console_format": (
"<green>{time:MM-DD HH:mm}</green> | <light-green>PFC</light-green> | <light-green>{message}</light-green>" "<level>{time:MM-DD HH:mm}</level> | <light-green>PFC</light-green> | <light-green>{message}</light-green>"
), ),
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | PFC | {message}", "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | PFC | {message}",
}, },
@ -112,16 +130,15 @@ PFC_STYLE_CONFIG = {
MOOD_STYLE_CONFIG = { MOOD_STYLE_CONFIG = {
"advanced": { "advanced": {
"console_format": ( "console_format": (
"<green>{time:YYYY-MM-DD HH:mm:ss}</green> | " "<white>{time:YYYY-MM-DD HH:mm:ss}</white> | "
"<level>{level: <8}</level> | " "<level>{level: <8}</level> | "
"<cyan>{extra[module]: <12}</cyan> | "
"<light-green>心情</light-green> | " "<light-green>心情</light-green> | "
"<level>{message}</level>" "<level>{message}</level>"
), ),
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 心情 | {message}", "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 心情 | {message}",
}, },
"simple": { "simple": {
"console_format": "<green>{time:MM-DD HH:mm}</green> | <magenta>心情</magenta> | {message}", "console_format": "<level>{time:MM-DD HH:mm}</level> | <magenta>心情</magenta> | {message}",
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 心情 | {message}", "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 心情 | {message}",
}, },
} }
@ -129,16 +146,15 @@ MOOD_STYLE_CONFIG = {
TOOL_USE_STYLE_CONFIG = { TOOL_USE_STYLE_CONFIG = {
"advanced": { "advanced": {
"console_format": ( "console_format": (
"<green>{time:YYYY-MM-DD HH:mm:ss}</green> | " "<white>{time:YYYY-MM-DD HH:mm:ss}</white> | "
"<level>{level: <8}</level> | " "<level>{level: <8}</level> | "
"<cyan>{extra[module]: <12}</cyan> | "
"<magenta>工具使用</magenta> | " "<magenta>工具使用</magenta> | "
"<level>{message}</level>" "<level>{message}</level>"
), ),
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 工具使用 | {message}", "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 工具使用 | {message}",
}, },
"simple": { "simple": {
"console_format": "<green>{time:MM-DD HH:mm}</green> | <magenta>工具使用</magenta> | {message}", "console_format": "<level>{time:MM-DD HH:mm}</level> | <magenta>工具使用</magenta> | {message}",
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 工具使用 | {message}", "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 工具使用 | {message}",
}, },
} }
@ -148,16 +164,15 @@ TOOL_USE_STYLE_CONFIG = {
RELATION_STYLE_CONFIG = { RELATION_STYLE_CONFIG = {
"advanced": { "advanced": {
"console_format": ( "console_format": (
"<green>{time:YYYY-MM-DD HH:mm:ss}</green> | " "<white>{time:YYYY-MM-DD HH:mm:ss}</white> | "
"<level>{level: <8}</level> | " "<level>{level: <8}</level> | "
"<cyan>{extra[module]: <12}</cyan> | "
"<light-magenta>关系</light-magenta> | " "<light-magenta>关系</light-magenta> | "
"<level>{message}</level>" "<level>{message}</level>"
), ),
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 关系 | {message}", "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 关系 | {message}",
}, },
"simple": { "simple": {
"console_format": "<green>{time:MM-DD HH:mm}</green> | <light-magenta>关系</light-magenta> | {message}", "console_format": "<level>{time:MM-DD HH:mm}</level> | <light-magenta>关系</light-magenta> | {message}",
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 关系 | {message}", "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 关系 | {message}",
}, },
} }
@ -166,16 +181,15 @@ RELATION_STYLE_CONFIG = {
CONFIG_STYLE_CONFIG = { CONFIG_STYLE_CONFIG = {
"advanced": { "advanced": {
"console_format": ( "console_format": (
"<green>{time:YYYY-MM-DD HH:mm:ss}</green> | " "<white>{time:YYYY-MM-DD HH:mm:ss}</white> | "
"<level>{level: <8}</level> | " "<level>{level: <8}</level> | "
"<cyan>{extra[module]: <12}</cyan> | "
"<light-cyan>配置</light-cyan> | " "<light-cyan>配置</light-cyan> | "
"<level>{message}</level>" "<level>{message}</level>"
), ),
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 配置 | {message}", "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 配置 | {message}",
}, },
"simple": { "simple": {
"console_format": "<green>{time:MM-DD HH:mm}</green> | <light-cyan>配置</light-cyan> | {message}", "console_format": "<level>{time:MM-DD HH:mm}</level> | <light-cyan>配置</light-cyan> | {message}",
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 配置 | {message}", "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 配置 | {message}",
}, },
} }
@ -183,16 +197,15 @@ CONFIG_STYLE_CONFIG = {
SENDER_STYLE_CONFIG = { SENDER_STYLE_CONFIG = {
"advanced": { "advanced": {
"console_format": ( "console_format": (
"<green>{time:YYYY-MM-DD HH:mm:ss}</green> | " "<white>{time:YYYY-MM-DD HH:mm:ss}</white> | "
"<level>{level: <8}</level> | " "<level>{level: <8}</level> | "
"<cyan>{extra[module]: <12}</cyan> | "
"<light-yellow>消息发送</light-yellow> | " "<light-yellow>消息发送</light-yellow> | "
"<level>{message}</level>" "<level>{message}</level>"
), ),
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 消息发送 | {message}", "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 消息发送 | {message}",
}, },
"simple": { "simple": {
"console_format": "<green>{time:MM-DD HH:mm}</green> | <green>消息发送</green> | {message}", "console_format": "<level>{time:MM-DD HH:mm}</level> | <green>消息发送</green> | {message}",
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 消息发送 | {message}", "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 消息发送 | {message}",
}, },
} }
@ -200,9 +213,8 @@ SENDER_STYLE_CONFIG = {
HEARTFLOW_STYLE_CONFIG = { HEARTFLOW_STYLE_CONFIG = {
"advanced": { "advanced": {
"console_format": ( "console_format": (
"<green>{time:YYYY-MM-DD HH:mm:ss}</green> | " "<white>{time:YYYY-MM-DD HH:mm:ss}</white> | "
"<level>{level: <8}</level> | " "<level>{level: <8}</level> | "
"<cyan>{extra[module]: <12}</cyan> | "
"<light-yellow>麦麦大脑袋</light-yellow> | " "<light-yellow>麦麦大脑袋</light-yellow> | "
"<level>{message}</level>" "<level>{message}</level>"
), ),
@ -210,7 +222,7 @@ HEARTFLOW_STYLE_CONFIG = {
}, },
"simple": { "simple": {
"console_format": ( "console_format": (
"<green>{time:MM-DD HH:mm}</green> | <light-green>麦麦大脑袋</light-green> | <light-green>{message}</light-green>" "<level>{time:MM-DD HH:mm}</level> | <light-green>麦麦大脑袋</light-green> | <light-green>{message}</light-green>"
), # noqa: E501 ), # noqa: E501
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 麦麦大脑袋 | {message}", "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 麦麦大脑袋 | {message}",
}, },
@ -219,16 +231,15 @@ HEARTFLOW_STYLE_CONFIG = {
SCHEDULE_STYLE_CONFIG = { SCHEDULE_STYLE_CONFIG = {
"advanced": { "advanced": {
"console_format": ( "console_format": (
"<green>{time:YYYY-MM-DD HH:mm:ss}</green> | " "<white>{time:YYYY-MM-DD HH:mm:ss}</white> | "
"<level>{level: <8}</level> | " "<level>{level: <8}</level> | "
"<cyan>{extra[module]: <12}</cyan> | "
"<light-yellow>在干嘛</light-yellow> | " "<light-yellow>在干嘛</light-yellow> | "
"<level>{message}</level>" "<level>{message}</level>"
), ),
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 在干嘛 | {message}", "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 在干嘛 | {message}",
}, },
"simple": { "simple": {
"console_format": "<green>{time:MM-DD HH:mm}</green> | <cyan>在干嘛</cyan> | <cyan>{message}</cyan>", "console_format": "<level>{time:MM-DD HH:mm}</level> | <cyan>在干嘛</cyan> | <cyan>{message}</cyan>",
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 在干嘛 | {message}", "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 在干嘛 | {message}",
}, },
} }
@ -236,16 +247,15 @@ SCHEDULE_STYLE_CONFIG = {
LLM_STYLE_CONFIG = { LLM_STYLE_CONFIG = {
"advanced": { "advanced": {
"console_format": ( "console_format": (
"<green>{time:YYYY-MM-DD HH:mm:ss}</green> | " "<white>{time:YYYY-MM-DD HH:mm:ss}</white> | "
"<level>{level: <8}</level> | " "<level>{level: <8}</level> | "
"<cyan>{extra[module]: <12}</cyan> | "
"<light-yellow>麦麦组织语言</light-yellow> | " "<light-yellow>麦麦组织语言</light-yellow> | "
"<level>{message}</level>" "<level>{message}</level>"
), ),
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 麦麦组织语言 | {message}", "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 麦麦组织语言 | {message}",
}, },
"simple": { "simple": {
"console_format": "<green>{time:MM-DD HH:mm}</green> | <light-green>麦麦组织语言</light-green> | {message}", "console_format": "<level>{time:MM-DD HH:mm}</level> | <light-green>麦麦组织语言</light-green> | {message}",
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 麦麦组织语言 | {message}", "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 麦麦组织语言 | {message}",
}, },
} }
@ -255,16 +265,15 @@ LLM_STYLE_CONFIG = {
TOPIC_STYLE_CONFIG = { TOPIC_STYLE_CONFIG = {
"advanced": { "advanced": {
"console_format": ( "console_format": (
"<green>{time:YYYY-MM-DD HH:mm:ss}</green> | " "<white>{time:YYYY-MM-DD HH:mm:ss}</white> | "
"<level>{level: <8}</level> | " "<level>{level: <8}</level> | "
"<cyan>{extra[module]: <12}</cyan> | "
"<light-blue>话题</light-blue> | " "<light-blue>话题</light-blue> | "
"<level>{message}</level>" "<level>{message}</level>"
), ),
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 话题 | {message}", "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 话题 | {message}",
}, },
"simple": { "simple": {
"console_format": "<green>{time:MM-DD HH:mm}</green> | <light-blue>主题</light-blue> | {message}", "console_format": "<level>{time:MM-DD HH:mm}</level> | <light-blue>主题</light-blue> | {message}",
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 话题 | {message}", "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 话题 | {message}",
}, },
} }
@ -273,9 +282,8 @@ TOPIC_STYLE_CONFIG = {
CHAT_STYLE_CONFIG = { CHAT_STYLE_CONFIG = {
"advanced": { "advanced": {
"console_format": ( "console_format": (
"<green>{time:YYYY-MM-DD HH:mm:ss}</green> | " "<white>{time:YYYY-MM-DD HH:mm:ss}</white> | "
"<level>{level: <8}</level> | " "<level>{level: <8}</level> | "
"<cyan>{extra[module]: <12}</cyan> | "
"<light-blue>见闻</light-blue> | " "<light-blue>见闻</light-blue> | "
"<level>{message}</level>" "<level>{message}</level>"
), ),
@ -283,18 +291,33 @@ CHAT_STYLE_CONFIG = {
}, },
"simple": { "simple": {
"console_format": ( "console_format": (
"<green>{time:MM-DD HH:mm}</green> | <light-blue>见闻</light-blue> | <green>{message}</green>" "<level>{time:MM-DD HH:mm}</level> | <light-blue>见闻</light-blue> | <green>{message}</green>"
), # noqa: E501 ), # noqa: E501
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 见闻 | {message}", "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 见闻 | {message}",
}, },
} }
REMOTE_STYLE_CONFIG = {
"advanced": {
"console_format": (
"<white>{time:YYYY-MM-DD HH:mm:ss}</white> | "
"<level>{level: <8}</level> | "
"<light-yellow>远程</light-yellow> | "
"<level>{message}</level>"
),
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 远程 | {message}",
},
"simple": {
"console_format": "<level>{time:MM-DD HH:mm}</level> | <light-yellow>远程</light-yellow> | {message}",
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 远程 | {message}",
},
}
SUB_HEARTFLOW_STYLE_CONFIG = { SUB_HEARTFLOW_STYLE_CONFIG = {
"advanced": { "advanced": {
"console_format": ( "console_format": (
"<green>{time:YYYY-MM-DD HH:mm:ss}</green> | " "<white>{time:YYYY-MM-DD HH:mm:ss}</white> | "
"<level>{level: <8}</level> | " "<level>{level: <8}</level> | "
"<cyan>{extra[module]: <12}</cyan> | "
"<light-blue>麦麦小脑袋</light-blue> | " "<light-blue>麦麦小脑袋</light-blue> | "
"<level>{message}</level>" "<level>{message}</level>"
), ),
@ -302,43 +325,128 @@ SUB_HEARTFLOW_STYLE_CONFIG = {
}, },
"simple": { "simple": {
"console_format": ( "console_format": (
"<green>{time:MM-DD HH:mm}</green> | <light-blue>麦麦小脑袋</light-blue> | <light-blue>{message}</light-blue>" "<level>{time:MM-DD HH:mm}</level> | <light-blue>麦麦小脑袋</light-blue> | <light-blue>{message}</light-blue>"
), # noqa: E501 ), # noqa: E501
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 麦麦小脑袋 | {message}", "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 麦麦小脑袋 | {message}",
}, },
} }
BASE_TOOL_STYLE_CONFIG = {
"advanced": {
"console_format": (
"<white>{time:YYYY-MM-DD HH:mm:ss}</white> | "
"<level>{level: <8}</level> | "
"<light-blue>工具使用</light-blue> | "
"<level>{message}</level>"
),
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 工具使用 | {message}",
},
"simple": {
"console_format": (
"<level>{time:MM-DD HH:mm}</level> | <light-blue>工具使用</light-blue> | <light-blue>{message}</light-blue>"
), # noqa: E501
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 工具使用 | {message}",
},
}
PERSON_INFO_STYLE_CONFIG = {
"advanced": {
"console_format": (
"<white>{time:YYYY-MM-DD HH:mm:ss}</white> | "
"<level>{level: <8}</level> | "
"<light-blue>人物信息</light-blue> | "
"<level>{message}</level>"
),
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 人物信息 | {message}",
},
"simple": {
"console_format": (
"<level>{time:MM-DD HH:mm}</level> | <light-blue>人物信息</light-blue> | <light-blue>{message}</light-blue>"
), # noqa: E501
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 人物信息 | {message}",
},
}
BACKGROUND_TASKS_STYLE_CONFIG = {
"advanced": {
"console_format": (
"<white>{time:YYYY-MM-DD HH:mm:ss}</white> | "
"<level>{level: <8}</level> | "
"<light-blue>后台任务</light-blue> | "
"<level>{message}</level>"
),
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 后台任务 | {message}",
},
"simple": {
"console_format": (
"<level>{time:MM-DD HH:mm}</level> | <light-blue>后台任务</light-blue> | <light-blue>{message}</light-blue>"
), # noqa: E501
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 后台任务 | {message}",
},
}
SUBHEARTFLOW_MANAGER_STYLE_CONFIG = {
"advanced": {
"console_format": (
"<white>{time:YYYY-MM-DD HH:mm:ss}</white> | "
"<level>{level: <8}</level> | "
"<light-blue>小脑袋管理</light-blue> | "
"<level>{message}</level>"
),
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 小脑袋管理 | {message}",
},
"simple": {
"console_format": (
"<level>{time:MM-DD HH:mm}</level> | <light-blue>小脑袋管理</light-blue> | <light-blue>{message}</light-blue>"
), # noqa: E501
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 小脑袋管理 | {message}",
},
}
WILLING_STYLE_CONFIG = { WILLING_STYLE_CONFIG = {
"advanced": { "advanced": {
"console_format": ( "console_format": (
"<green>{time:YYYY-MM-DD HH:mm:ss}</green> | " "<white>{time:YYYY-MM-DD HH:mm:ss}</white> | "
"<level>{level: <8}</level> | " "<level>{level: <8}</level> | "
"<cyan>{extra[module]: <12}</cyan> | "
"<light-blue>意愿</light-blue> | " "<light-blue>意愿</light-blue> | "
"<level>{message}</level>" "<level>{message}</level>"
), ),
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 意愿 | {message}", "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 意愿 | {message}",
}, },
"simple": { "simple": {
"console_format": "<green>{time:MM-DD HH:mm}</green> | <light-blue>意愿 | {message} </light-blue>", # noqa: E501 "console_format": "<level>{time:MM-DD HH:mm}</level> | <light-blue>意愿 | {message} </light-blue>", # noqa: E501
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 意愿 | {message}", "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 意愿 | {message}",
}, },
} }
EMOJI_STYLE_CONFIG = {
"advanced": {
"console_format": (
"<white>{time:YYYY-MM-DD HH:mm:ss}</white> | "
"<level>{level: <8}</level> | "
"<light-blue>表情</light-blue> | "
"<level>{message}</level>"
),
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 表情 | {message}",
},
"simple": {
"console_format": "<level>{time:MM-DD HH:mm}</level> | <light-blue>表情 | {message} </light-blue>", # noqa: E501
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 表情 | {message}",
},
}
MAI_STATE_CONFIG = { MAI_STATE_CONFIG = {
"advanced": { "advanced": {
"console_format": ( "console_format": (
"<green>{time:YYYY-MM-DD HH:mm:ss}</green> | " "<white>{time:YYYY-MM-DD HH:mm:ss}</white> | "
"<level>{level: <8}</level> | " "<level>{level: <8}</level> | "
"<cyan>{extra[module]: <12}</cyan> | "
"<light-blue>麦麦状态</light-blue> | " "<light-blue>麦麦状态</light-blue> | "
"<level>{message}</level>" "<level>{message}</level>"
), ),
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 麦麦状态 | {message}", "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 麦麦状态 | {message}",
}, },
"simple": { "simple": {
"console_format": "<green>{time:MM-DD HH:mm}</green> | <light-blue>麦麦状态 | {message} </light-blue>", # noqa: E501 "console_format": "<level>{time:MM-DD HH:mm}</level> | <light-blue>麦麦状态 | {message} </light-blue>", # noqa: E501
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 麦麦状态 | {message}", "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 麦麦状态 | {message}",
}, },
} }
@ -347,9 +455,8 @@ MAI_STATE_CONFIG = {
LPMM_STYLE_CONFIG = { LPMM_STYLE_CONFIG = {
"advanced": { "advanced": {
"console_format": ( "console_format": (
"<green>{time:YYYY-MM-DD HH:mm:ss}</green> | " "<white>{time:YYYY-MM-DD HH:mm:ss}</white> | "
"<level>{level: <8}</level> | " "<level>{level: <8}</level> | "
"<cyan>{extra[module]: <12}</cyan> | "
"<light-yellow>LPMM</light-yellow> | " "<light-yellow>LPMM</light-yellow> | "
"<level>{message}</level>" "<level>{message}</level>"
), ),
@ -357,18 +464,48 @@ LPMM_STYLE_CONFIG = {
}, },
"simple": { "simple": {
"console_format": ( "console_format": (
"<green>{time:MM-DD HH:mm}</green> | <light-green>LPMM</light-green> | <light-green>{message}</light-green>" "<level>{time:MM-DD HH:mm}</level> | <light-green>LPMM</light-green> | <light-green>{message}</light-green>"
), ),
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | LPMM | {message}", "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | LPMM | {message}",
}, },
} }
# 兴趣log
INTEREST_STYLE_CONFIG = {
"advanced": {
"console_format": (
"<white>{time:YYYY-MM-DD HH:mm:ss}</white> | "
"<level>{level: <8}</level> | "
"<light-yellow>兴趣</light-yellow> | "
"<level>{message}</level>"
),
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 兴趣 | {message}",
},
"simple": {
"console_format": (
"<level>{time:MM-DD HH:mm}</level> | <light-green>兴趣</light-green> | <light-green>{message}</light-green>"
),
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 兴趣 | {message}",
},
}
CONFIRM_STYLE_CONFIG = { CONFIRM_STYLE_CONFIG = {
"console_format": "<RED>{message}</RED>", # noqa: E501 "console_format": "<RED>{message}</RED>", # noqa: E501
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | EULA与PRIVACY确认 | {message}", "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | EULA与PRIVACY确认 | {message}",
} }
# 根据SIMPLE_OUTPUT选择配置 # 根据SIMPLE_OUTPUT选择配置
MAIN_STYLE_CONFIG = MAIN_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else MAIN_STYLE_CONFIG["advanced"]
EMOJI_STYLE_CONFIG = EMOJI_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else EMOJI_STYLE_CONFIG["advanced"]
REMOTE_STYLE_CONFIG = REMOTE_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else REMOTE_STYLE_CONFIG["advanced"]
BASE_TOOL_STYLE_CONFIG = BASE_TOOL_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else BASE_TOOL_STYLE_CONFIG["advanced"]
PERSON_INFO_STYLE_CONFIG = PERSON_INFO_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else PERSON_INFO_STYLE_CONFIG["advanced"]
SUBHEARTFLOW_MANAGER_STYLE_CONFIG = (
SUBHEARTFLOW_MANAGER_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else SUBHEARTFLOW_MANAGER_STYLE_CONFIG["advanced"]
)
BACKGROUND_TASKS_STYLE_CONFIG = (
BACKGROUND_TASKS_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else BACKGROUND_TASKS_STYLE_CONFIG["advanced"]
)
MEMORY_STYLE_CONFIG = MEMORY_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else MEMORY_STYLE_CONFIG["advanced"] MEMORY_STYLE_CONFIG = MEMORY_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else MEMORY_STYLE_CONFIG["advanced"]
TOPIC_STYLE_CONFIG = TOPIC_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else TOPIC_STYLE_CONFIG["advanced"] TOPIC_STYLE_CONFIG = TOPIC_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else TOPIC_STYLE_CONFIG["advanced"]
SENDER_STYLE_CONFIG = SENDER_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else SENDER_STYLE_CONFIG["advanced"] SENDER_STYLE_CONFIG = SENDER_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else SENDER_STYLE_CONFIG["advanced"]
@ -387,6 +524,7 @@ CONFIG_STYLE_CONFIG = CONFIG_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else CONFIG
TOOL_USE_STYLE_CONFIG = TOOL_USE_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else TOOL_USE_STYLE_CONFIG["advanced"] TOOL_USE_STYLE_CONFIG = TOOL_USE_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else TOOL_USE_STYLE_CONFIG["advanced"]
PFC_STYLE_CONFIG = PFC_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else PFC_STYLE_CONFIG["advanced"] PFC_STYLE_CONFIG = PFC_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else PFC_STYLE_CONFIG["advanced"]
LPMM_STYLE_CONFIG = LPMM_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else LPMM_STYLE_CONFIG["advanced"] LPMM_STYLE_CONFIG = LPMM_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else LPMM_STYLE_CONFIG["advanced"]
INTEREST_STYLE_CONFIG = INTEREST_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else INTEREST_STYLE_CONFIG["advanced"]
def is_registered_module(record: dict) -> bool: def is_registered_module(record: dict) -> bool:
@ -451,7 +589,7 @@ def get_module_logger(
sink=sys.stderr, sink=sys.stderr,
level=os.getenv("CONSOLE_LOG_LEVEL", console_level or current_config["console_level"]), level=os.getenv("CONSOLE_LOG_LEVEL", console_level or current_config["console_level"]),
format=current_config["console_format"], format=current_config["console_format"],
filter=lambda record: record["extra"].get("module") == module_name, filter=lambda record: record["extra"].get("module") == module_name and "custom_style" not in record["extra"],
enqueue=True, enqueue=True,
) )
handler_ids.append(console_id) handler_ids.append(console_id)
@ -470,7 +608,7 @@ def get_module_logger(
retention=current_config["retention"], retention=current_config["retention"],
compression=current_config["compression"], compression=current_config["compression"],
encoding="utf-8", encoding="utf-8",
filter=lambda record: record["extra"].get("module") == module_name, filter=lambda record: record["extra"].get("module") == module_name and "custom_style" not in record["extra"],
enqueue=True, enqueue=True,
) )
handler_ids.append(file_id) handler_ids.append(file_id)
@ -487,6 +625,87 @@ def get_module_logger(
return logger.bind(module=module_name) return logger.bind(module=module_name)
def add_custom_style_handler(
module_name: str,
style_name: str,
console_format: str,
console_level: str = "INFO",
# file_format: Optional[str] = None, # 暂时只支持控制台
# file_level: str = "DEBUG",
# config: Optional[LogConfig] = None, # 暂时不使用全局配置
) -> None:
"""为指定模块和样式名添加自定义日志处理器(目前仅支持控制台)."""
handler_key = (module_name, style_name)
# 如果已存在该模块和样式的处理器,则不重复添加
if handler_key in _custom_style_handlers:
# print(f"Custom handler for {handler_key} already exists.")
return
handler_ids = []
# 添加自定义控制台处理器
try:
custom_console_id = logger.add(
sink=sys.stderr,
level=os.getenv(f"{module_name.upper()}_{style_name.upper()}_CONSOLE_LEVEL", console_level),
format=console_format,
filter=lambda record: record["extra"].get("module") == module_name
and record["extra"].get("custom_style") == style_name,
enqueue=True,
)
handler_ids.append(custom_console_id)
# print(f"Added custom console handler {custom_console_id} for {handler_key}")
except Exception as e:
logger.error(f"Failed to add custom console handler for {handler_key}: {e}")
# 如果添加失败确保列表为空避免记录不存在的ID
handler_ids = []
# # 文件处理器 (可选,按需启用)
# if file_format:
# current_config = config.config if config else DEFAULT_CONFIG
# log_dir = Path(current_config["log_dir"])
# log_dir.mkdir(parents=True, exist_ok=True)
# # 可以考虑将自定义样式的日志写入单独文件或模块主文件
# log_file = log_dir / module_name / f"{style_name}_{{time:YYYY-MM-DD}}.log"
# log_file.parent.mkdir(parents=True, exist_ok=True)
# try:
# custom_file_id = logger.add(
# sink=str(log_file),
# level=os.getenv(f"{module_name.upper()}_{style_name.upper()}_FILE_LEVEL", file_level),
# format=file_format,
# rotation=current_config["rotation"],
# retention=current_config["retention"],
# compression=current_config["compression"],
# encoding="utf-8",
# filter=lambda record: record["extra"].get("module") == module_name
# and record["extra"].get("custom_style") == style_name,
# enqueue=True,
# )
# handler_ids.append(custom_file_id)
# except Exception as e:
# logger.error(f"Failed to add custom file handler for {handler_key}: {e}")
# 更新自定义处理器注册表
if handler_ids:
_custom_style_handlers[handler_key] = handler_ids
def remove_custom_style_handler(module_name: str, style_name: str) -> None:
"""移除指定模块和样式名的自定义日志处理器."""
handler_key = (module_name, style_name)
if handler_key in _custom_style_handlers:
for handler_id in _custom_style_handlers[handler_key]:
try:
logger.remove(handler_id)
# print(f"Removed custom handler {handler_id} for {handler_key}")
except ValueError:
# 可能已经被移除或不存在
# print(f"Handler {handler_id} for {handler_key} already removed or invalid.")
pass
del _custom_style_handlers[handler_key]
def remove_module_logger(module_name: str) -> None: def remove_module_logger(module_name: str) -> None:
"""清理指定模块的日志处理器""" """清理指定模块的日志处理器"""
if module_name in _handler_registry: if module_name in _handler_registry:

View File

@ -45,7 +45,8 @@ class Server:
async def run(self): async def run(self):
"""启动服务器""" """启动服务器"""
config = Config(app=self.app, host=self._host, port=self._port) # 禁用 uvicorn 默认日志和访问日志
config = Config(app=self.app, host=self._host, port=self._port, log_config=None, access_log=False)
self._server = UvicornServer(config=config) self._server = UvicornServer(config=config)
try: try:
await self._server.serve() await self._server.serve()

View File

@ -28,7 +28,7 @@ logger = get_module_logger("config", config=config_config)
# 考虑到实际上配置文件中的mai_version是不会自动更新的,所以采用硬编码 # 考虑到实际上配置文件中的mai_version是不会自动更新的,所以采用硬编码
is_test = True is_test = True
mai_version_main = "0.6.3" mai_version_main = "0.6.3"
mai_version_fix = "snapshot-3" mai_version_fix = "snapshot-4"
if mai_version_fix: if mai_version_fix:
if is_test: if is_test:

View File

@ -4,9 +4,14 @@ import inspect
import importlib import importlib
import pkgutil import pkgutil
import os import os
from src.common.logger import get_module_logger from src.common.logger import get_module_logger, LogConfig, BASE_TOOL_STYLE_CONFIG
logger = get_module_logger("base_tool") base_tool_log_config = LogConfig(
console_format=BASE_TOOL_STYLE_CONFIG["console_format"],
file_format=BASE_TOOL_STYLE_CONFIG["file_format"],
)
logger = get_module_logger("base_tool", config=base_tool_log_config)
# 工具注册表 # 工具注册表
TOOL_REGISTRY = {} TOOL_REGISTRY = {}

View File

@ -2,14 +2,24 @@ import asyncio
import traceback import traceback
from typing import Optional, Coroutine, Callable, Any, List from typing import Optional, Coroutine, Callable, Any, List
from src.common.logger import get_module_logger from src.common.logger import get_module_logger, LogConfig, BACKGROUND_TASKS_STYLE_CONFIG
# Need manager types for dependency injection # Need manager types for dependency injection
from src.heart_flow.mai_state_manager import MaiStateManager, MaiStateInfo from src.heart_flow.mai_state_manager import MaiStateManager, MaiStateInfo
from src.heart_flow.subheartflow_manager import SubHeartflowManager from src.heart_flow.subheartflow_manager import SubHeartflowManager
from src.heart_flow.interest_logger import InterestLogger from src.heart_flow.interest_logger import InterestLogger
logger = get_module_logger("background_tasks") background_tasks_log_config = LogConfig(
console_format=BACKGROUND_TASKS_STYLE_CONFIG["console_format"],
file_format=BACKGROUND_TASKS_STYLE_CONFIG["file_format"],
)
logger = get_module_logger("background_tasks", config=background_tasks_log_config)
# 新增随机停用间隔 (5 分钟)
RANDOM_DEACTIVATION_INTERVAL_SECONDS = 300
# 新增兴趣评估间隔
INTEREST_EVAL_INTERVAL_SECONDS = 5
class BackgroundTaskManager: class BackgroundTaskManager:
@ -25,6 +35,10 @@ class BackgroundTaskManager:
cleanup_interval: int, cleanup_interval: int,
log_interval: int, log_interval: int,
inactive_threshold: int, inactive_threshold: int,
# 新增兴趣评估间隔参数
interest_eval_interval: int = INTEREST_EVAL_INTERVAL_SECONDS,
# 新增随机停用间隔参数
random_deactivation_interval: int = RANDOM_DEACTIVATION_INTERVAL_SECONDS,
): ):
self.mai_state_info = mai_state_info self.mai_state_info = mai_state_info
self.mai_state_manager = mai_state_manager self.mai_state_manager = mai_state_manager
@ -36,46 +50,86 @@ class BackgroundTaskManager:
self.cleanup_interval = cleanup_interval self.cleanup_interval = cleanup_interval
self.log_interval = log_interval self.log_interval = log_interval
self.inactive_threshold = inactive_threshold # For cleanup task self.inactive_threshold = inactive_threshold # For cleanup task
self.interest_eval_interval = interest_eval_interval # 存储兴趣评估间隔
self.random_deactivation_interval = random_deactivation_interval # 存储随机停用间隔
# Task references # Task references
self._state_update_task: Optional[asyncio.Task] = None self._state_update_task: Optional[asyncio.Task] = None
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._interest_eval_task: Optional[asyncio.Task] = None # 新增兴趣评估任务引用
self._random_deactivation_task: Optional[asyncio.Task] = None # 新增随机停用任务引用
self._tasks: List[Optional[asyncio.Task]] = [] # Keep track of all tasks self._tasks: List[Optional[asyncio.Task]] = [] # Keep track of all tasks
async def start_tasks(self): async def start_tasks(self):
"""启动所有后台任务""" """启动所有后台任务
# 状态更新任务
if self._state_update_task is None or self._state_update_task.done():
self._state_update_task = asyncio.create_task(
self._run_state_update_cycle(self.update_interval), name="hf_state_update"
)
self._tasks.append(self._state_update_task)
logger.debug(f"聊天状态更新任务已启动 间隔:{self.update_interval}s")
else:
logger.warning("状态更新任务已在运行")
# 清理任务 功能说明:
if self._cleanup_task is None or self._cleanup_task.done(): - 启动核心后台任务: 状态更新清理日志记录兴趣评估和随机停用
self._cleanup_task = asyncio.create_task(self._run_cleanup_cycle(), name="hf_cleanup") - 每个任务启动前检查是否已在运行
self._tasks.append(self._cleanup_task) - 将任务引用保存到任务列表
logger.info(f"清理任务已启动 间隔:{self.cleanup_interval}s 阈值:{self.inactive_threshold}s") """
else:
logger.warning("清理任务已在运行")
# 日志任务 # 任务配置列表: (任务变量名, 任务函数, 任务名称, 日志级别, 额外日志信息, 任务对象引用属性名)
if self._logging_task is None or self._logging_task.done(): task_configs = [
self._logging_task = asyncio.create_task(self._run_logging_cycle(), name="hf_logging") (
self._tasks.append(self._logging_task) self._state_update_task,
logger.info(f"日志任务已启动 间隔:{self.log_interval}s") lambda: self._run_state_update_cycle(self.update_interval),
else: "hf_state_update",
logger.warning("日志任务已在运行") "debug",
f"聊天状态更新任务已启动 间隔:{self.update_interval}s",
"_state_update_task",
),
(
self._cleanup_task,
self._run_cleanup_cycle,
"hf_cleanup",
"info",
f"清理任务已启动 间隔:{self.cleanup_interval}s 阈值:{self.inactive_threshold}s",
"_cleanup_task",
),
(
self._logging_task,
self._run_logging_cycle,
"hf_logging",
"info",
f"日志任务已启动 间隔:{self.log_interval}s",
"_logging_task",
),
# 新增兴趣评估任务配置
(
self._interest_eval_task,
self._run_interest_eval_cycle,
"hf_interest_eval",
"debug", # 设为debug避免过多日志
f"兴趣评估任务已启动 间隔:{self.interest_eval_interval}s",
"_interest_eval_task",
),
# 新增随机停用任务配置
(
self._random_deactivation_task,
self._run_random_deactivation_cycle,
"hf_random_deactivation",
"debug", # 设为debug避免过多日志
f"随机停用任务已启动 间隔:{self.random_deactivation_interval}s",
"_random_deactivation_task",
),
]
# # 初始状态检查 # 统一启动所有任务
# initial_state = self.mai_state_info.get_current_state() for _task_var, task_func, task_name, log_level, log_msg, task_attr_name in task_configs:
# if initial_state != self.mai_state_info.mai_status.OFFLINE: # 检查任务变量是否存在且未完成
# logger.info(f"初始状态:{initial_state.value} 触发初始激活检查") current_task_var = getattr(self, task_attr_name)
# asyncio.create_task(self.subheartflow_manager.activate_random_subflows_to_chat(initial_state)) if current_task_var is None or current_task_var.done():
new_task = asyncio.create_task(task_func(), name=task_name)
setattr(self, task_attr_name, new_task) # 更新任务变量
if new_task not in self._tasks: # 避免重复添加
self._tasks.append(new_task)
# 根据配置记录不同级别的日志
getattr(logger, log_level)(log_msg)
else:
logger.warning(f"{task_name}任务已在运行")
async def stop_tasks(self): async def stop_tasks(self):
"""停止所有后台任务。 """停止所有后台任务。
@ -126,15 +180,11 @@ class BackgroundTaskManager:
# 计算并执行间隔等待 # 计算并执行间隔等待
elapsed = asyncio.get_event_loop().time() - start_time elapsed = asyncio.get_event_loop().time() - start_time
sleep_time = max(0, interval - elapsed) sleep_time = max(0, interval - elapsed)
if sleep_time < 0.1: # 任务超时处理 # if sleep_time < 0.1: # 任务超时处理, DEBUG 时可能干扰断点
logger.warning(f"任务 {task_name} 超时执行 ({elapsed:.2f}s > {interval}s)") # logger.warning(f"任务 {task_name} 超时执行 ({elapsed:.2f}s > {interval}s)")
await asyncio.sleep(sleep_time) await asyncio.sleep(sleep_time)
# 非离线状态时评估兴趣 logger.debug(f"任务循环结束: {task_name}") # 调整日志信息
if self.mai_state_info.get_current_state() != self.mai_state_info.mai_status.OFFLINE:
await self.subheartflow_manager.evaluate_interest_and_promote()
logger.debug(f"任务循环结束, 当前状态: {self.mai_state_info.get_current_state().value}")
async def _perform_state_update_work(self): async def _perform_state_update_work(self):
"""执行状态更新工作""" """执行状态更新工作"""
@ -184,8 +234,23 @@ class BackgroundTaskManager:
logger.debug("[Background Task Cleanup] Cleanup cycle finished. No inactive flows found.") logger.debug("[Background Task Cleanup] Cleanup cycle finished. No inactive flows found.")
async def _perform_logging_work(self): async def _perform_logging_work(self):
"""执行一轮兴趣日志记录。""" """执行一轮状态日志记录。"""
await self.interest_logger.log_interest_states() await self.interest_logger.log_all_states()
# --- 新增兴趣评估工作函数 ---
async def _perform_interest_eval_work(self):
"""执行一轮子心流兴趣评估与提升检查。"""
# 直接调用 subheartflow_manager 的方法,并传递当前状态信息
await self.subheartflow_manager.evaluate_interest_and_promote(self.mai_state_info)
# --- 结束新增 ---
# --- 新增随机停用工作函数 ---
async def _perform_random_deactivation_work(self):
"""执行一轮子心流随机停用检查。"""
await self.subheartflow_manager.randomly_deactivate_subflows()
# --- 结束新增 ---
# --- Specific Task Runners --- # # --- Specific Task Runners --- #
async def _run_state_update_cycle(self, interval: int): async def _run_state_update_cycle(self, interval: int):
@ -200,5 +265,26 @@ class BackgroundTaskManager:
async def _run_logging_cycle(self): async def _run_logging_cycle(self):
await self._run_periodic_loop( await self._run_periodic_loop(
task_name="Interest Logging", interval=self.log_interval, task_func=self._perform_logging_work task_name="State Logging", interval=self.log_interval, task_func=self._perform_logging_work
) )
# --- 新增兴趣评估任务运行器 ---
async def _run_interest_eval_cycle(self):
await self._run_periodic_loop(
task_name="Interest Evaluation",
interval=self.interest_eval_interval,
task_func=self._perform_interest_eval_work,
)
# --- 结束新增 ---
# --- 新增随机停用任务运行器 ---
async def _run_random_deactivation_cycle(self):
"""运行随机停用循环。"""
await self._run_periodic_loop(
task_name="Random Deactivation",
interval=self.random_deactivation_interval,
task_func=self._perform_random_deactivation_work,
)
# --- 结束新增 ---

View File

@ -4,7 +4,6 @@ from src.config.config import global_config
from src.plugins.schedule.schedule_generator import bot_schedule from src.plugins.schedule.schedule_generator import bot_schedule
from src.common.logger import get_module_logger, LogConfig, HEARTFLOW_STYLE_CONFIG from src.common.logger import get_module_logger, LogConfig, HEARTFLOW_STYLE_CONFIG
from typing import Any, Optional from typing import Any, Optional
from src.plugins.heartFC_chat.heartFC_generator import ResponseGenerator
from src.do_tool.tool_use import ToolUser from src.do_tool.tool_use import ToolUser
from src.plugins.person_info.relationship_manager import relationship_manager # Module instance from src.plugins.person_info.relationship_manager import relationship_manager # Module instance
from src.heart_flow.mai_state_manager import MaiStateInfo, MaiStateManager from src.heart_flow.mai_state_manager import MaiStateInfo, MaiStateManager
@ -23,7 +22,7 @@ logger = get_module_logger("heartflow", config=heartflow_config)
# Task Intervals (should be in BackgroundTaskManager or config) # Task Intervals (should be in BackgroundTaskManager or config)
CLEANUP_INTERVAL_SECONDS = 1200 CLEANUP_INTERVAL_SECONDS = 1200
STATE_UPDATE_INTERVAL_SECONDS = 30 STATE_UPDATE_INTERVAL_SECONDS = 60
# Thresholds (should be in SubHeartflowManager or config) # Thresholds (should be in SubHeartflowManager or config)
INACTIVE_THRESHOLD_SECONDS = 1200 INACTIVE_THRESHOLD_SECONDS = 1200
@ -57,13 +56,12 @@ class Heartflow:
) )
# 外部依赖模块 # 外部依赖模块
self.gpt_instance = ResponseGenerator() # 响应生成器
self.tool_user_instance = ToolUser() # 工具使用模块 self.tool_user_instance = ToolUser() # 工具使用模块
self.relationship_manager_instance = relationship_manager # 关系管理模块 self.relationship_manager_instance = relationship_manager # 关系管理模块
# 子系统初始化 # 子系统初始化
self.mind: Mind = Mind(self.subheartflow_manager, self.llm_model) # 思考管理器 self.mind: Mind = Mind(self.subheartflow_manager, self.llm_model) # 思考管理器
self.interest_logger: InterestLogger = InterestLogger(self.subheartflow_manager) # 兴趣日志记录器 self.interest_logger: InterestLogger = InterestLogger(self.subheartflow_manager, self) # 兴趣日志记录器
# 后台任务管理器 (整合所有定时任务) # 后台任务管理器 (整合所有定时任务)
self.background_task_manager: BackgroundTaskManager = BackgroundTaskManager( self.background_task_manager: BackgroundTaskManager = BackgroundTaskManager(

View File

@ -5,16 +5,21 @@ import os
import traceback import traceback
from typing import TYPE_CHECKING, Dict, List from typing import TYPE_CHECKING, Dict, List
from src.common.logger import get_module_logger from src.common.logger import get_module_logger, LogConfig, INTEREST_STYLE_CONFIG
# Need chat_manager to get stream names # Need chat_manager to get stream names
from src.plugins.chat.chat_stream import chat_manager from src.plugins.chat.chat_stream import chat_manager
if TYPE_CHECKING: if TYPE_CHECKING:
from src.heart_flow.subheartflow_manager import SubHeartflowManager from src.heart_flow.subheartflow_manager import SubHeartflowManager
from src.heart_flow.sub_heartflow import SubHeartflow # For type hint in get_interest_states from src.heart_flow.sub_heartflow import SubHeartflow
from src.heart_flow.heartflow import Heartflow # 导入 Heartflow 类型
logger = get_module_logger("interest_logger") interest_logger_config = LogConfig(
console_format=INTEREST_STYLE_CONFIG["console_format"],
file_format=INTEREST_STYLE_CONFIG["file_format"],
)
logger = get_module_logger("interest_logger", config=interest_logger_config)
# Consider moving log directory/filename constants here # Consider moving log directory/filename constants here
LOG_DIRECTORY = "logs/interest" LOG_DIRECTORY = "logs/interest"
@ -22,25 +27,28 @@ HISTORY_LOG_FILENAME = "interest_history.log"
class InterestLogger: class InterestLogger:
"""负责定期记录所有子心流的兴趣状态到日志文件。""" """负责定期记录主心流和所有子心流的状态到日志文件。"""
def __init__(self, subheartflow_manager: "SubHeartflowManager"): def __init__(self, subheartflow_manager: "SubHeartflowManager", heartflow: "Heartflow"):
"""
初始化 InterestLogger
Args:
subheartflow_manager: 子心流管理器实例
heartflow: 主心流实例用于获取主心流状态
"""
self.subheartflow_manager = subheartflow_manager self.subheartflow_manager = subheartflow_manager
self.heartflow = heartflow # 存储 Heartflow 实例
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()
def _ensure_log_directory(self): def _ensure_log_directory(self):
"""确保日志目录存在。""" """确保日志目录存在。"""
try: os.makedirs(LOG_DIRECTORY, exist_ok=True)
os.makedirs(LOG_DIRECTORY, exist_ok=True) logger.info(f"已确保日志目录 '{LOG_DIRECTORY}' 存在")
logger.info(f"已确保日志目录 '{LOG_DIRECTORY}' 存在")
except OSError as e:
logger.error(f"创建日志目录 '{LOG_DIRECTORY}' 出错: {e}")
async def get_all_interest_states(self) -> Dict[str, Dict]: async def get_all_subflow_states(self) -> Dict[str, Dict]:
"""并发获取所有活跃子心流的当前兴趣状态。""" """并发获取所有活跃子心流的当前完整状态。"""
_states = {}
# Get snapshot from the manager
all_flows: List["SubHeartflow"] = self.subheartflow_manager.get_all_subheartflows() all_flows: List["SubHeartflow"] = self.subheartflow_manager.get_all_subheartflows()
tasks = [] tasks = []
results = {} results = {}
@ -49,13 +57,10 @@ class InterestLogger:
logger.debug("未找到任何子心流状态") logger.debug("未找到任何子心流状态")
return results return results
# logger.debug(f"正在获取 {len(all_flows)} 个子心流的兴趣状态...")
for subheartflow in all_flows: for subheartflow in all_flows:
if self.subheartflow_manager.get_subheartflow(subheartflow.subheartflow_id): if self.subheartflow_manager.get_subheartflow(subheartflow.subheartflow_id):
tasks.append( tasks.append(
asyncio.create_task( asyncio.create_task(subheartflow.get_full_state(), name=f"get_state_{subheartflow.subheartflow_id}")
subheartflow.get_interest_state(), name=f"get_state_{subheartflow.subheartflow_id}"
)
) )
else: else:
logger.warning(f"子心流 {subheartflow.subheartflow_id} 在创建任务前已消失") logger.warning(f"子心流 {subheartflow.subheartflow_id} 在创建任务前已消失")
@ -64,74 +69,85 @@ class InterestLogger:
done, pending = await asyncio.wait(tasks, timeout=5.0) done, pending = await asyncio.wait(tasks, timeout=5.0)
if pending: if pending:
logger.warning(f"获取兴趣状态超时,有 {len(pending)} 个任务未完成") logger.warning(f"获取子心流状态超时,有 {len(pending)} 个任务未完成")
for task in pending: for task in pending:
task.cancel() task.cancel()
for task in done: for task in done:
try: stream_id_str = task.get_name().split("get_state_")[-1]
stream_id_str = task.get_name().split("get_state_")[-1] stream_id = stream_id_str
stream_id = stream_id_str
except IndexError:
logger.error(f"无法从任务名 {task.get_name()} 中提取 stream_id")
continue
try: if task.cancelled():
logger.warning(f"获取子心流 {stream_id} 状态的任务已取消(超时)", exc_info=False)
elif task.exception():
exc = task.exception()
logger.warning(f"获取子心流 {stream_id} 状态出错: {exc}")
else:
result = task.result() result = task.result()
results[stream_id] = result results[stream_id] = result
except asyncio.CancelledError:
logger.warning(f"获取子心流 {stream_id} 兴趣状态的任务已取消(超时)", exc_info=False)
except Exception as e:
logger.warning(f"获取子心流 {stream_id} 兴趣状态出错: {e}")
logger.trace(f"成功获取 {len(results)}兴趣状态") logger.trace(f"成功获取 {len(results)} 个子心流的完整状态")
return results return results
async def log_interest_states(self): async def log_all_states(self):
"""获取所有子心流的兴趣状态并写入日志文件。""" """获取主心流状态和所有子心流的完整状态并写入日志文件。"""
# logger.debug("开始定期记录兴趣状态...")
try: try:
current_timestamp = time.time() current_timestamp = time.time()
all_interest_states = await self.get_all_interest_states()
if not all_interest_states: main_mind = self.heartflow.current_mind
logger.debug("没有获取到任何兴趣状态") # 获取 Mai 状态名称
mai_state_name = self.heartflow.current_state.get_current_state().name
all_subflow_states = await self.get_all_subflow_states()
log_entry_base = {
"timestamp": round(current_timestamp, 2),
"main_mind": main_mind,
"mai_state": mai_state_name,
"subflow_count": len(all_subflow_states),
"subflows": [],
}
if not all_subflow_states:
logger.debug("没有获取到任何子心流状态,仅记录主心流状态")
with open(self._history_log_file_path, "a", encoding="utf-8") as f:
f.write(json.dumps(log_entry_base, ensure_ascii=False) + "\n")
return return
count = 0 subflow_details = []
try: items_snapshot = list(all_subflow_states.items())
with open(self._history_log_file_path, "a", encoding="utf-8") as f: for stream_id, state in items_snapshot:
items_snapshot = list(all_interest_states.items()) group_name = stream_id
for stream_id, state in items_snapshot: try:
group_name = stream_id chat_stream = chat_manager.get_stream(stream_id)
try: if chat_stream:
chat_stream = chat_manager.get_stream(stream_id) if chat_stream.group_info:
if chat_stream and chat_stream.group_info: group_name = chat_stream.group_info.group_name
group_name = chat_stream.group_info.group_name elif chat_stream.user_info:
elif chat_stream and not chat_stream.group_info: group_name = f"私聊_{chat_stream.user_info.user_nickname}"
group_name = ( except Exception as e:
f"私聊_{chat_stream.user_info.user_nickname}" logger.trace(f"无法获取 stream_id {stream_id} 的群组名: {e}")
if chat_stream.user_info
else stream_id
)
except Exception as e:
logger.trace(f"无法获取 stream_id {stream_id} 的群组名: {e}")
pass
log_entry = { interest_state = state.get("interest_state", {})
"timestamp": round(current_timestamp, 2),
"stream_id": stream_id,
"interest_level": state.get("interest_level", 0.0),
"group_name": group_name,
"reply_probability": state.get("current_reply_probability", 0.0),
"is_above_threshold": state.get("is_above_threshold", False),
}
f.write(json.dumps(log_entry, ensure_ascii=False) + "\n")
count += 1
# logger.debug(f"成功记录 {count} 条兴趣历史到 {self._history_log_file_path}")
except IOError as e:
logger.error(f"写入兴趣历史日志到 {self._history_log_file_path} 出错: {e}")
subflow_entry = {
"stream_id": stream_id,
"group_name": group_name,
"sub_mind": state.get("current_mind", "未知"),
"sub_chat_state": state.get("chat_state", "未知"),
"interest_level": interest_state.get("interest_level", 0.0),
"start_hfc_probability": interest_state.get("start_hfc_probability", 0.0),
"is_above_threshold": interest_state.get("is_above_threshold", False),
}
subflow_details.append(subflow_entry)
log_entry_base["subflows"] = subflow_details
with open(self._history_log_file_path, "a", encoding="utf-8") as f:
f.write(json.dumps(log_entry_base, ensure_ascii=False) + "\n")
except IOError as e:
logger.error(f"写入状态日志到 {self._history_log_file_path} 出错: {e}")
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())

View File

@ -13,6 +13,9 @@ mai_state_config = LogConfig(
logger = get_module_logger("mai_state_manager", config=mai_state_config) logger = get_module_logger("mai_state_manager", config=mai_state_config)
enable_unlimited_hfc_chat = False
class MaiState(enum.Enum): class MaiState(enum.Enum):
""" """
聊天状态: 聊天状态:
@ -23,11 +26,15 @@ class MaiState(enum.Enum):
""" """
OFFLINE = "不在线" OFFLINE = "不在线"
PEEKING = "看一眼手机" PEEKING = "看一眼"
NORMAL_CHAT = "正常聊天" NORMAL_CHAT = "正常聊天"
FOCUSED_CHAT = "聊天" FOCUSED_CHAT = "聊天"
def get_normal_chat_max_num(self): def get_normal_chat_max_num(self):
# 调试用
if enable_unlimited_hfc_chat:
return 1000
if self == MaiState.OFFLINE: if self == MaiState.OFFLINE:
return 0 return 0
elif self == MaiState.PEEKING: elif self == MaiState.PEEKING:
@ -38,6 +45,10 @@ class MaiState(enum.Enum):
return 2 return 2
def get_focused_chat_max_num(self): def get_focused_chat_max_num(self):
# 调试用
if enable_unlimited_hfc_chat:
return 1000
if self == MaiState.OFFLINE: if self == MaiState.OFFLINE:
return 0 return 0
elif self == MaiState.PEEKING: elif self == MaiState.PEEKING:
@ -125,11 +136,11 @@ class MaiStateManager:
if current_status == MaiState.OFFLINE: if current_status == MaiState.OFFLINE:
logger.info("当前[离线],没看手机,思考要不要上线看看......") logger.info("当前[离线],没看手机,思考要不要上线看看......")
elif current_status == MaiState.PEEKING: elif current_status == MaiState.PEEKING:
logger.info("当前[在窥屏],思考要不要继续聊下去......") logger.info("当前[看一眼],思考要不要继续聊下去......")
elif current_status == MaiState.NORMAL_CHAT: elif current_status == MaiState.NORMAL_CHAT:
logger.info("当前在[闲聊]思考要不要继续聊下去......") logger.info("当前在[正常聊天]思考要不要继续聊下去......")
elif current_status == MaiState.FOCUSED_CHAT: elif current_status == MaiState.FOCUSED_CHAT:
logger.info("当前在[激情聊天]思考要不要继续聊下去......") logger.info("当前在[专心聊天]思考要不要继续聊下去......")
# 1. 麦麦每分钟都有概率离线 # 1. 麦麦每分钟都有概率离线
if time_since_last_min_check >= 60: if time_since_last_min_check >= 60:
@ -149,7 +160,7 @@ class MaiStateManager:
next_state_candidate = random.choices(choices_list, weights=weights, k=1)[0] next_state_candidate = random.choices(choices_list, weights=weights, k=1)[0]
if next_state_candidate != MaiState.OFFLINE: if next_state_candidate != MaiState.OFFLINE:
next_state = next_state_candidate next_state = next_state_candidate
logger.debug(f"上线!开始 {next_state.name}") logger.debug(f"上线!开始 {next_state.value}")
else: else:
# 继续离线状态 # 继续离线状态
next_state = MaiState.OFFLINE next_state = MaiState.OFFLINE
@ -159,7 +170,7 @@ class MaiStateManager:
weights = [70, 20, 10] weights = [70, 20, 10]
choices_list = [MaiState.OFFLINE, MaiState.NORMAL_CHAT, MaiState.FOCUSED_CHAT] choices_list = [MaiState.OFFLINE, MaiState.NORMAL_CHAT, MaiState.FOCUSED_CHAT]
next_state = random.choices(choices_list, weights=weights, k=1)[0] next_state = random.choices(choices_list, weights=weights, k=1)[0]
logger.debug(f"手机看完了,接下来 {next_state.name}") logger.debug(f"手机看完了,接下来 {next_state.value}")
elif current_status == MaiState.NORMAL_CHAT: elif current_status == MaiState.NORMAL_CHAT:
if time_in_current_status >= 300: # NORMAL_CHAT 最多持续 300 秒 if time_in_current_status >= 300: # NORMAL_CHAT 最多持续 300 秒
@ -167,16 +178,20 @@ class MaiStateManager:
choices_list = [MaiState.OFFLINE, MaiState.FOCUSED_CHAT] choices_list = [MaiState.OFFLINE, MaiState.FOCUSED_CHAT]
next_state = random.choices(choices_list, weights=weights, k=1)[0] next_state = random.choices(choices_list, weights=weights, k=1)[0]
if next_state == MaiState.FOCUSED_CHAT: if next_state == MaiState.FOCUSED_CHAT:
logger.debug(f"继续深入聊天, {next_state.name}") logger.debug(f"继续深入聊天, {next_state.value}")
else: else:
logger.debug(f"聊完了,接下来 {next_state.name}") logger.debug(f"聊完了,接下来 {next_state.value}")
elif current_status == MaiState.FOCUSED_CHAT: elif current_status == MaiState.FOCUSED_CHAT:
if time_in_current_status >= 600: # FOCUSED_CHAT 最多持续 600 秒 if time_in_current_status >= 600: # FOCUSED_CHAT 最多持续 600 秒
weights = [80, 20] weights = [80, 20]
choices_list = [MaiState.OFFLINE, MaiState.NORMAL_CHAT] choices_list = [MaiState.OFFLINE, MaiState.NORMAL_CHAT]
next_state = random.choices(choices_list, weights=weights, k=1)[0] next_state = random.choices(choices_list, weights=weights, k=1)[0]
logger.debug(f"深入聊天结束,接下来 {next_state.name}") logger.debug(f"深入聊天结束,接下来 {next_state.value}")
if enable_unlimited_hfc_chat:
logger.debug("调试用:开挂了,强制切换到专注聊天")
next_state = MaiState.FOCUSED_CHAT
# 如果决定了下一个状态,且这个状态与当前状态不同,则返回下一个状态 # 如果决定了下一个状态,且这个状态与当前状态不同,则返回下一个状态
if next_state is not None and next_state != current_status: if next_state is not None and next_state != current_status:

View File

@ -18,8 +18,8 @@ from src.plugins.chat.chat_stream import chat_manager
import math import math
from src.plugins.heartFC_chat.heartFC_chat import HeartFChatting from src.plugins.heartFC_chat.heartFC_chat import HeartFChatting
from src.plugins.heartFC_chat.normal_chat import NormalChat from src.plugins.heartFC_chat.normal_chat import NormalChat
from src.plugins.heartFC_chat.normal_chat_generator import ResponseGenerator
from src.do_tool.tool_use import ToolUser # from src.do_tool.tool_use import ToolUser
from src.heart_flow.mai_state_manager import MaiStateInfo from src.heart_flow.mai_state_manager import MaiStateInfo
@ -61,16 +61,15 @@ def init_prompt():
class ChatState(enum.Enum): class ChatState(enum.Enum):
ABSENT = "不参与" ABSENT = "没在看群"
CHAT = "闲聊" CHAT = "随便水群"
FOCUSED = "专注" FOCUSED = "激情水群"
class ChatStateInfo: class ChatStateInfo:
def __init__(self): def __init__(self):
self.willing = 0
self.chat_status: ChatState = ChatState.ABSENT self.chat_status: ChatState = ChatState.ABSENT
self.current_state_time = 120
self.mood_manager = MoodManager() self.mood_manager = MoodManager()
self.mood = self.mood_manager.get_prompt() self.mood = self.mood_manager.get_prompt()
@ -106,128 +105,147 @@ 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.update_task: Optional[asyncio.Task] = None
self._stop_event = asyncio.Event()
self.interest_dict: Dict[str, tuple[MessageRecv, float, bool]] = {} self.interest_dict: Dict[str, tuple[MessageRecv, float, bool]] = {}
self.update_interval = 1.0
self.start_updates(self.update_interval) # 初始化时启动后台更新任务
self.above_threshold = False
self.start_hfc_probability = 0.0
def add_interest_dict(self, message: MessageRecv, interest_value: float, is_mentioned: bool): def add_interest_dict(self, message: MessageRecv, interest_value: float, is_mentioned: bool):
self.interest_dict[message.message_info.message_id] = (message, interest_value, is_mentioned) self.interest_dict[message.message_info.message_id] = (message, interest_value, is_mentioned)
self.last_interaction_time = time.time() self.last_interaction_time = time.time()
async def _calculate_decay(self, current_time: float): async def _calculate_decay(self):
time_delta = current_time - self.last_update_time """计算兴趣值的衰减
if time_delta > 0:
old_interest = self.interest_level
if self.interest_level < 1e-9:
self.interest_level = 0.0
else:
if self.decay_rate_per_second <= 0:
interest_logger.warning(
f"InterestChatting encountered non-positive decay rate: {self.decay_rate_per_second}. Setting interest to 0."
)
self.interest_level = 0.0
elif self.interest_level < 0:
interest_logger.warning(
f"InterestChatting encountered negative interest level: {self.interest_level}. Setting interest to 0."
)
self.interest_level = 0.0
else:
try:
decay_factor = math.pow(self.decay_rate_per_second, time_delta)
self.interest_level *= decay_factor
except ValueError as e:
interest_logger.error(
f"Math error during decay calculation: {e}. Rate: {self.decay_rate_per_second}, Delta: {time_delta}, Level: {self.interest_level}. Setting interest to 0."
)
self.interest_level = 0.0
if old_interest != self.interest_level: 参数:
self.last_update_time = current_time current_time: 当前时间戳
async def _update_reply_probability(self, current_time: float): 处理逻辑:
time_delta = current_time - self.last_update_time 1. 计算时间差
if time_delta <= 0: 2. 处理各种异常情况(负值/零值)
3. 正常计算衰减
4. 更新最后更新时间
"""
# 处理极小兴趣值情况
if self.interest_level < 1e-9:
self.interest_level = 0.0
return return
currently_above = self.interest_level >= self.trigger_threshold # 异常情况处理
previous_is_above = self.is_above_threshold if self.decay_rate_per_second <= 0:
interest_logger.warning(f"衰减率({self.decay_rate_per_second})无效重置兴趣值为0")
self.interest_level = 0.0
return
if currently_above: # 正常衰减计算
if not self.is_above_threshold: try:
self.current_reply_probability = self.base_reply_probability decay_factor = math.pow(self.decay_rate_per_second, self.update_interval)
interest_logger.debug( self.interest_level *= decay_factor
f"兴趣跨过阈值 ({self.trigger_threshold}). 概率重置为基础值: {self.base_reply_probability:.4f}" except ValueError as e:
) interest_logger.error(
else: f"衰减计算错误: {e} 参数: 衰减率={self.decay_rate_per_second} 时间差={self.update_interval} 当前兴趣={self.interest_level}"
increase_amount = self.probability_increase_rate * time_delta )
self.current_reply_probability += increase_amount self.interest_level = 0.0
self.current_reply_probability = min(self.current_reply_probability, self.max_reply_probability)
async def _update_reply_probability(self):
self.above_threshold = self.interest_level >= self.trigger_threshold
if self.above_threshold:
self.start_hfc_probability += 0.1
else: else:
if previous_is_above: if self.start_hfc_probability != 0:
if self.state_change_callback: self.start_hfc_probability -= 0.1
try:
await 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:
decay_multiplier = math.pow(self.probability_decay_factor, time_delta)
self.current_reply_probability *= decay_multiplier
if self.current_reply_probability < 1e-6:
self.current_reply_probability = 0.0
elif self.probability_decay_factor <= 0:
if self.current_reply_probability > 0:
interest_logger.warning(f"无效的衰减因子 ({self.probability_decay_factor}). 设置概率为0.")
self.current_reply_probability = 0.0
self.current_reply_probability = max(self.current_reply_probability, 0.0)
self.is_above_threshold = currently_above
async def increase_interest(self, current_time: float, value: float): async def increase_interest(self, current_time: float, value: float):
await self._update_reply_probability(current_time)
await self._calculate_decay(current_time)
self.interest_level += value self.interest_level += value
self.interest_level = min(self.interest_level, self.max_interest) self.interest_level = min(self.interest_level, self.max_interest)
self.last_update_time = current_time
self.last_interaction_time = current_time
async def decrease_interest(self, current_time: float, value: float): async def decrease_interest(self, current_time: float, value: float):
await self._update_reply_probability(current_time)
self.interest_level -= value self.interest_level -= value
self.interest_level = max(self.interest_level, 0.0) self.interest_level = max(self.interest_level, 0.0)
self.last_update_time = current_time
self.last_interaction_time = current_time
async def get_interest(self) -> float: async def get_interest(self) -> float:
current_time = time.time()
await self._update_reply_probability(current_time)
await self._calculate_decay(current_time)
self.last_update_time = current_time
return self.interest_level return self.interest_level
async def get_state(self) -> dict: async def get_state(self) -> dict:
interest = await self.get_interest() interest = self.interest_level # 直接使用属性值
return { return {
"interest_level": round(interest, 2), "interest_level": round(interest, 2),
"last_update_time": self.last_update_time, "start_hfc_probability": round(self.start_hfc_probability, 4),
"current_reply_probability": round(self.current_reply_probability, 4),
"is_above_threshold": self.is_above_threshold, "is_above_threshold": self.is_above_threshold,
"last_interaction_time": self.last_interaction_time,
} }
async def should_evaluate_reply(self) -> bool: async def should_evaluate_reply(self) -> bool:
current_time = time.time()
await self._update_reply_probability(current_time)
if self.current_reply_probability > 0: if self.current_reply_probability > 0:
trigger = random.random() < self.current_reply_probability trigger = random.random() < self.current_reply_probability
return trigger return trigger
else: else:
return False return False
# --- 新增后台更新任务相关方法 ---
async def _run_update_loop(self, update_interval: float = 1.0):
"""后台循环,定期更新兴趣和回复概率。"""
while not self._stop_event.is_set():
try:
if self.interest_level != 0:
await self._calculate_decay()
await self._update_reply_probability()
# 等待下一个周期或停止事件
await asyncio.wait_for(self._stop_event.wait(), timeout=update_interval)
except asyncio.TimeoutError:
# 正常超时,继续循环
continue
except asyncio.CancelledError:
interest_logger.info("InterestChatting 更新循环被取消。")
break
except Exception as e:
interest_logger.error(f"InterestChatting 更新循环出错: {e}")
interest_logger.error(traceback.format_exc())
# 防止错误导致CPU飙升稍作等待
await asyncio.sleep(5)
interest_logger.info("InterestChatting 更新循环已停止。")
def start_updates(self, update_interval: float = 1.0):
"""启动后台更新任务"""
if self.update_task is None or self.update_task.done():
self._stop_event.clear()
self.update_task = asyncio.create_task(self._run_update_loop(update_interval))
interest_logger.debug("后台兴趣更新任务已创建并启动。")
else:
interest_logger.debug("后台兴趣更新任务已在运行中。")
async def stop_updates(self):
"""停止后台更新任务"""
if self.update_task and not self.update_task.done():
interest_logger.info("正在停止 InterestChatting 后台更新任务...")
self._stop_event.set() # 发送停止信号
try:
# 等待任务结束,设置超时
await asyncio.wait_for(self.update_task, timeout=5.0)
interest_logger.info("InterestChatting 后台更新任务已成功停止。")
except asyncio.TimeoutError:
interest_logger.warning("停止 InterestChatting 后台任务超时,尝试取消...")
self.update_task.cancel()
try:
await self.update_task # 等待取消完成
except asyncio.CancelledError:
interest_logger.info("InterestChatting 后台更新任务已被取消。")
except Exception as e:
interest_logger.error(f"停止 InterestChatting 后台任务时发生异常: {e}")
finally:
self.update_task = None
else:
interest_logger.debug("InterestChatting 后台更新任务未运行或已完成。")
# --- 结束 新增方法 ---
class SubHeartflow: class SubHeartflow:
def __init__(self, subheartflow_id, mai_states: MaiStateInfo): def __init__(self, subheartflow_id, mai_states: MaiStateInfo):
@ -272,122 +290,178 @@ class SubHeartflow:
request_type="sub_heart_flow", request_type="sub_heart_flow",
) )
self.gpt_instance = ResponseGenerator() # 响应生成器
self.tool_user_instance = ToolUser() # 工具使用模块
self.log_prefix = chat_manager.get_stream_name(self.subheartflow_id) or self.subheartflow_id self.log_prefix = chat_manager.get_stream_name(self.subheartflow_id) or self.subheartflow_id
async def add_time_current_state(self, add_time: float):
self.current_state_time += add_time
async def change_to_state_chat(self):
self.current_state_time = 120
self._start_normal_chat()
async def change_to_state_focused(self):
self.current_state_time = 60
self._start_heart_fc_chat()
async def _stop_normal_chat(self):
"""停止 NormalChat 的兴趣监控"""
if self.normal_chat_instance:
logger.info(f"{self.log_prefix} 停止 NormalChat 兴趣监控...")
try:
await self.normal_chat_instance.stop_chat() # 调用 stop_chat
except Exception as e:
logger.error(f"{self.log_prefix} 停止 NormalChat 监控任务时出错: {e}")
logger.error(traceback.format_exc())
async def _start_normal_chat(self) -> bool:
"""启动 NormalChat 实例及其兴趣监控,确保 HeartFChatting 已停止"""
await self._stop_heart_fc_chat() # 确保专注聊天已停止
log_prefix = self.log_prefix
try:
# 总是尝试创建或获取最新的 stream 和 interest_dict
chat_stream = chat_manager.get_stream(self.chat_id)
if not chat_stream:
logger.error(f"{log_prefix} 无法获取 chat_stream无法启动 NormalChat。")
return False
# 如果实例不存在或需要更新,则创建新实例
# if not self.normal_chat_instance: # 或者总是重新创建以获取最新的 interest_dict?
self.normal_chat_instance = NormalChat(chat_stream=chat_stream, interest_dict=self.get_interest_dict())
logger.info(f"{log_prefix} 创建或更新 NormalChat 实例。")
logger.info(f"{log_prefix} 启动 NormalChat 兴趣监控...")
await self.normal_chat_instance.start_chat() # <--- 修正:调用 start_chat
return True
except Exception as e:
logger.error(f"{log_prefix} 启动 NormalChat 时出错: {e}")
logger.error(traceback.format_exc())
self.normal_chat_instance = None # 启动失败,清理实例
return False
async def _stop_heart_fc_chat(self):
"""停止并清理 HeartFChatting 实例"""
if self.heart_fc_instance:
logger.info(f"{self.log_prefix} 关闭 HeartFChatting 实例...")
try:
await self.heart_fc_instance.shutdown()
except Exception as e:
logger.error(f"{self.log_prefix} 关闭 HeartFChatting 实例时出错: {e}")
logger.error(traceback.format_exc())
finally:
# 无论是否成功关闭,都清理引用
self.heart_fc_instance = None
async def _start_heart_fc_chat(self) -> bool:
"""启动 HeartFChatting 实例,确保 NormalChat 已停止"""
await self._stop_normal_chat() # 确保普通聊天监控已停止
self.clear_interest_dict() # 清理兴趣字典,准备专注聊天
log_prefix = self.log_prefix
# 如果实例已存在,检查其循环任务状态
if self.heart_fc_instance:
# 如果任务已完成或不存在,则尝试重新启动
if self.heart_fc_instance._loop_task is None or self.heart_fc_instance._loop_task.done():
logger.info(f"{log_prefix} HeartFChatting 实例存在但循环未运行,尝试启动...")
try:
await self.heart_fc_instance.start() # 启动循环
logger.info(f"{log_prefix} HeartFChatting 循环已启动。")
return True
except Exception as e:
logger.error(f"{log_prefix} 尝试启动现有 HeartFChatting 循环时出错: {e}")
logger.error(traceback.format_exc())
return False # 启动失败
else:
# 任务正在运行
logger.debug(f"{log_prefix} HeartFChatting 已在运行中。")
return True # 已经在运行
# 如果实例不存在,则创建并启动
logger.info(f"{log_prefix} 麦麦准备开始专注聊天 (创建新实例)...")
try:
self.heart_fc_instance = HeartFChatting(
chat_id=self.chat_id,
)
if await self.heart_fc_instance._initialize():
await self.heart_fc_instance.start() # 初始化成功后启动循环
logger.info(f"{log_prefix} 麦麦已成功进入专注聊天模式 (新实例已启动)。")
return True
else:
logger.error(f"{log_prefix} HeartFChatting 初始化失败,无法进入专注模式。")
self.heart_fc_instance = None # 初始化失败,清理实例
return False
except Exception as e:
logger.error(f"{log_prefix} 创建或启动 HeartFChatting 实例时出错: {e}")
logger.error(traceback.format_exc())
self.heart_fc_instance = None # 创建或初始化异常,清理实例
return False
async def set_chat_state(self, new_state: "ChatState", current_states_num: tuple = ()): async def set_chat_state(self, new_state: "ChatState", current_states_num: tuple = ()):
"""更新sub_heartflow的聊天状态并管理 HeartFChatting 和 NormalChat 实例及任务""" """更新sub_heartflow的聊天状态并管理 HeartFChatting 和 NormalChat 实例及任务"""
current_state = self.chat_state.chat_status current_state = self.chat_state.chat_status
if current_state == new_state: if current_state == new_state:
logger.trace(f"{self.log_prefix} 状态已为 {current_state.value}, 无需更改。") # logger.trace(f"{self.log_prefix} 状态已为 {current_state.value}, 无需更改。") # 减少日志噪音
return return
log_prefix = self.log_prefix # 使用实例属性 log_prefix = self.log_prefix
current_mai_state = self.mai_states.get_current_state() current_mai_state = self.mai_states.get_current_state()
state_changed = False # 标记状态是否实际发生改变
# --- 状态转换逻辑 --- # --- 状态转换逻辑 ---
if new_state == ChatState.CHAT: if new_state == ChatState.CHAT:
normal_limit = current_mai_state.get_normal_chat_max_num() normal_limit = current_mai_state.get_normal_chat_max_num()
current_chat_count = current_states_num[1] current_chat_count = current_states_num[1] if len(current_states_num) > 1 else 0
if current_chat_count >= normal_limit and current_state != ChatState.CHAT: # 仅在状态转换时检查限制 if current_chat_count >= normal_limit and current_state != ChatState.CHAT:
logger.debug( logger.debug(
f"{log_prefix} 麦麦不能从 {current_state.value} 转换到 聊天。原因:聊不过来了 ({current_chat_count}/{normal_limit})" f"{log_prefix} 无法{current_state.value}到 聊天。原因:聊不过来了 ({current_chat_count}/{normal_limit})"
) )
return # 阻止状态转换 return # 阻止状态转换
else: else:
logger.debug(f"{log_prefix} 麦麦可以进入或保持 聊天 状态 ({current_chat_count}/{normal_limit})") logger.debug(f"{log_prefix} 准备进入或保持 聊天 状态 ({current_chat_count}/{normal_limit})")
if current_state == ChatState.FOCUSED and self.heart_fc_instance: if await self._start_normal_chat():
logger.info(f"{log_prefix} 麦麦不再专注聊天,转为随便水水...") logger.info(f"{log_prefix} 成功进入或保持 NormalChat 状态。")
await self.heart_fc_instance.shutdown() # 正确关闭 HeartFChatting state_changed = True
self.heart_fc_instance = None else:
logger.error(f"{log_prefix} 启动 NormalChat 失败,无法进入 CHAT 状态。")
chat_stream = chat_manager.get_stream(self.chat_id) # 考虑是否需要回滚状态或采取其他措施
self.normal_chat_instance = NormalChat(chat_stream=chat_stream, interest_dict=self.get_interest_dict()) return # 启动失败,不改变状态
await self.normal_chat_instance.start_monitoring_interest()
# NormalChat 启动/停止逻辑将在下面处理
elif new_state == ChatState.FOCUSED: elif new_state == ChatState.FOCUSED:
focused_limit = current_mai_state.get_focused_chat_max_num() focused_limit = current_mai_state.get_focused_chat_max_num()
current_focused_count = current_states_num[2] current_focused_count = current_states_num[2] if len(current_states_num) > 2 else 0
if current_focused_count >= focused_limit and current_state != ChatState.FOCUSED: # 仅在状态转换时检查限制 if current_focused_count >= focused_limit and current_state != ChatState.FOCUSED:
logger.debug( logger.debug(
f"{log_prefix} 麦麦不能从 {current_state.value} 转换到 专注的聊天,原因:聊不过来了。({current_focused_count}/{focused_limit})" f"{log_prefix} 无法从 {current_state.value} 转到 专注。原因:聊不过来了 ({current_focused_count}/{focused_limit})"
) )
return # 阻止状态转换 return # 阻止状态转换
else: else:
logger.debug(f"{log_prefix} 麦麦可以进入或保持 专注聊天 状态 ({current_focused_count}/{focused_limit})") logger.debug(f"{log_prefix} 准备进入或保持 专注聊天 状态 ({current_focused_count}/{focused_limit})")
if not self.heart_fc_instance: if await self._start_heart_fc_chat():
logger.info(f"{log_prefix} 麦麦准备开始专注聊天...") logger.info(f"{log_prefix} 成功进入或保持 HeartFChatting 状态。")
try: state_changed = True
await self.normal_chat_instance.stop_monitoring_interest()
self.clear_interest_dict()
logger.info(f"{log_prefix} 停止 NormalChat 兴趣监控成功。")
except Exception as e:
logger.error(f"{log_prefix} 停止 NormalChat 兴趣监控时出错: {e}")
logger.error(traceback.format_exc())
try:
self.heart_fc_instance = HeartFChatting(
chat_id=self.chat_id,
gpt_instance=self.gpt_instance,
tool_user_instance=self.tool_user_instance,
)
if await self.heart_fc_instance._initialize():
await self.heart_fc_instance.add_time() # 初始化成功后添加初始时间
logger.info(f"{log_prefix} 麦麦已成功进入专注聊天模式。")
else:
logger.error(
f"{log_prefix} 麦麦不能专注聊天,因为 HeartFChatting 初始化失败了,状态回滚到 {current_state.value}"
)
self.heart_fc_instance = None
return # 阻止进入 FOCUSED 状态
except Exception as e:
logger.error(f"{log_prefix} 创建麦麦专注聊天实例时出错: {e}")
logger.error(traceback.format_exc())
self.heart_fc_instance = None
return # 创建实例异常,阻止进入 FOCUSED 状态
else: else:
# 已经是 FOCUSED 状态,或者 heart_fc_instance 已存在但未运行(不太可能) logger.error(f"{log_prefix} 启动 HeartFChatting 失败,无法进入 FOCUSED 状态。")
if not self.heart_fc_instance._loop_active: # 启动失败状态回滚到之前的状态或ABSENT这里保持不改变
logger.warning(f"{log_prefix} HeartFChatting 实例存在但未激活,尝试重新激活...") return # 启动失败,不改变状态
await self.heart_fc_instance.add_time() # 尝试添加时间以激活循环
else:
logger.debug(f"{log_prefix} 麦麦已经在专注聊天中。")
# NormalChat 启动/停止逻辑将在下面处理
elif new_state == ChatState.ABSENT: elif new_state == ChatState.ABSENT:
if current_state == ChatState.FOCUSED and self.heart_fc_instance: logger.info(f"{log_prefix} 进入 ABSENT 状态,停止所有聊天活动...")
logger.info(f"{log_prefix} 麦麦离开专注的聊天,撤退了.....") await self._stop_normal_chat()
await self.heart_fc_instance.shutdown() # 正确关闭 HeartFChatting await self._stop_heart_fc_chat()
self.heart_fc_instance = None state_changed = True # 总是可以成功转换到 ABSENT
# NormalChat 启动/停止逻辑将在下面处理
# --- 更新状态和最后活动时间 (先更新状态,再根据新状态管理任务)--- # --- 更新状态和最后活动时间 ---
self.chat_state.chat_status = new_state if state_changed:
self.last_active_time = time.time() logger.info(f"{log_prefix} 麦麦的聊天状态从 {current_state.value} 变更为 {new_state.value}")
logger.info(f"{log_prefix} 麦麦的聊天状态从 {current_state.value} 变更为 {new_state.value}") self.chat_state.chat_status = new_state
self.last_active_time = time.time()
# --- 根据新的状态管理 NormalChat 的监控任务 ---
if self.normal_chat_instance:
try:
if new_state == ChatState.ABSENT:
logger.info(f"{log_prefix} 状态变为 ABSENT停止 NormalChat 兴趣监控...")
await self.normal_chat_instance.stop_monitoring_interest()
else: # CHAT or FOCUSED
logger.info(f"{log_prefix} 状态变为 {new_state.value},启动或确认 NormalChat 兴趣监控...")
await self.normal_chat_instance.start_monitoring_interest()
except Exception as e:
logger.error(f"{log_prefix} 管理 NormalChat 监控任务时出错 (新状态: {new_state.value}): {e}")
logger.error(traceback.format_exc())
else: else:
logger.warning(f"{log_prefix} NormalChat 实例不可用,无法管理其监控任务。") # 如果因为某些原因(如启动失败)没有成功改变状态,记录一下
logger.debug(
f"{log_prefix} 尝试将状态从 {current_state.value} 变为 {new_state.value},但未成功或未执行更改。"
)
async def subheartflow_start_working(self): async def subheartflow_start_working(self):
"""启动子心流的后台任务 """启动子心流的后台任务
@ -399,7 +473,6 @@ class SubHeartflow:
logger.info(f"{self.log_prefix} 子心流开始工作...") logger.info(f"{self.log_prefix} 子心流开始工作...")
while not self.should_stop: while not self.should_stop:
# 主循环保持简单,只做状态检查
await asyncio.sleep(30) # 30秒检查一次停止标志 await asyncio.sleep(30) # 30秒检查一次停止标志
logger.info(f"{self.log_prefix} 子心流后台任务已停止。") logger.info(f"{self.log_prefix} 子心流后台任务已停止。")
@ -439,7 +512,7 @@ class SubHeartflow:
extra_info_prompt = "无工具信息。\n" extra_info_prompt = "无工具信息。\n"
individuality = Individuality.get_instance() individuality = Individuality.get_instance()
prompt_personality = f"你的名字是{individuality.bot_nickname},你" prompt_personality = f"你的名字是{individuality.personality.bot_nickname},你"
prompt_personality += individuality.personality.personality_core prompt_personality += individuality.personality.personality_core
if individuality.personality.personality_sides: if individuality.personality.personality_sides:
@ -470,7 +543,7 @@ 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,
prompt_personality=prompt_personality, prompt_personality=prompt_personality,
bot_name=individuality.bot_nickname, bot_name=individuality.personality.bot_nickname,
current_thinking_info=current_thinking_info, current_thinking_info=current_thinking_info,
time_now=time_now, time_now=time_now,
chat_observe_info=chat_observe_info, chat_observe_info=chat_observe_info,
@ -543,6 +616,16 @@ class SubHeartflow:
def clear_interest_dict(self): def clear_interest_dict(self):
self.interest_chatting.interest_dict.clear() self.interest_chatting.interest_dict.clear()
async def get_full_state(self) -> dict:
"""获取子心流的完整状态,包括兴趣、思维和聊天状态。"""
interest_state = await self.get_interest_state()
return {
"interest_state": interest_state,
"current_mind": self.current_mind,
"chat_state": self.chat_state.chat_status.value,
"last_active_time": self.last_active_time,
}
async def shutdown(self): async def shutdown(self):
"""安全地关闭子心流及其管理的任务""" """安全地关闭子心流及其管理的任务"""
if self.should_stop: if self.should_stop:
@ -552,24 +635,14 @@ class SubHeartflow:
logger.info(f"{self.log_prefix} 开始关闭子心流...") logger.info(f"{self.log_prefix} 开始关闭子心流...")
self.should_stop = True # 标记为停止,让后台任务退出 self.should_stop = True # 标记为停止,让后台任务退出
# 停止 NormalChat 监控 (保持调用,确保清理) # 使用新的停止方法
if self.normal_chat_instance: await self._stop_normal_chat()
logger.info(f"{self.log_prefix} 停止 NormalChat 监控任务 (Shutdown)...") await self._stop_heart_fc_chat()
try:
await self.normal_chat_instance.stop_monitoring_interest()
except Exception as e:
logger.error(f"{self.log_prefix} 停止 NormalChat 监控任务时出错 (Shutdown): {e}")
logger.error(traceback.format_exc())
# 停止 HeartFChatting (如果存在且正在运行) # 停止兴趣更新任务
if self.heart_fc_instance: if self.interest_chatting:
logger.info(f"{self.log_prefix} 关闭 HeartFChatting 实例 (Shutdown)...") logger.info(f"{self.log_prefix} 停止兴趣系统后台任务...")
try: await self.interest_chatting.stop_updates()
await self.heart_fc_instance.shutdown()
except Exception as e:
logger.error(f"{self.log_prefix} 关闭 HeartFChatting 实例时出错 (Shutdown): {e}")
logger.error(traceback.format_exc())
self.heart_fc_instance = None # 清理实例引用
# 取消可能存在的旧后台任务 (self.task) # 取消可能存在的旧后台任务 (self.task)
if self.task and not self.task.done(): if self.task and not self.task.done():

View File

@ -4,7 +4,7 @@ import random
from typing import Dict, Any, Optional, List from typing import Dict, Any, Optional, List
# 导入日志模块 # 导入日志模块
from src.common.logger import get_module_logger from src.common.logger import get_module_logger, LogConfig, SUBHEARTFLOW_MANAGER_STYLE_CONFIG
# 导入聊天流管理模块 # 导入聊天流管理模块
from src.plugins.chat.chat_stream import chat_manager from src.plugins.chat.chat_stream import chat_manager
@ -15,7 +15,12 @@ from src.heart_flow.mai_state_manager import MaiState, MaiStateInfo
from .observation import ChattingObservation from .observation import ChattingObservation
# 初始化日志记录器 # 初始化日志记录器
logger = get_module_logger("subheartflow_manager")
subheartflow_manager_log_config = LogConfig(
console_format=SUBHEARTFLOW_MANAGER_STYLE_CONFIG["console_format"],
file_format=SUBHEARTFLOW_MANAGER_STYLE_CONFIG["file_format"],
)
logger = get_module_logger("subheartflow_manager", config=subheartflow_manager_log_config)
# 子心流管理相关常量 # 子心流管理相关常量
INACTIVE_THRESHOLD_SECONDS = 1200 # 子心流不活跃超时时间(秒) INACTIVE_THRESHOLD_SECONDS = 1200 # 子心流不活跃超时时间(秒)
@ -257,42 +262,169 @@ class SubHeartflowManager:
logger.info(f"[停用] 完成, 尝试停止{len(flow_ids)}个, 成功{stopped_count}") logger.info(f"[停用] 完成, 尝试停止{len(flow_ids)}个, 成功{stopped_count}")
async def evaluate_interest_and_promote(self): async def evaluate_interest_and_promote(self, current_mai_state: MaiStateInfo):
"""评估CHAT状态的子心流兴趣度满足条件则提升到FOCUSED状态""" """评估子心流兴趣度满足条件且未达上限则提升到FOCUSED状态基于start_hfc_probability"""
logger.debug("[子心流管理器] 开始兴趣评估周期...") log_prefix_manager = "[子心流管理器-兴趣评估]"
evaluated_count = 0 logger.debug(f"{log_prefix_manager} 开始周期... 当前状态: {current_mai_state.get_current_state().value}")
promoted_count = 0
# 获取 FOCUSED 状态的数量上限
current_state_enum = current_mai_state.get_current_state()
focused_limit = current_state_enum.get_focused_chat_max_num()
if focused_limit <= 0:
logger.debug(
f"{log_prefix_manager} 当前状态 ({current_state_enum.value}) 不允许 FOCUSED 子心流, 跳过提升检查。"
)
return
# 获取当前 FOCUSED 状态的数量 (初始值)
current_focused_count = self.count_subflows_by_state(ChatState.FOCUSED)
logger.debug(f"{log_prefix_manager} 专注上限: {focused_limit}, 当前专注数: {current_focused_count}")
# 使用快照安全遍历 # 使用快照安全遍历
subflows_snapshot = list(self.subheartflows.values()) subflows_snapshot = list(self.subheartflows.values())
promoted_count = 0 # 记录本次提升的数量
for sub_hf in subflows_snapshot: try:
flow_id = sub_hf.subheartflow_id for sub_hf in subflows_snapshot:
if flow_id in self.subheartflows and self.subheartflows[flow_id].chat_state.chat_status == ChatState.CHAT: flow_id = sub_hf.subheartflow_id
evaluated_count += 1
stream_name = chat_manager.get_stream_name(flow_id) or flow_id stream_name = chat_manager.get_stream_name(flow_id) or flow_id
log_prefix = f"[{stream_name}]" log_prefix_flow = f"[{stream_name}]"
should_promote = await sub_hf.should_evaluate_reply() # 只处理 CHAT 状态的子心流
if should_promote: if sub_hf.chat_state.chat_status != ChatState.CHAT:
logger.info(f"{log_prefix} 兴趣评估触发升级: CHAT -> FOCUSED") continue
states_num = (
self.count_subflows_by_state(ChatState.ABSENT), # 检查是否满足提升概率
self.count_subflows_by_state(ChatState.CHAT), should_hfc = random.random() < sub_hf.interest_chatting.start_hfc_probability
self.count_subflows_by_state(ChatState.FOCUSED), if not should_hfc:
continue
# --- 关键检查:检查 FOCUSED 数量是否已达上限 ---
# 注意:在循环内部再次获取当前数量,因为之前的提升可能已经改变了计数
# 使用已经记录并在循环中更新的 current_focused_count
if current_focused_count >= focused_limit:
logger.debug(
f"{log_prefix_manager} {log_prefix_flow} 达到专注上限 ({current_focused_count}/{focused_limit}), 无法提升。概率={sub_hf.interest_chatting.start_hfc_probability:.2f}"
) )
await sub_hf.set_chat_state(ChatState.FOCUSED, states_num) continue # 跳过这个子心流,继续检查下一个
if (
self.subheartflows.get(flow_id)
and self.subheartflows[flow_id].chat_state.chat_status == ChatState.FOCUSED
):
promoted_count += 1
logger.debug(f"{log_prefix} 成功升级到FOCUSED状态")
else:
logger.info(f"{log_prefix} 升级FOCUSED可能被限制阻止")
if evaluated_count > 0: # --- 执行提升 ---
logger.debug(f"[子心流管理器] 评估完成. 评估{evaluated_count}个CHAT流, 升级{promoted_count}个到FOCUSED") # 获取当前实例以检查最新状态 (防御性编程)
current_subflow = self.subheartflows.get(flow_id)
if not current_subflow or current_subflow.chat_state.chat_status != ChatState.CHAT:
logger.warning(f"{log_prefix_manager} {log_prefix_flow} 尝试提升时状态已改变或实例消失,跳过。")
continue
logger.info(
f"{log_prefix_manager} {log_prefix_flow} 兴趣评估触发升级 (prob={sub_hf.interest_chatting.start_hfc_probability:.2f}, 上限:{focused_limit}, 当前:{current_focused_count}) -> FOCUSED"
)
states_num = (
self.count_subflows_by_state(ChatState.ABSENT),
self.count_subflows_by_state(ChatState.CHAT), # 这个值在提升前计算
current_focused_count, # 这个值在提升前计算
)
# --- 状态设置 ---
original_state = current_subflow.chat_state.chat_status # 记录原始状态
await current_subflow.set_chat_state(ChatState.FOCUSED, states_num)
# --- 状态验证 ---
final_subflow = self.subheartflows.get(flow_id)
if final_subflow:
final_state = final_subflow.chat_state.chat_status
if final_state == ChatState.FOCUSED:
logger.debug(
f"{log_prefix_manager} {log_prefix_flow} 成功从 {original_state.value} 升级到 FOCUSED 状态"
)
promoted_count += 1
# 提升成功后,更新当前专注计数,以便后续检查能使用最新值
current_focused_count += 1
elif final_state == original_state: # 状态未变
logger.warning(
f"{log_prefix_manager} {log_prefix_flow} 尝试从 {original_state.value} 升级 FOCUSED 失败,状态仍为: {final_state.value} (可能被内部逻辑阻止)"
)
else: # 状态变成其他了?
logger.warning(
f"{log_prefix_manager} {log_prefix_flow} 尝试从 {original_state.value} 升级 FOCUSED 后状态变为 {final_state.value}"
)
else: # 子心流消失了?
logger.warning(f"{log_prefix_manager} {log_prefix_flow} 升级后验证时子心流 {flow_id} 消失")
except Exception as e:
logger.error(f"{log_prefix_manager} 兴趣评估周期出错: {e}", exc_info=True)
if promoted_count > 0:
logger.info(f"{log_prefix_manager} 评估周期结束, 成功提升 {promoted_count} 个子心流到 FOCUSED。")
else:
logger.debug(f"{log_prefix_manager} 评估周期结束, 未提升任何子心流。")
async def randomly_deactivate_subflows(self, deactivation_probability: float = 0.3):
"""以一定概率将 FOCUSED 或 CHAT 状态的子心流回退到 ABSENT 状态。"""
log_prefix_manager = "[子心流管理器-随机停用]"
logger.debug(f"{log_prefix_manager} 开始随机停用检查... (概率: {deactivation_probability:.0%})")
# 使用快照安全遍历
subflows_snapshot = list(self.subheartflows.values())
deactivated_count = 0
# 预先计算状态数量,因为 set_chat_state 需要
states_num_before = (
self.count_subflows_by_state(ChatState.ABSENT),
self.count_subflows_by_state(ChatState.CHAT),
self.count_subflows_by_state(ChatState.FOCUSED),
)
try:
for sub_hf in subflows_snapshot:
flow_id = sub_hf.subheartflow_id
stream_name = chat_manager.get_stream_name(flow_id) or flow_id
log_prefix_flow = f"[{stream_name}]"
current_state = sub_hf.chat_state.chat_status
# 只处理 FOCUSED 或 CHAT 状态
if current_state not in [ChatState.FOCUSED, ChatState.CHAT]:
continue
# 检查随机概率
if random.random() < deactivation_probability:
logger.info(
f"{log_prefix_manager} {log_prefix_flow} 随机触发停用 (从 {current_state.value}) -> ABSENT"
)
# 获取当前实例以检查最新状态
current_subflow = self.subheartflows.get(flow_id)
if not current_subflow or current_subflow.chat_state.chat_status != current_state:
logger.warning(f"{log_prefix_manager} {log_prefix_flow} 尝试停用时状态已改变或实例消失,跳过。")
continue
# --- 状态设置 --- #
# 注意:这里传递的状态数量是 *停用前* 的状态数量
await current_subflow.set_chat_state(ChatState.ABSENT, states_num_before)
# --- 状态验证 (可选) ---
final_subflow = self.subheartflows.get(flow_id)
if final_subflow:
final_state = final_subflow.chat_state.chat_status
if final_state == ChatState.ABSENT:
logger.debug(
f"{log_prefix_manager} {log_prefix_flow} 成功从 {current_state.value} 停用到 ABSENT 状态"
)
deactivated_count += 1
# 注意:停用后不需要更新 states_num_before因为它只用于 set_chat_state 的限制检查
else:
logger.warning(
f"{log_prefix_manager} {log_prefix_flow} 尝试停用到 ABSENT 后状态仍为 {final_state.value}"
)
else:
logger.warning(f"{log_prefix_manager} {log_prefix_flow} 停用后验证时子心流 {flow_id} 消失")
except Exception as e:
logger.error(f"{log_prefix_manager} 随机停用周期出错: {e}", exc_info=True)
if deactivated_count > 0:
logger.info(f"{log_prefix_manager} 随机停用周期结束, 成功停用 {deactivated_count} 个子心流。")
else:
logger.debug(f"{log_prefix_manager} 随机停用周期结束, 未停用任何子心流。")
def count_subflows_by_state(self, state: ChatState) -> int: def count_subflows_by_state(self, state: ChatState) -> int:
"""统计指定状态的子心流数量 """统计指定状态的子心流数量

View File

@ -13,12 +13,16 @@ from .plugins.chat.message_sender import message_manager
from .plugins.storage.storage import MessageStorage from .plugins.storage.storage import MessageStorage
from .config.config import global_config from .config.config import global_config
from .plugins.chat.bot import chat_bot from .plugins.chat.bot import chat_bot
from .common.logger import get_module_logger from .common.logger import get_module_logger, LogConfig, MAIN_STYLE_CONFIG
from .plugins.remote import heartbeat_thread # noqa: F401 from .plugins.remote import heartbeat_thread # noqa: F401
from .individuality.individuality import Individuality from .individuality.individuality import Individuality
from .common.server import global_server from .common.server import global_server
logger = get_module_logger("main") main_log_config = LogConfig(
console_format=MAIN_STYLE_CONFIG["console_format"],
file_format=MAIN_STYLE_CONFIG["file_format"],
)
logger = get_module_logger("main", config=main_log_config)
class MainSystem: class MainSystem:

View File

@ -3,7 +3,7 @@ from ...config.config import global_config
from .message import MessageRecv from .message import MessageRecv
from ..PFC.pfc_manager import PFCManager from ..PFC.pfc_manager import PFCManager
from .chat_stream import chat_manager from .chat_stream import chat_manager
from ..chat_module.only_process.only_message_process import MessageProcessor from .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.heartflow_processor import HeartFCProcessor from ..heartFC_chat.heartflow_processor import HeartFCProcessor
@ -82,6 +82,10 @@ class ChatBot:
logger.debug(f"用户{userinfo.user_id}被禁止回复") logger.debug(f"用户{userinfo.user_id}被禁止回复")
return return
if groupinfo.group_id not in global_config.talk_allowed_groups:
logger.debug(f"{groupinfo.group_id}被禁止回复")
return
if message.message_info.template_info and not message.message_info.template_info.template_default: if message.message_info.template_info and not message.message_info.template_info.template_default:
template_group_name = message.message_info.template_info.template_name template_group_name = message.message_info.template_info.template_name
template_items = message.message_info.template_info.template_items template_items = message.message_info.template_info.template_items

View File

@ -14,9 +14,14 @@ from ...config.config import global_config
from ..chat.utils import get_embedding from ..chat.utils import get_embedding
from ..chat.utils_image import ImageManager, image_path_to_base64 from ..chat.utils_image import ImageManager, image_path_to_base64
from ..models.utils_model import LLMRequest from ..models.utils_model import LLMRequest
from src.common.logger import get_module_logger from src.common.logger import get_module_logger, LogConfig, EMOJI_STYLE_CONFIG
logger = get_module_logger("emoji") emoji_log_config = LogConfig(
console_format=EMOJI_STYLE_CONFIG["console_format"],
file_format=EMOJI_STYLE_CONFIG["file_format"],
)
logger = get_module_logger("emoji", config=emoji_log_config)
image_manager = ImageManager() image_manager = ImageManager()

View File

@ -13,9 +13,7 @@ from src.plugins.models.utils_model import LLMRequest
from src.config.config import global_config from src.config.config import global_config
from src.plugins.chat.utils_image import image_path_to_base64 # Local import needed after move from src.plugins.chat.utils_image import image_path_to_base64 # Local import needed after move
from src.plugins.utils.timer_calculater import Timer # <--- Import Timer from src.plugins.utils.timer_calculater import Timer # <--- Import Timer
from src.plugins.heartFC_chat.heartFC_generator import HeartFCGenerator
# --- Import necessary dependencies directly ---
from .heartFC_generator import ResponseGenerator # Assuming this is the type for gpt
from src.do_tool.tool_use import ToolUser from src.do_tool.tool_use import ToolUser
from ..chat.message_sender import message_manager # <-- Import the global manager from ..chat.message_sender import message_manager # <-- Import the global manager
from src.plugins.chat.emoji_manager import emoji_manager from src.plugins.chat.emoji_manager import emoji_manager
@ -70,25 +68,16 @@ PLANNER_TOOL_DEFINITION = [
class HeartFChatting: class HeartFChatting:
""" """
管理一个连续的Plan-Replier-Sender循环 管理一个连续的Plan-Replier-Sender循环
用于在特定聊天流中生成回复由计时器控制 用于在特定聊天流中生成回复
只要计时器>0循环就会继续 其生命周期现在由其关联的 SubHeartflow FOCUSED 状态控制
现在由其关联的 SubHeartflow 管理生命周期
""" """
def __init__( def __init__(self, chat_id: str):
self,
chat_id: str,
gpt_instance: ResponseGenerator, # 文本回复生成器
tool_user_instance: ToolUser, # 工具使用实例
):
""" """
HeartFChatting 初始化函数 HeartFChatting 初始化函数
参数: 参数:
chat_id: 聊天流唯一标识符(如stream_id) chat_id: 聊天流唯一标识符(如stream_id)
gpt_instance: 文本回复生成器实例
tool_user_instance: 工具使用实例
emoji_manager_instance: 表情管理实例
""" """
# 基础属性 # 基础属性
self.stream_id: str = chat_id # 聊天流ID self.stream_id: str = chat_id # 聊天流ID
@ -97,13 +86,11 @@ class HeartFChatting:
# 初始化状态控制 # 初始化状态控制
self._initialized = False # 是否已初始化标志 self._initialized = False # 是否已初始化标志
self._init_lock = asyncio.Lock() # 初始化锁(确保只初始化一次)
self._processing_lock = asyncio.Lock() # 处理锁(确保单次Plan-Replier-Sender周期) self._processing_lock = asyncio.Lock() # 处理锁(确保单次Plan-Replier-Sender周期)
self._timer_lock = asyncio.Lock() # 计时器锁(安全更新计时器)
# 依赖注入存储 # 依赖注入存储
self.gpt_instance = gpt_instance # 文本回复生成器 self.gpt_instance = HeartFCGenerator() # 文本回复生成器
self.tool_user = tool_user_instance # 工具使用实例 self.tool_user = ToolUser() # 工具使用实例
# LLM规划器配置 # LLM规划器配置
self.planner_llm = LLMRequest( self.planner_llm = LLMRequest(
@ -114,12 +101,8 @@ class HeartFChatting:
) )
# 循环控制内部状态 # 循环控制内部状态
self._loop_timer: float = 0.0 # 循环剩余时间(秒)
self._loop_active: bool = False # 循环是否正在运行 self._loop_active: bool = False # 循环是否正在运行
self._loop_task: Optional[asyncio.Task] = None # 主循环任务 self._loop_task: Optional[asyncio.Task] = None # 主循环任务
self._trigger_count_this_activation: int = 0 # 当前激活周期内的触发计数
self._initial_duration: float = INITIAL_DURATION # 首次触发增加的时间
self._last_added_duration: float = self._initial_duration # 上次增加的时间
def _get_log_prefix(self) -> str: def _get_log_prefix(self) -> str:
"""获取日志前缀,包含可读的流名称""" """获取日志前缀,包含可读的流名称"""
@ -131,82 +114,72 @@ class HeartFChatting:
懒初始化以使用提供的标识符解析chat_stream和sub_hf 懒初始化以使用提供的标识符解析chat_stream和sub_hf
确保实例已准备好处理触发器 确保实例已准备好处理触发器
""" """
async with self._init_lock: if self._initialized:
if self._initialized: return True
return True log_prefix = self._get_log_prefix() # 获取前缀
log_prefix = self._get_log_prefix() # 获取前缀 try:
try: self.chat_stream = chat_manager.get_stream(self.stream_id)
self.chat_stream = chat_manager.get_stream(self.stream_id)
if not self.chat_stream: if not self.chat_stream:
logger.error(f"{log_prefix} 获取ChatStream失败。") logger.error(f"{log_prefix} 获取ChatStream失败。")
return False
# <-- 在这里导入 heartflow 实例
from src.heart_flow.heartflow import heartflow
self.sub_hf = heartflow.get_subheartflow(self.stream_id)
if not self.sub_hf:
logger.warning(f"{log_prefix} 获取SubHeartflow失败。一些功能可能受限。")
self._initialized = True
logger.info(f"麦麦感觉到了激发了HeartFChatting{log_prefix} 初始化成功。")
return True
except Exception as e:
logger.error(f"{log_prefix} 初始化失败: {e}")
logger.error(traceback.format_exc())
return False return False
async def add_time(self): # <-- 在这里导入 heartflow 实例
from src.heart_flow.heartflow import heartflow
self.sub_hf = heartflow.get_subheartflow(self.stream_id)
if not self.sub_hf:
logger.warning(f"{log_prefix} 获取SubHeartflow失败。一些功能可能受限。")
self._initialized = True
logger.info(f"麦麦感觉到了激发了HeartFChatting{log_prefix} 初始化成功。")
return True
except Exception as e:
logger.error(f"{log_prefix} 初始化失败: {e}")
logger.error(traceback.format_exc())
return False
async def start(self):
""" """
为麦麦添加时间麦麦有兴趣时时间增加 显式尝试启动 HeartFChatting 的主循环
如果循环未激活则启动循环
""" """
log_prefix = self._get_log_prefix() log_prefix = self._get_log_prefix()
if not self._initialized: if not self._initialized:
if not await self._initialize(): if not await self._initialize():
logger.error(f"{log_prefix} 无法添加时间: 未初始化。") logger.error(f"{log_prefix} 无法启动循环: 初始化失败")
return return
logger.info(f"{log_prefix} 尝试显式启动循环...")
await self._start_loop_if_needed()
async with self._timer_lock: async def _start_loop_if_needed(self):
duration_to_add: float = 0.0 """检查是否需要启动主循环,如果未激活则启动。"""
log_prefix = self._get_log_prefix()
should_start_loop = False
# 直接检查是否激活,无需检查计时器
if not self._loop_active:
should_start_loop = True
self._loop_active = True # 标记为活动,防止重复启动
if not self._loop_active: # First trigger for this activation cycle if should_start_loop:
duration_to_add = self._initial_duration # 使用初始值 # 检查是否已有任务在运行(理论上不应该,因为 _loop_active=False
self._last_added_duration = duration_to_add # 更新上次增加的值 if self._loop_task and not self._loop_task.done():
self._trigger_count_this_activation = 1 # Start counting logger.warning(f"{log_prefix} 发现之前的循环任务仍在运行(不符合预期)。取消旧任务。")
logger.info( self._loop_task.cancel()
f"{log_prefix} 麦麦有兴趣! #{self._trigger_count_this_activation}. 麦麦打算聊: {duration_to_add:.2f}s." try:
) # 等待旧任务确实被取消
else: # Loop is already active, apply 50% reduction await asyncio.wait_for(self._loop_task, timeout=0.5)
self._trigger_count_this_activation += 1 except (asyncio.CancelledError, asyncio.TimeoutError):
duration_to_add = self._last_added_duration * 0.5 pass # 忽略取消或超时错误
if duration_to_add < 1.5: self._loop_task = None # 清理旧任务引用
duration_to_add = 1.5
# Update _last_added_duration only if it's >= 0.5 to prevent it from becoming too small
self._last_added_duration = duration_to_add
logger.info(
f"{log_prefix} 麦麦兴趣增加! #{self._trigger_count_this_activation}. 想继续聊: {duration_to_add:.2f}s, 麦麦还能聊: {self._loop_timer:.1f}s."
)
# 添加计算出的时间 logger.info(f"{log_prefix} 循环未激活,启动主循环...")
new_timer_value = self._loop_timer + duration_to_add # 创建新的循环任务
# Add max timer duration limit? e.g., max(0, min(new_timer_value, 300)) self._loop_task = asyncio.create_task(self._run_pf_loop())
self._loop_timer = max(0, new_timer_value) # 添加完成回调
# Log less frequently, e.g., every 10 seconds or significant change? self._loop_task.add_done_callback(self._handle_loop_completion)
# if self._trigger_count_this_activation % 5 == 0: # else:
# logger.info(f"{log_prefix} 麦麦现在想聊{self._loop_timer:.1f}秒") # logger.trace(f"{log_prefix} 不需要启动循环(已激活)") # 可以取消注释以进行调试
# Start the loop if it wasn't active and timer is positive
if not self._loop_active and self._loop_timer > 0:
self._loop_active = True
if self._loop_task and not self._loop_task.done():
logger.warning(f"{log_prefix} 发现意外的循环任务正在进行。取消它。")
self._loop_task.cancel()
self._loop_task = asyncio.create_task(self._run_pf_loop())
self._loop_task.add_done_callback(self._handle_loop_completion)
elif self._loop_active:
logger.trace(f"{log_prefix} 循环已经激活。计时器延长。")
def _handle_loop_completion(self, task: asyncio.Task): def _handle_loop_completion(self, task: asyncio.Task):
"""当 _run_pf_loop 任务完成时执行的回调。""" """当 _run_pf_loop 任务完成时执行的回调。"""
@ -217,49 +190,38 @@ class HeartFChatting:
logger.error(f"{log_prefix} HeartFChatting: 麦麦脱离了聊天(异常): {exception}") logger.error(f"{log_prefix} HeartFChatting: 麦麦脱离了聊天(异常): {exception}")
logger.error(traceback.format_exc()) # Log full traceback for exceptions logger.error(traceback.format_exc()) # Log full traceback for exceptions
else: else:
logger.debug(f"{log_prefix} HeartFChatting: 麦麦脱离了聊天 (正常完成)") # Loop completing normally now means it was cancelled/shutdown externally
logger.info(f"{log_prefix} HeartFChatting: 麦麦脱离了聊天 (外部停止)")
except asyncio.CancelledError: except asyncio.CancelledError:
logger.info(f"{log_prefix} HeartFChatting: 麦麦脱离了聊天(任务取消)") logger.info(f"{log_prefix} HeartFChatting: 麦麦脱离了聊天(任务取消)")
finally: finally:
self._loop_active = False self._loop_active = False
self._loop_task = None self._loop_task = None
self._last_added_duration = self._initial_duration
self._trigger_count_this_activation = 0
if self._processing_lock.locked(): if self._processing_lock.locked():
logger.warning(f"{log_prefix} HeartFChatting: 处理锁在循环结束时仍被锁定,强制释放。") logger.warning(f"{log_prefix} HeartFChatting: 处理锁在循环结束时仍被锁定,强制释放。")
self._processing_lock.release() self._processing_lock.release()
# Instance removal is now handled by SubHeartflow
# asyncio.create_task(self.heartfc_controller._remove_heartFC_chat_instance(self.stream_id)) # Removed
async def _run_pf_loop(self): async def _run_pf_loop(self):
""" """
主循环当计时器>0持续进行计划并可能回复消息 主循环持续进行计划并可能回复消息直到被外部取消
管理每个循环周期的处理锁 管理每个循环周期的处理锁
""" """
log_prefix = self._get_log_prefix() log_prefix = self._get_log_prefix()
logger.info(f"{log_prefix} HeartFChatting: 麦麦打算好好聊聊 (定时器: {self._loop_timer:.1f}s)") logger.info(f"{log_prefix} HeartFChatting: 麦麦打算好好聊聊 (进入专注模式)")
try: try:
thinking_id = "" thinking_id = ""
while True: while True: # Loop indefinitely until cancelled
cycle_timers = {} # <--- Initialize timers dict for this cycle cycle_timers = {} # <--- Initialize timers dict for this cycle
# Access MessageManager directly # Access MessageManager directly
if message_manager.check_if_sending_message_exist(self.stream_id, thinking_id): if message_manager.check_if_sending_message_exist(self.stream_id, thinking_id):
# logger.info(f"{log_prefix} HeartFChatting: 11111111111111111111111111111111麦麦还在发消息,等会再规划") # logger.info(f"{log_prefix} HeartFChatting: 麦麦还在发消息,等会再规划")
await asyncio.sleep(1) await asyncio.sleep(1)
continue continue
else: else:
# logger.info(f"{log_prefix} HeartFChatting: 11111111111111111111111111111111麦麦不发消息了,开始规划") # logger.info(f"{log_prefix} HeartFChatting: 麦麦不发消息了,开始规划")
pass pass
async with self._timer_lock:
current_timer = self._loop_timer
if current_timer <= 0:
logger.info(
f"{log_prefix} HeartFChatting: 聊太久了,麦麦打算休息一下 (计时器为 {current_timer:.1f}s)。退出HeartFChatting。"
)
break
# 记录循环周期开始时间,用于计时和休眠计算 # 记录循环周期开始时间,用于计时和休眠计算
loop_cycle_start_time = time.monotonic() loop_cycle_start_time = time.monotonic()
action_taken_this_cycle = False action_taken_this_cycle = False
@ -292,7 +254,7 @@ class HeartFChatting:
logger.error(f"{log_prefix} Planner LLM 失败,跳过本周期回复尝试。理由: {reasoning}") logger.error(f"{log_prefix} Planner LLM 失败,跳过本周期回复尝试。理由: {reasoning}")
# Optionally add a longer sleep? # Optionally add a longer sleep?
action_taken_this_cycle = False # Ensure no action is counted action_taken_this_cycle = False # Ensure no action is counted
# Continue to timer decrement and sleep # Continue to sleep logic
elif action == "text_reply": elif action == "text_reply":
logger.debug(f"{log_prefix} HeartFChatting: 麦麦决定回复文本. 理由: {reasoning}") logger.debug(f"{log_prefix} HeartFChatting: 麦麦决定回复文本. 理由: {reasoning}")
@ -367,11 +329,11 @@ class HeartFChatting:
with Timer("Wait New Msg", cycle_timers): # <--- Start Wait timer with Timer("Wait New Msg", cycle_timers): # <--- Start Wait timer
wait_start_time = time.monotonic() wait_start_time = time.monotonic()
while True: while True:
# 检查计时器是否耗尽 # Removed timer check within wait loop
async with self._timer_lock: # async with self._timer_lock:
if self._loop_timer <= 0: # if self._loop_timer <= 0:
logger.info(f"{log_prefix} HeartFChatting: 等待新消息时计时器耗尽。") # logger.info(f"{log_prefix} HeartFChatting: 等待新消息时计时器耗尽。")
break # 计时器耗尽,退出等待 # break # 计时器耗尽,退出等待
# 检查是否有新消息 # 检查是否有新消息
has_new = await observation.has_new_messages_since(planner_start_db_time) has_new = await observation.has_new_messages_since(planner_start_db_time)
@ -417,7 +379,7 @@ class HeartFChatting:
if timer_strings: # 如果有有效计时器数据才打印 if timer_strings: # 如果有有效计时器数据才打印
logger.debug(f"{log_prefix} 该次决策耗时: {'; '.join(timer_strings)}") logger.debug(f"{log_prefix} 该次决策耗时: {'; '.join(timer_strings)}")
# --- Timer Decrement --- # # --- Timer Decrement Removed --- #
cycle_duration = time.monotonic() - loop_cycle_start_time cycle_duration = time.monotonic() - loop_cycle_start_time
except Exception as e_cycle: except Exception as e_cycle:
@ -433,20 +395,23 @@ class HeartFChatting:
self._processing_lock.release() self._processing_lock.release()
# logger.trace(f"{log_prefix} 循环释放了处理锁.") # Reduce noise # logger.trace(f"{log_prefix} 循环释放了处理锁.") # Reduce noise
async with self._timer_lock: # --- Timer Decrement Logging Removed ---
self._loop_timer -= cycle_duration # async with self._timer_lock:
# Log timer decrement less aggressively # self._loop_timer -= cycle_duration
if cycle_duration > 0.1 or not action_taken_this_cycle: # # Log timer decrement less aggressively
logger.debug( # if cycle_duration > 0.1 or not action_taken_this_cycle:
f"{log_prefix} HeartFChatting: 周期耗时 {cycle_duration:.2f}s. 剩余时间: {self._loop_timer:.1f}s." # logger.debug(
) # f"{log_prefix} HeartFChatting: 周期耗时 {cycle_duration:.2f}s. 剩余时间: {self._loop_timer:.1f}s."
# )
if cycle_duration > 0.1:
logger.debug(f"{log_prefix} HeartFChatting: 周期耗时 {cycle_duration:.2f}s.")
# --- Delay --- # # --- Delay --- #
try: try:
sleep_duration = 0.0 sleep_duration = 0.0
if not action_taken_this_cycle and cycle_duration < 1.5: if not action_taken_this_cycle and cycle_duration < 1.5:
sleep_duration = 1.5 - cycle_duration sleep_duration = 1.5 - cycle_duration
elif cycle_duration < 0.2: elif cycle_duration < 0.2: # Keep minimal sleep even after action
sleep_duration = 0.2 sleep_duration = 0.2
if sleep_duration > 0: if sleep_duration > 0:
@ -455,7 +420,7 @@ class HeartFChatting:
except asyncio.CancelledError: except asyncio.CancelledError:
logger.info(f"{log_prefix} Sleep interrupted, loop likely cancelling.") logger.info(f"{log_prefix} Sleep interrupted, loop likely cancelling.")
break break # Exit loop immediately on cancellation
except asyncio.CancelledError: except asyncio.CancelledError:
logger.info(f"{log_prefix} HeartFChatting: 麦麦的聊天主循环被取消了") logger.info(f"{log_prefix} HeartFChatting: 麦麦的聊天主循环被取消了")

View File

@ -22,7 +22,7 @@ llm_config = LogConfig(
logger = get_module_logger("llm_generator", config=llm_config) logger = get_module_logger("llm_generator", config=llm_config)
class ResponseGenerator: class HeartFCGenerator:
def __init__(self): def __init__(self):
self.model_normal = LLMRequest( self.model_normal = LLMRequest(
model=global_config.llm_normal, model=global_config.llm_normal,

View File

@ -67,11 +67,7 @@ class HeartFCProcessor:
group_info=groupinfo, group_info=groupinfo,
) )
# --- 确保 SubHeartflow 存在 ---
subheartflow = await heartflow.create_subheartflow(chat.stream_id) subheartflow = await heartflow.create_subheartflow(chat.stream_id)
if not subheartflow:
logger.error(f"无法为 stream_id {chat.stream_id} 创建或获取 SubHeartflow中止处理")
return
message.update_chat_stream(chat) message.update_chat_stream(chat)
@ -137,33 +133,16 @@ class HeartFCProcessor:
# --- 修改:兴趣度更新逻辑 --- # # --- 修改:兴趣度更新逻辑 --- #
if is_mentioned: if is_mentioned:
interest_increase_on_mention = 2 interest_increase_on_mention = 1
mentioned_boost = interest_increase_on_mention # 从配置获取提及增加值 mentioned_boost = interest_increase_on_mention # 从配置获取提及增加值
interested_rate += mentioned_boost interested_rate += mentioned_boost
logger.trace(f"消息提及机器人,额外增加兴趣 {mentioned_boost:.2f}")
# 更新兴趣度 (调用 SubHeartflow 的方法) # 更新兴趣度 (调用 SubHeartflow 的方法)
current_interest = 0.0 # 初始化 current_time = time.time()
try: await subheartflow.interest_chatting.increase_interest(current_time, value=interested_rate)
# 获取当前时间,传递给 increase_interest
current_time = time.time()
await subheartflow.interest_chatting.increase_interest(current_time, value=interested_rate)
current_interest = await subheartflow.get_interest_level() # 获取更新后的值
logger.trace( # 添加到 SubHeartflow 的 interest_dict给normal_chat处理
f"使用激活率 {interested_rate:.2f} 更新后 (通过缓冲后),当前兴趣度: {current_interest:.2f} (Stream: {chat.stream_id})" await subheartflow.add_interest_dict_entry(message, interested_rate, is_mentioned)
)
# 添加到 SubHeartflow 的 interest_dict
await subheartflow.add_interest_dict_entry(message, interested_rate, is_mentioned)
logger.trace(
f"Message {message.message_info.message_id} added to interest dict for stream {chat.stream_id}"
)
except Exception as e:
logger.error(f"更新兴趣度失败 (Stream: {chat.stream_id}): {e}")
logger.error(traceback.format_exc())
# --- 结束修改 --- #
# 打印消息接收和处理信息 # 打印消息接收和处理信息
mes_name = chat.group_info.group_name if chat.group_info else "私聊" mes_name = chat.group_info.group_name if chat.group_info else "私聊"
@ -172,7 +151,7 @@ class HeartFCProcessor:
f"[{current_time}][{mes_name}]" f"[{current_time}][{mes_name}]"
f"{chat.user_info.user_nickname}:" f"{chat.user_info.user_nickname}:"
f"{message.processed_plain_text}" f"{message.processed_plain_text}"
f"兴趣度: {current_interest:.2f}" f"[兴趣度: {interested_rate:.2f}]"
) )
try: try:

View File

@ -7,7 +7,7 @@ from typing import List, Optional # 导入 Optional
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 .normal_chat_generator import ResponseGenerator from .normal_chat_generator import NormalChatGenerator
from ..chat.message import MessageSending, MessageRecv, MessageThinking, MessageSet from ..chat.message import MessageSending, MessageRecv, MessageThinking, MessageSet
from ..chat.message_sender import message_manager from ..chat.message_sender import message_manager
from ..chat.utils_image import image_path_to_base64 from ..chat.utils_image import image_path_to_base64
@ -43,12 +43,10 @@ class NormalChat:
self.interest_dict = interest_dict self.interest_dict = interest_dict
logger.info(f"[{self.stream_name}] 正在初始化 NormalChat 实例...") self.gpt = NormalChatGenerator()
self.gpt = ResponseGenerator()
self.mood_manager = MoodManager.get_instance() # MoodManager 保持单例 self.mood_manager = MoodManager.get_instance() # MoodManager 保持单例
# 存储此实例的兴趣监控任务 # 存储此实例的兴趣监控任务
self._interest_monitoring_task: Optional[asyncio.Task] = None self._chat_task: Optional[asyncio.Task] = None
logger.info(f"[{self.stream_name}] NormalChat 实例初始化完成。") logger.info(f"[{self.stream_name}] NormalChat 实例初始化完成。")
# 改为实例方法 # 改为实例方法
@ -73,7 +71,6 @@ class NormalChat:
) )
await message_manager.add_message(thinking_message) await message_manager.add_message(thinking_message)
return thinking_id return thinking_id
# 改为实例方法 # 改为实例方法
@ -176,7 +173,7 @@ class NormalChat:
await asyncio.sleep(1) # 每秒检查一次 await asyncio.sleep(1) # 每秒检查一次
# 检查任务是否已被取消 # 检查任务是否已被取消
if self._interest_monitoring_task is None or self._interest_monitoring_task.cancelled(): if self._chat_task is None or self._chat_task.cancelled():
logger.info(f"[{self.stream_name}] 兴趣监控任务被取消或置空,退出") logger.info(f"[{self.stream_name}] 兴趣监控任务被取消或置空,退出")
break break
@ -233,7 +230,7 @@ class NormalChat:
willing_log = f"[回复意愿:{await willing_manager.get_willing(self.stream_id):.2f}]" if is_willing else "" willing_log = f"[回复意愿:{await willing_manager.get_willing(self.stream_id):.2f}]" if is_willing else ""
logger.info( logger.info(
f"[{current_time}][{mes_name}]" f"[{current_time}][{mes_name}]"
f"{self.chat_stream.user_info.user_nickname}:" # 使用 self.chat_stream f"{message.message_info.user_info.user_nickname}:" # 使用 self.chat_stream
f"{message.processed_plain_text}{willing_log}[概率:{reply_probability * 100:.1f}%]" f"{message.processed_plain_text}{willing_log}[概率:{reply_probability * 100:.1f}%]"
) )
do_reply = False do_reply = False
@ -352,19 +349,20 @@ class NormalChat:
return False return False
# 改为实例方法, 移除 chat 参数 # 改为实例方法, 移除 chat 参数
async def start_monitoring_interest(self):
"""为此 NormalChat 实例关联的 ChatStream 启动兴趣消息监控任务(如果尚未运行)。""" async def start_chat(self):
if self._interest_monitoring_task is None or self._interest_monitoring_task.done(): """为此 NormalChat 实例关联的 ChatStream 启动聊天任务(如果尚未运行)。"""
logger.info(f"[{self.stream_name}] 启动兴趣消息监控任务...") if self._chat_task is None or self._chat_task.done():
logger.info(f"[{self.stream_name}] 启动聊天任务...")
task = asyncio.create_task(self._find_interested_message()) task = asyncio.create_task(self._find_interested_message())
task.add_done_callback(lambda t: self._handle_task_completion(t)) # 回调现在是实例方法 task.add_done_callback(lambda t: self._handle_task_completion(t)) # 回调现在是实例方法
self._interest_monitoring_task = task self._chat_task = task
# 改为实例方法, 移除 stream_id 参数 # 改为实例方法, 移除 stream_id 参数
def _handle_task_completion(self, task: asyncio.Task): def _handle_task_completion(self, task: asyncio.Task):
"""兴趣监控任务完成时的回调函数。""" """兴趣监控任务完成时的回调函数。"""
# 检查完成的任务是否是当前实例的任务 # 检查完成的任务是否是当前实例的任务
if task is not self._interest_monitoring_task: if task is not self._chat_task:
logger.warning(f"[{self.stream_name}] 收到一个未知或过时任务的完成回调。") logger.warning(f"[{self.stream_name}] 收到一个未知或过时任务的完成回调。")
return return
@ -382,27 +380,25 @@ class NormalChat:
logger.error(f"[{self.stream_name}] 处理任务完成回调时出错: {e}") logger.error(f"[{self.stream_name}] 处理任务完成回调时出错: {e}")
finally: finally:
# 标记任务已完成/移除 # 标记任务已完成/移除
if self._interest_monitoring_task is task: # 再次确认是当前任务 if self._chat_task is task: # 再次确认是当前任务
self._interest_monitoring_task = None self._chat_task = None
logger.debug(f"[{self.stream_name}] 兴趣监控任务已被标记为完成/移除。") logger.debug(f"[{self.stream_name}] 聊天任务已被标记为完成/移除。")
# 改为实例方法, 移除 stream_id 参数 # 改为实例方法, 移除 stream_id 参数
async def stop_monitoring_interest(self): async def stop_chat(self):
"""停止当前实例的兴趣监控任务。""" """停止当前实例的兴趣监控任务。"""
if self._interest_monitoring_task and not self._interest_monitoring_task.done(): if self._chat_task and not self._chat_task.done():
task = self._interest_monitoring_task task = self._chat_task
logger.info(f"[{self.stream_name}] 尝试取消兴趣监控任务。") logger.info(f"[{self.stream_name}] 尝试取消聊天任务。")
task.cancel() task.cancel()
try: try:
await task # 等待任务响应取消 await task # 等待任务响应取消
except asyncio.CancelledError: except asyncio.CancelledError:
logger.info(f"[{self.stream_name}] 兴趣监控任务已成功取消。") logger.info(f"[{self.stream_name}] 聊天任务已成功取消。")
except Exception as e: except Exception as e:
# 回调函数 _handle_task_completion 会处理异常日志 # 回调函数 _handle_task_completion 会处理异常日志
logger.warning(f"[{self.stream_name}] 等待监控任务取消时捕获到异常 (可能已在回调中记录): {e}") logger.warning(f"[{self.stream_name}] 等待监控任务取消时捕获到异常 (可能已在回调中记录): {e}")
finally: finally:
# 确保任务状态更新,即使等待出错 (回调函数也会尝试更新) # 确保任务状态更新,即使等待出错 (回调函数也会尝试更新)
if self._interest_monitoring_task is task: if self._chat_task is task:
self._interest_monitoring_task = None self._chat_task = None
# else:
# logger.debug(f"[{self.stream_name}] 没有正在运行的兴趣监控任务可停止。")

View File

@ -19,7 +19,7 @@ llm_config = LogConfig(
logger = get_module_logger("llm_generator", config=llm_config) logger = get_module_logger("llm_generator", config=llm_config)
class ResponseGenerator: class NormalChatGenerator:
def __init__(self): def __init__(self):
self.model_reasoning = LLMRequest( self.model_reasoning = LLMRequest(
model=global_config.llm_reasoning, model=global_config.llm_reasoning,
@ -77,8 +77,6 @@ class ResponseGenerator:
sender_name = f"({message.chat_stream.user_info.user_id}){message.chat_stream.user_info.user_nickname}" sender_name = f"({message.chat_stream.user_info.user_id}){message.chat_stream.user_info.user_nickname}"
else: else:
sender_name = f"用户({message.chat_stream.user_info.user_id})" sender_name = f"用户({message.chat_stream.user_info.user_id})"
logger.debug("开始使用生成回复-2")
# 构建prompt # 构建prompt
with Timer() as t_build_prompt: with Timer() as t_build_prompt:
prompt = await prompt_builder.build_prompt( prompt = await prompt_builder.build_prompt(

View File

@ -2,6 +2,7 @@ import os
import toml import toml
import sys import sys
import argparse import argparse
from .global_logger import logger
PG_NAMESPACE = "paragraph" PG_NAMESPACE = "paragraph"
ENT_NAMESPACE = "entity" ENT_NAMESPACE = "entity"
@ -63,8 +64,8 @@ def _load_config(config, config_file_path):
if "persistence" in file_config: if "persistence" in file_config:
config["persistence"] = file_config["persistence"] config["persistence"] = file_config["persistence"]
print(config) # print(config)
print("Configurations loaded from file: ", config_file_path) logger.info(f"从文件中读取配置: {config_file_path}")
parser = argparse.ArgumentParser(description="Configurations for the pipeline") parser = argparse.ArgumentParser(description="Configurations for the pipeline")

View File

@ -147,7 +147,10 @@ class MessageServer(BaseMessageHandler):
try: try:
if self.own_app: if self.own_app:
# 如果使用自己的 FastAPI 实例,运行 uvicorn 服务器 # 如果使用自己的 FastAPI 实例,运行 uvicorn 服务器
config = uvicorn.Config(self.app, host=self.host, port=self.port, loop="asyncio") # 禁用 uvicorn 默认日志和访问日志
config = uvicorn.Config(
self.app, host=self.host, port=self.port, loop="asyncio", log_config=None, access_log=False
)
self.server = uvicorn.Server(config) self.server = uvicorn.Server(config)
await self.server.serve() await self.server.serve()
else: else:

View File

@ -1,4 +1,4 @@
from src.common.logger import get_module_logger from src.common.logger import get_module_logger, LogConfig, PERSON_INFO_STYLE_CONFIG
from ...common.database import db from ...common.database import db
import copy import copy
import hashlib import hashlib
@ -33,7 +33,12 @@ PersonInfoManager 类方法功能摘要:
9. personal_habit_deduction - 定时推断个人习惯 9. personal_habit_deduction - 定时推断个人习惯
""" """
logger = get_module_logger("person_info") person_info_log_config = LogConfig(
console_format=PERSON_INFO_STYLE_CONFIG["console_format"],
file_format=PERSON_INFO_STYLE_CONFIG["file_format"],
)
logger = get_module_logger("person_info", config=person_info_log_config)
person_info_default = { person_info_default = {
"person_id": None, "person_id": None,

View File

@ -5,10 +5,15 @@ import platform
import os import os
import json import json
import threading import threading
from src.common.logger import get_module_logger from src.common.logger import get_module_logger, LogConfig, REMOTE_STYLE_CONFIG
from src.config.config import global_config from src.config.config import global_config
logger = get_module_logger("remote")
remote_log_config = LogConfig(
console_format=REMOTE_STYLE_CONFIG["console_format"],
file_format=REMOTE_STYLE_CONFIG["file_format"],
)
logger = get_module_logger("remote", config=remote_log_config)
# UUID文件路径 # UUID文件路径
UUID_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "client_uuid.json") UUID_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "client_uuid.json")