diff --git a/.github/workflows/monitor_databricks_pricing.py b/.github/workflows/monitor_databricks_pricing.py
new file mode 100644
index 00000000000..de645c3bd05
--- /dev/null
+++ b/.github/workflows/monitor_databricks_pricing.py
@@ -0,0 +1,151 @@
+#!/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[2]
+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"
(.*?)
", html, flags=re.S):
+ cells = re.findall(r"]*>(.*?)", tr, flags=re.S)
+ if not cells:
+ continue
+ label = re.sub(r"<[^>]+>", "", cells[0]).strip()
+ nums = []
+ for c in cells[1:]:
+ txt = re.sub(r"<[^>]+>", "", c).strip()
+ if re.fullmatch(r"\d+(?:\.\d+)?", txt):
+ nums.append(float(txt))
+ if label and len(nums) >= 2:
+ rows[label] = (nums[0], nums[1])
+
+ results: dict[str, tuple[float, float]] = {}
+ for label in MONITORED.values():
+ if label not in rows:
+ raise RuntimeError(f"Could not locate pricing row for '{label}' on {FMS_PAGE}")
+ results[label] = rows[label]
+ return results
+
+
+def build_entry(model_key: str, input_dbu: float, output_dbu: float) -> dict:
+ fx = MODEL_FIXTURE[model_key]
+ return {
+ "input_cost_per_token": input_dbu / 1_000_000 * DBU_TO_USD,
+ "input_dbu_cost_per_token": input_dbu,
+ "litellm_provider": "databricks",
+ "max_input_tokens": fx["max_input_tokens"],
+ "max_output_tokens": fx["max_output_tokens"],
+ "max_tokens": fx["max_output_tokens"],
+ "metadata": {
+ "notes": (
+ f"Pricing derived from Databricks Foundation Model Serving DBU rates "
+ f"({input_dbu:g} in / {output_dbu:g} out DBU per 1M tokens × ${DBU_TO_USD:.2f}/DBU "
+ f"= ${input_dbu*DBU_TO_USD:.2f}/${output_dbu*DBU_TO_USD:.2f} per 1M). "
+ f"Auto-refreshed daily by monitor_databricks_pricing workflow."
+ )
+ },
+ "mode": "chat",
+ "output_cost_per_token": output_dbu / 1_000_000 * DBU_TO_USD,
+ "output_dbu_cost_per_token": output_dbu,
+ "source": FMS_PAGE,
+ "supports_function_calling": True,
+ "supports_reasoning": True,
+ "supports_tool_choice": True,
+ }
+
+
+def main() -> int:
+ html = fetch(FMS_PAGE)
+ rates = parse_dbu_table(html)
+
+ with MAIN_MAP.open() as f:
+ main_data = json.load(f)
+ with BACKUP_MAP.open() as f:
+ backup_data = json.load(f)
+
+ changed = False
+ for model_key, label in MONITORED.items():
+ in_dbu, out_dbu = rates[label]
+ entry = build_entry(model_key, in_dbu, out_dbu)
+ old = main_data.get(model_key)
+ if old != entry:
+ main_data[model_key] = entry
+ backup_data[model_key] = entry
+ changed = True
+ print(f"CHANGED {model_key}: {old and old.get('input_cost_per_token')} -> {entry['input_cost_per_token']}")
+
+ if not changed:
+ print("NO_CHANGE")
+ return 0
+
+ with MAIN_MAP.open("w") as f:
+ json.dump(main_data, f, indent=4)
+ f.write("\n")
+ with BACKUP_MAP.open("w") as f:
+ json.dump(backup_data, f, indent=4)
+ f.write("\n")
+ print("WROTE updated model map and backup")
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
\ No newline at end of file
diff --git a/.github/workflows/monitor_databricks_pricing.yml b/.github/workflows/monitor_databricks_pricing.yml
new file mode 100644
index 00000000000..ea2eaeefb2d
--- /dev/null
+++ b/.github/workflows/monitor_databricks_pricing.yml
@@ -0,0 +1,56 @@
+name: Monitor Databricks Pricing (deepseek/glm/kimi)
+
+on:
+ schedule:
+ - cron: "0 2 * * *" # daily 02:00 UTC
+ workflow_dispatch:
+
+permissions:
+ contents: write
+ pull-requests: write
+
+jobs:
+ monitor-db-pricing:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+ fetch-depth: 0
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: "3.12"
+
+ - name: Run monitor
+ id: monitor
+ run: |
+ python .github/workflows/monitor_databricks_pricing.py | tee /tmp/monitor_out.txt
+ if grep -q '^NO_CHANGE' /tmp/monitor_out.txt; then
+ echo "changed=false" >> "$GITHUB_OUTPUT"
+ else
+ echo "changed=true" >> "$GITHUB_OUTPUT"
+ fi
+
+ - name: Open PR if changed
+ if: steps.monitor.outputs.changed == 'true'
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ BRANCH="monitor-dbx-pricing-$(date +'%Y-%m-%d')"
+ git config user.name "github-actions[bot]"
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
+ git checkout -b "$BRANCH"
+ git add model_prices_and_context_window.json litellm/model_prices_and_context_window_backup.json
+ git commit -m "chore(model_prices): refresh Databricks Foundation Model Serving rates
+
+Automated daily monitor detected changed DBU rates on
+https://www.databricks.com/product/pricing/foundation-model-serving
+Updated entries: databricks/databricks-deepseek-v4-*"
+ git push origin "$BRANCH"
+ gh pr create --repo "${{ github.repository }}" \
+ --base litellm_internal_staging \
+ --head "$BRANCH" \
+ --title "chore(model_prices): refresh Databricks Foundation Model Serving rates" \
+ --body "Automated daily check of the Databricks Foundation Model Serving pricing page detected rate changes. Updated model_prices_and_context_window.json and packaged backup for monitored models." || true
\ No newline at end of file