mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-05 02:41:56 +00:00
feat(workflow): daily monitor of Databricks Foundation Model Serving pricing
Adds a GitHub Action (daily 02:00 UTC + manual dispatch) that: - Scrapes the official DBU rates for DeepSeek V4 Flash (0731) / V4 Pro (0813) from https://www.databricks.com/product/pricing/foundation-model-serving - Derives per-token USD at $0.07/DBU and updates both model_prices_and_context_window.json and the packaged backup - Opens a PR (base litellm_internal_staging) only when a rate actually changed Runs on the leecoder fork (no repo guard) so the fork stays current and can feed an upstream PR on demand.
This commit is contained in:
parent
1009976c49
commit
8aa1076ad8
2 changed files with 207 additions and 0 deletions
151
.github/workflows/monitor_databricks_pricing.py
vendored
Normal file
151
.github/workflows/monitor_databricks_pricing.py
vendored
Normal file
|
|
@ -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"<tr>(.*?)</tr>", html, flags=re.S):
|
||||
cells = re.findall(r"<t[hd][^>]*>(.*?)</t[hd]>", 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())
|
||||
56
.github/workflows/monitor_databricks_pricing.yml
vendored
Normal file
56
.github/workflows/monitor_databricks_pricing.yml
vendored
Normal file
|
|
@ -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
|
||||
Loading…
Add table
Reference in a new issue