Skip to content

Commit 3749394

Browse files
committed
feat: add bot token usage analyzer
1 parent 6605804 commit 3749394

File tree

8 files changed

+3730
-2998
lines changed

8 files changed

+3730
-2998
lines changed

client/app/services/TokensController.ts

+17-1
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,20 @@ export async function deleteToken(id: string) {
1616
export async function createToken(data: LLMTokenInsert) {
1717
const response = await axios.post(`${apiDomain}/api/user/llm_token`, data);
1818
return response.data;
19-
}
19+
}
20+
21+
export async function analyzeTokenUsage() {
22+
const response = await axios.get(`${apiDomain}/api/user/llm_token_usages/analyzer`);
23+
return response.data;
24+
}
25+
26+
export async function analyzeTopBots() {
27+
const response = await axios.get(`${apiDomain}/api/user/llm_token_usages/top_bots`);
28+
return response.data;
29+
}
30+
31+
export async function analyzeTopUsers() {
32+
const response = await axios.get(`${apiDomain}/api/user/llm_token_usages/top_users`);
33+
return response.data;
34+
}
35+

client/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
"node": ">=18"
1818
},
1919
"dependencies": {
20+
"@ant-design/charts": "^2.2.6",
21+
"@antv/g2": "^5.2.10",
2022
"@auth0/nextjs-auth0": "^3.3.0",
2123
"@fingerprintjs/fingerprintjs": "^4.3.0",
2224
"@fullpage/react-fullpage": "^0.1.42",

client/yarn.lock

+3,569-2,993
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
drop function if exists "public"."analyze_user_token_usage"(start_date date, end_date date);
2+
3+
alter table "public"."profiles" add column "is_admin" boolean default false;
4+
5+
set check_function_bodies = off;
6+
7+
CREATE OR REPLACE FUNCTION public.bot_token_usage_rate(start_date date, end_date date)
8+
RETURNS TABLE(bot_id text, bot_name text, input_tokens bigint, output_tokens bigint, total_tokens bigint)
9+
LANGUAGE plpgsql
10+
AS $function$
11+
BEGIN
12+
RETURN QUERY
13+
SELECT * from (
14+
SELECT
15+
u.bot_id AS bot_id,
16+
COALESCE(b.name, '未命名')::text as bot_name,
17+
SUM(u.input_token)::BIGINT AS input_tokens, -- 将结果转换为 BIGINT
18+
SUM(u.output_token)::BIGINT AS output_tokens, -- 将结果转换为 BIGINT
19+
SUM(u.total_token)::BIGINT AS total_tokens -- 将结果转换为 BIGINT
20+
FROM user_token_usage u
21+
LEFT JOIN bots b
22+
ON b.id::text = u.bot_id
23+
GROUP BY u.bot_id, b.name
24+
) bu
25+
ORDER BY total_tokens DESC
26+
limit 3;
27+
END;
28+
$function$
29+
;
30+
31+
CREATE OR REPLACE FUNCTION public.user_token_usage_rate(start_date date, end_date date)
32+
RETURNS TABLE(user_id text, user_name text, input_tokens bigint, output_tokens bigint, total_tokens bigint)
33+
LANGUAGE plpgsql
34+
AS $function$
35+
BEGIN
36+
RETURN QUERY
37+
SELECT * from (
38+
SELECT
39+
u.user_id AS user_id,
40+
COALESCE(p.name, '匿名用户')::text as user_name,
41+
SUM(u.input_token)::BIGINT AS input_tokens, -- 将结果转换为 BIGINT
42+
SUM(u.output_token)::BIGINT AS output_tokens, -- 将结果转换为 BIGINT
43+
SUM(u.total_token)::BIGINT AS total_tokens -- 将结果转换为 BIGINT
44+
FROM user_token_usage u
45+
LEFT JOIN profiles p
46+
ON p.id = u.user_id
47+
WHERE
48+
u.token_id = 'DEFAULT_TOKEN' AND
49+
u.date >= start_date AND
50+
u.date <= end_date
51+
GROUP BY u.user_id, p.name
52+
) bu
53+
ORDER BY total_tokens DESC
54+
limit 3;
55+
END;
56+
$function$
57+
;
58+
59+
CREATE OR REPLACE FUNCTION public.analyze_user_token_usage(start_date date, end_date date)
60+
RETURNS TABLE(bot_id text, bot_name text, usage_date date, input_tokens bigint, output_tokens bigint, total_tokens bigint)
61+
LANGUAGE plpgsql
62+
AS $function$
63+
BEGIN
64+
RETURN QUERY
65+
SELECT
66+
u.bot_id AS bot_id,
67+
COALESCE(b.name, '未命名')::text as bot_name,
68+
Date(u.date) AS usage_date, -- 使用别名来避免歧义
69+
SUM(u.input_token)::BIGINT AS input_tokens, -- 将结果转换为 BIGINT
70+
SUM(u.output_token)::BIGINT AS output_tokens, -- 将结果转换为 BIGINT
71+
SUM(u.total_token)::BIGINT AS total_tokens -- 将结果转换为 BIGINT
72+
FROM user_token_usage u
73+
LEFT JOIN bots b
74+
ON b.id::text = u.bot_id
75+
WHERE
76+
u.date >= start_date AND
77+
u.date <= end_date
78+
GROUP BY u.date, u.bot_id, b.name
79+
ORDER by u.date DESC;
80+
END;
81+
$function$
82+
;
83+
84+

server/core/dao/userTokenUsageDAO.py

+19-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from datetime import datetime
22
from supabase.client import Client
33

4-
from core.models.user_token_usage import BotTokenUsageStats, UserTokenUsage, UserTokenUsageStats
4+
from core.models.user_token_usage import BotTokenUsageRate, BotTokenUsageStats, UserTokenUsage, UserTokenUsageRate, UserTokenUsageStats
55
from petercat_utils.db.client.supabase import get_client
66
from core.dao.BaseDAO import BaseDAO
77

@@ -36,4 +36,21 @@ def analyze(self, start_date: datetime, end_date: datetime):
3636
"end_date": end_date.strftime("%Y-%m-%d"),
3737
}).execute()
3838

