Merge branch 'dev' of https://github.com/MoFox-Studio/MoFox_Bot into dev
This commit is contained in:
@@ -65,16 +65,30 @@ class HTMLReportGenerator:
|
||||
f"<tr>"
|
||||
f"<td>{model_name}</td>"
|
||||
f"<td>{count}</td>"
|
||||
f"<td>{stat_data[IN_TOK_BY_MODEL].get(model_name, 0)}</td>"
|
||||
f"<td>{stat_data[OUT_TOK_BY_MODEL].get(model_name, 0)}</td>"
|
||||
f"<td>{stat_data[AVG_TOK_BY_MODEL].get(model_name, 0)}</td>"
|
||||
f"<td>{stat_data[TOTAL_TOK_BY_MODEL].get(model_name, 0)}</td>"
|
||||
f"<td>{stat_data[TPS_BY_MODEL].get(model_name, 0):.2f}</td>"
|
||||
f"<td>{stat_data[COST_PER_KTOK_BY_MODEL].get(model_name, 0):.4f} ¥</td>"
|
||||
f"<td>{stat_data[COST_BY_MODEL].get(model_name, 0):.4f} ¥</td>"
|
||||
f"<td>{stat_data[AVG_TIME_COST_BY_MODEL].get(model_name, 0):.3f} 秒</td>"
|
||||
f"<td>{stat_data[STD_TIME_COST_BY_MODEL].get(model_name, 0):.3f} 秒</td>"
|
||||
f"</tr>"
|
||||
for model_name, count in sorted(stat_data[REQ_CNT_BY_MODEL].items())
|
||||
]
|
||||
)
|
||||
# 按供应商分类统计
|
||||
provider_rows = "\n".join(
|
||||
[
|
||||
f"<tr>"
|
||||
f"<td>{provider_name}</td>"
|
||||
f"<td>{count}</td>"
|
||||
f"<td>{stat_data[TOTAL_TOK_BY_PROVIDER].get(provider_name, 0)}</td>"
|
||||
f"<td>{stat_data[TPS_BY_PROVIDER].get(provider_name, 0):.2f}</td>"
|
||||
f"<td>{stat_data[COST_PER_KTOK_BY_PROVIDER].get(provider_name, 0):.4f} ¥</td>"
|
||||
f"<td>{stat_data[COST_BY_PROVIDER].get(provider_name, 0):.4f} ¥</td>"
|
||||
f"</tr>"
|
||||
for provider_name, count in sorted(stat_data[REQ_CNT_BY_PROVIDER].items())
|
||||
]
|
||||
)
|
||||
# 按请求类型分类统计
|
||||
type_rows = "\n".join(
|
||||
[
|
||||
@@ -114,23 +128,55 @@ class HTMLReportGenerator:
|
||||
for chat_id, count in sorted(stat_data[MSG_CNT_BY_CHAT].items())
|
||||
]
|
||||
)
|
||||
summary_cards = f"""
|
||||
<div class="summary-cards">
|
||||
<div class="card">
|
||||
<h3>总花费</h3>
|
||||
<p>{stat_data.get(TOTAL_COST, 0):.4f} ¥</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>总请求数</h3>
|
||||
<p>{stat_data.get(TOTAL_REQ_CNT, 0)}</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>总Token数</h3>
|
||||
<p>{sum(stat_data.get(TOTAL_TOK_BY_MODEL, {}).values())}</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>总消息数</h3>
|
||||
<p>{stat_data.get(TOTAL_MSG_CNT, 0)}</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>总在线时间</h3>
|
||||
<p>{format_online_time(int(stat_data.get(ONLINE_TIME, 0)))}</p>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
|
||||
# 增加饼图和条形图
|
||||
# static_charts = self._generate_static_charts_div(stat_data, div_id) # 该功能尚未实现
|
||||
static_charts = ""
|
||||
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(int(stat_data.get(ONLINE_TIME, 0)))}</p>
|
||||
<p class="info-item"><strong>总消息数: </strong>{stat_data.get(TOTAL_MSG_CNT, 0)}</p>
|
||||
<p class="info-item"><strong>总请求数: </strong>{stat_data.get(TOTAL_REQ_CNT, 0)}</p>
|
||||
<p class="info-item"><strong>总花费: </strong>{stat_data.get(TOTAL_COST, 0):.4f} ¥</p>
|
||||
{summary_cards}
|
||||
{static_charts}
|
||||
|
||||
<h2>按模型分类统计</h2>
|
||||
<table>
|
||||
<tr><th>模型名称</th><th>调用次数</th><th>输入Token</th><th>输出Token</th><th>Token总量</th><th>累计花费</th><th>平均耗时(秒)</th><th>标准差(秒)</th></tr>
|
||||
<tr><th>模型名称</th><th>调用次数</th><th>平均Token数</th><th>Token总量</th><th>TPS</th><th>每K Token成本</th><th>累计花费</th><th>平均耗时(秒)</th></tr>
|
||||
<tbody>{model_rows}</tbody>
|
||||
</table>
|
||||
|
||||
<h2>按供应商分类统计</h2>
|
||||
<table>
|
||||
<tr><th>供应商名称</th><th>调用次数</th><th>Token总量</th><th>TPS</th><th>每K Token成本</th><th>累计花费</th></tr>
|
||||
<tbody>{provider_rows}</tbody>
|
||||
</table>
|
||||
|
||||
<h2>按模块分类统计</h2>
|
||||
<table>
|
||||
<thead>
|
||||
@@ -156,7 +202,6 @@ class HTMLReportGenerator:
|
||||
</table>
|
||||
</div>
|
||||
"""
|
||||
|
||||
def _generate_chart_tab(self, chart_data: dict) -> str:
|
||||
"""生成图表选项卡的HTML内容。"""
|
||||
return f"""
|
||||
|
||||
@@ -246,6 +246,7 @@ class StatisticOutputTask(AsyncTask):
|
||||
REQ_CNT_BY_USER: defaultdict(int),
|
||||
REQ_CNT_BY_MODEL: defaultdict(int),
|
||||
REQ_CNT_BY_MODULE: defaultdict(int),
|
||||
REQ_CNT_BY_PROVIDER: defaultdict(int), # New
|
||||
IN_TOK_BY_TYPE: defaultdict(int),
|
||||
IN_TOK_BY_USER: defaultdict(int),
|
||||
IN_TOK_BY_MODEL: defaultdict(int),
|
||||
@@ -258,15 +259,18 @@ class StatisticOutputTask(AsyncTask):
|
||||
TOTAL_TOK_BY_USER: defaultdict(int),
|
||||
TOTAL_TOK_BY_MODEL: defaultdict(int),
|
||||
TOTAL_TOK_BY_MODULE: defaultdict(int),
|
||||
TOTAL_TOK_BY_PROVIDER: defaultdict(int), # New
|
||||
TOTAL_COST: 0.0,
|
||||
COST_BY_TYPE: defaultdict(float),
|
||||
COST_BY_USER: defaultdict(float),
|
||||
COST_BY_MODEL: defaultdict(float),
|
||||
COST_BY_MODULE: defaultdict(float),
|
||||
COST_BY_PROVIDER: defaultdict(float), # New
|
||||
TIME_COST_BY_TYPE: defaultdict(list),
|
||||
TIME_COST_BY_USER: defaultdict(list),
|
||||
TIME_COST_BY_MODEL: defaultdict(list),
|
||||
TIME_COST_BY_MODULE: defaultdict(list),
|
||||
TIME_COST_BY_PROVIDER: defaultdict(list), # New
|
||||
AVG_TIME_COST_BY_TYPE: defaultdict(float),
|
||||
AVG_TIME_COST_BY_USER: defaultdict(float),
|
||||
AVG_TIME_COST_BY_MODEL: defaultdict(float),
|
||||
@@ -275,6 +279,17 @@ class StatisticOutputTask(AsyncTask):
|
||||
STD_TIME_COST_BY_USER: defaultdict(float),
|
||||
STD_TIME_COST_BY_MODEL: defaultdict(float),
|
||||
STD_TIME_COST_BY_MODULE: defaultdict(float),
|
||||
# New calculated fields
|
||||
TPS_BY_MODEL: defaultdict(float),
|
||||
COST_PER_KTOK_BY_MODEL: defaultdict(float),
|
||||
AVG_TOK_BY_MODEL: defaultdict(float),
|
||||
TPS_BY_PROVIDER: defaultdict(float),
|
||||
COST_PER_KTOK_BY_PROVIDER: defaultdict(float),
|
||||
# Chart data
|
||||
PIE_CHART_COST_BY_PROVIDER: {},
|
||||
PIE_CHART_REQ_BY_PROVIDER: {},
|
||||
BAR_CHART_COST_BY_MODEL: {},
|
||||
BAR_CHART_REQ_BY_MODEL: {},
|
||||
}
|
||||
for period_key, _ in collect_period
|
||||
}
|
||||
@@ -309,6 +324,7 @@ class StatisticOutputTask(AsyncTask):
|
||||
request_type = record.get("request_type") or "unknown"
|
||||
user_id = record.get("user_id") or "unknown"
|
||||
model_name = record.get("model_name") or "unknown"
|
||||
provider_name = record.get("model_api_provider") or "unknown"
|
||||
|
||||
# 提取模块名:如果请求类型包含".",取第一个"."之前的部分
|
||||
module_name = request_type.split(".")[0] if "." in request_type else request_type
|
||||
@@ -317,6 +333,7 @@ class StatisticOutputTask(AsyncTask):
|
||||
stats[period_key][REQ_CNT_BY_USER][user_id] += 1
|
||||
stats[period_key][REQ_CNT_BY_MODEL][model_name] += 1
|
||||
stats[period_key][REQ_CNT_BY_MODULE][module_name] += 1
|
||||
stats[period_key][REQ_CNT_BY_PROVIDER][provider_name] += 1
|
||||
|
||||
prompt_tokens = record.get("prompt_tokens") or 0
|
||||
completion_tokens = record.get("completion_tokens") or 0
|
||||
@@ -336,6 +353,7 @@ class StatisticOutputTask(AsyncTask):
|
||||
stats[period_key][TOTAL_TOK_BY_USER][user_id] += total_tokens
|
||||
stats[period_key][TOTAL_TOK_BY_MODEL][model_name] += total_tokens
|
||||
stats[period_key][TOTAL_TOK_BY_MODULE][module_name] += total_tokens
|
||||
stats[period_key][TOTAL_TOK_BY_PROVIDER][provider_name] += total_tokens
|
||||
|
||||
cost = record.get("cost") or 0.0
|
||||
stats[period_key][TOTAL_COST] += cost
|
||||
@@ -343,6 +361,7 @@ class StatisticOutputTask(AsyncTask):
|
||||
stats[period_key][COST_BY_USER][user_id] += cost
|
||||
stats[period_key][COST_BY_MODEL][model_name] += cost
|
||||
stats[period_key][COST_BY_MODULE][module_name] += cost
|
||||
stats[period_key][COST_BY_PROVIDER][provider_name] += cost
|
||||
|
||||
# 收集time_cost数据
|
||||
time_cost = record.get("time_cost") or 0.0
|
||||
@@ -351,32 +370,84 @@ class StatisticOutputTask(AsyncTask):
|
||||
stats[period_key][TIME_COST_BY_USER][user_id].append(time_cost)
|
||||
stats[period_key][TIME_COST_BY_MODEL][model_name].append(time_cost)
|
||||
stats[period_key][TIME_COST_BY_MODULE][module_name].append(time_cost)
|
||||
stats[period_key][TIME_COST_BY_PROVIDER][provider_name].append(time_cost)
|
||||
break
|
||||
|
||||
# 计算平均耗时和标准差
|
||||
for period_key in stats:
|
||||
for category in [REQ_CNT_BY_TYPE, REQ_CNT_BY_USER, REQ_CNT_BY_MODEL, REQ_CNT_BY_MODULE]:
|
||||
time_cost_key = f"time_costs_by_{category.split('_')[-1]}"
|
||||
avg_key = f"avg_time_costs_by_{category.split('_')[-1]}"
|
||||
std_key = f"std_time_costs_by_{category.split('_')[-1]}"
|
||||
# -- 计算派生指标 --
|
||||
for period_key, period_stats in stats.items():
|
||||
# 计算模型相关指标
|
||||
for model_name, req_count in period_stats[REQ_CNT_BY_MODEL].items():
|
||||
total_tok = period_stats[TOTAL_TOK_BY_MODEL].get(model_name, 0)
|
||||
total_cost = period_stats[COST_BY_MODEL].get(model_name, 0.0)
|
||||
time_costs = period_stats[TIME_COST_BY_MODEL].get(model_name, [])
|
||||
total_time_cost = sum(time_costs)
|
||||
|
||||
for item_name in stats[period_key][category]:
|
||||
time_costs = stats[period_key][time_cost_key].get(item_name, [])
|
||||
# TPS
|
||||
if total_time_cost > 0:
|
||||
period_stats[TPS_BY_MODEL][model_name] = round(total_tok / total_time_cost, 2)
|
||||
# Cost per 1K Tokens
|
||||
if total_tok > 0:
|
||||
period_stats[COST_PER_KTOK_BY_MODEL][model_name] = round((total_cost / total_tok) * 1000, 4)
|
||||
# Avg Tokens per Request
|
||||
period_stats[AVG_TOK_BY_MODEL][model_name] = round(total_tok / req_count) if req_count > 0 else 0
|
||||
|
||||
# 计算供应商相关指标
|
||||
for provider_name, req_count in period_stats[REQ_CNT_BY_PROVIDER].items():
|
||||
total_tok = period_stats[TOTAL_TOK_BY_PROVIDER].get(provider_name, 0)
|
||||
total_cost = period_stats[COST_BY_PROVIDER].get(provider_name, 0.0)
|
||||
time_costs = period_stats[TIME_COST_BY_PROVIDER].get(provider_name, [])
|
||||
total_time_cost = sum(time_costs)
|
||||
|
||||
# TPS
|
||||
if total_time_cost > 0:
|
||||
period_stats[TPS_BY_PROVIDER][provider_name] = round(total_tok / total_time_cost, 2)
|
||||
# Cost per 1K Tokens
|
||||
if total_tok > 0:
|
||||
period_stats[COST_PER_KTOK_BY_PROVIDER][provider_name] = round((total_cost / total_tok) * 1000, 4)
|
||||
|
||||
# 计算平均耗时和标准差
|
||||
for category_key, items in [
|
||||
(REQ_CNT_BY_TYPE, "type"),
|
||||
(REQ_CNT_BY_USER, "user"),
|
||||
(REQ_CNT_BY_MODEL, "model"),
|
||||
(REQ_CNT_BY_MODULE, "module"),
|
||||
]:
|
||||
time_cost_key = f"time_costs_by_{items}"
|
||||
avg_key = f"avg_time_costs_by_{items}"
|
||||
std_key = f"std_time_costs_by_{items}"
|
||||
|
||||
for item_name in period_stats[category_key]:
|
||||
time_costs = period_stats[time_cost_key].get(item_name, [])
|
||||
if time_costs:
|
||||
# 计算平均耗时
|
||||
avg_time_cost = sum(time_costs) / len(time_costs)
|
||||
stats[period_key][avg_key][item_name] = round(avg_time_cost, 3)
|
||||
|
||||
# 计算标准差
|
||||
avg_time = sum(time_costs) / len(time_costs)
|
||||
period_stats[avg_key][item_name] = round(avg_time, 3)
|
||||
if len(time_costs) > 1:
|
||||
variance = sum((x - avg_time_cost) ** 2 for x in time_costs) / len(time_costs)
|
||||
std_time_cost = variance**0.5
|
||||
stats[period_key][std_key][item_name] = round(std_time_cost, 3)
|
||||
variance = sum((x - avg_time) ** 2 for x in time_costs) / len(time_costs)
|
||||
period_stats[std_key][item_name] = round(variance**0.5, 3)
|
||||
else:
|
||||
stats[period_key][std_key][item_name] = 0.0
|
||||
period_stats[std_key][item_name] = 0.0
|
||||
else:
|
||||
stats[period_key][avg_key][item_name] = 0.0
|
||||
stats[period_key][std_key][item_name] = 0.0
|
||||
period_stats[avg_key][item_name] = 0.0
|
||||
period_stats[std_key][item_name] = 0.0
|
||||
|
||||
# 准备图表数据
|
||||
# 按供应商花费饼图
|
||||
provider_costs = period_stats[COST_BY_PROVIDER]
|
||||
if provider_costs:
|
||||
sorted_providers = sorted(provider_costs.items(), key=lambda item: item[1], reverse=True)
|
||||
period_stats[PIE_CHART_COST_BY_PROVIDER] = {
|
||||
"labels": [item[0] for item in sorted_providers],
|
||||
"data": [round(item[1], 4) for item in sorted_providers],
|
||||
}
|
||||
|
||||
# 按模型花费条形图
|
||||
model_costs = period_stats[COST_BY_MODEL]
|
||||
if model_costs:
|
||||
sorted_models = sorted(model_costs.items(), key=lambda item: item[1], reverse=True)
|
||||
period_stats[BAR_CHART_COST_BY_MODEL] = {
|
||||
"labels": [item[0] for item in sorted_models],
|
||||
"data": [round(item[1], 4) for item in sorted_models],
|
||||
}
|
||||
return stats
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -40,4 +40,23 @@ AVG_TIME_COST_BY_MODULE = "avg_time_costs_by_module"
|
||||
STD_TIME_COST_BY_TYPE = "std_time_costs_by_type"
|
||||
STD_TIME_COST_BY_USER = "std_time_costs_by_user"
|
||||
STD_TIME_COST_BY_MODEL = "std_time_costs_by_model"
|
||||
STD_TIME_COST_BY_MODULE = "std_time_costs_by_module"
|
||||
STD_TIME_COST_BY_MODULE = "std_time_costs_by_module"
|
||||
|
||||
# 新增模型性能指标
|
||||
TPS_BY_MODEL = "tps_by_model" # Tokens Per Second
|
||||
COST_PER_KTOK_BY_MODEL = "cost_per_ktok_by_model"
|
||||
AVG_TOK_BY_MODEL = "avg_tok_by_model"
|
||||
|
||||
# 新增按供应商统计
|
||||
REQ_CNT_BY_PROVIDER = "requests_by_provider"
|
||||
COST_BY_PROVIDER = "costs_by_provider"
|
||||
TOTAL_TOK_BY_PROVIDER = "tokens_by_provider"
|
||||
TPS_BY_PROVIDER = "tps_by_provider"
|
||||
COST_PER_KTOK_BY_PROVIDER = "cost_per_ktok_by_provider"
|
||||
TIME_COST_BY_PROVIDER = "time_cost_by_provider"
|
||||
|
||||
# 新增饼图和条形图数据
|
||||
PIE_CHART_COST_BY_PROVIDER = "pie_chart_cost_by_provider"
|
||||
PIE_CHART_REQ_BY_PROVIDER = "pie_chart_req_by_provider"
|
||||
BAR_CHART_COST_BY_MODEL = "bar_chart_cost_by_model"
|
||||
BAR_CHART_REQ_BY_MODEL = "bar_chart_req_by_model"
|
||||
Reference in New Issue
Block a user