Skip to content

Commit fcf8402

Browse files
Merge pull request #14555 from mubashir1osmani/dd_spend_metric
DataDog shows spend metrics
2 parents 635dc72 + 439577f commit fcf8402

File tree

7 files changed

+495
-188
lines changed

7 files changed

+495
-188
lines changed

litellm/integrations/datadog/datadog_llm_obs.py

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,10 +148,22 @@ async def async_send_batch(self):
148148
),
149149
),
150150
}
151-
verbose_logger.debug("payload %s", json.dumps(payload, indent=4))
151+
152+
# serialize datetime objects - for budget reset time in spend metrics
153+
from litellm.litellm_core_utils.safe_json_dumps import safe_dumps
154+
155+
try:
156+
verbose_logger.debug("payload %s", safe_dumps(payload))
157+
except Exception as debug_error:
158+
verbose_logger.debug(
159+
"payload serialization failed: %s", str(debug_error)
160+
)
161+
162+
json_payload = safe_dumps(payload)
163+
152164
response = await self.async_client.post(
153165
url=self.intake_url,
154-
json=payload,
166+
content=json_payload,
155167
headers={
156168
"DD-API-KEY": self.DD_API_KEY,
157169
"Content-Type": "application/json",
@@ -494,6 +506,12 @@ def _get_dd_llm_obs_payload_metadata(
494506
latency_metrics = self._get_latency_metrics(standard_logging_payload)
495507
_metadata.update({"latency_metrics": dict(latency_metrics)})
496508

509+
#########################################################
510+
# Add spend metrics to metadata
511+
#########################################################
512+
spend_metrics = self._get_spend_metrics(standard_logging_payload)
513+
_metadata.update({"spend_metrics": dict(spend_metrics)})
514+
497515
## extract tool calls and add to metadata
498516
tool_call_metadata = self._extract_tool_call_metadata(standard_logging_payload)
499517
_metadata.update(tool_call_metadata)
@@ -543,6 +561,71 @@ def _get_latency_metrics(
543561

544562
return latency_metrics
545563

564+
def _get_spend_metrics(
565+
self, standard_logging_payload: StandardLoggingPayload
566+
) -> DDLLMObsSpendMetrics:
567+
"""
568+
Get the spend metrics from the standard logging payload
569+
"""
570+
spend_metrics: DDLLMObsSpendMetrics = DDLLMObsSpendMetrics()
571+
572+
# send response cost
573+
spend_metrics["response_cost"] = standard_logging_payload.get(
574+
"response_cost", 0.0
575+
)
576+
577+
# Get budget information from metadata
578+
metadata = standard_logging_payload.get("metadata", {})
579+
580+
# API key max budget
581+
user_api_key_max_budget = metadata.get("user_api_key_max_budget")
582+
if user_api_key_max_budget is not None:
583+
spend_metrics["user_api_key_max_budget"] = float(user_api_key_max_budget)
584+
585+
# API key spend
586+
user_api_key_spend = metadata.get("user_api_key_spend")
587+
if user_api_key_spend is not None:
588+
try:
589+
spend_metrics["user_api_key_spend"] = float(user_api_key_spend)
590+
except (ValueError, TypeError):
591+
verbose_logger.debug(
592+
f"Invalid user_api_key_spend value: {user_api_key_spend}"
593+
)
594+
595+
# API key budget reset datetime
596+
user_api_key_budget_reset_at = metadata.get("user_api_key_budget_reset_at")
597+
if user_api_key_budget_reset_at is not None:
598+
try:
599+
from datetime import datetime, timezone
600+
601+
budget_reset_at = None
602+
if isinstance(user_api_key_budget_reset_at, str):
603+
# Handle ISO format strings that might have 'Z' suffix
604+
iso_string = user_api_key_budget_reset_at.replace("Z", "+00:00")
605+
budget_reset_at = datetime.fromisoformat(iso_string)
606+
elif isinstance(user_api_key_budget_reset_at, datetime):
607+
budget_reset_at = user_api_key_budget_reset_at
608+
609+
if budget_reset_at is not None:
610+
# Preserve timezone info if already present
611+
if budget_reset_at.tzinfo is None:
612+
budget_reset_at = budget_reset_at.replace(tzinfo=timezone.utc)
613+
614+
# Convert to ISO string format for JSON serialization
615+
# This prevents circular reference issues and ensures proper timezone representation
616+
iso_string = budget_reset_at.isoformat()
617+
spend_metrics["user_api_key_budget_reset_at"] = iso_string
618+
619+
# Debug logging to verify the conversion
620+
verbose_logger.debug(
621+
f"Converted budget_reset_at to ISO format: {iso_string}"
622+
)
623+
except Exception as e:
624+
verbose_logger.debug(f"Error processing budget reset datetime: {e}")
625+
verbose_logger.debug(f"Original value: {user_api_key_budget_reset_at}")
626+
627+
return spend_metrics
628+
546629
def _process_input_messages_preserving_tool_calls(
547630
self, messages: List[Any]
548631
) -> List[Dict[str, Any]]:

0 commit comments

Comments
 (0)