@@ -148,23 +148,16 @@ async def async_send_batch(self):
148
148
),
149
149
),
150
150
}
151
- # serialize datetime objects - for budget reset time in spend metrics
152
- import json
153
- from datetime import datetime , date
154
151
155
- def custom_json_encoder (obj ):
156
- if isinstance (obj , (datetime , date )):
157
- return obj .isoformat ()
158
- raise TypeError (f"Object of type { type (obj )} is not JSON serializable" )
152
+ # serialize datetime objects - for budget reset time in spend metrics
153
+ from litellm .litellm_core_utils .safe_json_dumps import safe_dumps
159
154
160
- # Serialize payload with custom encoder for debugging
161
155
try :
162
- verbose_logger .debug ("payload %s" , json . dumps (payload , indent = 4 , default = custom_json_encoder ))
156
+ verbose_logger .debug ("payload %s" , safe_dumps (payload ))
163
157
except Exception as debug_error :
164
158
verbose_logger .debug ("payload serialization failed: %s" , str (debug_error ))
165
159
166
- # Convert payload to JSON string with custom encoder for HTTP request
167
- json_payload = json .dumps (payload , default = custom_json_encoder )
160
+ json_payload = safe_dumps (payload )
168
161
169
162
response = await self .async_client .post (
170
163
url = self .intake_url ,
@@ -338,7 +331,6 @@ def _get_response_messages(
338
331
if isinstance (response_obj , str ):
339
332
try :
340
333
import ast
341
-
342
334
response_obj = ast .literal_eval (response_obj )
343
335
except (ValueError , SyntaxError ):
344
336
try :
@@ -573,48 +565,55 @@ def _get_spend_metrics(
573
565
Get the spend metrics from the standard logging payload
574
566
"""
575
567
spend_metrics : DDLLMObsSpendMetrics = DDLLMObsSpendMetrics ()
576
-
577
- # Get response cost for litellm_spend_metric
578
- response_cost = standard_logging_payload .get ("response_cost" , 0.0 )
579
- if response_cost > 0 :
580
- spend_metrics ["litellm_spend_metric" ] = response_cost
568
+
569
+ # send response cost
570
+ spend_metrics ["response_cost" ] = standard_logging_payload .get ("response_cost" , 0.0 )
581
571
582
572
# Get budget information from metadata
583
573
metadata = standard_logging_payload .get ("metadata" , {})
584
574
585
575
# API key max budget
586
576
user_api_key_max_budget = metadata .get ("user_api_key_max_budget" )
587
577
if user_api_key_max_budget is not None :
588
- # type casting to make sure its a float value
578
+ spend_metrics ["user_api_key_max_budget" ] = float (user_api_key_max_budget )
579
+
580
+ # API key spend
581
+ user_api_key_spend = metadata .get ("user_api_key_spend" )
582
+ if user_api_key_spend is not None :
589
583
try :
590
- if isinstance (user_api_key_max_budget , (int , float )):
591
- spend_metrics ["litellm_api_key_max_budget_metric" ] = float (user_api_key_max_budget )
592
- elif isinstance (user_api_key_max_budget , str ):
593
- spend_metrics ["litellm_api_key_max_budget_metric" ] = float (user_api_key_max_budget )
584
+ spend_metrics ["user_api_key_spend" ] = float (user_api_key_spend )
594
585
except (ValueError , TypeError ):
595
- verbose_logger .debug (f"Invalid user_api_key_max_budget value: { user_api_key_max_budget } " )
586
+ verbose_logger .debug (f"Invalid user_api_key_spend value: { user_api_key_spend } " )
596
587
597
- # API key budget remaining hours
588
+ # API key budget reset datetime
598
589
user_api_key_budget_reset_at = metadata .get ("user_api_key_budget_reset_at" )
599
590
if user_api_key_budget_reset_at is not None :
600
591
try :
601
- from datetime import datetime
602
- budget_reset_at : datetime
592
+ from datetime import datetime , timezone
593
+
594
+ budget_reset_at = None
603
595
if isinstance (user_api_key_budget_reset_at , str ):
604
- # Parse ISO string if it's a string
605
- budget_reset_at = datetime .fromisoformat (user_api_key_budget_reset_at .replace ('Z' , '+00:00' ))
596
+ # Handle ISO format strings that might have 'Z' suffix
597
+ iso_string = user_api_key_budget_reset_at .replace ('Z' , '+00:00' )
598
+ budget_reset_at = datetime .fromisoformat (iso_string )
606
599
elif isinstance (user_api_key_budget_reset_at , datetime ):
607
600
budget_reset_at = user_api_key_budget_reset_at
608
- else :
609
- verbose_logger .debug (f"Invalid user_api_key_budget_reset_at type: { type (user_api_key_budget_reset_at )} " )
610
- return spend_metrics
611
-
612
- remaining_hours = (
613
- budget_reset_at - datetime .now (budget_reset_at .tzinfo )
614
- ).total_seconds () / 3600
615
- spend_metrics ["litellm_api_key_budget_remaining_hours_metric" ] = max (0 , remaining_hours )
601
+
602
+ if budget_reset_at is not None :
603
+ # Preserve timezone info if already present
604
+ if budget_reset_at .tzinfo is None :
605
+ budget_reset_at = budget_reset_at .replace (tzinfo = timezone .utc )
606
+
607
+ # Convert to ISO string format for JSON serialization
608
+ # This prevents circular reference issues and ensures proper timezone representation
609
+ iso_string = budget_reset_at .isoformat ()
610
+ spend_metrics ["user_api_key_budget_reset_at" ] = iso_string
611
+
612
+ # Debug logging to verify the conversion
613
+ verbose_logger .debug (f"Converted budget_reset_at to ISO format: { iso_string } " )
616
614
except Exception as e :
617
- verbose_logger .debug (f"Error calculating remaining hours for budget reset: { e } " )
615
+ verbose_logger .debug (f"Error processing budget reset datetime: { e } " )
616
+ verbose_logger .debug (f"Original value: { user_api_key_budget_reset_at } " )
618
617
619
618
return spend_metrics
620
619
0 commit comments