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/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 TokenRate, TokenUsageReport, ModelUsage, ProviderUsage
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
- if not state.is_tokenator_enabled:
19
- logger.info("Tokenator is disabled. Database access is unavailable.")
20
-
21
- self.MODEL_COSTS = self._get_model_costs()
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
- if not state.is_tokenator_enabled:
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
- if not state.is_tokenator_enabled:
43
- logger.warning("Tokenator is disabled. Skipping cost calculation.")
44
- return TokenUsageReport()
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
- if not self.MODEL_COSTS:
47
- logger.warning("No model costs available.")
48
- return TokenUsageReport()
88
+ provider_model_usages: Dict[str, Dict[str, list[TokenUsage]]] = {}
89
+ logger.debug(f"usages: {len(usages)}")
49
90
 
50
- GPT4O_PRICING = self.MODEL_COSTS.get(
51
- "gpt-4o", TokenRate(prompt=0.0000025, completion=0.000010)
52
- )
53
-
54
- # Existing calculation logic...
55
- provider_model_usages: Dict[str, Dict[str, list[TokenUsage]]] = {}
56
- logger.debug(f"usages: {len(usages)}")
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
- # Fallback to GPT4O pricing
76
- logger.warning(
77
- f"Model {model_key} not found in pricing data. Using gpt-4o pricing as fallback "
78
- f"(prompt: ${GPT4O_PRICING.prompt}/token, completion: ${GPT4O_PRICING.completion}/token)"
79
- )
80
- self.MODEL_COSTS[model_key] = GPT4O_PRICING
81
-
82
- provider_key = usage.provider or "default"
83
- provider_model_usages.setdefault(provider_key, {}).setdefault(
84
- model_key, []
85
- ).append(usage)
86
-
87
- # Calculate totals for each level
88
- providers_list = []
89
- total_metrics = {
90
- "total_cost": 0.0,
91
- "total_tokens": 0,
92
- "prompt_tokens": 0,
93
- "completion_tokens": 0,
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 model_key, usages in model_usages.items():
106
- model_cost = sum(
107
- usage.prompt_tokens * self.MODEL_COSTS[model_key].prompt
108
- + usage.completion_tokens * self.MODEL_COSTS[model_key].completion
109
- for usage in usages
110
- )
111
- model_total = sum(usage.total_tokens for usage in usages)
112
- model_prompt = sum(usage.prompt_tokens for usage in usages)
113
- model_completion = sum(usage.completion_tokens for usage in usages)
114
-
115
- models_list.append(
116
- ModelUsage(
117
- model=model_key,
118
- total_cost=round(model_cost, 6),
119
- total_tokens=model_total,
120
- prompt_tokens=model_prompt,
121
- completion_tokens=model_completion,
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
- provider_metrics["total_cost"] += model_cost
126
- provider_metrics["total_tokens"] += model_total
127
- provider_metrics["prompt_tokens"] += model_prompt
128
- provider_metrics["completion_tokens"] += model_completion
129
-
130
- providers_list.append(
131
- ProviderUsage(
132
- provider=provider,
133
- models=models_list,
134
- **{
135
- k: (round(v, 6) if k == "total_cost" else v)
136
- for k, v in provider_metrics.items()
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
- for key in total_metrics:
142
- total_metrics[key] += provider_metrics[key]
347
+ for key in total_metrics:
348
+ total_metrics[key] += provider_metrics[key]
143
349
 
144
- return TokenUsageReport(
145
- providers=providers_list,
146
- **{
147
- k: (round(v, 6) if k == "total_cost" else v)
148
- for k, v in total_metrics.items()
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
- query = session.query(TokenUsage).filter(
166
- TokenUsage.created_at.between(start_date, end_date)
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
- usages = query.all()
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
- return self._calculate_cost(usages, provider or "all")
177
- finally:
178
- session.close()
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
- if not state.is_tokenator_enabled:
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
- if not state.is_tokenator_enabled:
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
- if not state.is_tokenator_enabled:
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
- if not state.is_tokenator_enabled:
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
- if not state.is_tokenator_enabled:
236
- return TokenUsageReport()
237
- logger.debug(
238
- f"Getting cost analysis between {start_date} and {end_date} (provider={provider}, model={model})"
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
- if isinstance(start_date, str):
242
- try:
243
- start = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S")
244
- except ValueError:
245
- logger.warning(
246
- f"Date-only string provided for start_date: {start_date}. Setting time to 00:00:00"
247
- )
248
- start = datetime.strptime(start_date, "%Y-%m-%d")
249
- else:
250
- start = start_date
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
- if isinstance(end_date, str):
253
- try:
254
- end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
255
- except ValueError:
256
- logger.warning(
257
- f"Date-only string provided for end_date: {end_date}. Setting time to 23:59:59"
258
- )
259
- end = (
260
- datetime.strptime(end_date, "%Y-%m-%d")
261
- + timedelta(days=1)
262
- - timedelta(seconds=1)
263
- )
264
- else:
265
- end = end_date
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
- return self._query_usage(start, end, provider, model)
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
- query = session.query(TokenUsage).filter(
276
- TokenUsage.execution_id == execution_id
277
- )
278
- return self._calculate_cost(query.all())
279
- finally:
280
- session.close()
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
- query = (
289
- session.query(TokenUsage).order_by(TokenUsage.created_at.desc()).first()
290
- )
291
- if query:
292
- return self.for_execution(query.execution_id)
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
- if not state.is_tokenator_enabled:
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
- logger.warning("Getting cost analysis for all time. This may take a while...")
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
- query = session.query(TokenUsage)
305
- return self._calculate_cost(query.all())
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()