From 0fd8fcb7424ca929f090e927d03b07aefe8e8677 Mon Sep 17 00:00:00 2001 From: Kagamine Rin Date: Sat, 13 Jun 2026 16:42:39 -0400 Subject: [PATCH] feat: natural language date shortcuts for litellm_cost() Adds _resolve_date() helper that converts natural language shortcuts into YYYY-MM-DD before calling the LiteLLM API. Supported shortcuts: - today, yesterday, tomorrow - N days ago (e.g. "two days ago") - monday-sunday (most recent occurrence) - last monday-last sunday - this week, last week - this month, last month Falls back to passthrough for already-formatted YYYY-MM-DD strings. Updated README with new parameter documentation. --- README.md | 4 +-- litellm_dashboard.py | 80 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 79 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 13db541..4b5d3b8 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,8 @@ Check LiteLLM proxy health and authentication status. Returns which models are h Get LiteLLM cost breakdown for a date range. Returns per-model and per-day spend, token counts, cache hit rate, and request stats. **Parameters:** -- `start_date` (str, required): Start date in YYYY-MM-DD format -- `end_date` (str, required): End date in YYYY-MM-DD format +- `start_date` (str, required): Start date — YYYY-MM-DD or natural language shortcut (e.g. "2026-06-01", "today", "yesterday", "monday", "last monday", "two days ago", "this week", "last month") +- `end_date` (str, required): End date — YYYY-MM-DD or natural language shortcut - `hide_zero_spend` (bool, default false): Hide models with sub-$0.001 spend. Useful when free-tier or subscription models (e.g. Ollama Cloud) dominate the breakdown with high token counts but negligible cost. ### `litellm_history` diff --git a/litellm_dashboard.py b/litellm_dashboard.py index 5f832b3..c437c0b 100644 --- a/litellm_dashboard.py +++ b/litellm_dashboard.py @@ -10,6 +10,7 @@ licence: MIT """ import requests +from datetime import datetime, timedelta from pydantic import BaseModel, Field from typing import Optional @@ -40,6 +41,76 @@ class Tools: """Get base URL, strip trailing slash.""" return self.valves.litellm_url.rstrip("/") + def _resolve_date(self, date_str: str) -> str: + """Resolve natural language date shortcuts to YYYY-MM-DD. + + Supports: today, yesterday, tomorrow, N days ago, + monday-sunday (this week), last monday-last sunday, + this week, last week, this month, last month. + Falls back to passthrough if already YYYY-MM-DD or unrecognized. + """ + date_str = date_str.strip().lower() + today = datetime.utcnow().date() + + # Direct shortcuts + if date_str == "today": + return today.isoformat() + if date_str == "yesterday": + return (today - timedelta(days=1)).isoformat() + if date_str == "tomorrow": + return (today + timedelta(days=1)).isoformat() + + # "N days ago" + if "days ago" in date_str: + try: + n = int(date_str.split()[0]) + return (today - timedelta(days=n)).isoformat() + except (ValueError, IndexError): + pass + + # Day names — most recent occurrence (including today if it matches) + days = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"] + if date_str in days: + target_day = days.index(date_str) + days_back = (today.weekday() - target_day) % 7 + return (today - timedelta(days=days_back)).isoformat() + + # "last monday", "last tuesday", etc. + if date_str.startswith("last "): + day_part = date_str[5:].strip() + if day_part in days: + target_day = days.index(day_part) + days_back = (today.weekday() - target_day) % 7 + 7 + return (today - timedelta(days=days_back)).isoformat() + + # "this week" → Monday of this week + if date_str == "this week": + return (today - timedelta(days=today.weekday())).isoformat() + + # "last week" → Monday of last week + if date_str == "last week": + return (today - timedelta(days=today.weekday() + 7)).isoformat() + + # "this month" → 1st of this month + if date_str == "this month": + return today.replace(day=1).isoformat() + + # "last month" → 1st of last month + if date_str == "last month": + if today.month == 1: + return today.replace(year=today.year - 1, month=12, day=1).isoformat() + return today.replace(month=today.month - 1, day=1).isoformat() + + # Already YYYY-MM-DD — passthrough + try: + datetime.strptime(date_str, "%Y-%m-%d") + return date_str + except ValueError: + pass + + # Unknown — return as-is (API will reject with clear error) + return date_str + def litellm_health(self) -> str: """ Check LiteLLM proxy health and authentication status. @@ -123,17 +194,20 @@ class Tools: ) -> str: """ Get LiteLLM cost breakdown for a date range. - :param start_date: Start date in YYYY-MM-DD format (e.g. "2026-06-01") - :param end_date: End date in YYYY-MM-DD format (e.g. "2026-06-04") + :param start_date: Start date — YYYY-MM-DD or natural language shortcut (e.g. "2026-06-01", "today", "yesterday", "monday", "last monday", "two days ago", "this week", "last month") + :param end_date: End date — YYYY-MM-DD or natural language shortcut :param hide_zero_spend: Hide models with sub-$0.001 spend (default False). Useful when free-tier or subscription models (e.g. Ollama Cloud) dominate the breakdown with high token counts but negligible cost. Returns formatted cost report with per-model and per-day breakdowns, token counts, cache hit rate, and request stats. """ if not self.valves.litellm_master_key: return "❌ Master key required for cost data. Set the litellm_master_key valve." + resolved_start = self._resolve_date(start_date) + resolved_end = self._resolve_date(end_date) + url = f"{self._base_url()}/user/daily/activity" headers = self._headers() - params = {"start_date": start_date, "end_date": end_date} + params = {"start_date": resolved_start, "end_date": resolved_end} try: resp = requests.get(url, headers=headers, params=params, timeout=30)