feat:添加后处理器统计时间
This commit is contained in:
@@ -22,9 +22,10 @@ class CycleDetail:
|
||||
|
||||
# 新字段
|
||||
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_action_info: Dict[str, Any] = {}
|
||||
self.loop_post_processor_info: Dict[str, Any] = {} # 后处理器信息
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""将循环信息转换为字典格式"""
|
||||
@@ -76,9 +77,10 @@ class CycleDetail:
|
||||
"timers": self.timers,
|
||||
"thinking_id": self.thinking_id,
|
||||
"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_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):
|
||||
@@ -133,3 +135,4 @@ class CycleDetail:
|
||||
self.loop_processor_info = loop_info["loop_processor_info"]
|
||||
self.loop_plan_info = loop_info["loop_plan_info"]
|
||||
self.loop_action_info = loop_info["loop_action_info"]
|
||||
self.loop_post_processor_info = loop_info["loop_post_processor_info"]
|
||||
|
||||
@@ -454,7 +454,19 @@ class HeartFChatting:
|
||||
formatted_ptime = f"{ptime * 1000:.2f}毫秒" if ptime < 1 else f"{ptime:.2f}秒"
|
||||
processor_time_strings.append(f"{pname}: {formatted_ptime}")
|
||||
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(
|
||||
@@ -463,6 +475,7 @@ class HeartFChatting:
|
||||
f"动作: {self._current_cycle_detail.loop_plan_info.get('action_result', {}).get('action_type', '未知动作')}"
|
||||
+ (f"\n详情: {'; '.join(timer_strings)}" if timer_strings else "")
|
||||
+ processor_time_log
|
||||
+ post_processor_time_log
|
||||
)
|
||||
|
||||
# 记录性能数据
|
||||
@@ -473,6 +486,8 @@ class HeartFChatting:
|
||||
"action_type": action_result.get("action_type", "unknown"),
|
||||
"total_time": self._current_cycle_detail.end_time - self._current_cycle_detail.start_time,
|
||||
"step_times": cycle_timers.copy(),
|
||||
"processor_time_costs": processor_time_costs, # 前处理器时间
|
||||
"post_processor_time_costs": post_processor_time_costs, # 后处理器时间
|
||||
"reasoning": action_result.get("reasoning", ""),
|
||||
"success": self._current_cycle_detail.loop_action_info.get("action_taken", False),
|
||||
}
|
||||
@@ -754,6 +769,180 @@ class HeartFChatting:
|
||||
|
||||
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:
|
||||
try:
|
||||
loop_start_time = time.time()
|
||||
@@ -836,28 +1025,37 @@ class HeartFChatting:
|
||||
"observed_messages": plan_result.get("observed_messages", ""),
|
||||
}
|
||||
|
||||
with Timer("执行动作", cycle_timers):
|
||||
action_type, action_data, reasoning = (
|
||||
plan_result.get("action_result", {}).get("action_type", "error"),
|
||||
plan_result.get("action_result", {}).get("action_data", {}),
|
||||
plan_result.get("action_result", {}).get("reasoning", "未提供理由"),
|
||||
)
|
||||
# 修正:将后期处理器从执行动作Timer中分离出来
|
||||
action_type, action_data, reasoning = (
|
||||
plan_result.get("action_result", {}).get("action_type", "error"),
|
||||
plan_result.get("action_result", {}).get("action_data", {}),
|
||||
plan_result.get("action_result", {}).get("reasoning", "未提供理由"),
|
||||
)
|
||||
|
||||
if action_type == "reply":
|
||||
action_str = "回复"
|
||||
elif action_type == "no_reply":
|
||||
action_str = "不回复"
|
||||
else:
|
||||
action_str = action_type
|
||||
if action_type == "reply":
|
||||
action_str = "回复"
|
||||
elif action_type == "no_reply":
|
||||
action_str = "不回复"
|
||||
else:
|
||||
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":
|
||||
with Timer("后期处理器", cycle_timers):
|
||||
logger.debug(f"{self.log_prefix} 执行后期处理器(动作类型: {action_type})")
|
||||
action_data = await self._process_post_planning_processors(self.observations, action_data)
|
||||
# 添加:单独计时后期处理器,并收集详细统计
|
||||
post_processor_time_costs = {}
|
||||
if action_type != "no_reply":
|
||||
with Timer("后期处理器", cycle_timers):
|
||||
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(
|
||||
action_type, reasoning, action_data, cycle_timers, thinking_id
|
||||
)
|
||||
@@ -869,11 +1067,17 @@ class HeartFChatting:
|
||||
"taken_time": time.time(),
|
||||
}
|
||||
|
||||
# 添加后处理器统计到loop_info
|
||||
loop_post_processor_info = {
|
||||
"post_processor_time_costs": post_processor_time_costs,
|
||||
}
|
||||
|
||||
loop_info = {
|
||||
"loop_observation_info": loop_observation_info,
|
||||
"loop_processor_info": loop_processor_info,
|
||||
"loop_plan_info": loop_plan_info,
|
||||
"loop_action_info": loop_action_info,
|
||||
"loop_post_processor_info": loop_post_processor_info, # 新增
|
||||
}
|
||||
|
||||
return loop_info
|
||||
|
||||
@@ -41,6 +41,8 @@ class HFCPerformanceLogger:
|
||||
"action_type": cycle_data.get("action_type", "unknown"),
|
||||
"total_time": cycle_data.get("total_time", 0),
|
||||
"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", ""),
|
||||
"success": cycle_data.get("success", False),
|
||||
}
|
||||
@@ -51,9 +53,22 @@ class HFCPerformanceLogger:
|
||||
# 立即写入文件(防止数据丢失)
|
||||
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:
|
||||
logger.error(f"记录HFC循环数据失败: {e}")
|
||||
|
||||
@@ -440,6 +440,7 @@ class DefaultReplyer:
|
||||
chat_info=chat_talking_prompt,
|
||||
memory_block=memory_block,
|
||||
structured_info_block=structured_info_block,
|
||||
relation_info_block=relation_info_block,
|
||||
extra_info_block=extra_info_block,
|
||||
time_block=time_block,
|
||||
keywords_reaction_prompt=keywords_reaction_prompt,
|
||||
|
||||
@@ -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_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):
|
||||
"""在线时间记录任务"""
|
||||
@@ -495,6 +501,10 @@ class StatisticOutputTask(AsyncTask):
|
||||
FOCUS_AVG_TIMES_BY_VERSION: defaultdict(lambda: defaultdict(list)),
|
||||
"focus_exec_times_by_version_action": defaultdict(lambda: defaultdict(list)),
|
||||
"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
|
||||
}
|
||||
@@ -556,6 +566,10 @@ class StatisticOutputTask(AsyncTask):
|
||||
total_time = cycle_data.get("total_time", 0.0)
|
||||
step_times = cycle_data.get("step_times", {})
|
||||
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名称映射
|
||||
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)
|
||||
# 按版本和action类型收集执行时间
|
||||
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
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to process cycle data: {e}")
|
||||
@@ -651,6 +674,20 @@ class StatisticOutputTask(AsyncTask):
|
||||
else:
|
||||
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]]:
|
||||
"""
|
||||
收集各时间段的统计数据
|
||||
@@ -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_sections = []
|
||||
|
||||
@@ -1242,11 +1024,11 @@ class StatisticOutputTask(AsyncTask):
|
||||
# 按聊天流统计(横向表格,显示各阶段时间差异和不同action的平均时间)
|
||||
focus_chat_rows = ""
|
||||
if stat_data[FOCUS_AVG_TIMES_BY_CHAT_ACTION]:
|
||||
# 获取前三个阶段(不包括执行动作)
|
||||
basic_stages = ["观察", "并行调整动作、处理", "规划器"]
|
||||
# 获取所有阶段(包括后处理器)
|
||||
basic_stages = ["观察", "并行调整动作、处理", "规划器", "后期处理器", "动作执行"]
|
||||
existing_basic_stages = []
|
||||
for stage in basic_stages:
|
||||
# 检查是否有任何聊天流在这个阶段有数据
|
||||
# 检查是否有任何聊天流在这个阶段有数据
|
||||
stage_exists = False
|
||||
for _chat_id, stage_times in stat_data[FOCUS_AVG_TIMES_BY_CHAT_ACTION].items():
|
||||
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选择比例对比表(横向表格)
|
||||
focus_chat_action_ratios_rows = ""
|
||||
if stat_data.get("focus_action_ratios_by_chat"):
|
||||
@@ -1351,8 +1226,8 @@ class StatisticOutputTask(AsyncTask):
|
||||
# 按Action类型的阶段时间统计(横向表格)
|
||||
focus_action_stage_rows = ""
|
||||
if stat_data[FOCUS_AVG_TIMES_BY_ACTION]:
|
||||
# 获取所有阶段(按固定顺序)
|
||||
stage_order = ["观察", "并行调整动作、处理", "规划器", "执行动作"]
|
||||
# 获取所有阶段(按固定顺序,确保与实际Timer名称一致)
|
||||
stage_order = ["观察", "并行调整动作、处理", "规划器", "后期处理器", "动作执行"]
|
||||
all_stages = []
|
||||
for stage in stage_order:
|
||||
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_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":
|
||||
from src.manager.local_store_manager import local_storage
|
||||
@@ -1437,6 +1340,24 @@ class StatisticOutputTask(AsyncTask):
|
||||
<tbody>{focus_action_stage_rows}</tbody>
|
||||
</table>
|
||||
</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>
|
||||
"""
|
||||
|
||||
|
||||
@@ -230,9 +230,22 @@ class NoReplyAction(BaseAction):
|
||||
)
|
||||
return True, f"累计消息数量达到{new_message_count}条,直接结束等待 (等待时间: {elapsed_time:.1f}秒)"
|
||||
|
||||
# 如果有新消息且距离上次判断>=1秒,进行LLM判断
|
||||
if new_message_count > 0 and (current_time - last_judge_time) >= min_judge_interval:
|
||||
logger.info(f"{self.log_prefix} 检测到{new_message_count}条新消息,进行智能判断...")
|
||||
# 判定条件:累计3条消息或等待超过5秒且有新消息
|
||||
time_since_last_judge = current_time - last_judge_time
|
||||
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(
|
||||
@@ -309,6 +322,14 @@ class NoReplyAction(BaseAction):
|
||||
skip_probability = self._skip_probability_medium
|
||||
else:
|
||||
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(
|
||||
f"{self.log_prefix} 过去10分钟发言{bot_message_count}条,超过阈值{talk_frequency_threshold},添加疲惫提示"
|
||||
|
||||
Reference in New Issue
Block a user