feat:添加后处理器统计时间

This commit is contained in:
SengokuCola
2025-06-24 01:09:04 +08:00
parent 2cfa0c04db
commit f3762df7dc
6 changed files with 452 additions and 287 deletions

View File

@@ -22,9 +22,10 @@ class CycleDetail:
# 新字段 # 新字段
self.loop_observation_info: Dict[str, Any] = {} self.loop_observation_info: Dict[str, Any] = {}
self.loop_process_info: Dict[str, Any] = {} self.loop_processor_info: Dict[str, Any] = {} # 前处理器信息
self.loop_plan_info: Dict[str, Any] = {} self.loop_plan_info: Dict[str, Any] = {}
self.loop_action_info: Dict[str, Any] = {} self.loop_action_info: Dict[str, Any] = {}
self.loop_post_processor_info: Dict[str, Any] = {} # 后处理器信息
def to_dict(self) -> Dict[str, Any]: def to_dict(self) -> Dict[str, Any]:
"""将循环信息转换为字典格式""" """将循环信息转换为字典格式"""
@@ -76,9 +77,10 @@ class CycleDetail:
"timers": self.timers, "timers": self.timers,
"thinking_id": self.thinking_id, "thinking_id": self.thinking_id,
"loop_observation_info": convert_to_serializable(self.loop_observation_info), "loop_observation_info": convert_to_serializable(self.loop_observation_info),
"loop_process_info": convert_to_serializable(self.loop_process_info), "loop_processor_info": convert_to_serializable(self.loop_processor_info),
"loop_plan_info": convert_to_serializable(self.loop_plan_info), "loop_plan_info": convert_to_serializable(self.loop_plan_info),
"loop_action_info": convert_to_serializable(self.loop_action_info), "loop_action_info": convert_to_serializable(self.loop_action_info),
"loop_post_processor_info": convert_to_serializable(self.loop_post_processor_info),
} }
def complete_cycle(self): def complete_cycle(self):
@@ -133,3 +135,4 @@ class CycleDetail:
self.loop_processor_info = loop_info["loop_processor_info"] self.loop_processor_info = loop_info["loop_processor_info"]
self.loop_plan_info = loop_info["loop_plan_info"] self.loop_plan_info = loop_info["loop_plan_info"]
self.loop_action_info = loop_info["loop_action_info"] self.loop_action_info = loop_info["loop_action_info"]
self.loop_post_processor_info = loop_info["loop_post_processor_info"]

View File

