#!/usr/bin/env python3 """Monitor Databricks Foundation Model Serving pricing pages and update LiteLLM's model_prices_and_context_window.json + packaged backup when rates change. Triggered daily by .github/workflows/monitor_databricks_pricing.yml. Behavior: - Fetches the two official Databricks pricing pages (HTML, JS-rendered price table). Parses the embedded price data rows via regex extraction of the DBU table (works with the current page markup; fails loudly otherwise). - Applies the LiteLLM convention: USD = DBU * 0.07 per token. - Updates entries for the monitored model set (see MONITORED below). - If any monitored rate changed, writes BOTH files, prints a diff summary and exits 0 (so the workflow can create the PR). If nothing changed, exits 0 with "NO_CHANGE" marker so the workflow skips PR creation. """ import json import re import sys import urllib.request from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[1] MAIN_MAP = REPO_ROOT / "model_prices_and_context_window.json" BACKUP_MAP = REPO_ROOT / "litellm" / "model_prices_and_context_window_backup.json" DBU_TO_USD = 0.07 # Databricks Foundation Model Serving page (open models, incl. DeepSeek V4) FMS_PAGE = "https://www.databricks.com/product/pricing/foundation-model-serving" # Proprietary page (GPT/Claude/Gemini) — fetched but not used for the monitored # monitorset; kept for future expansion. PROPRIETARY_PAGE = "https://www.databricks.com/product/pricing/proprietary-foundation-model-serving" # model_map key -> (name pattern in the DBU table row, ) # name pattern is the model label as it appears on the pricing page table. MONITORED = { "databricks/databricks-deepseek-v4-flash-0731": "Deepseek V4 Flash (0731)", "databricks/databricks-deepseek-v4-pro-0813": "Deepseek V4 Pro (0813)", } # Context windows / output caps from Databricks Foundation Model APIs limits doc # (kept in sync with what we know; only rates are refreshed by this script). MODEL_FIXTURE = { "databricks/databricks-deepseek-v4-flash-0731": { "max_input_tokens": 200000, "max_output_tokens": 10000, }, "databricks/databricks-deepseek-v4-pro-0813": { "max_input_tokens": 200000, "max_output_tokens": 4000, }, } def fetch(url: str, max_bytes: int = 5_000_000) -> str: """Fetch page HTML; returns text. Raises on non-200.""" req = urllib.request.Request(url, headers={"User-Agent": "litellm-price-monitor/1.0"}) with urllib.request.urlopen(req, timeout=60) as resp: if resp.status != 200: raise RuntimeError(f"HTTP {resp.status} fetching {url}") return resp.read(max_bytes + 1).decode("utf-8", errors="replace") def parse_dbu_table(html: str) -> dict[str, tuple[float, float]]: rows: dict[str, tuple[float, float]] = {} for tr in re.findall(r"