39-
return [BotTokenUsageStats(**stats) for stats in resp.data]
39+
return [BotTokenUsageStats(**stats) for stats in resp.data]
40+
41+
def top_bots(self, start_date: datetime, end_date: datetime):
42+
resp = self.client.rpc("bot_token_usage_rate", {
43+
"start_date": start_date.strftime("%Y-%m-%d"),
44+
"end_date": end_date.strftime("%Y-%m-%d"),
45+
}).execute()
46+
47+
return [BotTokenUsageRate(**stats) for stats in resp.data]
48+
49+
def top_users(self, start_date: datetime, end_date: datetime):
50+
resp = self.client.rpc("user_token_usage_rate", {
51+
"start_date": start_date.strftime("%Y-%m-%d"),
52+
"end_date": end_date.strftime("%Y-%m-%d"),
53+
}).execute()
54+
55+
return [UserTokenUsageRate(**stats) for stats in resp.data]
56+

server/core/models/user_token_usage.py

+15
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,22 @@ class UserTokenUsageStats(BaseModel):
2323

2424
class BotTokenUsageStats(BaseModel):
2525
bot_id: str
26+
bot_name: str
2627
usage_date: datetime
2728
input_tokens: Optional[int] = 0
2829
output_tokens: Optional[int] = 0
30+
total_tokens: Optional[int] = 0
31+
32+
class BotTokenUsageRate(BaseModel):
33+
bot_id: str
34+
bot_name: str
35+
input_tokens: Optional[int] = 0
36+
output_tokens: Optional[int] = 0
37+
total_tokens: Optional[int] = 0
38+
39+
class UserTokenUsageRate(BaseModel):
40+
user_id: str
41+
user_name: str
42+
input_tokens: Optional[int] = 0
43+
output_tokens: Optional[int] = 0
2944
total_tokens: Optional[int] = 0

server/core/service/user_token_usage.py

+6
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ def usage_stats(self, user_id: str, start_date: datetime.date, end_date: datetim
2727
def analyze_token_usage(self, start_date: datetime.date, end_date: datetime.date):
2828
return self.user_token_usage_dao.analyze(start_date=start_date, end_date=end_date)
2929

30+
def top_bots(self, start_date: datetime.date, end_date: datetime.date):
31+
return self.user_token_usage_dao.top_bots(start_date=start_date, end_date=end_date)
32+
33+
def top_users(self, start_date: datetime.date, end_date: datetime.date):
34+
return self.user_token_usage_dao.top_users(start_date=start_date, end_date=end_date)
35+
3036
def get_user_token_usage_service():
3137
return UserTokenUsageService()
3238

server/user/router.py

+18-2
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,24 @@ def token_usage(
6969

7070
@router.get("/llm_token_usages/analyzer")
7171
def token_usage_analyze(
72-
start_date: datetime = datetime.now() - timedelta(days=7),
72+
start_date: datetime = datetime.now() - timedelta(days=30),
7373
end_date: datetime = datetime.now(),
7474
user_token_usage_service: Annotated[UserTokenUsageService | None, Depends(get_user_token_usage_service)] = None,
7575
):
76-
return user_token_usage_service.analyze_token_usage(start_date=start_date, end_date=end_date)
76+
return user_token_usage_service.analyze_token_usage(start_date=start_date, end_date=end_date)
77+
78+
@router.get("/llm_token_usages/top_bots")
79+
def top_used_bots(
80+
start_date: datetime = datetime.now() - timedelta(days=30),
81+
end_date: datetime = datetime.now(),
82+
user_token_usage_service: Annotated[UserTokenUsageService | None, Depends(get_user_token_usage_service)] = None,
83+
):
84+
return user_token_usage_service.top_bots(start_date=start_date, end_date=end_date)
85+
86+
@router.get("/llm_token_usages/top_users")
87+
def top_used_users(
88+
start_date: datetime = datetime.now() - timedelta(days=30),
89+
end_date: datetime = datetime.now(),
90+
user_token_usage_service: Annotated[UserTokenUsageService | None, Depends(get_user_token_usage_service)] = None,
91+
):
92+
return user_token_usage_service.top_users(start_date=start_date, end_date=end_date)

0 commit comments

Comments
 (0)