@@ -454,7 +454,19 @@ class HeartFChatting:
formatted_ptime = f"{ptime * 1000:.2f}毫秒" if ptime < 1 else f"{ptime:.2f}" formatted_ptime = f"{ptime * 1000:.2f}毫秒" if ptime < 1 else f"{ptime:.2f}"
processor_time_strings.append(f"{pname}: {formatted_ptime}") processor_time_strings.append(f"{pname}: {formatted_ptime}")
processor_time_log = ( processor_time_log = (
("\n处理器耗时: " + "; ".join(processor_time_strings)) if processor_time_strings else "" ("\n处理器耗时: " + "; ".join(processor_time_strings)) if processor_time_strings else ""
)
# 新增:输出每个后处理器的耗时
post_processor_time_costs = self._current_cycle_detail.loop_post_processor_info.get(
"post_processor_time_costs", {}
)
post_processor_time_strings = []
for pname, ptime in post_processor_time_costs.items():
formatted_ptime = f"{ptime * 1000:.2f}毫秒" if ptime < 1 else f"{ptime:.2f}"
post_processor_time_strings.append(f"{pname}: {formatted_ptime}")
post_processor_time_log = (
("\n后处理器耗时: " + "; ".join(post_processor_time_strings)) if post_processor_time_strings else ""
) )
logger.info( logger.info(
@@ -463,6 +475,7 @@ class HeartFChatting:
f"动作: {self._current_cycle_detail.loop_plan_info.get('action_result', {}).get('action_type', '未知动作')}" f"动作: {self._current_cycle_detail.loop_plan_info.get('action_result', {}).get('action_type', '未知动作')}"
+ (f"\n详情: {'; '.join(timer_strings)}" if timer_strings else "") + (f"\n详情: {'; '.join(timer_strings)}" if timer_strings else "")
+ processor_time_log + processor_time_log
+ post_processor_time_log
) )
# 记录性能数据 # 记录性能数据
@@ -473,6 +486,8 @@ class HeartFChatting:
"action_type": action_result.get("action_type", "unknown"), "action_type": action_result.get("action_type", "unknown"),
"total_time": self._current_cycle_detail.end_time - self._current_cycle_detail.start_time, "total_time": self._current_cycle_detail.end_time - self._current_cycle_detail.start_time,
"step_times": cycle_timers.copy(), "step_times": cycle_timers.copy(),
"processor_time_costs": processor_time_costs, # 前处理器时间
"post_processor_time_costs": post_processor_time_costs, # 后处理器时间
"reasoning": action_result.get("reasoning", ""), "reasoning": action_result.get("reasoning", ""),
"success": self._current_cycle_detail.loop_action_info.get("action_taken", False), "success": self._current_cycle_detail.loop_action_info.get("action_taken", False),
} }
@@ -754,6 +769,180 @@ class HeartFChatting:
return updated_action_data return updated_action_data
async def _process_post_planning_processors_with_timing(self, observations: List[Observation], action_data: dict) -> tuple[dict, dict]:
"""
处理后期处理器(规划后执行的处理器)并收集详细时间统计
包括:关系处理器、表达选择器、记忆激活器
参数:
observations: 观察器列表
action_data: 原始动作数据
返回:
tuple[dict, dict]: (更新后的动作数据, 后处理器时间统计)
"""
logger.info(f"{self.log_prefix} 开始执行后期处理器(带详细统计)")
# 创建所有后期任务
task_list = []
task_to_name_map = {}
task_start_times = {}
post_processor_time_costs = {}
# 添加后期处理器任务
for processor in self.post_planning_processors:
processor_name = processor.__class__.__name__
async def run_processor_with_timeout_and_timing(proc=processor, name=processor_name):
start_time = time.time()
try:
result = await asyncio.wait_for(
proc.process_info(observations=observations),
timeout=global_config.focus_chat.processor_max_time,
)
end_time = time.time()
post_processor_time_costs[name] = end_time - start_time
logger.debug(f"{self.log_prefix} 后期处理器 {name} 耗时: {end_time - start_time:.3f}")
return result
except Exception as e:
end_time = time.time()
post_processor_time_costs[name] = end_time - start_time
logger.warning(f"{self.log_prefix} 后期处理器 {name} 执行异常,耗时: {end_time - start_time:.3f}")
raise e
task = asyncio.create_task(run_processor_with_timeout_and_timing())
task_list.append(task)
task_to_name_map[task] = ("processor", processor_name)
task_start_times[task] = time.time()
logger.info(f"{self.log_prefix} 启动后期处理器任务: {processor_name}")
# 添加记忆激活器任务
async def run_memory_with_timeout_and_timing():
start_time = time.time()
try:
result = await asyncio.wait_for(
self.memory_activator.activate_memory(observations),
timeout=MEMORY_ACTIVATION_TIMEOUT,
)
end_time = time.time()
post_processor_time_costs["MemoryActivator"] = end_time - start_time
logger.debug(f"{self.log_prefix} 记忆激活器耗时: {end_time - start_time:.3f}")
return result
except Exception as e:
end_time = time.time()
post_processor_time_costs["MemoryActivator"] = end_time - start_time
logger.warning(f"{self.log_prefix} 记忆激活器执行异常,耗时: {end_time - start_time:.3f}")
raise e
memory_task = asyncio.create_task(run_memory_with_timeout_and_timing())
task_list.append(memory_task)
task_to_name_map[memory_task] = ("memory", "MemoryActivator")
task_start_times[memory_task] = time.time()
logger.info(f"{self.log_prefix} 启动记忆激活器任务")
# 如果没有任何后期任务,直接返回
if not task_list:
logger.info(f"{self.log_prefix} 没有启用的后期处理器或记忆激活器")
return action_data, {}
# 等待所有任务完成
pending_tasks = set(task_list)
all_post_plan_info = []
running_memorys = []
while pending_tasks:
done, pending_tasks = await asyncio.wait(pending_tasks, return_when=asyncio.FIRST_COMPLETED)
for task in done:
task_type, task_name = task_to_name_map[task]
try:
result = await task
if task_type == "processor":
logger.info(f"{self.log_prefix} 后期处理器 {task_name} 已完成!")
if result is not None:
all_post_plan_info.extend(result)
else:
logger.warning(f"{self.log_prefix} 后期处理器 {task_name} 返回了 None")
elif task_type == "memory":
logger.info(f"{self.log_prefix} 记忆激活器已完成!")
if result is not None:
running_memorys = result
else:
logger.warning(f"{self.log_prefix} 记忆激活器返回了 None")
running_memorys = []
except asyncio.TimeoutError:
# 对于超时任务,记录已用时间
elapsed_time = time.time() - task_start_times[task]
if task_type == "processor":
post_processor_time_costs[task_name] = elapsed_time
logger.warning(
f"{self.log_prefix} 后期处理器 {task_name} 超时(>{global_config.focus_chat.processor_max_time}s已跳过耗时: {elapsed_time:.3f}"
)
elif task_type == "memory":
post_processor_time_costs["MemoryActivator"] = elapsed_time
logger.warning(f"{self.log_prefix} 记忆激活器超时(>{MEMORY_ACTIVATION_TIMEOUT}s已跳过耗时: {elapsed_time:.3f}")
running_memorys = []
except Exception as e:
# 对于异常任务,记录已用时间
elapsed_time = time.time() - task_start_times[task]
if task_type == "processor":
post_processor_time_costs[task_name] = elapsed_time
logger.error(
f"{self.log_prefix} 后期处理器 {task_name} 执行失败,耗时: {elapsed_time:.3f}秒. 错误: {e}",
exc_info=True,
)
elif task_type == "memory":
post_processor_time_costs["MemoryActivator"] = elapsed_time
logger.error(f"{self.log_prefix} 记忆激活器执行失败,耗时: {elapsed_time:.3f}秒. 错误: {e}", exc_info=True)
running_memorys = []
# 将后期处理器的结果整合到 action_data 中
updated_action_data = action_data.copy()
relation_info = ""
selected_expressions = []
structured_info = ""
for info in all_post_plan_info:
if isinstance(info, RelationInfo):
relation_info = info.get_processed_info()
elif isinstance(info, ExpressionSelectionInfo):
selected_expressions = info.get_expressions_for_action_data()
elif isinstance(info, StructuredInfo):
structured_info = info.get_processed_info()
if relation_info:
updated_action_data["relation_info_block"] = relation_info
if selected_expressions:
updated_action_data["selected_expressions"] = selected_expressions
if structured_info:
updated_action_data["structured_info"] = structured_info
# 特殊处理running_memorys
if running_memorys:
memory_str = "以下是当前在聊天中,你回忆起的记忆:\n"
for running_memory in running_memorys:
memory_str += f"{running_memory['content']}\n"
updated_action_data["memory_block"] = memory_str
logger.info(f"{self.log_prefix} 添加了 {len(running_memorys)} 个激活的记忆到action_data")
if all_post_plan_info or running_memorys:
logger.info(
f"{self.log_prefix} 后期处理完成,产生了 {len(all_post_plan_info)} 个信息项和 {len(running_memorys)} 个记忆"
)
# 输出详细统计信息
if post_processor_time_costs:
stats_str = ", ".join([f"{name}: {time_cost:.3f}s" for name, time_cost in post_processor_time_costs.items()])
logger.info(f"{self.log_prefix} 后期处理器详细耗时统计: {stats_str}")
return updated_action_data, post_processor_time_costs
async def _observe_process_plan_action_loop(self, cycle_timers: dict, thinking_id: str) -> dict: async def _observe_process_plan_action_loop(self, cycle_timers: dict, thinking_id: str) -> dict:
try: try:
loop_start_time = time.time() loop_start_time = time.time()
@@ -836,28 +1025,37 @@ class HeartFChatting:
"observed_messages": plan_result.get("observed_messages", ""), "observed_messages": plan_result.get("observed_messages", ""),
} }
with Timer("执行动作", cycle_timers): # 修正将后期处理器从执行动作Timer中分离出来
action_type, action_data, reasoning = ( action_type, action_data, reasoning = (
plan_result.get("action_result", {}).get("action_type", "error"), plan_result.get("action_result", {}).get("action_type", "error"),
plan_result.get("action_result", {}).get("action_data", {}), plan_result.get("action_result", {}).get("action_data", {}),
plan_result.get("action_result", {}).get("reasoning", "未提供理由"), plan_result.get("action_result", {}).get("reasoning", "未提供理由"),
) )
if action_type == "reply": if action_type == "reply":
action_str = "回复" action_str = "回复"
elif action_type == "no_reply": elif action_type == "no_reply":
action_str = "不回复" action_str = "不回复"
else: else:
action_str = action_type action_str = action_type
logger.debug(f"{self.log_prefix} 麦麦想要:'{action_str}'") logger.debug(f"{self.log_prefix} 麦麦想要:'{action_str}'")
# 如果动作不是no_reply则执行后期处理器 # 添加:单独计时后期处理器,并收集详细统计
if action_type != "no_reply": post_processor_time_costs = {}
with Timer("后期处理器", cycle_timers): if action_type != "no_reply":
logger.debug(f"{self.log_prefix} 执行后期处理器(动作类型: {action_type}") with Timer("后期处理器", cycle_timers):
action_data = await self._process_post_planning_processors(self.observations, action_data) logger.debug(f"{self.log_prefix} 执行后期处理器(动作类型: {action_type}")
# 记录详细的后处理器时间
post_start_time = time.time()
action_data, post_processor_time_costs = await self._process_post_planning_processors_with_timing(self.observations, action_data)
post_end_time = time.time()
logger.info(f"{self.log_prefix} 后期处理器总耗时: {post_end_time - post_start_time:.3f}")
else:
logger.debug(f"{self.log_prefix} 跳过后期处理器(动作类型: {action_type}")
# 修正:纯动作执行计时
with Timer("动作执行", cycle_timers):
success, reply_text, command = await self._handle_action( success, reply_text, command = await self._handle_action(
action_type, reasoning, action_data, cycle_timers, thinking_id action_type, reasoning, action_data, cycle_timers, thinking_id
) )
@@ -869,11 +1067,17 @@ class HeartFChatting:
"taken_time": time.time(), "taken_time": time.time(),
} }
# 添加后处理器统计到loop_info
loop_post_processor_info = {
"post_processor_time_costs": post_processor_time_costs,
}
loop_info = { loop_info = {
"loop_observation_info": loop_observation_info, "loop_observation_info": loop_observation_info,
"loop_processor_info": loop_processor_info, "loop_processor_info": loop_processor_info,
"loop_plan_info": loop_plan_info, "loop_plan_info": loop_plan_info,
"loop_action_info": loop_action_info, "loop_action_info": loop_action_info,
"loop_post_processor_info": loop_post_processor_info, # 新增
} }
return loop_info return loop_info

