Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 2 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3555,9 +3555,9 @@ jobs:
- checkout

- run:
name: Copy model_prices_and_context_window File to model_prices_and_context_window_backup
name: Copy model_prices_and_context_window File to litellm package
command: |
cp model_prices_and_context_window.json litellm/model_prices_and_context_window_backup.json
cp model_prices_and_context_window.json litellm/model_prices_and_context_window.json
Comment thread
Chesars marked this conversation as resolved.

- run:
name: Checkout code
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/simple_pypi_publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:

- name: Copy model prices file
run: |
cp model_prices_and_context_window.json litellm/model_prices_and_context_window_backup.json
cp model_prices_and_context_window.json litellm/model_prices_and_context_window.json

- name: Build package
run: |
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,5 @@ STABILIZATION_TODO.md
**/test-results
**/playwright-report
**/*.storageState.json
**/coverage
**/coverage
litellm/model_prices_and_context_window.json
6 changes: 0 additions & 6 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,3 @@ repos:
hooks:
- id: poetry-check
files: ^(pyproject.toml|litellm-proxy-extras/pyproject.toml)$
- repo: local
hooks:
- id: check-files-match
name: Check if files match
entry: python3 ci_cd/check_files_match.py
language: system
32 changes: 0 additions & 32 deletions ci_cd/check_files_match.py

This file was deleted.

87 changes: 51 additions & 36 deletions litellm/litellm_core_utils/get_model_cost_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

import json
import os
from pathlib import Path

from importlib.resources import files
from typing import Dict, List, Optional

Expand All @@ -26,46 +28,59 @@ class GetModelCostMap:
"""
Handles fetching, validating, and loading the model cost map.

Only the backup model *count* is cached (a single int). The full
backup dict is never held in memory β€” it is only parsed when it
Only the local model *count* is cached (a single int). The full
local dict is never held in memory β€” it is only parsed when it
needs to be *returned* as a fallback.
"""

_backup_model_count: int = -1 # -1 = not yet loaded
_local_model_count: int = -1 # -1 = not yet loaded

@staticmethod
def load_local_model_cost_map() -> dict:
"""Load the local backup model cost map bundled with the package."""
content = json.loads(
files("litellm")
.joinpath("model_prices_and_context_window_backup.json")
.read_text(encoding="utf-8")
)
return content
"""Load the local model cost map.

Tries to load from:
1. Package resources (production, after pip install)
2. Project root (development)
"""
try:
content = json.loads(
files("litellm")
.joinpath("model_prices_and_context_window.json")
.read_text(encoding="utf-8")
)
return content
except (FileNotFoundError, ModuleNotFoundError):
pass

current_dir = Path(__file__).parent.parent.parent
model_cost_map_path = current_dir / "model_prices_and_context_window.json"
with open(model_cost_map_path, "r") as f:
return json.load(f)

@classmethod
def _get_backup_model_count(cls) -> int:
"""Return the number of models in the local backup (cached int)."""
if cls._backup_model_count < 0:
backup = cls.load_local_model_cost_map()
cls._backup_model_count = len(backup)
return cls._backup_model_count
def _get_local_model_count(cls) -> int:
"""Return the number of models in the local model cost map (cached int)."""
if cls._local_model_count < 0:
local = cls.load_local_model_cost_map()
cls._local_model_count = len(local)
return cls._local_model_count

@staticmethod
def _check_is_valid_dict(fetched_map: dict) -> bool:
"""Check 1: fetched map is a non-empty dict."""
if not isinstance(fetched_map, dict):
verbose_logger.warning(
"LiteLLM: Fetched model cost map is not a dict (type=%s). "
"Falling back to local backup.",
"Falling back to local model cost map.",
type(fetched_map).__name__,
)
return False

if len(fetched_map) == 0:
verbose_logger.warning(
"LiteLLM: Fetched model cost map is empty. "
"Falling back to local backup.",
"Falling back to local model cost map.",
)
return False

