diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 90fe179fcd8..5272252df40 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -516,6 +516,7 @@ class LiteLLMRoutes(enum.Enum): "/.well-known/litellm-ui-config", "/public/model_hub", "/public/agent_hub", + "/public/litellm_model_cost_map", ] ) @@ -538,7 +539,6 @@ class LiteLLMRoutes(enum.Enum): "/global/predict/spend/logs", "/global/activity", "/health/services", - "/get/litellm_model_cost_map", ] + info_routes internal_user_routes = ( diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 1bc96556136..009f49782cb 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -9710,31 +9710,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"], diff --git a/litellm/proxy/public_endpoints/public_endpoints.py b/litellm/proxy/public_endpoints/public_endpoints.py index 159d357c2a6..61e7a57eafc 100644 --- a/litellm/proxy/public_endpoints/public_endpoints.py +++ b/litellm/proxy/public_endpoints/public_endpoints.py @@ -116,3 +116,24 @@ async def get_provider_fields() -> List[ProviderCreateInfo]: """ return get_provider_create_metadata() + + +@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)})", + ) diff --git a/tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py b/tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py index 8456cf55389..bcbec836825 100644 --- a/tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py +++ b/tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py @@ -64,3 +64,28 @@ def test_get_provider_fields_returns_metadata(): } assert {"api_base", "api_key"}.issubset(runway_credential_keys) + +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 + diff --git a/tests/test_litellm/proxy/test_proxy_server.py b/tests/test_litellm/proxy/test_proxy_server.py index 865dc1b19aa..fa1afbb23de 100644 --- a/tests/test_litellm/proxy/test_proxy_server.py +++ b/tests/test_litellm/proxy/test_proxy_server.py @@ -1338,31 +1338,17 @@ class TestPriceDataReloadAPI: 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( @@ -1572,7 +1558,7 @@ class TestPriceDataReloadIntegration: 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): diff --git a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx index 8d65c0e1702..e8456a68693 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx @@ -419,7 +419,7 @@ const ModelsAndEndpointsView: React.FC = ({ } const fetchModelMap = async () => { - const data = await modelCostMap(accessToken); + const data = await modelCostMap(); console.log(`received model cost map data: ${Object.keys(data)}`); setModelMap(data); }; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/PriceDataManagementTab.tsx b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/PriceDataManagementTab.tsx index 6edb6c5b445..4076c19c665 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/PriceDataManagementTab.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/PriceDataManagementTab.tsx @@ -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(); diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index ddb95294e92..bb3aae0e714 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -306,13 +306,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", }, }); @@ -6677,7 +6676,6 @@ export const getGuardrailProviderSpecificParams = async (accessToken: string) => } }; - export const getAgentsList = async (accessToken: string) => { try { const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/agents` : `/v1/agents`; @@ -6795,7 +6793,6 @@ export const patchAgentCall = async ( } }; - export const updateGuardrailCall = async ( accessToken: string, guardrailId: string, diff --git a/ui/litellm-dashboard/src/components/templates/model_dashboard.tsx b/ui/litellm-dashboard/src/components/templates/model_dashboard.tsx index 13bd411cc0a..deaa9b5d682 100644 --- a/ui/litellm-dashboard/src/components/templates/model_dashboard.tsx +++ b/ui/litellm-dashboard/src/components/templates/model_dashboard.tsx @@ -660,7 +660,7 @@ const OldModelDashboard: React.FC = ({ } const fetchModelMap = async () => { - const data = await modelCostMap(accessToken); + const data = await modelCostMap(); console.log(`received model cost map data: ${Object.keys(data)}`); setModelMap(data); }; @@ -1734,7 +1734,7 @@ const OldModelDashboard: React.FC = ({ onReloadSuccess={() => { // Refresh the model map after successful reload const fetchModelMap = async () => { - const data = await modelCostMap(accessToken); + const data = await modelCostMap(); setModelMap(data); }; fetchModelMap();