""" Track guardrail and policy usage for the dashboard: upsert daily metrics and insert into SpendLogGuardrailIndex when spend logs are written. """ import json from collections import defaultdict from datetime import datetime, timezone from typing import Any, Dict, List, Optional from litellm._logging import verbose_proxy_logger from litellm.proxy.utils import PrismaClient def _guardrail_status_to_action(status: Optional[str]) -> str: """Map StandardLogging guardrail_status to blocked/passed/flagged.""" if not status: return "passed" s = (status or "").lower() if "intervened" in s or "block" in s: return "blocked" if "fail" in s or "error" in s: return "flagged" return "passed" def _parse_guardrail_info_from_payload(payload: Dict[str, Any]) -> List[Dict[str, Any]]: """Extract guardrail_information from spend log payload metadata.""" meta = payload.get("metadata") if not meta: return [] if isinstance(meta, str): try: meta = json.loads(meta) except (json.JSONDecodeError, TypeError): return [] if not isinstance(meta, dict): return [] info = meta.get("guardrail_information") or meta.get( "standard_logging_guardrail_information" ) if not isinstance(info, list): return [] return info def _date_str(dt: datetime) -> str: """YYYY-MM-DD in UTC.""" if dt.tzinfo is None: dt = dt.replace(tzinfo=timezone.utc) return dt.astimezone(timezone.utc).strftime("%Y-%m-%d") async def process_spend_logs_guardrail_usage( prisma_client: PrismaClient, logs_to_process: List[Dict[str, Any]], ) -> None: """ After spend logs are written: update DailyGuardrailMetrics and insert SpendLogGuardrailIndex rows from guardrail_information in each payload. """ if not logs_to_process: return # Aggregate daily metrics by (guardrail_id, date). Latency/score metrics dropped. daily_guardrail: Dict[tuple, Dict[str, Any]] = defaultdict( lambda: { "requests_evaluated": 0, "passed_count": 0, "blocked_count": 0, "flagged_count": 0, } ) index_rows: List[Dict[str, Any]] = [] for payload in logs_to_process: request_id = payload.get("request_id") start_time = payload.get("startTime") if not request_id or not start_time: continue if isinstance(start_time, str): try: start_time = datetime.fromisoformat(start_time.replace("Z", "+00:00")) except (ValueError, TypeError): continue date_key = _date_str(start_time) for entry in _parse_guardrail_info_from_payload(payload): guardrail_id = ( entry.get("guardrail_id") or entry.get("guardrail_name") or "" ) if not guardrail_id: continue key = (guardrail_id, date_key) daily_guardrail[key]["requests_evaluated"] += 1 action = _guardrail_status_to_action(entry.get("guardrail_status")) if action == "passed": daily_guardrail[key]["passed_count"] += 1 elif action == "blocked": daily_guardrail[key]["blocked_count"] += 1 else: daily_guardrail[key]["flagged_count"] += 1 policy_id = entry.get("policy_id") index_rows.append( { "request_id": request_id, "guardrail_id": guardrail_id, "policy_id": policy_id, "start_time": start_time, } ) if not daily_guardrail and not index_rows: return try: # Insert index rows (skip duplicates by request_id + guardrail_id) if index_rows: index_data = [] for r in index_rows: st = r["start_time"] if isinstance(st, str): try: st = datetime.fromisoformat(st.replace("Z", "+00:00")) except (ValueError, TypeError): continue index_data.append( { "request_id": r["request_id"], "guardrail_id": r["guardrail_id"], "policy_id": r.get("policy_id"), "start_time": st, } ) try: await prisma_client.db.litellm_spendlogguardrailindex.create_many( data=index_data, skip_duplicates=True, ) except Exception as e: verbose_proxy_logger.debug( "Guardrail usage tracking: index create_many skipped: %s", e ) # Upsert daily guardrail metrics (counts only; latency/score dropped) for (guardrail_id, date_key), agg in daily_guardrail.items(): n = int(agg["requests_evaluated"]) if n == 0: continue await prisma_client.db.litellm_dailyguardrailmetrics.upsert( where={ "guardrail_id_date": { "guardrail_id": guardrail_id, "date": date_key, } }, data={ "create": { "guardrail_id": guardrail_id, "date": date_key, "requests_evaluated": n, "passed_count": int(agg["passed_count"]), "blocked_count": int(agg["blocked_count"]), "flagged_count": int(agg["flagged_count"]), }, "update": { "requests_evaluated": {"increment": n}, "passed_count": {"increment": int(agg["passed_count"])}, "blocked_count": {"increment": int(agg["blocked_count"])}, "flagged_count": {"increment": int(agg["flagged_count"])}, }, }, ) except Exception as e: verbose_proxy_logger.warning( "Guardrail usage tracking failed (non-fatal): %s", e )