View File

@@ -41,6 +41,8 @@ class HFCPerformanceLogger:
"action_type": cycle_data.get("action_type", "unknown"), "action_type": cycle_data.get("action_type", "unknown"),
"total_time": cycle_data.get("total_time", 0), "total_time": cycle_data.get("total_time", 0),
"step_times": cycle_data.get("step_times", {}), "step_times": cycle_data.get("step_times", {}),
"processor_time_costs": cycle_data.get("processor_time_costs", {}), # 前处理器时间
"post_processor_time_costs": cycle_data.get("post_processor_time_costs", {}), # 后处理器时间
"reasoning": cycle_data.get("reasoning", ""), "reasoning": cycle_data.get("reasoning", ""),
"success": cycle_data.get("success", False), "success": cycle_data.get("success", False),
} }
@@ -51,9 +53,22 @@ class HFCPerformanceLogger:
# 立即写入文件(防止数据丢失) # 立即写入文件(防止数据丢失)
self._write_session_data() self._write_session_data()
logger.debug( # 构建详细的日志信息
f"记录HFC循环数据: cycle_id={record['cycle_id']}, action={record['action_type']}, time={record['total_time']:.2f}s" log_parts = [
) f"cycle_id={record['cycle_id']}",
f"action={record['action_type']}",
f"time={record['total_time']:.2f}s"
]
# 添加后处理器时间信息到日志
if record['post_processor_time_costs']:
post_processor_stats = ", ".join([
f"{name}: {time_cost:.3f}s"
for name, time_cost in record['post_processor_time_costs'].items()
])
log_parts.append(f"post_processors=({post_processor_stats})")
logger.debug(f"记录HFC循环数据: {', '.join(log_parts)}")
except Exception as e: except Exception as e:
logger.error(f"记录HFC循环数据失败: {e}") logger.error(f"记录HFC循环数据失败: {e}")

