mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-02 02:30:16 +08:00
Compare commits
6 Commits
codex/add-
...
feat/conv-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d3a09f3db | ||
|
|
071f7b5701 | ||
|
|
2ce6b1b885 | ||
|
|
8ca8231176 | ||
|
|
6ba01a4775 | ||
|
|
f02444146d |
@@ -1,3 +1,6 @@
|
||||
from sqlalchemy import case, func, select
|
||||
from sqlmodel import col
|
||||
|
||||
from astrbot.api import sp, star
|
||||
from astrbot.api.event import AstrMessageEvent, MessageEventResult
|
||||
from astrbot.core import logger
|
||||
@@ -7,6 +10,7 @@ from astrbot.core.agent.runners.deerflow.constants import (
|
||||
DEERFLOW_THREAD_ID_KEY,
|
||||
)
|
||||
from astrbot.core.agent.runners.deerflow.deerflow_api_client import DeerFlowAPIClient
|
||||
from astrbot.core.db.po import ProviderStat
|
||||
from astrbot.core.utils.active_event_registry import active_event_registry
|
||||
|
||||
from .utils.rst_scene import RstScene
|
||||
@@ -246,3 +250,62 @@ class ConversationCommands:
|
||||
f"✅ Switched to new conversation: {cid[:4]}。"
|
||||
),
|
||||
)
|
||||
|
||||
async def stats(self, message: AstrMessageEvent) -> None:
|
||||
"""Show token usage statistics for the current conversation."""
|
||||
umo = message.unified_msg_origin
|
||||
cid = await self.context.conversation_manager.get_curr_conversation_id(umo)
|
||||
|
||||
if not cid:
|
||||
message.set_result(
|
||||
MessageEventResult().message(
|
||||
"❌ You are not in a conversation. Use /new to create one."
|
||||
),
|
||||
)
|
||||
return
|
||||
|
||||
db = self.context.get_db()
|
||||
async with db.get_db() as session:
|
||||
result = await session.execute(
|
||||
select(
|
||||
func.count(case((col(ProviderStat.id).is_not(None), 1))).label(
|
||||
"record_count",
|
||||
),
|
||||
func.coalesce(func.sum(ProviderStat.token_input_other), 0).label(
|
||||
"total_input_other",
|
||||
),
|
||||
func.coalesce(func.sum(ProviderStat.token_input_cached), 0).label(
|
||||
"total_input_cached",
|
||||
),
|
||||
func.coalesce(func.sum(ProviderStat.token_output), 0).label(
|
||||
"total_output",
|
||||
),
|
||||
).where(
|
||||
col(ProviderStat.agent_type) == "internal",
|
||||
col(ProviderStat.conversation_id) == cid,
|
||||
)
|
||||
)
|
||||
stats = result.one()
|
||||
|
||||
if stats.record_count == 0:
|
||||
message.set_result(
|
||||
MessageEventResult().message(
|
||||
"📊 No stats available for this conversation yet."
|
||||
),
|
||||
)
|
||||
return
|
||||
|
||||
total_input_other = stats.total_input_other
|
||||
total_input_cached = stats.total_input_cached
|
||||
total_output = stats.total_output
|
||||
total_tokens = total_input_other + total_input_cached + total_output
|
||||
|
||||
ret = (
|
||||
f"📊 Conversation Token usage (ID: {cid[:8]}...)\n"
|
||||
f"Total: {total_tokens:,}\n"
|
||||
f"Input (cached): {total_input_cached:,}\n"
|
||||
f"Input (other): {total_input_other:,}\n"
|
||||
f"Output: {total_output:,}\n"
|
||||
)
|
||||
|
||||
message.set_result(MessageEventResult().message(ret))
|
||||
|
||||
@@ -47,6 +47,11 @@ class Main(star.Star):
|
||||
"""Create new conversation"""
|
||||
await self.conversation_c.new_conv(message)
|
||||
|
||||
@filter.command("stats")
|
||||
async def stats(self, message: AstrMessageEvent) -> None:
|
||||
"""Show token usage statistics for the current conversation"""
|
||||
await self.conversation_c.stats(message)
|
||||
|
||||
@filter.permission_type(filter.PermissionType.ADMIN)
|
||||
@filter.command("provider")
|
||||
async def provider(
|
||||
|
||||
@@ -293,6 +293,15 @@
|
||||
/>
|
||||
</template>
|
||||
<v-card class="stats-card" elevation="4">
|
||||
<div
|
||||
v-if="cachedInputTokens(messageContent(msg).agentStats) > 0"
|
||||
class="stats-row"
|
||||
>
|
||||
<span>{{ tm("stats.cachedTokens") }}</span>
|
||||
<strong>{{
|
||||
cachedInputTokens(messageContent(msg).agentStats)
|
||||
}}</strong>
|
||||
</div>
|
||||
<div class="stats-row">
|
||||
<span>{{ tm("stats.inputTokens") }}</span>
|
||||
<strong>{{ inputTokens(messageContent(msg).agentStats) }}</strong>
|
||||
@@ -850,13 +859,17 @@ function formatTime(value: string) {
|
||||
|
||||
function inputTokens(stats: any) {
|
||||
const usage = stats?.token_usage || {};
|
||||
return (usage.input_other || 0) + (usage.input_cached || 0);
|
||||
return usage.input_other || 0;
|
||||
}
|
||||
|
||||
function outputTokens(stats: any) {
|
||||
return stats?.token_usage?.output || 0;
|
||||
}
|
||||
|
||||
function cachedInputTokens(stats: any) {
|
||||
return stats?.token_usage?.input_cached || 0;
|
||||
}
|
||||
|
||||
function agentDuration(stats: any) {
|
||||
const directDuration = readPositiveNumber(stats, [
|
||||
"duration",
|
||||
|
||||
@@ -185,6 +185,15 @@
|
||||
/>
|
||||
</template>
|
||||
<v-card class="stats-card" elevation="4">
|
||||
<div
|
||||
v-if="cachedInputTokens(messageContent(msg).agentStats) > 0"
|
||||
class="stats-row"
|
||||
>
|
||||
<span>{{ tm("stats.cachedTokens") }}</span>
|
||||
<strong>{{
|
||||
cachedInputTokens(messageContent(msg).agentStats)
|
||||
}}</strong>
|
||||
</div>
|
||||
<div class="stats-row">
|
||||
<span>{{ tm("stats.inputTokens") }}</span>
|
||||
<strong>{{
|
||||
@@ -512,13 +521,17 @@ function formatTime(value: string) {
|
||||
|
||||
function inputTokens(stats: any) {
|
||||
const usage = stats?.token_usage || {};
|
||||
return (usage.input_other || 0) + (usage.input_cached || 0);
|
||||
return usage.input_other || 0;
|
||||
}
|
||||
|
||||
function outputTokens(stats: any) {
|
||||
return stats?.token_usage?.output || 0;
|
||||
}
|
||||
|
||||
function cachedInputTokens(stats: any) {
|
||||
return stats?.token_usage?.input_cached || 0;
|
||||
}
|
||||
|
||||
function agentDuration(stats: any) {
|
||||
const directDuration = readPositiveNumber(stats, [
|
||||
"duration",
|
||||
|
||||
@@ -137,9 +137,9 @@
|
||||
},
|
||||
"stats": {
|
||||
"tokens": "Tokens",
|
||||
"inputTokens": "Input Tokens",
|
||||
"inputTokens": "Input (other)",
|
||||
"outputTokens": "Output Tokens",
|
||||
"cachedTokens": "Cached Tokens",
|
||||
"cachedTokens": "Input (cached)",
|
||||
"duration": "Duration",
|
||||
"ttft": "Time to First Token"
|
||||
},
|
||||
|
||||
@@ -137,9 +137,9 @@
|
||||
},
|
||||
"stats": {
|
||||
"tokens": "Токены",
|
||||
"inputTokens": "Входящие",
|
||||
"inputTokens": "Входящие (прочие)",
|
||||
"outputTokens": "Исходящие",
|
||||
"cachedTokens": "Кэшированные",
|
||||
"cachedTokens": "Входящие (кэш)",
|
||||
"duration": "Время",
|
||||
"ttft": "Время до первого токена"
|
||||
},
|
||||
|
||||
@@ -137,9 +137,9 @@
|
||||
},
|
||||
"stats": {
|
||||
"tokens": "Token",
|
||||
"inputTokens": "输入 Token",
|
||||
"inputTokens": "输入(其他)",
|
||||
"outputTokens": "输出 Token",
|
||||
"cachedTokens": "缓存 Token",
|
||||
"cachedTokens": "输入(缓存)",
|
||||
"duration": "耗时",
|
||||
"ttft": "首字时间"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user