tokenator 0.1.14__py3-none-any.whl → 0.1.16__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tokenator/anthropic/client_anthropic.py +25 -7
- tokenator/base_wrapper.py +26 -0
- tokenator/migrations/versions/f028b8155fed_adding_detailed_input_and_output_token_.py +64 -0
- tokenator/models.py +29 -8
- tokenator/openai/client_openai.py +86 -4
- tokenator/schemas.py +15 -15
- tokenator/usage.py +485 -213
- tokenator/utils.py +14 -1
- {tokenator-0.1.14.dist-info → tokenator-0.1.16.dist-info}/METADATA +11 -5
- tokenator-0.1.16.dist-info/RECORD +21 -0
- tokenator-0.1.14.dist-info/RECORD +0 -20
- {tokenator-0.1.14.dist-info → tokenator-0.1.16.dist-info}/LICENSE +0 -0
- {tokenator-0.1.14.dist-info → tokenator-0.1.16.dist-info}/WHEEL +0 -0
tokenator/usage.py
CHANGED
@@ -4,150 +4,359 @@ from datetime import datetime, timedelta
|
|
4
4
|
from typing import Dict, Optional, Union
|
5
5
|
|
6
6
|
from .schemas import get_session, TokenUsage
|
7
|
-
from .models import
|
7
|
+
from .models import (
|
8
|
+
CompletionTokenDetails,
|
9
|
+
PromptTokenDetails,
|
10
|
+
TokenRate,
|
11
|
+
TokenUsageReport,
|
12
|
+
ModelUsage,
|
13
|
+
ProviderUsage,
|
14
|
+
)
|
8
15
|
from . import state
|
9
16
|
|
10
17
|
import requests
|
11
18
|
import logging
|
19
|
+
import time
|
12
20
|
|
13
21
|
logger = logging.getLogger(__name__)
|
14
22
|
|
15
23
|
|
16
24
|
class TokenUsageService:
|
17
25
|
def __init__(self):
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
26
|
+
try:
|
27
|
+
if not state.is_tokenator_enabled:
|
28
|
+
logger.info("Tokenator is disabled. Database access is unavailable.")
|
29
|
+
self.MODEL_COSTS = self._get_model_costs()
|
30
|
+
except Exception as e:
|
31
|
+
logger.error(f"Error in __init__: {e}")
|
32
|
+
self.MODEL_COSTS = {}
|
22
33
|
|
23
34
|
def _get_model_costs(self) -> Dict[str, TokenRate]:
|
24
|
-
|
35
|
+
try:
|
36
|
+
if not state.is_tokenator_enabled:
|
37
|
+
return {}
|
38
|
+
url = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json"
|
39
|
+
response = requests.get(url)
|
40
|
+
response.raise_for_status()
|
41
|
+
data = response.json()
|
42
|
+
|
43
|
+
model_costs = {}
|
44
|
+
for model, info in data.items():
|
45
|
+
if (
|
46
|
+
"input_cost_per_token" not in info
|
47
|
+
or "output_cost_per_token" not in info
|
48
|
+
):
|
49
|
+
continue
|
50
|
+
|
51
|
+
rate = TokenRate(
|
52
|
+
prompt=info["input_cost_per_token"],
|
53
|
+
completion=info["output_cost_per_token"],
|
54
|
+
prompt_audio=info.get("input_cost_per_audio_token"),
|
55
|
+
completion_audio=info.get("output_cost_per_audio_token"),
|
56
|
+
prompt_cached_input=info.get("cache_read_input_token_cost") or 0,
|
57
|
+
prompt_cached_creation=info.get("cache_read_creation_token_cost") or 0,
|
58
|
+
)
|
59
|
+
model_costs[model] = rate
|
60
|
+
|
61
|
+
return model_costs
|
62
|
+
except Exception as e:
|
63
|
+
logger.error(f"Error in _get_model_costs: {e}")
|
25
64
|
return {}
|
26
|
-
url = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json"
|
27
|
-
response = requests.get(url)
|
28
|
-
data = response.json()
|
29
|
-
|
30
|
-
return {
|
31
|
-
model: TokenRate(
|
32
|
-
prompt=info["input_cost_per_token"],
|
33
|
-
completion=info["output_cost_per_token"],
|
34
|
-
)
|
35
|
-
for model, info in data.items()
|
36
|
-
if "input_cost_per_token" in info and "output_cost_per_token" in info
|
37
|
-
}
|
38
65
|
|
39
66
|
def _calculate_cost(
|
40
67
|
self, usages: list[TokenUsage], provider: Optional[str] = None
|
41
68
|
) -> TokenUsageReport:
|
42
|
-
|
43
|
-
|
44
|
-
|
69
|
+
try:
|
70
|
+
if not state.is_tokenator_enabled:
|
71
|
+
logger.warning("Tokenator is disabled. Skipping cost calculation.")
|
72
|
+
return TokenUsageReport()
|
73
|
+
|
74
|
+
if not self.MODEL_COSTS:
|
75
|
+
logger.warning("No model costs available.")
|
76
|
+
return TokenUsageReport()
|
77
|
+
|
78
|
+
# Default GPT4O pricing updated with provided values
|
79
|
+
GPT4O_PRICING = TokenRate(
|
80
|
+
prompt=0.0000025,
|
81
|
+
completion=0.000010,
|
82
|
+
prompt_audio=0.0001,
|
83
|
+
completion_audio=0.0002,
|
84
|
+
prompt_cached_input=0.00000125,
|
85
|
+
prompt_cached_creation=0.00000125,
|
86
|
+
)
|
45
87
|
|
46
|
-
|
47
|
-
logger.
|
48
|
-
return TokenUsageReport()
|
88
|
+
provider_model_usages: Dict[str, Dict[str, list[TokenUsage]]] = {}
|
89
|
+
logger.debug(f"usages: {len(usages)}")
|
49
90
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
for usage in usages:
|
59
|
-
# 1st priority - direct match
|
60
|
-
model_key = usage.model
|
61
|
-
if model_key in self.MODEL_COSTS:
|
62
|
-
pass
|
63
|
-
# 2nd priority - provider/model format
|
64
|
-
elif f"{usage.provider}/{usage.model}" in self.MODEL_COSTS:
|
65
|
-
model_key = f"{usage.provider}/{usage.model}"
|
66
|
-
# 3rd priority - contains search
|
67
|
-
else:
|
68
|
-
matched_keys = [k for k in self.MODEL_COSTS.keys() if usage.model in k]
|
69
|
-
if matched_keys:
|
70
|
-
model_key = matched_keys[0]
|
71
|
-
logger.warning(
|
72
|
-
f"Model {usage.model} matched with {model_key} in pricing data via contains search"
|
73
|
-
)
|
91
|
+
for usage in usages:
|
92
|
+
# Model key resolution logic (unchanged)
|
93
|
+
model_key = usage.model
|
94
|
+
if model_key in self.MODEL_COSTS:
|
95
|
+
pass
|
96
|
+
elif f"{usage.provider}/{usage.model}" in self.MODEL_COSTS:
|
97
|
+
model_key = f"{usage.provider}/{usage.model}"
|
74
98
|
else:
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
for provider, model_usages in provider_model_usages.items():
|
97
|
-
provider_metrics = {
|
99
|
+
matched_keys = [k for k in self.MODEL_COSTS.keys() if usage.model in k]
|
100
|
+
if matched_keys:
|
101
|
+
model_key = matched_keys[0]
|
102
|
+
logger.warning(
|
103
|
+
f"Model {usage.model} matched with {model_key} in pricing data via contains search"
|
104
|
+
)
|
105
|
+
else:
|
106
|
+
logger.warning(
|
107
|
+
f"Model {model_key} not found in pricing data. Using gpt-4o pricing as fallback"
|
108
|
+
)
|
109
|
+
self.MODEL_COSTS[model_key] = GPT4O_PRICING
|
110
|
+
|
111
|
+
provider_key = usage.provider or "default"
|
112
|
+
provider_model_usages.setdefault(provider_key, {}).setdefault(
|
113
|
+
model_key, []
|
114
|
+
).append(usage)
|
115
|
+
|
116
|
+
# Calculate totals for each level
|
117
|
+
providers_list = []
|
118
|
+
total_metrics = {
|
98
119
|
"total_cost": 0.0,
|
99
120
|
"total_tokens": 0,
|
100
121
|
"prompt_tokens": 0,
|
101
122
|
"completion_tokens": 0,
|
102
123
|
}
|
103
|
-
models_list = []
|
104
124
|
|
105
|
-
for
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
125
|
+
for provider, model_usages in provider_model_usages.items():
|
126
|
+
provider_metrics = {
|
127
|
+
"total_cost": 0.0,
|
128
|
+
"total_tokens": 0,
|
129
|
+
"prompt_tokens": 0,
|
130
|
+
"completion_tokens": 0,
|
131
|
+
"prompt_cached_input_tokens": 0,
|
132
|
+
"prompt_cached_creation_tokens": 0,
|
133
|
+
"prompt_audio_tokens": 0,
|
134
|
+
"completion_audio_tokens": 0,
|
135
|
+
"completion_reasoning_tokens": 0,
|
136
|
+
"completion_accepted_prediction_tokens": 0,
|
137
|
+
"completion_rejected_prediction_tokens": 0,
|
138
|
+
}
|
139
|
+
models_list = []
|
140
|
+
|
141
|
+
for model_key, usages in model_usages.items():
|
142
|
+
model_rates = self.MODEL_COSTS[model_key]
|
143
|
+
model_cost = 0.0
|
144
|
+
model_total = 0
|
145
|
+
model_prompt = 0
|
146
|
+
model_completion = 0
|
147
|
+
|
148
|
+
for usage in usages:
|
149
|
+
# Base token costs
|
150
|
+
prompt_text_tokens = usage.prompt_tokens
|
151
|
+
if usage.prompt_cached_input_tokens:
|
152
|
+
prompt_text_tokens = (
|
153
|
+
usage.prompt_tokens - usage.prompt_cached_input_tokens
|
154
|
+
)
|
155
|
+
if usage.prompt_audio_tokens:
|
156
|
+
prompt_text_tokens = (
|
157
|
+
usage.prompt_tokens - usage.prompt_audio_tokens
|
158
|
+
)
|
159
|
+
|
160
|
+
completion_text_tokens = usage.completion_tokens
|
161
|
+
if usage.completion_audio_tokens:
|
162
|
+
completion_text_tokens = (
|
163
|
+
usage.completion_tokens - usage.completion_audio_tokens
|
164
|
+
)
|
165
|
+
|
166
|
+
prompt_cost = prompt_text_tokens * model_rates.prompt
|
167
|
+
completion_cost = completion_text_tokens * model_rates.completion
|
168
|
+
model_cost += prompt_cost + completion_cost
|
169
|
+
|
170
|
+
# Audio token costs
|
171
|
+
if usage.prompt_audio_tokens:
|
172
|
+
if model_rates.prompt_audio:
|
173
|
+
model_cost += (
|
174
|
+
usage.prompt_audio_tokens * model_rates.prompt_audio
|
175
|
+
)
|
176
|
+
else:
|
177
|
+
logger.warning(
|
178
|
+
f"Audio prompt tokens present for {model_key} but no audio rate defined"
|
179
|
+
)
|
180
|
+
|
181
|
+
if usage.completion_audio_tokens:
|
182
|
+
if model_rates.completion_audio:
|
183
|
+
model_cost += (
|
184
|
+
usage.completion_audio_tokens
|
185
|
+
* model_rates.completion_audio
|
186
|
+
)
|
187
|
+
else:
|
188
|
+
logger.warning(
|
189
|
+
f"Audio completion tokens present for {model_key} but no audio rate defined"
|
190
|
+
)
|
191
|
+
|
192
|
+
# Cached token costs
|
193
|
+
if usage.prompt_cached_input_tokens:
|
194
|
+
if model_rates.prompt_cached_input:
|
195
|
+
model_cost += (
|
196
|
+
usage.prompt_cached_input_tokens
|
197
|
+
* model_rates.prompt_cached_input
|
198
|
+
)
|
199
|
+
else:
|
200
|
+
logger.warning(
|
201
|
+
f"Cached input tokens present for {model_key} but no cache input rate defined"
|
202
|
+
)
|
203
|
+
|
204
|
+
if usage.prompt_cached_creation_tokens:
|
205
|
+
if model_rates.prompt_cached_creation:
|
206
|
+
model_cost += (
|
207
|
+
usage.prompt_cached_creation_tokens
|
208
|
+
* model_rates.prompt_cached_creation
|
209
|
+
)
|
210
|
+
else:
|
211
|
+
logger.warning(
|
212
|
+
f"Cached creation tokens present for {model_key} but no cache creation rate defined"
|
213
|
+
)
|
214
|
+
|
215
|
+
model_total += usage.total_tokens
|
216
|
+
model_prompt += usage.prompt_tokens
|
217
|
+
model_completion += usage.completion_tokens
|
218
|
+
|
219
|
+
models_list.append(
|
220
|
+
ModelUsage(
|
221
|
+
model=model_key,
|
222
|
+
total_cost=round(model_cost, 6),
|
223
|
+
total_tokens=model_total,
|
224
|
+
prompt_tokens=model_prompt,
|
225
|
+
completion_tokens=model_completion,
|
226
|
+
prompt_tokens_details=PromptTokenDetails(
|
227
|
+
cached_input_tokens=sum(
|
228
|
+
u.prompt_cached_input_tokens or 0 for u in usages
|
229
|
+
),
|
230
|
+
cached_creation_tokens=sum(
|
231
|
+
u.prompt_cached_creation_tokens or 0 for u in usages
|
232
|
+
),
|
233
|
+
audio_tokens=sum(
|
234
|
+
u.prompt_audio_tokens or 0 for u in usages
|
235
|
+
),
|
236
|
+
)
|
237
|
+
if any(
|
238
|
+
u.prompt_cached_input_tokens
|
239
|
+
or u.prompt_cached_creation_tokens
|
240
|
+
or u.prompt_audio_tokens
|
241
|
+
for u in usages
|
242
|
+
)
|
243
|
+
else None,
|
244
|
+
completion_tokens_details=CompletionTokenDetails(
|
245
|
+
audio_tokens=sum(
|
246
|
+
u.completion_audio_tokens or 0 for u in usages
|
247
|
+
),
|
248
|
+
reasoning_tokens=sum(
|
249
|
+
u.completion_reasoning_tokens or 0 for u in usages
|
250
|
+
),
|
251
|
+
accepted_prediction_tokens=sum(
|
252
|
+
u.completion_accepted_prediction_tokens or 0
|
253
|
+
for u in usages
|
254
|
+
),
|
255
|
+
rejected_prediction_tokens=sum(
|
256
|
+
u.completion_rejected_prediction_tokens or 0
|
257
|
+
for u in usages
|
258
|
+
),
|
259
|
+
)
|
260
|
+
if any(
|
261
|
+
getattr(u, attr, None)
|
262
|
+
for u in usages
|
263
|
+
for attr in [
|
264
|
+
"completion_audio_tokens",
|
265
|
+
"completion_reasoning_tokens",
|
266
|
+
"completion_accepted_prediction_tokens",
|
267
|
+
"completion_rejected_prediction_tokens",
|
268
|
+
]
|
269
|
+
)
|
270
|
+
else None,
|
271
|
+
)
|
122
272
|
)
|
123
|
-
)
|
124
273
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
274
|
+
# Update provider metrics with all token types
|
275
|
+
provider_metrics["total_cost"] += model_cost
|
276
|
+
provider_metrics["total_tokens"] += model_total
|
277
|
+
provider_metrics["prompt_tokens"] += model_prompt
|
278
|
+
provider_metrics["completion_tokens"] += model_completion
|
279
|
+
provider_metrics["prompt_cached_input_tokens"] += sum(
|
280
|
+
u.prompt_cached_input_tokens or 0 for u in usages
|
281
|
+
)
|
282
|
+
provider_metrics["prompt_cached_creation_tokens"] += sum(
|
283
|
+
u.prompt_cached_creation_tokens or 0 for u in usages
|
284
|
+
)
|
285
|
+
provider_metrics["prompt_audio_tokens"] += sum(
|
286
|
+
u.prompt_audio_tokens or 0 for u in usages
|
287
|
+
)
|
288
|
+
provider_metrics["completion_audio_tokens"] += sum(
|
289
|
+
u.completion_audio_tokens or 0 for u in usages
|
290
|
+
)
|
291
|
+
provider_metrics["completion_reasoning_tokens"] += sum(
|
292
|
+
u.completion_reasoning_tokens or 0 for u in usages
|
293
|
+
)
|
294
|
+
provider_metrics["completion_accepted_prediction_tokens"] += sum(
|
295
|
+
u.completion_accepted_prediction_tokens or 0 for u in usages
|
296
|
+
)
|
297
|
+
provider_metrics["completion_rejected_prediction_tokens"] += sum(
|
298
|
+
u.completion_rejected_prediction_tokens or 0 for u in usages
|
299
|
+
)
|
300
|
+
|
301
|
+
providers_list.append(
|
302
|
+
ProviderUsage(
|
303
|
+
provider=provider,
|
304
|
+
models=models_list,
|
305
|
+
total_cost=round(provider_metrics["total_cost"], 6),
|
306
|
+
total_tokens=provider_metrics["total_tokens"],
|
307
|
+
prompt_tokens=provider_metrics["prompt_tokens"],
|
308
|
+
completion_tokens=provider_metrics["completion_tokens"],
|
309
|
+
prompt_tokens_details=PromptTokenDetails(
|
310
|
+
cached_input_tokens=provider_metrics[
|
311
|
+
"prompt_cached_input_tokens"
|
312
|
+
],
|
313
|
+
cached_creation_tokens=provider_metrics[
|
314
|
+
"prompt_cached_creation_tokens"
|
315
|
+
],
|
316
|
+
audio_tokens=provider_metrics["prompt_audio_tokens"],
|
317
|
+
)
|
318
|
+
if provider_metrics["prompt_cached_input_tokens"]
|
319
|
+
or provider_metrics["prompt_cached_creation_tokens"]
|
320
|
+
or provider_metrics["prompt_audio_tokens"]
|
321
|
+
else None,
|
322
|
+
completion_tokens_details=CompletionTokenDetails(
|
323
|
+
audio_tokens=provider_metrics["completion_audio_tokens"],
|
324
|
+
reasoning_tokens=provider_metrics[
|
325
|
+
"completion_reasoning_tokens"
|
326
|
+
],
|
327
|
+
accepted_prediction_tokens=provider_metrics[
|
328
|
+
"completion_accepted_prediction_tokens"
|
329
|
+
],
|
330
|
+
rejected_prediction_tokens=provider_metrics[
|
331
|
+
"completion_rejected_prediction_tokens"
|
332
|
+
],
|
333
|
+
)
|
334
|
+
if any(
|
335
|
+
provider_metrics[k]
|
336
|
+
for k in [
|
337
|
+
"completion_audio_tokens",
|
338
|
+
"completion_reasoning_tokens",
|
339
|
+
"completion_accepted_prediction_tokens",
|
340
|
+
"completion_rejected_prediction_tokens",
|
341
|
+
]
|
342
|
+
)
|
343
|
+
else None,
|
344
|
+
)
|
138
345
|
)
|
139
|
-
)
|
140
346
|
|
141
|
-
|
142
|
-
|
347
|
+
for key in total_metrics:
|
348
|
+
total_metrics[key] += provider_metrics[key]
|
143
349
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
350
|
+
return TokenUsageReport(
|
351
|
+
providers=providers_list,
|
352
|
+
**{
|
353
|
+
k: (round(v, 6) if k == "total_cost" else v)
|
354
|
+
for k, v in total_metrics.items()
|
355
|
+
},
|
356
|
+
)
|
357
|
+
except Exception as e:
|
358
|
+
logger.error(f"Error in _calculate_cost: {e}")
|
359
|
+
return TokenUsageReport()
|
151
360
|
|
152
361
|
def _query_usage(
|
153
362
|
self,
|
@@ -156,74 +365,97 @@ class TokenUsageService:
|
|
156
365
|
provider: Optional[str] = None,
|
157
366
|
model: Optional[str] = None,
|
158
367
|
) -> TokenUsageReport:
|
159
|
-
if not state.is_tokenator_enabled:
|
160
|
-
logger.warning("Tokenator is disabled. Skipping usage query.")
|
161
|
-
return TokenUsageReport()
|
162
|
-
|
163
|
-
session = get_session()()
|
164
368
|
try:
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
if provider:
|
170
|
-
query = query.filter(TokenUsage.provider == provider)
|
171
|
-
if model:
|
172
|
-
query = query.filter(TokenUsage.model == model)
|
369
|
+
if not state.is_tokenator_enabled:
|
370
|
+
logger.warning("Tokenator is disabled. Skipping usage query.")
|
371
|
+
return TokenUsageReport()
|
173
372
|
|
174
|
-
|
373
|
+
session = get_session()()
|
374
|
+
try:
|
375
|
+
query = session.query(TokenUsage).filter(
|
376
|
+
TokenUsage.created_at.between(start_date, end_date)
|
377
|
+
)
|
175
378
|
|
176
|
-
|
177
|
-
|
178
|
-
|
379
|
+
if provider:
|
380
|
+
query = query.filter(TokenUsage.provider == provider)
|
381
|
+
if model:
|
382
|
+
query = query.filter(TokenUsage.model == model)
|
383
|
+
|
384
|
+
usages = query.all()
|
385
|
+
|
386
|
+
return self._calculate_cost(usages, provider or "all")
|
387
|
+
except Exception as e:
|
388
|
+
logger.error(f"Error querying usage: {e}")
|
389
|
+
return TokenUsageReport()
|
390
|
+
finally:
|
391
|
+
session.close()
|
392
|
+
except Exception as e:
|
393
|
+
logger.error(f"Unexpected error in _query_usage: {e}")
|
394
|
+
return TokenUsageReport()
|
179
395
|
|
180
396
|
def last_hour(
|
181
397
|
self, provider: Optional[str] = None, model: Optional[str] = None
|
182
398
|
) -> TokenUsageReport:
|
183
|
-
|
399
|
+
try:
|
400
|
+
if not state.is_tokenator_enabled:
|
401
|
+
return TokenUsageReport()
|
402
|
+
logger.debug(
|
403
|
+
f"Getting cost analysis for last hour (provider={provider}, model={model})"
|
404
|
+
)
|
405
|
+
end = datetime.now()
|
406
|
+
start = end - timedelta(hours=1)
|
407
|
+
return self._query_usage(start, end, provider, model)
|
408
|
+
except Exception as e:
|
409
|
+
logger.error(f"Error in last_hour: {e}")
|
184
410
|
return TokenUsageReport()
|
185
|
-
logger.debug(
|
186
|
-
f"Getting cost analysis for last hour (provider={provider}, model={model})"
|
187
|
-
)
|
188
|
-
end = datetime.now()
|
189
|
-
start = end - timedelta(hours=1)
|
190
|
-
return self._query_usage(start, end, provider, model)
|
191
411
|
|
192
412
|
def last_day(
|
193
413
|
self, provider: Optional[str] = None, model: Optional[str] = None
|
194
414
|
) -> TokenUsageReport:
|
195
|
-
|
415
|
+
try:
|
416
|
+
if not state.is_tokenator_enabled:
|
417
|
+
return TokenUsageReport()
|
418
|
+
logger.debug(
|
419
|
+
f"Getting cost analysis for last 24 hours (provider={provider}, model={model})"
|
420
|
+
)
|
421
|
+
end = datetime.now()
|
422
|
+
start = end - timedelta(days=1)
|
423
|
+
return self._query_usage(start, end, provider, model)
|
424
|
+
except Exception as e:
|
425
|
+
logger.error(f"Error in last_day: {e}")
|
196
426
|
return TokenUsageReport()
|
197
|
-
logger.debug(
|
198
|
-
f"Getting cost analysis for last 24 hours (provider={provider}, model={model})"
|
199
|
-
)
|
200
|
-
end = datetime.now()
|
201
|
-
start = end - timedelta(days=1)
|
202
|
-
return self._query_usage(start, end, provider, model)
|
203
427
|
|
204
428
|
def last_week(
|
205
429
|
self, provider: Optional[str] = None, model: Optional[str] = None
|
206
430
|
) -> TokenUsageReport:
|
207
|
-
|
431
|
+
try:
|
432
|
+
if not state.is_tokenator_enabled:
|
433
|
+
return TokenUsageReport()
|
434
|
+
logger.debug(
|
435
|
+
f"Getting cost analysis for last 7 days (provider={provider}, model={model})"
|
436
|
+
)
|
437
|
+
end = datetime.now()
|
438
|
+
start = end - timedelta(weeks=1)
|
439
|
+
return self._query_usage(start, end, provider, model)
|
440
|
+
except Exception as e:
|
441
|
+
logger.error(f"Error in last_week: {e}")
|
208
442
|
return TokenUsageReport()
|
209
|
-
logger.debug(
|
210
|
-
f"Getting cost analysis for last 7 days (provider={provider}, model={model})"
|
211
|
-
)
|
212
|
-
end = datetime.now()
|
213
|
-
start = end - timedelta(weeks=1)
|
214
|
-
return self._query_usage(start, end, provider, model)
|
215
443
|
|
216
444
|
def last_month(
|
217
445
|
self, provider: Optional[str] = None, model: Optional[str] = None
|
218
446
|
) -> TokenUsageReport:
|
219
|
-
|
447
|
+
try:
|
448
|
+
if not state.is_tokenator_enabled:
|
449
|
+
return TokenUsageReport()
|
450
|
+
logger.debug(
|
451
|
+
f"Getting cost analysis for last 30 days (provider={provider}, model={model})"
|
452
|
+
)
|
453
|
+
end = datetime.now()
|
454
|
+
start = end - timedelta(days=30)
|
455
|
+
return self._query_usage(start, end, provider, model)
|
456
|
+
except Exception as e:
|
457
|
+
logger.error(f"Error in last_month: {e}")
|
220
458
|
return TokenUsageReport()
|
221
|
-
logger.debug(
|
222
|
-
f"Getting cost analysis for last 30 days (provider={provider}, model={model})"
|
223
|
-
)
|
224
|
-
end = datetime.now()
|
225
|
-
start = end - timedelta(days=30)
|
226
|
-
return self._query_usage(start, end, provider, model)
|
227
459
|
|
228
460
|
def between(
|
229
461
|
self,
|
@@ -232,76 +464,116 @@ class TokenUsageService:
|
|
232
464
|
provider: Optional[str] = None,
|
233
465
|
model: Optional[str] = None,
|
234
466
|
) -> TokenUsageReport:
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
467
|
+
try:
|
468
|
+
if not state.is_tokenator_enabled:
|
469
|
+
return TokenUsageReport()
|
470
|
+
logger.debug(
|
471
|
+
f"Getting cost analysis between {start_date} and {end_date} (provider={provider}, model={model})"
|
472
|
+
)
|
240
473
|
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
474
|
+
if isinstance(start_date, str):
|
475
|
+
try:
|
476
|
+
start = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S")
|
477
|
+
except ValueError:
|
478
|
+
logger.warning(
|
479
|
+
f"Date-only string provided for start_date: {start_date}. Setting time to 00:00:00"
|
480
|
+
)
|
481
|
+
start = datetime.strptime(start_date, "%Y-%m-%d")
|
482
|
+
else:
|
483
|
+
start = start_date
|
251
484
|
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
485
|
+
if isinstance(end_date, str):
|
486
|
+
try:
|
487
|
+
end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
|
488
|
+
except ValueError:
|
489
|
+
logger.warning(
|
490
|
+
f"Date-only string provided for end_date: {end_date}. Setting time to 23:59:59"
|
491
|
+
)
|
492
|
+
end = (
|
493
|
+
datetime.strptime(end_date, "%Y-%m-%d")
|
494
|
+
+ timedelta(days=1)
|
495
|
+
- timedelta(seconds=1)
|
496
|
+
)
|
497
|
+
else:
|
498
|
+
end = end_date
|
266
499
|
|
267
|
-
|
500
|
+
return self._query_usage(start, end, provider, model)
|
501
|
+
except Exception as e:
|
502
|
+
logger.error(f"Error in between: {e}")
|
503
|
+
return TokenUsageReport()
|
268
504
|
|
269
505
|
def for_execution(self, execution_id: str) -> TokenUsageReport:
|
270
|
-
if not state.is_tokenator_enabled:
|
271
|
-
return TokenUsageReport()
|
272
|
-
logger.debug(f"Getting cost analysis for execution_id={execution_id}")
|
273
|
-
session = get_session()()
|
274
506
|
try:
|
275
|
-
|
276
|
-
|
277
|
-
)
|
278
|
-
|
279
|
-
|
280
|
-
|
507
|
+
if not state.is_tokenator_enabled:
|
508
|
+
return TokenUsageReport()
|
509
|
+
logger.debug(f"Getting cost analysis for execution_id={execution_id}")
|
510
|
+
session = get_session()()
|
511
|
+
try:
|
512
|
+
query = session.query(TokenUsage).filter(
|
513
|
+
TokenUsage.execution_id == execution_id
|
514
|
+
)
|
515
|
+
return self._calculate_cost(query.all())
|
516
|
+
except Exception as e:
|
517
|
+
logger.error(f"Error querying for_execution: {e}")
|
518
|
+
return TokenUsageReport()
|
519
|
+
finally:
|
520
|
+
session.close()
|
521
|
+
except Exception as e:
|
522
|
+
logger.error(f"Unexpected error in for_execution: {e}")
|
523
|
+
return TokenUsageReport()
|
281
524
|
|
282
525
|
def last_execution(self) -> TokenUsageReport:
|
283
|
-
if not state.is_tokenator_enabled:
|
284
|
-
return TokenUsageReport()
|
285
|
-
logger.debug("Getting cost analysis for last execution")
|
286
|
-
session = get_session()()
|
287
526
|
try:
|
288
|
-
|
289
|
-
|
290
|
-
)
|
291
|
-
|
292
|
-
|
527
|
+
if not state.is_tokenator_enabled:
|
528
|
+
return TokenUsageReport()
|
529
|
+
logger.debug("Getting cost analysis for last execution")
|
530
|
+
session = get_session()()
|
531
|
+
try:
|
532
|
+
query = (
|
533
|
+
session.query(TokenUsage).order_by(TokenUsage.created_at.desc()).first()
|
534
|
+
)
|
535
|
+
if query:
|
536
|
+
return self.for_execution(query.execution_id)
|
537
|
+
return TokenUsageReport()
|
538
|
+
except Exception as e:
|
539
|
+
logger.error(f"Error querying last_execution: {e}")
|
540
|
+
return TokenUsageReport()
|
541
|
+
finally:
|
542
|
+
session.close()
|
543
|
+
except Exception as e:
|
544
|
+
logger.error(f"Unexpected error in last_execution: {e}")
|
293
545
|
return TokenUsageReport()
|
294
|
-
finally:
|
295
|
-
session.close()
|
296
546
|
|
297
547
|
def all_time(self) -> TokenUsageReport:
|
298
|
-
|
548
|
+
try:
|
549
|
+
if not state.is_tokenator_enabled:
|
550
|
+
return TokenUsageReport()
|
551
|
+
|
552
|
+
logger.warning("Getting cost analysis for all time. This may take a while...")
|
553
|
+
session = get_session()()
|
554
|
+
try:
|
555
|
+
query = session.query(TokenUsage)
|
556
|
+
return self._calculate_cost(query.all())
|
557
|
+
except Exception as e:
|
558
|
+
logger.error(f"Error querying all_time usage: {e}")
|
559
|
+
return TokenUsageReport()
|
560
|
+
finally:
|
561
|
+
session.close()
|
562
|
+
except Exception as e:
|
563
|
+
logger.error(f"Unexpected error in all_time: {e}")
|
299
564
|
return TokenUsageReport()
|
300
565
|
|
301
|
-
|
566
|
+
def wipe(self):
|
567
|
+
logger.warning("All your usage data is about to be wiped, are you sure you want to do this? You have 5 seconds to cancel this operation.")
|
568
|
+
for i in range(5, 0, -1):
|
569
|
+
logger.warning(str(i))
|
570
|
+
time.sleep(1)
|
302
571
|
session = get_session()()
|
303
572
|
try:
|
304
|
-
|
305
|
-
|
573
|
+
session.query(TokenUsage).delete()
|
574
|
+
session.commit()
|
575
|
+
logger.warning("All usage data has been deleted.")
|
576
|
+
except Exception as e:
|
577
|
+
logger.error(f"Error wiping data: {e}")
|
306
578
|
finally:
|
307
579
|
session.close()
|