77from enum import StrEnum
88import decimal
99
10- from app .api .dependencies import get_async_db , get_current_active_user , get_current_active_user_from_clerk
10+ from app .api .dependencies import (
11+ get_async_db ,
12+ get_current_active_user ,
13+ get_current_active_user_from_clerk ,
14+ )
1115from app .models .user import User
1216from app .models .usage_tracker import UsageTracker
1317from app .models .provider_key import ProviderKey
@@ -40,7 +44,11 @@ async def get_usage_realtime(
4044 # Calculate the date 7 days ago
4145 now = datetime .now (UTC )
4246 seven_days_ago = now - timedelta (days = 7 )
43- started_at = started_at if started_at is not None and started_at > seven_days_ago else seven_days_ago
47+ started_at = (
48+ started_at
49+ if started_at is not None and started_at > seven_days_ago
50+ else seven_days_ago
51+ )
4452 ended_at = ended_at if ended_at is not None and ended_at < now else None
4553
4654 # Build the query
@@ -51,6 +59,11 @@ async def get_usage_realtime(
5159 ProviderKey .provider_name .label ("provider_name" ),
5260 UsageTracker .model .label ("model_name" ),
5361 (UsageTracker .input_tokens + UsageTracker .output_tokens ).label ("tokens" ),
62+ (UsageTracker .input_tokens - UsageTracker .cached_tokens ).label (
63+ "input_tokens"
64+ ),
65+ UsageTracker .output_tokens .label ("output_tokens" ),
66+ UsageTracker .cached_tokens .label ("cached_tokens" ),
5467 UsageTracker .cost .label ("cost" ),
5568 func .extract (
5669 "epoch" , UsageTracker .updated_at - UsageTracker .created_at
@@ -62,13 +75,15 @@ async def get_usage_realtime(
6275 UsageTracker .user_id == current_user .id ,
6376 UsageTracker .created_at >= started_at ,
6477 ended_at is None or UsageTracker .created_at <= ended_at ,
65- forge_key is None or or_ (
78+ forge_key is None
79+ or or_ (
6680 ForgeApiKey .key .ilike (f"%{ forge_key } %" ),
67- ForgeApiKey .name .ilike (f"%{ forge_key } %" )
81+ ForgeApiKey .name .ilike (f"%{ forge_key } %" ),
6882 ),
69- provider_name is None or ProviderKey .provider_name .ilike (f"%{ provider_name } %" ),
83+ provider_name is None
84+ or ProviderKey .provider_name .ilike (f"%{ provider_name } %" ),
7085 model_name is None or UsageTracker .model .ilike (f"%{ model_name } %" ),
71- UsageTracker .updated_at .is_not (None )
86+ UsageTracker .updated_at .is_not (None ),
7287 )
7388 .order_by (desc (UsageTracker .created_at ))
7489 .offset (offset )
@@ -89,16 +104,18 @@ async def get_usage_realtime(
89104 "provider_name" : row .provider_name ,
90105 "model_name" : row .model_name ,
91106 "tokens" : row .tokens ,
107+ "input_tokens" : row .input_tokens ,
108+ "output_tokens" : row .output_tokens ,
109+ "cached_tokens" : row .cached_tokens ,
92110 "cost" : decimal .Decimal (row .cost ).normalize (),
93111 "duration" : round (float (row .duration ), 2 )
94112 if row .duration is not None
95113 else 0.0 ,
96114 }
97115 )
98- print (usage_stats )
99-
100116 return [UsageRealtimeResponse (** usage_stat ) for usage_stat in usage_stats ]
101117
118+
102119@router .get ("/usage/realtime/clerk" , response_model = list [UsageRealtimeResponse ])
103120async def get_usage_realtime_clerk (
104121 current_user : User = Depends (get_current_active_user_from_clerk ),
@@ -111,7 +128,17 @@ async def get_usage_realtime_clerk(
111128 started_at : datetime = Query (None ),
112129 ended_at : datetime = Query (None ),
113130):
114- return await get_usage_realtime (current_user , db , offset , limit , forge_key , provider_name , model_name , started_at , ended_at )
131+ return await get_usage_realtime (
132+ current_user ,
133+ db ,
134+ offset ,
135+ limit ,
136+ forge_key ,
137+ provider_name ,
138+ model_name ,
139+ started_at ,
140+ ended_at ,
141+ )
115142
116143
117144class UsageSummaryTimeSpan (StrEnum ):
@@ -152,16 +179,21 @@ async def get_usage_summary(
152179 func .sum (UsageTracker .input_tokens + UsageTracker .output_tokens ).label (
153180 "tokens"
154181 ),
182+ func .sum (UsageTracker .input_tokens - UsageTracker .cached_tokens ).label (
183+ "input_tokens"
184+ ),
185+ func .sum (UsageTracker .output_tokens ).label ("output_tokens" ),
186+ func .sum (UsageTracker .cached_tokens ).label ("cached_tokens" ),
155187 func .sum (UsageTracker .cost ).label ("cost" ),
156188 )
157189 .join (ForgeApiKey , UsageTracker .forge_key_id == ForgeApiKey .id )
158190 .where (
159191 UsageTracker .user_id == current_user .id ,
160192 UsageTracker .created_at >= start_time ,
161- UsageTracker .updated_at .is_not (None )
193+ UsageTracker .updated_at .is_not (None ),
162194 )
163195 .group_by (time_group , ForgeApiKey .name , ForgeApiKey .key )
164- .order_by (time_group , desc ("tokens " ), "forge_key" )
196+ .order_by (time_group , desc ("cost " ), "forge_key" )
165197 )
166198
167199 # Execute the query
@@ -171,19 +203,41 @@ async def get_usage_summary(
171203 data_points = dict ()
172204 for row in rows :
173205 if row .time_point not in data_points :
174- data_points [row .time_point ] = {"breakdown" : [], "total_tokens" : 0 , "total_cost" : 0 }
206+ data_points [row .time_point ] = {
207+ "breakdown" : [],
208+ "total_tokens" : 0 ,
209+ "total_cost" : 0 ,
210+ "total_input_tokens" : 0 ,
211+ "total_output_tokens" : 0 ,
212+ "total_cached_tokens" : 0 ,
213+ }
175214 data_points [row .time_point ]["breakdown" ].append (
176- {"forge_key" : row .forge_key , "tokens" : row .tokens , "cost" : decimal .Decimal (row .cost ).normalize ()}
215+ {
216+ "forge_key" : row .forge_key ,
217+ "tokens" : row .tokens ,
218+ "cost" : decimal .Decimal (row .cost ).normalize (),
219+ "input_tokens" : row .input_tokens ,
220+ "output_tokens" : row .output_tokens ,
221+ "cached_tokens" : row .cached_tokens ,
222+ }
177223 )
178224 data_points [row .time_point ]["total_tokens" ] += row .tokens
179- data_points [row .time_point ]["total_cost" ] += decimal .Decimal (row .cost ).normalize ()
225+ data_points [row .time_point ]["total_cost" ] += decimal .Decimal (
226+ row .cost
227+ ).normalize ()
228+ data_points [row .time_point ]["total_input_tokens" ] += row .input_tokens
229+ data_points [row .time_point ]["total_output_tokens" ] += row .output_tokens
230+ data_points [row .time_point ]["total_cached_tokens" ] += row .cached_tokens
180231
181232 return [
182233 UsageSummaryResponse (
183234 time_point = time_point ,
184235 breakdown = data_point ["breakdown" ],
185236 total_tokens = data_point ["total_tokens" ],
186237 total_cost = data_point ["total_cost" ],
238+ total_input_tokens = data_point ["total_input_tokens" ],
239+ total_output_tokens = data_point ["total_output_tokens" ],
240+ total_cached_tokens = data_point ["total_cached_tokens" ],
187241 )
188242 for time_point , data_point in data_points .items ()
189243 ]
@@ -231,6 +285,11 @@ async def get_forge_keys_usage(
231285 func .sum (UsageTracker .input_tokens + UsageTracker .output_tokens ).label (
232286 "tokens"
233287 ),
288+ func .sum (UsageTracker .input_tokens - UsageTracker .cached_tokens ).label (
289+ "input_tokens"
290+ ),
291+ func .sum (UsageTracker .output_tokens ).label ("output_tokens" ),
292+ func .sum (UsageTracker .cached_tokens ).label ("cached_tokens" ),
234293 func .sum (UsageTracker .cost ).label ("cost" ),
235294 )
236295 .join (ForgeApiKey , UsageTracker .forge_key_id == ForgeApiKey .id )
@@ -240,19 +299,28 @@ async def get_forge_keys_usage(
240299 UsageTracker .updated_at .is_not (None ),
241300 )
242301 .group_by (ForgeApiKey .name , ForgeApiKey .key )
243- .order_by (desc ("tokens " ), "forge_key" )
302+ .order_by (desc ("cost " ), "forge_key" )
244303 )
245304
246305 result = await db .execute (query )
247306 rows = result .fetchall ()
248307
249308 return [
250- ForgeKeysUsageSummaryResponse (forge_key = row .forge_key , tokens = row .tokens , cost = decimal .Decimal (row .cost ).normalize ())
309+ ForgeKeysUsageSummaryResponse (
310+ forge_key = row .forge_key ,
311+ tokens = row .tokens ,
312+ cost = decimal .Decimal (row .cost ).normalize (),
313+ input_tokens = row .input_tokens ,
314+ output_tokens = row .output_tokens ,
315+ cached_tokens = row .cached_tokens ,
316+ )
251317 for row in rows
252318 ]
253319
254320
255- @router .get ("/forge-keys/usage/clerk" , response_model = list [ForgeKeysUsageSummaryResponse ])
321+ @router .get (
322+ "/forge-keys/usage/clerk" , response_model = list [ForgeKeysUsageSummaryResponse ]
323+ )
256324async def get_forge_keys_usage_clerk (
257325 current_user : User = Depends (get_current_active_user_from_clerk ),
258326 db : AsyncSession = Depends (get_async_db ),
0 commit comments