diff --git a/Dockerfile b/Dockerfile index 091527eeb1..a41d2fd379 100644 --- a/Dockerfile +++ b/Dockerfile @@ -68,8 +68,8 @@ FROM $LITELLM_RUNTIME_IMAGE AS runtime USER root -RUN apk add --no-cache bash openssl tzdata nodejs npm python3 libsndfile supervisor && \ - npm install -g npm@11.14.0 tar@7.5.11 glob@13.0.6 @isaacs/brace-expansion@5.0.1 brace-expansion@5.0.5 minimatch@10.2.4 diff@8.0.3 picomatch@4.0.4 && \ +RUN apk add --no-cache bash openssl tzdata nodejs npm python3 libsndfile && \ + npm install -g npm@11.12.1 tar@7.5.11 glob@13.0.6 @isaacs/brace-expansion@5.0.1 brace-expansion@5.0.5 minimatch@10.2.4 diff@8.0.3 picomatch@4.0.4 && \ GLOBAL="$(npm root -g)" && \ for pkg in tar glob @isaacs/brace-expansion brace-expansion minimatch diff picomatch; do \ name="${pkg##*/}"; \ @@ -97,7 +97,5 @@ RUN find /app/.venv -type f -path "*/tornado/test/*" -delete && \ EXPOSE 4000/tcp -COPY docker/supervisord.conf /etc/supervisord.conf - ENTRYPOINT ["docker/prod_entrypoint.sh"] CMD ["--port", "4000"] diff --git a/deploy/charts/litellm-helm/templates/deployment.yaml b/deploy/charts/litellm-helm/templates/deployment.yaml index a9b41e0d8e..6aa1771b7b 100644 --- a/deploy/charts/litellm-helm/templates/deployment.yaml +++ b/deploy/charts/litellm-helm/templates/deployment.yaml @@ -129,12 +129,6 @@ spec: value: {{ $val | quote }} {{- end }} {{- end }} - {{- if .Values.separateHealthApp }} - - name: SEPARATE_HEALTH_APP - value: "1" - - name: SEPARATE_HEALTH_PORT - value: {{ .Values.separateHealthPort | default "8081" | quote }} - {{- end }} {{- with .Values.extraEnvVars }} {{- toYaml . | nindent 12 }} {{- end }} @@ -175,15 +169,10 @@ spec: - name: http containerPort: {{ .Values.service.port }} protocol: TCP - {{- if .Values.separateHealthApp }} - - name: health - containerPort: {{ .Values.separateHealthPort | default 8081 }} - protocol: TCP - {{- end }} livenessProbe: httpGet: path: {{ .Values.livenessProbe.path | quote }} - port: {{ if .Values.separateHealthApp }}"health"{{ else }}"http"{{ end }} + port: "http" initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.livenessProbe.periodSeconds }} timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} @@ -192,7 +181,7 @@ spec: readinessProbe: httpGet: path: {{ .Values.readinessProbe.path | quote }} - port: {{ if .Values.separateHealthApp }}"health"{{ else }}"http"{{ end }} + port: "http" initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.readinessProbe.periodSeconds }} timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} @@ -201,7 +190,7 @@ spec: startupProbe: httpGet: path: {{ .Values.startupProbe.path | quote }} - port: {{ if .Values.separateHealthApp }}"health"{{ else }}"http"{{ end }} + port: "http" initialDelaySeconds: {{ .Values.startupProbe.initialDelaySeconds }} periodSeconds: {{ .Values.startupProbe.periodSeconds }} timeoutSeconds: {{ .Values.startupProbe.timeoutSeconds }} diff --git a/deploy/charts/litellm-helm/values.yaml b/deploy/charts/litellm-helm/values.yaml index 3dd1112bea..ba4059e084 100644 --- a/deploy/charts/litellm-helm/values.yaml +++ b/deploy/charts/litellm-helm/values.yaml @@ -88,12 +88,6 @@ service: # optionally specify loadBalancerClass # loadBalancerClass: tailscale -# Separate health app configuration -# When enabled, health checks will use a separate port and the application -# will receive SEPARATE_HEALTH_APP=1 and SEPARATE_HEALTH_PORT from environment variables -separateHealthApp: false -separateHealthPort: 8081 - # Probes for LiteLLM gateway container livenessProbe: path: /health/liveliness diff --git a/docker/Dockerfile.database b/docker/Dockerfile.database index 671f374ca2..c84003a065 100644 --- a/docker/Dockerfile.database +++ b/docker/Dockerfile.database @@ -66,7 +66,7 @@ FROM $LITELLM_RUNTIME_IMAGE AS runtime USER root -RUN apk add --no-cache bash openssl tzdata nodejs npm python3 libsndfile supervisor && \ +RUN apk add --no-cache bash openssl tzdata nodejs npm python3 libsndfile && \ npm install -g npm@11.12.1 tar@7.5.11 glob@11.1.0 @isaacs/brace-expansion@5.0.1 minimatch@10.2.4 diff@8.0.3 && \ GLOBAL="$(npm root -g)" && \ find "$GLOBAL/npm" -type d -name "tar" -path "*/node_modules/tar" | while read d; do \ @@ -102,7 +102,5 @@ RUN find /app/.venv -type f -path "*/tornado/test/*" -delete && \ EXPOSE 4000/tcp -COPY docker/supervisord.conf /etc/supervisord.conf - ENTRYPOINT ["docker/prod_entrypoint.sh"] CMD ["--port", "4000"] diff --git a/docker/Dockerfile.non_root b/docker/Dockerfile.non_root index 3fa73f4243..4de4a55981 100644 --- a/docker/Dockerfile.non_root +++ b/docker/Dockerfile.non_root @@ -103,13 +103,12 @@ RUN for i in 1 2 3; do \ apk upgrade --no-cache && break || sleep 5; \ done && \ for i in 1 2 3; do \ - apk add --no-cache python3 bash openssl tzdata supervisor libsndfile nodejs && break || sleep 5; \ + apk add --no-cache python3 bash openssl tzdata libsndfile nodejs && break || sleep 5; \ done COPY --from=builder /app /app COPY --from=builder /var/lib/litellm/ui /var/lib/litellm/ui COPY --from=builder /var/lib/litellm/assets /var/lib/litellm/assets -COPY --from=builder /app/docker/supervisord.conf /etc/supervisord.conf ENV PATH="/app/.venv/bin:${PATH}" \ PRISMA_BINARY_CACHE_DIR=/app/.cache/prisma-python/binaries \ diff --git a/docker/prod_entrypoint.sh b/docker/prod_entrypoint.sh index 28d1bdcc29..bd78bf6687 100644 --- a/docker/prod_entrypoint.sh +++ b/docker/prod_entrypoint.sh @@ -1,14 +1,8 @@ #!/bin/sh -if [ "$SEPARATE_HEALTH_APP" = "1" ]; then - export LITELLM_ARGS="$@" - export SUPERVISORD_STOPWAITSECS="${SUPERVISORD_STOPWAITSECS:-3600}" - exec supervisord -c /etc/supervisord.conf -fi - if [ "$USE_DDTRACE" = "true" ]; then export DD_TRACE_OPENAI_ENABLED="False" exec ddtrace-run litellm "$@" else exec litellm "$@" -fi \ No newline at end of file +fi diff --git a/docker/supervisord.conf b/docker/supervisord.conf deleted file mode 100644 index ba9d99d18a..0000000000 --- a/docker/supervisord.conf +++ /dev/null @@ -1,46 +0,0 @@ -[supervisord] -nodaemon=true -loglevel=info -logfile=/tmp/supervisord.log -pidfile=/tmp/supervisord.pid - -[group:litellm] -programs=main,health - -[program:main] -command=sh -c 'if [ "$USE_DDTRACE" = "true" ]; then export DD_TRACE_OPENAI_ENABLED="False"; exec ddtrace-run python -m litellm.proxy.proxy_cli --host 0.0.0.0 --port=4000 $LITELLM_ARGS; else exec python -m litellm.proxy.proxy_cli --host 0.0.0.0 --port=4000 $LITELLM_ARGS; fi' -autostart=true -autorestart=true -startretries=3 -priority=1 -exitcodes=0 -stopasgroup=true -killasgroup=true -stopwaitsecs=%(ENV_SUPERVISORD_STOPWAITSECS)s -stdout_logfile=/dev/stdout -stderr_logfile=/dev/stderr -stdout_logfile_maxbytes = 0 -stderr_logfile_maxbytes = 0 -environment=PYTHONUNBUFFERED=true - -[program:health] -command=sh -c '[ "$SEPARATE_HEALTH_APP" = "1" ] && exec uvicorn litellm.proxy.health_endpoints.health_app_factory:build_health_app --factory --host 0.0.0.0 --port=${SEPARATE_HEALTH_PORT:-4001} || exit 0' -autostart=true -autorestart=true -startretries=3 -priority=2 -exitcodes=0 -stopasgroup=true -killasgroup=true -stopwaitsecs=%(ENV_SUPERVISORD_STOPWAITSECS)s -stdout_logfile=/dev/stdout -stderr_logfile=/dev/stderr -stdout_logfile_maxbytes = 0 -stderr_logfile_maxbytes = 0 -environment=PYTHONUNBUFFERED=true - -[eventlistener:process_monitor] -command=python -c "from supervisor import childutils; import os, signal; [os.kill(os.getppid(), signal.SIGTERM) for h,p in iter(lambda: childutils.listener.wait(), None) if h['eventname'] in ['PROCESS_STATE_FATAL', 'PROCESS_STATE_EXITED'] and dict([x.split(':') for x in p.split(' ')])['processname'] in ['main', 'health'] or childutils.listener.ok()]" -events=PROCESS_STATE_EXITED,PROCESS_STATE_FATAL -autostart=true -autorestart=true \ No newline at end of file diff --git a/litellm/proxy/health_endpoints/health_app_factory.py b/litellm/proxy/health_endpoints/health_app_factory.py deleted file mode 100644 index c4fe383365..0000000000 --- a/litellm/proxy/health_endpoints/health_app_factory.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import FastAPI -from litellm.proxy.health_endpoints._health_endpoints import router as health_router - - -def build_health_app(): - health_app = FastAPI(title="LiteLLM Health Endpoints") - health_app.include_router(health_router) - return health_app diff --git a/litellm/proxy/proxy_cli.py b/litellm/proxy/proxy_cli.py index 90bbfdd25a..06fc0819a7 100644 --- a/litellm/proxy/proxy_cli.py +++ b/litellm/proxy/proxy_cli.py @@ -1050,11 +1050,6 @@ def run_server( # noqa: PLR0915 litellm_settings=litellm_settings if config else None, # type: ignore[possibly-unbound] ) - # --- SEPARATE HEALTH APP LOGIC --- - # To run the health app separately, use: - # uvicorn litellm.proxy.health_app_factory:build_health_app --factory --host 0.0.0.0 --port=4001 - # This is compatible with the SEPARATE_HEALTH_APP Docker/supervisord pattern. - # --- END SEPARATE HEALTH APP LOGIC --- # Skip server startup if requested (after all setup is done) if skip_server_startup: print( # noqa diff --git a/pyproject.toml b/pyproject.toml index 7ff388f184..df6920c5e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,18 +10,22 @@ authors = [ { name = "BerriAI" }, ] dependencies = [ - "fastuuid==0.14.0", - "httpx==0.28.1", - "openai==2.33.0", - "python-dotenv==1.2.2", - "tiktoken==0.12.0", - "importlib-metadata==8.5.0", - "tokenizers==0.23.1", - "click==8.1.8", - "jinja2==3.1.6", - "aiohttp==3.13.4", - "pydantic==2.12.5", - "jsonschema==4.23.0", + # Ranges (not exact pins) so SDK consumers can coexist with their other + # deps. Reproducibility for our Docker/CI comes from `uv.lock`. + # When changing a floor, verify it installs + imports on every supported + # Python with: `uv pip install --resolution=lowest-direct .` + "fastuuid>=0.14.0,<1.0", + "httpx>=0.28.0,<1.0", + "openai>=2.20.0,<3.0.0", + "python-dotenv>=1.0.0,<2.0", + "tiktoken>=0.8.0,<1.0", + "importlib-metadata>=8.0.0,<9.0", + "tokenizers>=0.21.0,<1.0", + "click>=8.0.0,<9.0", + "jinja2>=3.1.0,<4.0", + "aiohttp>=3.10,<4.0", + "pydantic>=2.10.0,<3.0.0", + "jsonschema>=4.0.0,<5.0", ] [project.urls] diff --git a/tests/test_litellm/proxy/test_proxy_cli.py b/tests/test_litellm/proxy/test_proxy_cli.py index f05e95f9e5..59b43330a2 100644 --- a/tests/test_litellm/proxy/test_proxy_cli.py +++ b/tests/test_litellm/proxy/test_proxy_cli.py @@ -2,7 +2,6 @@ import os import sys from unittest.mock import MagicMock, patch -import fastapi import pytest sys.path.insert( @@ -12,7 +11,6 @@ sys.path.insert( import builtins import types -from litellm.proxy.health_endpoints.health_app_factory import build_health_app from litellm.proxy.proxy_cli import ProxyInitializationHelpers @@ -771,62 +769,8 @@ class TestProxyInitializationHelpers: mock_uvicorn_run.assert_called_once() -class TestHealthAppFactory: - """Test cases for the health app factory module""" - - def test_build_health_app(self): - """Test that build_health_app creates a FastAPI app with the correct title and includes the health router""" - # Execute - health_app = build_health_app() - - # Assert - assert health_app.title == "LiteLLM Health Endpoints" - assert isinstance(health_app, fastapi.FastAPI) - - # Verify that the app has the expected health endpoints by checking route paths - # When a router is included, its routes are flattened into the main app's routes - route_paths = [] - for route in health_app.routes: - if hasattr(route, "path"): - route_paths.append(route.path) - - # Check for some expected health endpoints - expected_paths = [ - "/test", - "/health/services", - "/health", - "/health/history", - "/health/latest", - "/settings", - "/active/callbacks", - "/health/readiness", - "/health/liveliness", - "/health/liveness", - "/health/test_connection", - ] - - # At least some of the expected health endpoints should be present - found_paths = [path for path in expected_paths if path in route_paths] - assert ( - len(found_paths) > 0 - ), f"Expected to find health endpoints, but found: {route_paths}" - - # Verify that the app has routes (indicating the router was included) - assert ( - len(health_app.routes) > 0 - ), "Health app should have routes from the included router" - - def test_build_health_app_returns_different_instances(self): - """Test that build_health_app returns different FastAPI instances on each call""" - # Execute - health_app_1 = build_health_app() - health_app_2 = build_health_app() - - # Assert - assert health_app_1 is not health_app_2 - assert health_app_1.title == health_app_2.title - assert isinstance(health_app_1, fastapi.FastAPI) - assert isinstance(health_app_2, fastapi.FastAPI) +class TestRunServerDbSetup: + """Tests for run_server's prisma setup_database behavior.""" @patch("subprocess.run") @patch("atexit.register") diff --git a/uv.lock b/uv.lock index eed58c76bc..292c99e6a1 100644 --- a/uv.lock +++ b/uv.lock @@ -9,7 +9,7 @@ resolution-markers = [ ] [options] -exclude-newer = "2026-05-02T11:18:44.200141Z" +exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. exclude-newer-span = "P3D" [manifest] @@ -3374,7 +3374,7 @@ proxy-dev = [ [package.metadata] requires-dist = [ { name = "a2a-sdk", marker = "extra == 'extra-proxy'", specifier = "==0.3.24" }, - { name = "aiohttp", specifier = "==3.13.4" }, + { name = "aiohttp", specifier = ">=3.10,<4.0" }, { name = "anthropic", extras = ["vertex"], marker = "extra == 'proxy-runtime'", specifier = "==0.84.0" }, { name = "apscheduler", marker = "extra == 'proxy'", specifier = "==3.11.2" }, { name = "audioread", marker = "extra == 'stt-nvidia-riva'", specifier = ">=3.0.1" }, @@ -3387,14 +3387,14 @@ requires-dist = [ { name = "azure-storage-file-datalake", marker = "extra == 'proxy-runtime'", specifier = "==12.20.0" }, { name = "backoff", marker = "extra == 'proxy'", specifier = "==2.2.1" }, { name = "boto3", marker = "extra == 'proxy'", specifier = "==1.43.1" }, - { name = "click", specifier = "==8.1.8" }, + { name = "click", specifier = ">=8.0.0,<9.0" }, { name = "cryptography", marker = "extra == 'proxy'", specifier = "==46.0.7" }, { name = "ddtrace", marker = "extra == 'proxy-runtime'", specifier = "==2.19.0" }, { name = "detect-secrets", marker = "extra == 'proxy-runtime'", specifier = "==1.5.0" }, { name = "diskcache", marker = "extra == 'caching'", specifier = "==5.6.3" }, { name = "fastapi", marker = "extra == 'proxy'", specifier = "==0.124.4" }, { name = "fastapi-sso", marker = "extra == 'proxy'", specifier = "==0.19.0" }, - { name = "fastuuid", specifier = "==0.14.0" }, + { name = "fastuuid", specifier = ">=0.14.0,<1.0" }, { name = "google-cloud-aiplatform", marker = "extra == 'google'", specifier = "==1.133.0" }, { name = "google-cloud-aiplatform", marker = "extra == 'proxy-runtime'", specifier = "==1.133.0" }, { name = "google-cloud-iam", marker = "extra == 'extra-proxy'", specifier = "==2.19.1" }, @@ -3403,10 +3403,10 @@ requires-dist = [ { name = "grpcio", marker = "extra == 'grpc'", specifier = "==1.78.0" }, { name = "grpcio", marker = "extra == 'proxy-runtime'", specifier = "==1.78.0" }, { name = "gunicorn", marker = "extra == 'proxy'", specifier = "==23.0.0" }, - { name = "httpx", specifier = "==0.28.1" }, - { name = "importlib-metadata", specifier = "==8.5.0" }, - { name = "jinja2", specifier = "==3.1.6" }, - { name = "jsonschema", specifier = "==4.23.0" }, + { name = "httpx", specifier = ">=0.28.0,<1.0" }, + { name = "importlib-metadata", specifier = ">=8.0.0,<9.0" }, + { name = "jinja2", specifier = ">=3.1.0,<4.0" }, + { name = "jsonschema", specifier = ">=4.0.0,<5.0" }, { name = "langfuse", marker = "extra == 'proxy-runtime'", specifier = "==2.59.7" }, { name = "litellm-enterprise", marker = "extra == 'proxy'", editable = "enterprise" }, { name = "litellm-proxy-extras", marker = "extra == 'proxy'", editable = "litellm-proxy-extras" }, @@ -3417,7 +3417,7 @@ requires-dist = [ { name = "numpy", marker = "extra == 'stt-nvidia-riva'", specifier = ">=1.26.0" }, { name = "numpydoc", marker = "extra == 'utils'", specifier = "==1.8.0" }, { name = "nvidia-riva-client", marker = "extra == 'stt-nvidia-riva'", specifier = ">=2.15.0" }, - { name = "openai", specifier = "==2.33.0" }, + { name = "openai", specifier = ">=2.20.0,<3.0.0" }, { name = "opentelemetry-api", marker = "extra == 'proxy-runtime'", specifier = "==1.28.0" }, { name = "opentelemetry-exporter-otlp", marker = "extra == 'proxy-runtime'", specifier = "==1.28.0" }, { name = "opentelemetry-sdk", marker = "extra == 'proxy-runtime'", specifier = "==1.28.0" }, @@ -3425,12 +3425,12 @@ requires-dist = [ { name = "polars", marker = "extra == 'proxy'", specifier = "==1.38.1" }, { name = "prisma", marker = "extra == 'extra-proxy'", specifier = "==0.11.0" }, { name = "prometheus-client", marker = "extra == 'proxy-runtime'", specifier = "==0.20.0" }, - { name = "pydantic", specifier = "==2.12.5" }, + { name = "pydantic", specifier = ">=2.10.0,<3.0.0" }, { name = "pyjwt", marker = "extra == 'proxy'", specifier = "==2.12.0" }, { name = "pynacl", marker = "extra == 'proxy'", specifier = "==1.6.2" }, { name = "pypdf", marker = "python_full_version < '3.14' and extra == 'proxy-runtime'", specifier = "==6.10.2" }, { name = "pyroscope-io", marker = "sys_platform != 'win32' and extra == 'proxy'", specifier = "==0.8.16" }, - { name = "python-dotenv", specifier = "==1.2.2" }, + { name = "python-dotenv", specifier = ">=1.0.0,<2.0" }, { name = "python-multipart", marker = "extra == 'proxy'", specifier = "==0.0.27" }, { name = "pyyaml", marker = "extra == 'proxy'", specifier = "==6.0.3" }, { name = "redisvl", marker = "python_full_version < '3.14' and extra == 'extra-proxy'", specifier = "==0.4.1" }, @@ -3442,8 +3442,8 @@ requires-dist = [ { name = "sentry-sdk", marker = "extra == 'proxy-runtime'", specifier = "==2.21.0" }, { name = "soundfile", marker = "extra == 'proxy'", specifier = "==0.12.1" }, { name = "soundfile", marker = "extra == 'stt-nvidia-riva'", specifier = ">=0.12.1" }, - { name = "tiktoken", specifier = "==0.12.0" }, - { name = "tokenizers", specifier = "==0.23.1" }, + { name = "tiktoken", specifier = ">=0.8.0,<1.0" }, + { name = "tokenizers", specifier = ">=0.21.0,<1.0" }, { name = "uvicorn", marker = "extra == 'proxy'", specifier = "==0.33.0" }, { name = "uvloop", marker = "sys_platform != 'win32' and extra == 'proxy'", specifier = "==0.21.0" }, { name = "websockets", marker = "extra == 'proxy'", specifier = "==15.0.1" },