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