From 3d87da555ab0d13c48636dc9e2d9999adfe23679 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Fri, 9 May 2025 18:58:37 -0700 Subject: [PATCH] Add `--version` flag to `litellm-proxy` CLI (#10704) * Add --version flag to litellm-proxy CLI ```shell $ litellm-proxy --version litellm-proxy version: 1.68.1 ``` * Return both client and server version * Update docs * Add a test for the version command * Add litellm/proxy/client/health.py --- litellm/proxy/client/__init__.py | 3 +- litellm/proxy/client/cli/README.md | 14 +++++++ litellm/proxy/client/cli/main.py | 36 +++++++++++++++++ litellm/proxy/client/health.py | 40 +++++++++++++++++++ .../proxy/client/cli/test_global_options.py | 34 ++++++++++++++++ 5 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 litellm/proxy/client/health.py create mode 100644 tests/litellm/proxy/client/cli/test_global_options.py diff --git a/litellm/proxy/client/__init__.py b/litellm/proxy/client/__init__.py index 2d71458ed62..89574bfd241 100644 --- a/litellm/proxy/client/__init__.py +++ b/litellm/proxy/client/__init__.py @@ -4,5 +4,6 @@ from .models import ModelsManagementClient from .model_groups import ModelGroupsManagementClient from .exceptions import UnauthorizedError from .users import UsersManagementClient +from .health import HealthManagementClient -__all__ = ["Client", "ChatClient", "ModelsManagementClient", "ModelGroupsManagementClient", "UsersManagementClient", "UnauthorizedError"] +__all__ = ["Client", "ChatClient", "ModelsManagementClient", "ModelGroupsManagementClient", "UsersManagementClient", "UnauthorizedError", "HealthManagementClient"] diff --git a/litellm/proxy/client/cli/README.md b/litellm/proxy/client/cli/README.md index 74d1e654f0f..77724245b29 100644 --- a/litellm/proxy/client/cli/README.md +++ b/litellm/proxy/client/cli/README.md @@ -15,6 +15,20 @@ The CLI can be configured using environment variables or command-line options: - `LITELLM_PROXY_URL`: Base URL of the LiteLLM proxy server (default: http://localhost:4000) - `LITELLM_PROXY_API_KEY`: API key for authentication +## Global Options + +- `--version`, `-v`: Print the LiteLLM Proxy client and server version and exit. + +Example: + +```bash +litellm-proxy version +# or +litellm-proxy --version +# or +litellm-proxy -v +``` + ## Commands ### Models Management diff --git a/litellm/proxy/client/cli/main.py b/litellm/proxy/client/cli/main.py index f2ee28c44be..f053ffe84f4 100644 --- a/litellm/proxy/client/cli/main.py +++ b/litellm/proxy/client/cli/main.py @@ -11,9 +11,38 @@ from .commands.chat import chat from .commands.http import http from .commands.keys import keys from .commands.users import users +from litellm._version import version as litellm_version +from litellm.proxy.client.health import HealthManagementClient + + +def print_version(base_url: str, api_key: Optional[str]): + """Print CLI and server version info.""" + click.echo(f"LiteLLM Proxy CLI Version: {litellm_version}") + if base_url: + click.echo(f"LiteLLM Proxy Server URL: {base_url}") + try: + health_client = HealthManagementClient(base_url=base_url, api_key=api_key) + server_version = health_client.get_server_version() + if server_version: + click.echo(f"LiteLLM Proxy Server Version: {server_version}") + else: + click.echo("LiteLLM Proxy Server Version: (unavailable)") + except Exception as e: + click.echo(f"Could not retrieve server version: {e}") @click.group() +@click.option( + "--version", "-v", is_flag=True, is_eager=True, expose_value=False, + help="Show the LiteLLM Proxy CLI and server version and exit.", + callback=lambda ctx, param, value: ( + print_version( + ctx.params.get("base_url") or "http://localhost:4000", + ctx.params.get("api_key") + ) + or ctx.exit() + ) if value and not ctx.resilient_parsing else None, +) @click.option( "--base-url", envvar="LITELLM_PROXY_URL", @@ -35,6 +64,13 @@ def cli(ctx: click.Context, base_url: str, api_key: Optional[str]) -> None: ctx.obj["api_key"] = api_key +@cli.command() +@click.pass_context +def version(ctx: click.Context): + """Show the LiteLLM Proxy CLI and server version.""" + print_version(ctx.obj.get("base_url"), ctx.obj.get("api_key")) + + # Add the models command group cli.add_command(models) # Add the credentials command group diff --git a/litellm/proxy/client/health.py b/litellm/proxy/client/health.py new file mode 100644 index 00000000000..b9da8d9c380 --- /dev/null +++ b/litellm/proxy/client/health.py @@ -0,0 +1,40 @@ +from typing import Optional, Dict, Any +from .http_client import HTTPClient + +class HealthManagementClient: + """ + Client for interacting with the health endpoints of the LiteLLM proxy server. + """ + def __init__(self, base_url: str, api_key: Optional[str] = None, timeout: int = 30): + """ + Initialize the HealthManagementClient. + + Args: + base_url (str): The base URL of the LiteLLM proxy server (e.g., "http://localhost:4000") + api_key (Optional[str]): API key for authentication. If provided, it will be sent as a Bearer token. + timeout (int): Request timeout in seconds (default: 30) + """ + self._http = HTTPClient(base_url=base_url, api_key=api_key, timeout=timeout) + + def get_readiness(self) -> Dict[str, Any]: + """ + Check the readiness of the LiteLLM proxy server. + + Returns: + Dict[str, Any]: The readiness status and details from the server. + + Raises: + requests.exceptions.RequestException: If the request fails + ValueError: If the response is not valid JSON + """ + return self._http.request("GET", "/health/readiness") + + def get_server_version(self) -> Optional[str]: + """ + Get the LiteLLM server version from the readiness endpoint. + + Returns: + Optional[str]: The server version if available, otherwise None. + """ + readiness = self.get_readiness() + return readiness.get("litellm_version") \ No newline at end of file diff --git a/tests/litellm/proxy/client/cli/test_global_options.py b/tests/litellm/proxy/client/cli/test_global_options.py new file mode 100644 index 00000000000..9a2f64a2780 --- /dev/null +++ b/tests/litellm/proxy/client/cli/test_global_options.py @@ -0,0 +1,34 @@ +# stdlib imports +from litellm.proxy.client.cli import cli +from litellm._version import version as litellm_version +from click.testing import CliRunner +import pytest +from unittest.mock import patch +import os + + +@pytest.fixture +def cli_runner(): + return CliRunner() + + +def test_cli_version_flag(cli_runner): + """Test that --version prints the correct version, server URL, and server version, and exits successfully""" + with patch("litellm.proxy.client.health.HealthManagementClient.get_server_version", return_value="1.2.3"), \ + patch.dict(os.environ, {"LITELLM_PROXY_URL": "http://localhost:4000"}): + result = cli_runner.invoke(cli, ["--version"]) + assert result.exit_code == 0 + assert f"LiteLLM Proxy CLI Version: {litellm_version}" in result.output + assert "LiteLLM Proxy Server URL: http://localhost:4000" in result.output + assert "LiteLLM Proxy Server Version: 1.2.3" in result.output + + +def test_cli_version_command(cli_runner): + """Test that 'version' command prints the correct version, server URL, and server version, and exits successfully""" + with patch("litellm.proxy.client.health.HealthManagementClient.get_server_version", return_value="1.2.3"), \ + patch.dict(os.environ, {"LITELLM_PROXY_URL": "http://localhost:4000"}): + result = cli_runner.invoke(cli, ["version"]) + assert result.exit_code == 0 + assert f"LiteLLM Proxy CLI Version: {litellm_version}" in result.output + assert "LiteLLM Proxy Server URL: http://localhost:4000" in result.output + assert "LiteLLM Proxy Server Version: 1.2.3" in result.output