View File

@@ -440,6 +440,7 @@ class DefaultReplyer:
chat_info=chat_talking_prompt, chat_info=chat_talking_prompt,
memory_block=memory_block, memory_block=memory_block,
structured_info_block=structured_info_block, structured_info_block=structured_info_block,
relation_info_block=relation_info_block,
extra_info_block=extra_info_block, extra_info_block=extra_info_block,
time_block=time_block, time_block=time_block,
keywords_reaction_prompt=keywords_reaction_prompt, keywords_reaction_prompt=keywords_reaction_prompt,

View File

@@ -58,6 +58,12 @@ FOCUS_CYCLE_CNT_BY_VERSION = "focus_cycle_count_by_version"
FOCUS_ACTION_RATIOS_BY_VERSION = "focus_action_ratios_by_version" FOCUS_ACTION_RATIOS_BY_VERSION = "focus_action_ratios_by_version"
FOCUS_AVG_TIMES_BY_VERSION = "focus_avg_times_by_version" FOCUS_AVG_TIMES_BY_VERSION = "focus_avg_times_by_version"
# 新增: 后处理器统计数据的键
FOCUS_POST_PROCESSOR_TIMES = "focus_post_processor_times"
FOCUS_POST_PROCESSOR_COUNT = "focus_post_processor_count"
FOCUS_POST_PROCESSOR_SUCCESS_RATE = "focus_post_processor_success_rate"
FOCUS_PROCESSOR_TIMES = "focus_processor_times" # 前处理器统计
class OnlineTimeRecordTask(AsyncTask): class OnlineTimeRecordTask(AsyncTask):
"""在线时间记录任务""" """在线时间记录任务"""
@@ -495,6 +501,10 @@ class StatisticOutputTask(AsyncTask):
FOCUS_AVG_TIMES_BY_VERSION: defaultdict(lambda: defaultdict(list)), FOCUS_AVG_TIMES_BY_VERSION: defaultdict(lambda: defaultdict(list)),
"focus_exec_times_by_version_action": defaultdict(lambda: defaultdict(list)), "focus_exec_times_by_version_action": defaultdict(lambda: defaultdict(list)),
"focus_action_ratios_by_chat": defaultdict(lambda: defaultdict(int)), "focus_action_ratios_by_chat": defaultdict(lambda: defaultdict(int)),
# 新增:前处理器和后处理器统计字段
FOCUS_PROCESSOR_TIMES: defaultdict(list), # 前处理器时间
FOCUS_POST_PROCESSOR_TIMES: defaultdict(list), # 后处理器时间
FOCUS_POST_PROCESSOR_COUNT: defaultdict(int), # 后处理器执行次数
} }
for period_key, _ in collect_period for period_key, _ in collect_period
} }
@@ -556,6 +566,10 @@ class StatisticOutputTask(AsyncTask):
total_time = cycle_data.get("total_time", 0.0) total_time = cycle_data.get("total_time", 0.0)
step_times = cycle_data.get("step_times", {}) step_times = cycle_data.get("step_times", {})
version = cycle_data.get("version", "unknown") version = cycle_data.get("version", "unknown")
# 新增:获取前处理器和后处理器时间
processor_time_costs = cycle_data.get("processor_time_costs", {})
post_processor_time_costs = cycle_data.get("post_processor_time_costs", {})
# 更新聊天ID名称映射 # 更新聊天ID名称映射
if chat_id not in self.name_mapping: if chat_id not in self.name_mapping:
@@ -594,6 +608,15 @@ class StatisticOutputTask(AsyncTask):
stat["focus_exec_times_by_chat_action"][chat_id][action_type].append(time_val) stat["focus_exec_times_by_chat_action"][chat_id][action_type].append(time_val)
# 按版本和action类型收集执行时间 # 按版本和action类型收集执行时间
stat["focus_exec_times_by_version_action"][version][action_type].append(time_val) stat["focus_exec_times_by_version_action"][version][action_type].append(time_val)
# 新增:前处理器时间统计
for processor_name, time_val in processor_time_costs.items():
stat[FOCUS_PROCESSOR_TIMES][processor_name].append(time_val)
# 新增:后处理器时间统计
for processor_name, time_val in post_processor_time_costs.items():
stat[FOCUS_POST_PROCESSOR_TIMES][processor_name].append(time_val)
stat[FOCUS_POST_PROCESSOR_COUNT][processor_name] += 1
break break
except Exception as e: except Exception as e:
logger.warning(f"Failed to process cycle data: {e}") logger.warning(f"Failed to process cycle data: {e}")
@@ -651,6 +674,20 @@ class StatisticOutputTask(AsyncTask):
else: else:
stat["focus_exec_times_by_version_action"][version][action_type] = 0.0 stat["focus_exec_times_by_version_action"][version][action_type] = 0.0
# 新增:计算前处理器平均时间
for processor_name, times in stat[FOCUS_PROCESSOR_TIMES].items():
if times:
stat[FOCUS_PROCESSOR_TIMES][processor_name] = sum(times) / len(times)
else:
stat[FOCUS_PROCESSOR_TIMES][processor_name] = 0.0
# 新增:计算后处理器平均时间
for processor_name, times in stat[FOCUS_POST_PROCESSOR_TIMES].items():
if times:
stat[FOCUS_POST_PROCESSOR_TIMES][processor_name] = sum(times) / len(times)
else:
stat[FOCUS_POST_PROCESSOR_TIMES][processor_name] = 0.0
def _collect_all_statistics(self, now: datetime) -> Dict[str, Dict[str, Any]]: def _collect_all_statistics(self, now: datetime) -> Dict[str, Dict[str, Any]]:
""" """
收集各时间段的统计数据 收集各时间段的统计数据
@@ -959,261 +996,6 @@ class StatisticOutputTask(AsyncTask):
] ]
) )
# 按聊天流统计
_focus_chat_rows = "\n".join(
[
f"<tr><td>{self.name_mapping.get(chat_id, (chat_id, 0))[0]}</td><td>{count}</td><td>{stat_data[FOCUS_TOTAL_TIME_BY_CHAT].get(chat_id, 0):.2f}秒</td></tr>"
for chat_id, count in sorted(
stat_data[FOCUS_CYCLE_CNT_BY_CHAT].items(), key=lambda x: x[1], reverse=True
)
]
)
# 全局阶段时间统计
_focus_stage_rows = "\n".join(
[
f"<tr><td>{stage}</td><td>{avg_time:.3f}秒</td></tr>"
for stage, avg_time in sorted(stat_data[FOCUS_AVG_TIMES_BY_STAGE].items())
]
)
# 按Action类型的阶段时间统计
focus_action_stage_items = []
for action_type, stage_times in stat_data[FOCUS_AVG_TIMES_BY_ACTION].items():
for stage, avg_time in stage_times.items():
focus_action_stage_items.append((action_type, stage, avg_time))
_focus_action_stage_rows = "\n".join(
[
f"<tr><td>{action_type}</td><td>{stage}</td><td>{avg_time:.3f}秒</td></tr>"
for action_type, stage, avg_time in sorted(focus_action_stage_items)
]
)
# 生成HTML
return f"""
<div id=\"{div_id}\" class=\"tab-content\">
<p class=\"info-item\">
<strong>统计时段: </strong>
{start_time.strftime("%Y-%m-%d %H:%M:%S")} ~ {now.strftime("%Y-%m-%d %H:%M:%S")}
</p>
<p class=\"info-item\"><strong>总在线时间: </strong>{_format_online_time(stat_data[ONLINE_TIME])}</p>
<p class=\"info-item\"><strong>总消息数: </strong>{stat_data[TOTAL_MSG_CNT]}</p>
<p class=\"info-item\"><strong>总请求数: </strong>{stat_data[TOTAL_REQ_CNT]}</p>
<p class=\"info-item\"><strong>总花费: </strong>{stat_data[TOTAL_COST]:.4f} ¥</p>
<h2>按模型分类统计</h2>
<table>
<thead><tr><th>模型名称</th><th>调用次数</th><th>输入Token</th><th>输出Token</th><th>Token总量</th><th>累计花费</th></tr></thead>
<tbody>
{model_rows}
</tbody>
</table>
<h2>按模块分类统计</h2>
<table>
<thead>
<tr><th>模块名称</th><th>调用次数</th><th>输入Token</th><th>输出Token</th><th>Token总量</th><th>累计花费</th></tr>
</thead>
<tbody>
{module_rows}
</tbody>
</table>
<h2>按请求类型分类统计</h2>
<table>
<thead>
<tr><th>请求类型</th><th>调用次数</th><th>输入Token</th><th>输出Token</th><th>Token总量</th><th>累计花费</th></tr>
</thead>
<tbody>
{type_rows}
</tbody>
</table>
<h2>聊天消息统计</h2>
<table>
<thead>
<tr><th>联系人/群组名称</th><th>消息数量</th></tr>
</thead>
<tbody>
{chat_rows}
</tbody>
</table>
</div>
"""
tab_content_list = [
_format_stat_data(stat[period[0]], period[0], now - period[1])
for period in self.stat_period
if period[0] != "all_time"
]
tab_content_list.append(
_format_stat_data(stat["all_time"], "all_time", datetime.fromtimestamp(local_storage["deploy_time"]))
)
# 添加Focus统计内容
focus_tab = self._generate_focus_tab(stat)
tab_content_list.append(focus_tab)
# 添加版本对比内容
versions_tab = self._generate_versions_tab(stat)
tab_content_list.append(versions_tab)
# 添加图表内容
chart_data = self._generate_chart_data(stat)
tab_content_list.append(self._generate_chart_tab(chart_data))
joined_tab_list = "\n".join(tab_list)
joined_tab_content = "\n".join(tab_content_list)
html_template = (
"""
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MaiBot运行统计报告</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f4f7f6;
color: #333;
line-height: 1.6;
}
.container {
max-width: 900px;
margin: 20px auto;
background-color: #fff;
padding: 25px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1, h2 {
color: #2c3e50;
border-bottom: 2px solid #3498db;
padding-bottom: 10px;
margin-top: 0;
}
h1 {
text-align: center;
font-size: 2em;
}
h2 {
font-size: 1.5em;
margin-top: 30px;
}
p {
margin-bottom: 10px;
}
.info-item {
background-color: #ecf0f1;
padding: 8px 12px;
border-radius: 4px;
margin-bottom: 8px;
font-size: 0.95em;
}
.info-item strong {
color: #2980b9;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 15px;
font-size: 0.9em;
}
th, td {
border: 1px solid #ddd;
padding: 10px;
text-align: left;
}
th {
background-color: #3498db;
color: white;
font-weight: bold;
}
tr:nth-child(even) {
background-color: #f9f9f9;
}
.footer {
text-align: center;
margin-top: 30px;
font-size: 0.8em;
color: #7f8c8d;
}
.tabs {
overflow: hidden;
background: #ecf0f1;
display: flex;
}
.tabs button {
background: inherit; border: none; outline: none;
padding: 14px 16px; cursor: pointer;
transition: 0.3s; font-size: 16px;
}
.tabs button:hover {
background-color: #d4dbdc;
}
.tabs button.active {
background-color: #b3bbbd;
}
.tab-content {
display: none;
padding: 20px;
background-color: #fff;
border: 1px solid #ccc;
}
.tab-content.active {
display: block;
}
</style>
</head>
<body>
"""
+ f"""
<div class="container">
<h1>MaiBot运行统计报告</h1>
<p class="info-item"><strong>统计截止时间:</strong> {now.strftime("%Y-%m-%d %H:%M:%S")}</p>
<div class="tabs">
{joined_tab_list}
</div>
{joined_tab_content}
</div>
"""
+ """
<script>
let i, tab_content, tab_links;
tab_content = document.getElementsByClassName("tab-content");
tab_links = document.getElementsByClassName("tab-link");
tab_content[0].classList.add("active");
tab_links[0].classList.add("active");
function showTab(evt, tabName) {{
for (i = 0; i < tab_content.length; i++) tab_content[i].classList.remove("active");
for (i = 0; i < tab_links.length; i++) tab_links[i].classList.remove("active");
document.getElementById(tabName).classList.add("active");
evt.currentTarget.classList.add("active");
}}
</script>
</body>
</html>
"""
)
with open(self.record_file_path, "w", encoding="utf-8") as f:
f.write(html_template)
def _generate_focus_tab(self, stat: dict[str, Any]) -> str:
"""生成Focus统计独立分页的HTML内容"""
# 为每个时间段准备Focus数据 # 为每个时间段准备Focus数据
focus_sections = [] focus_sections = []
@@ -1242,11 +1024,11 @@ class StatisticOutputTask(AsyncTask):
# 按聊天流统计横向表格显示各阶段时间差异和不同action的平均时间 # 按聊天流统计横向表格显示各阶段时间差异和不同action的平均时间
focus_chat_rows = "" focus_chat_rows = ""
if stat_data[FOCUS_AVG_TIMES_BY_CHAT_ACTION]: if stat_data[FOCUS_AVG_TIMES_BY_CHAT_ACTION]:
# 获取前三个阶段(包括执行动作 # 获取所有阶段(包括后处理器
basic_stages = ["观察", "并行调整动作、处理", "规划器"] basic_stages = ["观察", "并行调整动作、处理", "规划器", "后期处理器", "动作执行"]
existing_basic_stages = [] existing_basic_stages = []
for stage in basic_stages: for stage in basic_stages:
# 检查是否有任何聊天流在这个阶段有数据 # 检查是否有任何聊天流在这个阶段有数据
stage_exists = False stage_exists = False
for _chat_id, stage_times in stat_data[FOCUS_AVG_TIMES_BY_CHAT_ACTION].items(): for _chat_id, stage_times in stat_data[FOCUS_AVG_TIMES_BY_CHAT_ACTION].items():
if stage in stage_times: if stage in stage_times:
@@ -1310,6 +1092,99 @@ class StatisticOutputTask(AsyncTask):
] ]
) )
# 聊天流Action选择比例对比表横向表格
focus_chat_action_ratios_rows = ""
if stat_data.get("focus_action_ratios_by_chat"):
# 获取所有action类型按全局频率排序
all_action_types_for_ratio = sorted(
stat_data[FOCUS_ACTION_RATIOS].keys(), key=lambda x: stat_data[FOCUS_ACTION_RATIOS][x], reverse=True
)
if all_action_types_for_ratio:
# 为每个聊天流生成数据行(按循环数排序)
chat_ratio_rows = []
for chat_id in sorted(
stat_data[FOCUS_CYCLE_CNT_BY_CHAT].keys(),
key=lambda x: stat_data[FOCUS_CYCLE_CNT_BY_CHAT][x],
reverse=True,
):
chat_name = self.name_mapping.get(chat_id, (chat_id, 0))[0]
total_cycles = stat_data[FOCUS_CYCLE_CNT_BY_CHAT][chat_id]
chat_action_counts = stat_data["focus_action_ratios_by_chat"].get(chat_id, {})
row_cells = [f"<td><strong>{chat_name}</strong><br><small>({total_cycles}次循环)</small></td>"]
# 添加每个action类型的数量和百分比
for action_type in all_action_types_for_ratio:
count = chat_action_counts.get(action_type, 0)
ratio = (count / total_cycles * 100) if total_cycles > 0 else 0
if count > 0:
row_cells.append(f"<td>{count}<br><small>({ratio:.1f}%)</small></td>")
else:
row_cells.append("<td>-<br><small>(0%)</small></td>")
chat_ratio_rows.append(f"<tr>{''.join(row_cells)}</tr>")
# 生成表头
action_headers = "".join([f"<th>{action_type}</th>" for action_type in all_action_types_for_ratio])
chat_action_ratio_table_header = f"<tr><th>聊天流</th>{action_headers}</tr>"
focus_chat_action_ratios_rows = chat_action_ratio_table_header + "\n" + "\n".join(chat_ratio_rows)
# 获取所有action类型按出现频率排序
all_action_types = sorted(
stat_data[FOCUS_ACTION_RATIOS].keys(), key=lambda x: stat_data[FOCUS_ACTION_RATIOS][x], reverse=True
)
# 为每个聊天流生成一行
chat_rows = []
for chat_id in sorted(
stat_data[FOCUS_CYCLE_CNT_BY_CHAT].keys(),
key=lambda x: stat_data[FOCUS_CYCLE_CNT_BY_CHAT][x],
reverse=True,
):
chat_name = self.name_mapping.get(chat_id, (chat_id, 0))[0]
cycle_count = stat_data[FOCUS_CYCLE_CNT_BY_CHAT][chat_id]
# 获取该聊天流的各阶段平均时间
stage_times = stat_data[FOCUS_AVG_TIMES_BY_CHAT_ACTION].get(chat_id, {})
row_cells = [f"<td><strong>{chat_name}</strong><br><small>({cycle_count}次循环)</small></td>"]
# 添加基础阶段时间
for stage in existing_basic_stages:
time_val = stage_times.get(stage, 0.0)
row_cells.append(f"<td>{time_val:.3f}秒</td>")
# 添加每个action类型的平均执行时间
for action_type in all_action_types:
# 使用真实的按聊天流+action类型分组的执行时间数据
exec_times_by_chat_action = stat_data.get("focus_exec_times_by_chat_action", {})
chat_action_times = exec_times_by_chat_action.get(chat_id, {})
avg_exec_time = chat_action_times.get(action_type, 0.0)
if avg_exec_time > 0:
row_cells.append(f"<td>{avg_exec_time:.3f}秒</td>")
else:
row_cells.append("<td>-</td>")
chat_rows.append(f"<tr>{''.join(row_cells)}</tr>")
# 生成表头
stage_headers = "".join([f"<th>{stage}</th>" for stage in existing_basic_stages])
action_headers = "".join(
[f"<th>{action_type}<br><small>(执行)</small></th>" for action_type in all_action_types]
)
focus_chat_table_header = f"<tr><th>聊天流</th>{stage_headers}{action_headers}</tr>"
focus_chat_rows = focus_chat_table_header + "\n" + "\n".join(chat_rows)
# 全局阶段时间统计
focus_stage_rows = "\n".join(
[
f"<tr><td>{stage}</td><td>{avg_time:.3f}秒</td></tr>"
for stage, avg_time in sorted(stat_data[FOCUS_AVG_TIMES_BY_STAGE].items())
]
)
# 聊天流Action选择比例对比表横向表格 # 聊天流Action选择比例对比表横向表格
focus_chat_action_ratios_rows = "" focus_chat_action_ratios_rows = ""
if stat_data.get("focus_action_ratios_by_chat"): if stat_data.get("focus_action_ratios_by_chat"):
@@ -1351,8 +1226,8 @@ class StatisticOutputTask(AsyncTask):
# 按Action类型的阶段时间统计横向表格 # 按Action类型的阶段时间统计横向表格
focus_action_stage_rows = "" focus_action_stage_rows = ""
if stat_data[FOCUS_AVG_TIMES_BY_ACTION]: if stat_data[FOCUS_AVG_TIMES_BY_ACTION]:
# 获取所有阶段(按固定顺序) # 获取所有阶段(按固定顺序确保与实际Timer名称一致
stage_order = ["观察", "并行调整动作、处理", "规划器", "执行动作"] stage_order = ["观察", "并行调整动作、处理", "规划器", "后期处理器", "动作执行"]
all_stages = [] all_stages = []
for stage in stage_order: for stage in stage_order:
if any(stage in stage_times for stage_times in stat_data[FOCUS_AVG_TIMES_BY_ACTION].values()): if any(stage in stage_times for stage_times in stat_data[FOCUS_AVG_TIMES_BY_ACTION].values()):
@@ -1375,6 +1250,34 @@ class StatisticOutputTask(AsyncTask):
focus_action_stage_table_header = f"<tr><th>Action类型</th>{stage_headers}</tr>" focus_action_stage_table_header = f"<tr><th>Action类型</th>{stage_headers}</tr>"
focus_action_stage_rows = focus_action_stage_table_header + "\n" + "\n".join(action_rows) focus_action_stage_rows = focus_action_stage_table_header + "\n" + "\n".join(action_rows)
# 新增:前处理器统计表格
focus_processor_rows = ""
if stat_data.get(FOCUS_PROCESSOR_TIMES):
processor_rows = []
for processor_name in sorted(stat_data[FOCUS_PROCESSOR_TIMES].keys()):
avg_time = stat_data[FOCUS_PROCESSOR_TIMES][processor_name]
processor_rows.append(f"<tr><td>{processor_name}</td><td>{avg_time:.3f}秒</td></tr>")
focus_processor_rows = "\n".join(processor_rows)
# 新增:前处理器统计表格
focus_processor_rows = ""
if stat_data.get(FOCUS_PROCESSOR_TIMES):
processor_rows = []
for processor_name in sorted(stat_data[FOCUS_PROCESSOR_TIMES].keys()):
avg_time = stat_data[FOCUS_PROCESSOR_TIMES][processor_name]
processor_rows.append(f"<tr><td>{processor_name}</td><td>{avg_time:.3f}秒</td></tr>")
focus_processor_rows = "\n".join(processor_rows)
# 新增:后处理器统计表格
focus_post_processor_rows = ""
if stat_data.get(FOCUS_POST_PROCESSOR_TIMES):
post_processor_rows = []
for processor_name in sorted(stat_data[FOCUS_POST_PROCESSOR_TIMES].keys()):
avg_time = stat_data[FOCUS_POST_PROCESSOR_TIMES][processor_name]
count = stat_data[FOCUS_POST_PROCESSOR_COUNT].get(processor_name, 0)
post_processor_rows.append(f"<tr><td>{processor_name}</td><td>{avg_time:.3f}秒</td><td>{count}</td></tr>")
focus_post_processor_rows = "\n".join(post_processor_rows)
# 计算时间范围 # 计算时间范围
if period_name == "all_time": if period_name == "all_time":
from src.manager.local_store_manager import local_storage from src.manager.local_store_manager import local_storage
@@ -1437,6 +1340,24 @@ class StatisticOutputTask(AsyncTask):
<tbody>{focus_action_stage_rows}</tbody> <tbody>{focus_action_stage_rows}</tbody>
</table> </table>
</div> </div>
<div class="focus-stats-grid">
<div class="focus-stat-item">
<h3>前处理器平均时间</h3>
<table>
<thead><tr><th>处理器名称</th><th>平均耗时</th></tr></thead>
<tbody>{focus_processor_rows}</tbody>
</table>
</div>
<div class="focus-stat-item">
<h3>后处理器统计</h3>
<table>
<thead><tr><th>处理器名称</th><th>平均耗时</th><th>执行次数</th></tr></thead>
<tbody>{focus_post_processor_rows}</tbody>
</table>
</div>
</div>
</div> </div>
""" """

