Skip to content

Commit f9a4ba3

Browse files
authored
Merge pull request #16795 from BerriAI/litellm_add_model_fix_team_admin
[Fix] Make /litellm_model_cost_map public
2 parents d75f24e + 539e372 commit f9a4ba3

File tree

9 files changed

+58
-51
lines changed

9 files changed

+58
-51
lines changed

litellm/proxy/_types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,7 @@ class LiteLLMRoutes(enum.Enum):
539539
"/public/model_hub",
540540
"/public/agent_hub",
541541
"/public/mcp_hub",
542+
"/public/litellm_model_cost_map",
542543
]
543544
)
544545

@@ -562,7 +563,6 @@ class LiteLLMRoutes(enum.Enum):
562563
"/global/predict/spend/logs",
563564
"/global/activity",
564565
"/health/services",
565-
"/get/litellm_model_cost_map",
566566
] + info_routes
567567

568568
internal_user_routes = (

litellm/proxy/proxy_server.py

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9632,31 +9632,6 @@ async def config_yaml_endpoint(config_info: ConfigYAML):
96329632
return {"hello": "world"}
96339633

96349634

9635-
@router.get(
9636-
"/get/litellm_model_cost_map",
9637-
include_in_schema=False,
9638-
dependencies=[Depends(user_api_key_auth)],
9639-
)
9640-
async def get_litellm_model_cost_map(
9641-
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
9642-
):
9643-
# Check if user is admin
9644-
if user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN:
9645-
raise HTTPException(
9646-
status_code=403,
9647-
detail=f"Access denied. Admin role required. Current role: {user_api_key_dict.user_role}",
9648-
)
9649-
9650-
try:
9651-
_model_cost_map = litellm.model_cost
9652-
return _model_cost_map
9653-
except Exception as e:
9654-
raise HTTPException(
9655-
status_code=500,
9656-
detail=f"Internal Server Error ({str(e)})",
9657-
)
9658-
9659-
96609635
@router.post(
96619636
"/reload/model_cost_map",
96629637
tags=["model management"],

litellm/proxy/public_endpoints/public_endpoints.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,3 +146,24 @@ async def get_provider_fields() -> List[ProviderCreateInfo]:
146146
provider_create_fields = json.load(f)
147147

148148
return provider_create_fields
149+
150+
151+
@router.get(
152+
"/public/litellm_model_cost_map",
153+
tags=["public", "model management"],
154+
)
155+
async def get_litellm_model_cost_map():
156+
"""
157+
Public endpoint to get the LiteLLM model cost map.
158+
Returns pricing information for all supported models.
159+
"""
160+
import litellm
161+
162+
try:
163+
_model_cost_map = litellm.model_cost
164+
return _model_cost_map
165+
except Exception as e:
166+
raise HTTPException(
167+
status_code=500,
168+
detail=f"Internal Server Error ({str(e)})",
169+
)

tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,28 @@ def test_get_provider_create_fields():
5353
)
5454
assert has_detailed_fields, "Expected at least one provider to have detailed credential fields"
5555

56+
57+
def test_get_litellm_model_cost_map_returns_cost_map():
58+
app = FastAPI()
59+
app.include_router(router)
60+
client = TestClient(app)
61+
62+
response = client.get("/public/litellm_model_cost_map")
63+
64+
assert response.status_code == 200
65+
payload = response.json()
66+
assert isinstance(payload, dict)
67+
assert len(payload) > 0, "Expected model cost map to contain at least one model"
68+
69+
# Verify the structure contains expected keys for at least one model
70+
# Check for a common model like gpt-4 or gpt-3.5-turbo
71+
model_keys = list(payload.keys())
72+
assert len(model_keys) > 0
73+
74+
# Verify at least one model has expected cost fields
75+
sample_model = model_keys[0]
76+
sample_model_data = payload[sample_model]
77+
assert isinstance(sample_model_data, dict)
78+
# Check for common cost fields that should be present
79+
assert "input_cost_per_token" in sample_model_data or "output_cost_per_token" in sample_model_data
80+

tests/test_litellm/proxy/test_proxy_server.py

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1391,31 +1391,17 @@ def test_reload_model_cost_map_non_admin_access(self, client_with_auth):
13911391
assert "Access denied" in data["detail"]
13921392
assert "Admin role required" in data["detail"]
13931393

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

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

1405-
def test_get_model_cost_map_non_admin_access(self, client_with_auth):
1406-
"""Test that non-admin users cannot access the get model cost map endpoint"""
1407-
# Mock non-admin user
1408-
mock_auth = MagicMock()
1409-
mock_auth.user_role = "user" # Non-admin role
1410-
app.dependency_overrides[user_api_key_auth] = lambda: mock_auth
1411-
1412-
response = client_with_auth.get("/get/litellm_model_cost_map")
1413-
1414-
assert response.status_code == 403
1415-
data = response.json()
1416-
assert "Access denied" in data["detail"]
1417-
assert "Admin role required" in data["detail"]
1418-
14191405
def test_reload_model_cost_map_error_handling(self, client_with_auth):
14201406
"""Test error handling in the reload endpoint"""
14211407
with patch(
@@ -1625,7 +1611,7 @@ def test_complete_reload_flow(self, client_with_auth):
16251611
assert response.status_code == 200
16261612

16271613
# Test get endpoint
1628-
response = client_with_auth.get("/get/litellm_model_cost_map")
1614+
response = client_with_auth.get("/public/litellm_model_cost_map")
16291615
assert response.status_code == 200
16301616

16311617
def test_distributed_reload_check_function(self):

ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,8 @@ const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({
377377
}
378378

379379
const fetchModelMap = async () => {
380-
const data = await modelCostMap(accessToken);
380+
const data = await modelCostMap();
381+
console.log(`received model cost map data: ${Object.keys(data)}`);
381382
setModelMap(data);
382383
};
383384
if (modelMap == null) {

ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/PriceDataManagementTab.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const PriceDataManagementTab = ({ setModelMap }: PriceDataManagementPanelProps)
2525
onReloadSuccess={() => {
2626
// Refresh the model map after successful reload
2727
const fetchModelMap = async () => {
28-
const data = await modelCostMap(accessToken);
28+
const data = await modelCostMap();
2929
setModelMap(data);
3030
};
3131
fetchModelMap();

ui/litellm-dashboard/src/components/networking.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -310,13 +310,12 @@ export const getOpenAPISchema = async () => {
310310
return jsonData;
311311
};
312312

313-
export const modelCostMap = async (accessToken: string) => {
313+
export const modelCostMap = async () => {
314314
try {
315-
const url = proxyBaseUrl ? `${proxyBaseUrl}/get/litellm_model_cost_map` : `/get/litellm_model_cost_map`;
315+
const url = proxyBaseUrl ? `${proxyBaseUrl}/public/litellm_model_cost_map` : `/public/litellm_model_cost_map`;
316316
const response = await fetch(url, {
317317
method: "GET",
318318
headers: {
319-
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
320319
"Content-Type": "application/json",
321320
},
322321
});

ui/litellm-dashboard/src/components/templates/model_dashboard.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -660,7 +660,7 @@ const OldModelDashboard: React.FC<ModelDashboardProps> = ({
660660
}
661661

662662
const fetchModelMap = async () => {
663-
const data = await modelCostMap(accessToken);
663+
const data = await modelCostMap();
664664
console.log(`received model cost map data: ${Object.keys(data)}`);
665665
setModelMap(data);
666666
};
@@ -1729,7 +1729,7 @@ const OldModelDashboard: React.FC<ModelDashboardProps> = ({
17291729
onReloadSuccess={() => {
17301730
// Refresh the model map after successful reload
17311731
const fetchModelMap = async () => {
1732-
const data = await modelCostMap(accessToken);
1732+
const data = await modelCostMap();
17331733
setModelMap(data);
17341734
};
17351735
fetchModelMap();

0 commit comments

Comments
 (0)