Expand All @@ -75,34 +90,34 @@ def _check_is_valid_dict(fetched_map: dict) -> bool:
def _check_model_count_not_reduced(
cls,
fetched_map: dict,
backup_model_count: int,
local_model_count: int,
min_model_count: int = MODEL_COST_MAP_MIN_MODEL_COUNT,
max_shrink_ratio: float = MODEL_COST_MAP_MAX_SHRINK_RATIO,
) -> bool:
"""Check 2: model count has not reduced significantly vs backup."""
"""Check 2: model count has not reduced significantly vs local."""
fetched_count = len(fetched_map)

if fetched_count < min_model_count:
verbose_logger.warning(
"LiteLLM: Fetched model cost map has only %d models (minimum=%d). "
"This may indicate a corrupted upstream file. "
"Falling back to local backup.",
"Falling back to local model cost map.",
fetched_count,
min_model_count,
)
return False

if (
backup_model_count > 0
and fetched_count < backup_model_count * max_shrink_ratio
local_model_count > 0
and fetched_count < local_model_count * max_shrink_ratio
):
verbose_logger.warning(
"LiteLLM: Fetched model cost map shrank significantly "
"(fetched=%d, backup=%d, threshold=%.0f%%). "
"(fetched=%d, local=%d, threshold=%.0f%%). "
"This may indicate a corrupted upstream file. "
"Falling back to local backup.",
"Falling back to local model cost map.",
fetched_count,
backup_model_count,
local_model_count,
max_shrink_ratio * 100,
)
return False
Expand All @@ -113,7 +128,7 @@ def _check_model_count_not_reduced(
def validate_model_cost_map(
cls,
fetched_map: dict,
backup_model_count: int,
local_model_count: int,
min_model_count: int = MODEL_COST_MAP_MIN_MODEL_COUNT,
max_shrink_ratio: float = MODEL_COST_MAP_MAX_SHRINK_RATIO,
) -> bool:
Expand All @@ -125,7 +140,7 @@ def validate_model_cost_map(
Checks:
1. ``_check_is_valid_dict`` -- fetched map is a non-empty dict.
2. ``_check_model_count_not_reduced`` -- model count meets minimum
and has not shrunk >``max_shrink_ratio`` vs backup.
and has not shrunk >``max_shrink_ratio`` vs local.

Returns True if all checks pass, False otherwise.
"""
Expand All @@ -134,7 +149,7 @@ def validate_model_cost_map(

if not cls._check_model_count_not_reduced(
fetched_map=fetched_map,
backup_model_count=backup_model_count,
local_model_count=local_model_count,
min_model_count=min_model_count,
max_shrink_ratio=max_shrink_ratio,
):
Expand Down Expand Up @@ -245,12 +260,12 @@ def get_model_cost_map(url: str) -> dict:
"""
Public entry point β€” returns the model cost map dict.

1. If ``LITELLM_LOCAL_MODEL_COST_MAP`` is set, uses the local backup only.
1. If ``LITELLM_LOCAL_MODEL_COST_MAP`` is set, uses the local model cost map only.
2. Otherwise fetches from ``url``, validates integrity, and falls back
to the local backup on any failure.
to the local model cost map on any failure.

Only the backup model count is cached (a single int) for validation.
The full backup dict is only parsed when it must be *returned* as a
Only the local model count is cached (a single int) for validation.
The full local dict is only parsed when it must be *returned* as a
fallback β€” it is never held in memory long-term.
"""
# Note: can't use get_secret_bool here β€” this runs during litellm.__init__
Expand All @@ -270,7 +285,7 @@ def get_model_cost_map(url: str) -> dict:
except Exception as e:
verbose_logger.warning(
"LiteLLM: Failed to fetch remote model cost map from %s: %s. "
"Falling back to local backup.",
"Falling back to local model cost map.",
url,
str(e),
)
Expand All @@ -281,11 +296,11 @@ def get_model_cost_map(url: str) -> dict:
# Validate using cached count (cheap int comparison, no file I/O)
if not GetModelCostMap.validate_model_cost_map(
fetched_map=content,
backup_model_count=GetModelCostMap._get_backup_model_count(),
local_model_count=GetModelCostMap._get_local_model_count(),
):
verbose_logger.warning(
"LiteLLM: Fetched model cost map failed integrity check. "
"Using local backup instead. url=%s",
"Using local model cost map instead. url=%s",
url,
)
_cost_map_source_info.source = "local"
Expand Down
Loading
Loading