Skip to content
2 changes: 1 addition & 1 deletion litellm/proxy/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,7 @@ class LiteLLMRoutes(enum.Enum):
"/public/model_hub",
"/public/agent_hub",
"/public/mcp_hub",
"/public/litellm_model_cost_map",
]
)

Expand All @@ -562,7 +563,6 @@ class LiteLLMRoutes(enum.Enum):
"/global/predict/spend/logs",
"/global/activity",
"/health/services",
"/get/litellm_model_cost_map",
] + info_routes

internal_user_routes = (
Expand Down
25 changes: 0 additions & 25 deletions litellm/proxy/proxy_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -9632,31 +9632,6 @@ async def config_yaml_endpoint(config_info: ConfigYAML):
return {"hello": "world"}


@router.get(
"/get/litellm_model_cost_map",
include_in_schema=False,
dependencies=[Depends(user_api_key_auth)],
)
async def get_litellm_model_cost_map(
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
):
# Check if user is admin
if user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN:
raise HTTPException(
status_code=403,
detail=f"Access denied. Admin role required. Current role: {user_api_key_dict.user_role}",
)

try:
_model_cost_map = litellm.model_cost
return _model_cost_map
except Exception as e:
raise HTTPException(
status_code=500,
detail=f"Internal Server Error ({str(e)})",
)


@router.post(
"/reload/model_cost_map",
tags=["model management"],
Expand Down
21 changes: 21 additions & 0 deletions litellm/proxy/public_endpoints/public_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,24 @@ async def get_provider_fields() -> List[ProviderCreateInfo]:
provider_create_fields = json.load(f)

return provider_create_fields


@router.get(
"/public/litellm_model_cost_map",
tags=["public", "model management"],
)
async def get_litellm_model_cost_map():
"""
Public endpoint to get the LiteLLM model cost map.
Returns pricing information for all supported models.
"""
import litellm

try:
_model_cost_map = litellm.model_cost
return _model_cost_map
except Exception as e:
raise HTTPException(
status_code=500,
detail=f"Internal Server Error ({str(e)})",
)
25 changes: 25 additions & 0 deletions tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,28 @@ def test_get_provider_create_fields():
)
assert has_detailed_fields, "Expected at least one provider to have detailed credential fields"


def test_get_litellm_model_cost_map_returns_cost_map():
app = FastAPI()
app.include_router(router)
client = TestClient(app)

response = client.get("/public/litellm_model_cost_map")

assert response.status_code == 200
payload = response.json()
assert isinstance(payload, dict)
assert len(payload) > 0, "Expected model cost map to contain at least one model"

# Verify the structure contains expected keys for at least one model
# Check for a common model like gpt-4 or gpt-3.5-turbo
model_keys = list(payload.keys())
assert len(model_keys) > 0

# Verify at least one model has expected cost fields
sample_model = model_keys[0]
sample_model_data = payload[sample_model]
assert isinstance(sample_model_data, dict)
# Check for common cost fields that should be present
assert "input_cost_per_token" in sample_model_data or "output_cost_per_token" in sample_model_data

22 changes: 4 additions & 18 deletions tests/test_litellm/proxy/test_proxy_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -1391,31 +1391,17 @@ def test_reload_model_cost_map_non_admin_access(self, client_with_auth):
assert "Access denied" in data["detail"]
assert "Admin role required" in data["detail"]

def test_get_model_cost_map_admin_access(self, client_with_auth):
"""Test that admin users can access the get model cost map endpoint"""
def test_get_model_cost_map_public_access(self, client_no_auth):
"""Test that the model cost map endpoint is publicly accessible"""
with patch(
"litellm.model_cost", {"gpt-3.5-turbo": {"input_cost_per_token": 0.001}}
):
response = client_with_auth.get("/get/litellm_model_cost_map")
response = client_no_auth.get("/public/litellm_model_cost_map")

assert response.status_code == 200
data = response.json()
assert "gpt-3.5-turbo" in data

def test_get_model_cost_map_non_admin_access(self, client_with_auth):
"""Test that non-admin users cannot access the get model cost map endpoint"""
# Mock non-admin user
mock_auth = MagicMock()
mock_auth.user_role = "user" # Non-admin role
app.dependency_overrides[user_api_key_auth] = lambda: mock_auth

response = client_with_auth.get("/get/litellm_model_cost_map")

assert response.status_code == 403
data = response.json()
assert "Access denied" in data["detail"]
assert "Admin role required" in data["detail"]

def test_reload_model_cost_map_error_handling(self, client_with_auth):
"""Test error handling in the reload endpoint"""
with patch(
Expand Down Expand Up @@ -1625,7 +1611,7 @@ def test_complete_reload_flow(self, client_with_auth):
assert response.status_code == 200

# Test get endpoint
response = client_with_auth.get("/get/litellm_model_cost_map")
response = client_with_auth.get("/public/litellm_model_cost_map")
assert response.status_code == 200

def test_distributed_reload_check_function(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,8 @@ const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({
}

const fetchModelMap = async () => {
const data = await modelCostMap(accessToken);
const data = await modelCostMap();
console.log(`received model cost map data: ${Object.keys(data)}`);
setModelMap(data);
};
if (modelMap == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const PriceDataManagementTab = ({ setModelMap }: PriceDataManagementPanelProps)
onReloadSuccess={() => {
// Refresh the model map after successful reload
const fetchModelMap = async () => {
const data = await modelCostMap(accessToken);
const data = await modelCostMap();
setModelMap(data);
};
fetchModelMap();
Expand Down
5 changes: 2 additions & 3 deletions ui/litellm-dashboard/src/components/networking.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -310,13 +310,12 @@ export const getOpenAPISchema = async () => {
return jsonData;
};

export const modelCostMap = async (accessToken: string) => {
export const modelCostMap = async () => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/get/litellm_model_cost_map` : `/get/litellm_model_cost_map`;
const url = proxyBaseUrl ? `${proxyBaseUrl}/public/litellm_model_cost_map` : `/public/litellm_model_cost_map`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -660,7 +660,7 @@ const OldModelDashboard: React.FC<ModelDashboardProps> = ({
}

const fetchModelMap = async () => {
const data = await modelCostMap(accessToken);
const data = await modelCostMap();
console.log(`received model cost map data: ${Object.keys(data)}`);
setModelMap(data);
};
Expand Down Expand Up @@ -1729,7 +1729,7 @@ const OldModelDashboard: React.FC<ModelDashboardProps> = ({
onReloadSuccess={() => {
// Refresh the model map after successful reload
const fetchModelMap = async () => {
const data = await modelCostMap(accessToken);
const data = await modelCostMap();
setModelMap(data);
};
fetchModelMap();
Expand Down
Loading