View File

@@ -230,9 +230,22 @@ class NoReplyAction(BaseAction):
) )
return True, f"累计消息数量达到{new_message_count}条,直接结束等待 (等待时间: {elapsed_time:.1f}秒)" return True, f"累计消息数量达到{new_message_count}条,直接结束等待 (等待时间: {elapsed_time:.1f}秒)"
# 如果有新消息且距离上次判断>=1秒进行LLM判断 # 判定条件累计3条消息或等待超过5秒且有新消息
if new_message_count > 0 and (current_time - last_judge_time) >= min_judge_interval: time_since_last_judge = current_time - last_judge_time
logger.info(f"{self.log_prefix} 检测到{new_message_count}条新消息,进行智能判断...") should_judge = (
new_message_count >= 3 or # 累计3条消息
(new_message_count > 0 and time_since_last_judge >= 5.0) # 等待超过5秒且有新消息
)
if should_judge and time_since_last_judge >= min_judge_interval:
# 判断触发原因
trigger_reason = ""
if new_message_count >= 3:
trigger_reason = f"累计{new_message_count}条消息"
elif time_since_last_judge >= 5.0:
trigger_reason = f"等待{time_since_last_judge:.1f}秒且有{new_message_count}条新消息"
logger.info(f"{self.log_prefix} 触发判定({trigger_reason}),进行智能判断...")
# 获取最近的消息内容用于判断 # 获取最近的消息内容用于判断
recent_messages = message_api.get_messages_by_time_in_chat( recent_messages = message_api.get_messages_by_time_in_chat(
@@ -309,6 +322,14 @@ class NoReplyAction(BaseAction):
skip_probability = self._skip_probability_medium skip_probability = self._skip_probability_medium
else: else:
frequency_block = "你发现自己说话太多了,感觉很累,想要安静一会儿,除非有重要的事情否则不想回复。\n" frequency_block = "你发现自己说话太多了,感觉很累,想要安静一会儿,除非有重要的事情否则不想回复。\n"
skip_probability = self._skip_probability_heavy
# 根据配置和概率决定是否跳过LLM判断
if self._skip_judge_when_tired and random.random() < skip_probability:
should_skip_llm_judge = True
logger.info(
f"{self.log_prefix} 发言过多(超过{over_count}条)随机决定跳过此次LLM判断(概率{skip_probability*100:.0f}%)"
)
logger.info( logger.info(
f"{self.log_prefix} 过去10分钟发言{bot_message_count}条,超过阈值{talk_frequency_threshold},添加疲惫提示" f"{self.log_prefix} 过去10分钟发言{bot_message_count}条,超过阈值{talk_frequency_threshold},添加疲惫提示"