diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 5fe17d79ab5..e7188cbf093 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -655,6 +655,7 @@ class LiteLLMRoutes(enum.Enum): "/public/mcp_hub", "/public/skill_hub", "/public/litellm_model_cost_map", + "/version", ] ) diff --git a/litellm/proxy/public_endpoints/public_endpoints.py b/litellm/proxy/public_endpoints/public_endpoints.py index 76d211d2c8f..b0e7a51295d 100644 --- a/litellm/proxy/public_endpoints/public_endpoints.py +++ b/litellm/proxy/public_endpoints/public_endpoints.py @@ -29,6 +29,7 @@ from litellm.types.proxy.public_endpoints.public_endpoints import ( ProviderCreateInfo, PublicModelHubInfo, SupportedEndpointsResponse, + VersionResponse, ) from litellm.types.utils import LlmProviders @@ -287,6 +288,13 @@ async def public_skill_hub(): raise HTTPException(status_code=500, detail=str(e)) +@router.get("/version", tags=["public", "version"], response_model=VersionResponse) +async def version_info() -> VersionResponse: + from litellm._version import version + + return VersionResponse(version=version) + + @router.get( "/public/model_hub/info", tags=["public", "model management"], diff --git a/litellm/types/proxy/public_endpoints/public_endpoints.py b/litellm/types/proxy/public_endpoints/public_endpoints.py index caa9a978530..f4c7d4e0383 100644 --- a/litellm/types/proxy/public_endpoints/public_endpoints.py +++ b/litellm/types/proxy/public_endpoints/public_endpoints.py @@ -68,3 +68,7 @@ class SupportedEndpoint(BaseModel): class SupportedEndpointsResponse(BaseModel): endpoints: List[SupportedEndpoint] + + +class VersionResponse(BaseModel): + version: str diff --git a/tests/test_litellm/proxy/auth/test_user_api_key_auth.py b/tests/test_litellm/proxy/auth/test_user_api_key_auth.py index 4141b1f20c3..83b7fa7b42b 100644 --- a/tests/test_litellm/proxy/auth/test_user_api_key_auth.py +++ b/tests/test_litellm/proxy/auth/test_user_api_key_auth.py @@ -86,6 +86,7 @@ def test_public_ai_hub_routes_remain_public(): "/public/agent_hub", "/public/mcp_hub", "/public/skill_hub", + "/version", ): assert route in LiteLLMRoutes.public_routes.value assert _route_requires_auth_despite_public(route, {}) is False 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 d920488c352..e936693a8ee 100644 --- a/tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py +++ b/tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py @@ -91,6 +91,40 @@ def test_get_litellm_model_cost_map_returns_cost_map(): ) +def test_version_endpoint(): + import litellm._version as _ver + + app = FastAPI() + app.include_router(router) + client = TestClient(app) + + response = client.get("/version") + + assert response.status_code == 200 + body = response.json() + assert isinstance(body, dict) + assert "version" in body + assert isinstance(body["version"], str) + assert body["version"] == _ver.version + + +@pytest.mark.parametrize("fake_version", ["unknown", "0.0.0"]) +def test_version_endpoint_fallback_strings_still_return_200(fake_version: str): + import litellm._version as _ver + + app = FastAPI() + app.include_router(router) + client = TestClient(app) + + with patch.object(_ver, "version", fake_version): + response = client.get("/version") + + assert response.status_code == 200 + body = response.json() + assert body["version"] == fake_version + assert isinstance(body["version"], str) + + def test_public_ai_hub_info_is_public_by_default(monkeypatch): app = FastAPI() app.include_router(router)