diff --git a/.circleci/config.yml b/.circleci/config.yml index e5bc82a5967..133a7184f9b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -44,8 +44,8 @@ commands: pip install "pytest-asyncio==0.21.1" pip install "respx==0.22.0" pip install "hypercorn==0.17.3" - pip install "pydantic==2.11.0" - pip install "mcp==1.25.0" + pip install "pydantic==2.10.2" + pip install "mcp==1.10.1" pip install "requests-mock>=1.12.1" pip install "responses==0.25.7" pip install "pytest-xdist==3.6.1" @@ -112,14 +112,14 @@ jobs: python -m mypy . cd .. no_output_timeout: 10m - local_testing_part1: + local_testing: docker: - image: cimg/python:3.12 auth: username: ${DOCKERHUB_USERNAME} password: ${DOCKERHUB_PASSWORD} working_directory: ~/project - parallelism: 4 + steps: - checkout - setup_google_dns @@ -205,32 +205,20 @@ jobs: # Run pytest and generate JUnit XML report - run: - name: Run tests (Part 1 - A-M) + name: Run tests command: | - mkdir test-results - - # Discover test files (A-M) - TEST_FILES=$(circleci tests glob "tests/local_testing/**/test_[a-mA-M]*.py") - - echo "$TEST_FILES" | circleci tests run \ - --split-by=timings \ - --verbose \ - --command="xargs python -m pytest \ - -vv \ - --cov=litellm \ - --cov-report=xml \ - --junitxml=test-results/junit.xml \ - --durations=20 \ - -k \"not test_python_38.py and not test_basic_python_version.py and not router and not assistants and not langfuse and not caching and not cache\" \ - -n 4 \ - --timeout=300 \ - --timeout_method=thread" + pwd + ls + # Add --timeout to kill hanging tests after 300s (5 min) + # Add -v to show test names as they run for debugging + # Add --tb=short for shorter tracebacks + python -m pytest -vv tests/local_testing --cov=litellm --cov-report=xml --junitxml=test-results/junit.xml --durations=20 -k "not test_python_38.py and not test_basic_python_version.py and not router and not assistants and not langfuse and not caching and not cache" -n 4 --timeout=300 --timeout_method=thread no_output_timeout: 120m - run: name: Rename the coverage files command: | - mv coverage.xml local_testing_part1_coverage.xml - mv .coverage local_testing_part1_coverage + mv coverage.xml local_testing_coverage.xml + mv .coverage local_testing_coverage # Store test results - store_test_results: @@ -238,136 +226,8 @@ jobs: - persist_to_workspace: root: . paths: - - local_testing_part1_coverage.xml - - local_testing_part1_coverage - local_testing_part2: - docker: - - image: cimg/python:3.12 - auth: - username: ${DOCKERHUB_USERNAME} - password: ${DOCKERHUB_PASSWORD} - working_directory: ~/project - parallelism: 4 - steps: - - checkout - - setup_google_dns - - run: - name: Show git commit hash - command: | - echo "Git commit hash: $CIRCLE_SHA1" - - - restore_cache: - keys: - - v1-dependencies-{{ checksum ".circleci/requirements.txt" }} - - run: - name: Install Dependencies - command: | - python -m pip install --upgrade pip - python -m pip install -r .circleci/requirements.txt - pip install "pytest==7.3.1" - pip install "pytest-retry==1.6.3" - pip install "pytest-asyncio==0.21.1" - pip install "pytest-cov==5.0.0" - pip install "mypy==1.18.2" - pip install "google-generativeai==0.3.2" - pip install "google-cloud-aiplatform==1.43.0" - pip install pyarrow - pip install "boto3==1.36.0" - pip install "aioboto3==13.4.0" - pip install langchain - pip install lunary==0.2.5 - pip install "azure-identity==1.16.1" - pip install "langfuse==2.59.7" - pip install "logfire==0.29.0" - pip install numpydoc - pip install traceloop-sdk==0.21.1 - pip install opentelemetry-api==1.25.0 - pip install opentelemetry-sdk==1.25.0 - pip install opentelemetry-exporter-otlp==1.25.0 - pip install openai==1.100.1 - pip install prisma==0.11.0 - pip install "detect_secrets==1.5.0" - pip install "httpx==0.24.1" - pip install "respx==0.22.0" - pip install fastapi - pip install "gunicorn==21.2.0" - pip install "anyio==4.2.0" - pip install "aiodynamo==23.10.1" - pip install "asyncio==3.4.3" - pip install "apscheduler==3.10.4" - pip install "PyGithub==1.59.1" - pip install argon2-cffi - pip install "pytest-mock==3.12.0" - pip install python-multipart - pip install google-cloud-aiplatform - pip install prometheus-client==0.20.0 - pip install "pydantic==2.10.2" - pip install "diskcache==5.6.1" - pip install "Pillow==10.3.0" - pip install "jsonschema==4.22.0" - pip install "pytest-xdist==3.6.1" - pip install "pytest-timeout==2.2.0" - pip install "websockets==13.1.0" - pip install semantic_router --no-deps - pip install aurelio_sdk --no-deps - pip uninstall posthog -y - - setup_litellm_enterprise_pip - - save_cache: - paths: - - ./venv - key: v1-dependencies-{{ checksum ".circleci/requirements.txt" }} - - run: - name: Run prisma ./docker/entrypoint.sh - command: | - set +e - chmod +x docker/entrypoint.sh - ./docker/entrypoint.sh - set -e - - run: - name: Black Formatting - command: | - cd litellm - python -m pip install black - python -m black . - cd .. - - # Run pytest and generate JUnit XML report - - run: - name: Run tests (Part 2 - N-Z) - command: | - mkdir test-results - - # Discover test files (N-Z) - TEST_FILES=$(circleci tests glob "tests/local_testing/**/test_[n-zN-Z]*.py") - - echo "$TEST_FILES" | circleci tests run \ - --split-by=timings \ - --verbose \ - --command="xargs python -m pytest \ - -vv \ - --cov=litellm \ - --cov-report=xml \ - --junitxml=test-results/junit.xml \ - --durations=20 \ - -k \"not test_python_38.py and not test_basic_python_version.py and not router and not assistants and not langfuse and not caching and not cache\" \ - -n 4 \ - --timeout=300 \ - --timeout_method=thread" - no_output_timeout: 120m - - run: - name: Rename the coverage files - command: | - mv coverage.xml local_testing_part2_coverage.xml - mv .coverage local_testing_part2_coverage - - # Store test results - - store_test_results: - path: test-results - - persist_to_workspace: - root: . - paths: - - local_testing_part2_coverage.xml - - local_testing_part2_coverage + - local_testing_coverage.xml + - local_testing_coverage langfuse_logging_unit_tests: docker: - image: cimg/python:3.11 @@ -639,6 +499,7 @@ jobs: username: ${DOCKERHUB_USERNAME} password: ${DOCKERHUB_PASSWORD} working_directory: ~/project + steps: - checkout - setup_google_dns @@ -652,7 +513,6 @@ jobs: pip install "pytest-cov==5.0.0" pip install "pytest-retry==1.6.3" pip install "pytest-asyncio==0.21.1" - pip install "pytest-xdist==3.6.1" pip install semantic_router --no-deps pip install aurelio_sdk --no-deps # Run pytest and generate JUnit XML report @@ -715,8 +575,8 @@ jobs: - run: name: Rename the coverage files command: | - mv coverage.xml litellm_router_unit_coverage.xml - mv .coverage litellm_router_unit_coverage + mv coverage.xml litellm_router_coverage.xml + mv .coverage litellm_router_coverage # Store test results - store_test_results: path: test-results @@ -724,8 +584,8 @@ jobs: - persist_to_workspace: root: . paths: - - litellm_router_unit_coverage.xml - - litellm_router_unit_coverage + - litellm_router_coverage.xml + - litellm_router_coverage litellm_security_tests: machine: image: ubuntu-2204:2023.10.1 @@ -1292,8 +1152,8 @@ jobs: pip install "pytest-cov==5.0.0" pip install "pytest-asyncio==0.21.1" pip install "respx==0.22.0" - pip install "pydantic==2.11.0" - pip install "mcp==1.25.0" + pip install "pydantic==2.10.2" + pip install "mcp==1.10.1" # Run pytest and generate JUnit XML report - run: name: Run tests @@ -1696,8 +1556,8 @@ jobs: pip install "pytest-asyncio==0.21.1" pip install "respx==0.22.0" pip install "hypercorn==0.17.3" - pip install "pydantic==2.11.0" - pip install "mcp==1.25.0" + pip install "pydantic==2.10.2" + pip install "mcp==1.10.1" pip install "requests-mock>=1.12.1" pip install "responses==0.25.7" pip install "pytest-xdist==3.6.1" @@ -1883,14 +1743,13 @@ jobs: pip install "pytest-cov==5.0.0" pip install "pytest-asyncio==0.21.1" pip install "respx==0.22.0" - pip install "pytest-xdist==3.6.1" # Run pytest and generate JUnit XML report - run: name: Run tests command: | pwd ls - python -m pytest -vv tests/image_gen_tests -n 4 --cov=litellm --cov-report=xml -x -v --junitxml=test-results/junit.xml --durations=5 + python -m pytest -vv tests/image_gen_tests --cov=litellm --cov-report=xml -x -v --junitxml=test-results/junit.xml --durations=5 no_output_timeout: 120m - run: name: Rename the coverage files @@ -1933,7 +1792,6 @@ jobs: pip install "mlflow==2.17.2" pip install "anthropic==0.52.0" pip install "blockbuster==1.5.24" - pip install "pytest-xdist==3.6.1" # Run pytest and generate JUnit XML report - setup_litellm_enterprise_pip - run: @@ -1941,7 +1799,7 @@ jobs: command: | pwd ls - python -m pytest -vv tests/logging_callback_tests --cov=litellm -n 4 --cov-report=xml -s -v --junitxml=test-results/junit.xml --durations=5 + python -m pytest -vv tests/logging_callback_tests --cov=litellm --cov-report=xml -s -v --junitxml=test-results/junit.xml --durations=5 no_output_timeout: 120m - run: name: Rename the coverage files @@ -2057,7 +1915,7 @@ jobs: pip install "pytest-asyncio==0.21.1" pip install "pytest-cov==5.0.0" pip install "tomli==2.2.1" - pip install "mcp==1.25.0" + pip install "mcp==1.10.1" - run: name: Run tests command: | @@ -2334,8 +2192,6 @@ jobs: pip install "asyncio==3.4.3" pip install "PyGithub==1.59.1" pip install "openai==1.100.1" - pip install "litellm[proxy]" - pip install "pytest-xdist==3.6.1" - run: name: Install dockerize command: | @@ -2412,7 +2268,7 @@ jobs: command: | pwd ls - python -m pytest -s -vv tests/*.py -x --junitxml=test-results/junit.xml -n 4 --durations=5 --ignore=tests/otel_tests --ignore=tests/spend_tracking_tests --ignore=tests/pass_through_tests --ignore=tests/proxy_admin_ui_tests --ignore=tests/load_tests --ignore=tests/llm_translation --ignore=tests/llm_responses_api_testing --ignore=tests/mcp_tests --ignore=tests/guardrails_tests --ignore=tests/image_gen_tests --ignore=tests/pass_through_unit_tests + python -m pytest -s -vv tests/*.py -x --junitxml=test-results/junit.xml --durations=5 --ignore=tests/otel_tests --ignore=tests/spend_tracking_tests --ignore=tests/pass_through_tests --ignore=tests/proxy_admin_ui_tests --ignore=tests/load_tests --ignore=tests/llm_translation --ignore=tests/llm_responses_api_testing --ignore=tests/mcp_tests --ignore=tests/guardrails_tests --ignore=tests/image_gen_tests --ignore=tests/pass_through_unit_tests no_output_timeout: 120m # Store test results @@ -3407,110 +3263,6 @@ jobs: - store_test_results: path: test-results - proxy_e2e_anthropic_messages_tests: - machine: - image: ubuntu-2204:2023.10.1 - resource_class: xlarge - working_directory: ~/project - steps: - - checkout - - setup_google_dns - - run: - name: Install Docker CLI (In case it's not already installed) - command: | - curl -fsSL https://get.docker.com | sh - sudo usermod -aG docker $USER - docker version - - run: - name: Install Python 3.10 - command: | - curl https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh --output miniconda.sh - bash miniconda.sh -b -p $HOME/miniconda - export PATH="$HOME/miniconda/bin:$PATH" - conda init bash - source ~/.bashrc - conda create -n myenv python=3.10 -y - conda activate myenv - python --version - - run: - name: Install Dependencies - command: | - export PATH="$HOME/miniconda/bin:$PATH" - source $HOME/miniconda/etc/profile.d/conda.sh - conda activate myenv - pip install "pytest==7.3.1" - pip install "pytest-asyncio==0.21.1" - pip install "boto3==1.36.0" - pip install "httpx==0.27.0" - pip install "claude-agent-sdk" - pip install -r requirements.txt - - run: - name: Install dockerize - command: | - wget https://github.com/jwilder/dockerize/releases/download/v0.6.1/dockerize-linux-amd64-v0.6.1.tar.gz - sudo tar -C /usr/local/bin -xzvf dockerize-linux-amd64-v0.6.1.tar.gz - rm dockerize-linux-amd64-v0.6.1.tar.gz - - run: - name: Start PostgreSQL Database - command: | - docker run -d \ - --name postgres-db \ - -e POSTGRES_USER=postgres \ - -e POSTGRES_PASSWORD=postgres \ - -e POSTGRES_DB=circle_test \ - -p 5432:5432 \ - postgres:14 - - run: - name: Wait for PostgreSQL to be ready - command: dockerize -wait tcp://localhost:5432 -timeout 1m - - attach_workspace: - at: ~/project - - run: - name: Load Docker Database Image - command: | - gunzip -c litellm-docker-database.tar.gz | docker load - docker images | grep litellm-docker-database - - run: - name: Run Docker container with test config - command: | - docker run -d \ - -p 4000:4000 \ - -e DATABASE_URL=postgresql://postgres:postgres@host.docker.internal:5432/circle_test \ - -e LITELLM_MASTER_KEY="sk-1234" \ - -e AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \ - -e AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \ - -e AWS_REGION_NAME="us-east-1" \ - --add-host host.docker.internal:host-gateway \ - --name my-app \ - -v $(pwd)/tests/proxy_e2e_anthropic_messages_tests/test_config.yaml:/app/config.yaml \ - litellm-docker-database:ci \ - --config /app/config.yaml \ - --port 4000 \ - --detailed_debug - - run: - name: Start outputting logs - command: docker logs -f my-app - background: true - - run: - name: Wait for app to be ready - command: dockerize -wait http://localhost:4000 -timeout 5m - - run: - name: Run Claude Agent SDK E2E Tests - command: | - export PATH="$HOME/miniconda/bin:$PATH" - source $HOME/miniconda/etc/profile.d/conda.sh - conda activate myenv - export LITELLM_PROXY_URL="http://localhost:4000" - export LITELLM_API_KEY="sk-1234" - pwd - ls - python -m pytest -vv tests/proxy_e2e_anthropic_messages_tests/ -x -s --junitxml=test-results/junit.xml --durations=5 - no_output_timeout: 120m - - # Store test results - - store_test_results: - path: test-results - upload-coverage: docker: - image: cimg/python:3.9 @@ -3532,7 +3284,7 @@ jobs: python -m venv venv . venv/bin/activate pip install coverage - coverage combine llm_translation_coverage llm_responses_api_coverage ocr_coverage search_coverage mcp_coverage logging_coverage audio_coverage litellm_router_coverage litellm_router_unit_coverage local_testing_part1_coverage local_testing_part2_coverage litellm_assistants_api_coverage auth_ui_unit_tests_coverage langfuse_coverage caching_coverage litellm_proxy_unit_tests_part1_coverage litellm_proxy_unit_tests_part2_coverage image_gen_coverage pass_through_unit_tests_coverage batches_coverage litellm_security_tests_coverage guardrails_coverage litellm_mapped_tests_coverage + coverage combine llm_translation_coverage llm_responses_api_coverage ocr_coverage search_coverage mcp_coverage logging_coverage audio_coverage litellm_router_coverage local_testing_coverage litellm_assistants_api_coverage auth_ui_unit_tests_coverage langfuse_coverage caching_coverage litellm_proxy_unit_tests_part1_coverage litellm_proxy_unit_tests_part2_coverage image_gen_coverage pass_through_unit_tests_coverage batches_coverage litellm_security_tests_coverage guardrails_coverage litellm_mapped_tests_coverage coverage xml - codecov/upload: file: ./coverage.xml @@ -3582,22 +3334,8 @@ jobs: ls dist/ twine upload --verbose dist/* else - echo "Version ${VERSION} of package is already published on PyPI." - - # Check if corresponding Docker nightly image exists - NIGHTLY_TAG="v${VERSION}-nightly" - echo "Checking for Docker nightly image: litellm/litellm:${NIGHTLY_TAG}" - - # Check Docker Hub for the nightly image - if curl -s "https://hub.docker.com/v2/repositories/litellm/litellm/tags/${NIGHTLY_TAG}" | grep -q "name"; then - echo "Docker nightly image ${NIGHTLY_TAG} exists. This release was already completed successfully." - echo "Skipping PyPI publish and continuing to ensure Docker images are up to date." - circleci step halt - else - echo "ERROR: PyPI package ${VERSION} exists but Docker nightly image ${NIGHTLY_TAG} does not exist!" - echo "This indicates an incomplete release. Please investigate." - exit 1 - fi + echo "Version ${VERSION} of package is already published on PyPI. Skipping PyPI publish." + circleci step halt fi - run: name: Trigger Github Action for new Docker Container + Trigger Load Testing @@ -3606,21 +3344,11 @@ jobs: python3 -m pip install toml VERSION=$(python3 -c "import toml; print(toml.load('pyproject.toml')['tool']['poetry']['version'])") echo "LiteLLM Version ${VERSION}" - - # Determine which branch to use for Docker build - if [[ "$CIRCLE_BRANCH" =~ ^litellm_release_day_.* ]]; then - BUILD_BRANCH="$CIRCLE_BRANCH" - echo "Using release branch: $BUILD_BRANCH" - else - BUILD_BRANCH="main" - echo "Using default branch: $BUILD_BRANCH" - fi - curl -X POST \ -H "Accept: application/vnd.github.v3+json" \ -H "Authorization: Bearer $GITHUB_TOKEN" \ "https://api.github.com/repos/BerriAI/litellm/actions/workflows/ghcr_deploy.yml/dispatches" \ - -d "{\"ref\":\"${BUILD_BRANCH}\", \"inputs\":{\"tag\":\"v${VERSION}-nightly\", \"commit_hash\":\"$CIRCLE_SHA1\"}}" + -d "{\"ref\":\"main\", \"inputs\":{\"tag\":\"v${VERSION}-nightly\", \"commit_hash\":\"$CIRCLE_SHA1\"}}" echo "triggering load testing server for version ${VERSION} and commit ${CIRCLE_SHA1}" curl -X POST "https://proxyloadtester-production.up.railway.app/start/load/test?version=${VERSION}&commit_hash=${CIRCLE_SHA1}&release_type=nightly" @@ -4011,13 +3739,7 @@ workflows: only: - main - /litellm_.*/ - - local_testing_part1: - filters: - branches: - only: - - main - - /litellm_.*/ - - local_testing_part2: + - local_testing: filters: branches: only: @@ -4179,14 +3901,6 @@ workflows: only: - main - /litellm_.*/ - - proxy_e2e_anthropic_messages_tests: - requires: - - build_docker_database_image - filters: - branches: - only: - - main - - /litellm_.*/ - llm_translation_testing: filters: branches: @@ -4330,8 +4044,7 @@ workflows: - litellm_proxy_unit_testing_part2 - litellm_security_tests - langfuse_logging_unit_tests - - local_testing_part1 - - local_testing_part2 + - local_testing - litellm_assistants_api_testing - auth_ui_unit_tests - db_migration_disable_update_check: @@ -4371,12 +4084,10 @@ workflows: branches: only: - main - - /litellm_release_day_.*/ - publish_to_pypi: requires: - mypy_linting - - local_testing_part1 - - local_testing_part2 + - local_testing - build_and_test - e2e_openai_endpoints - test_bad_database_url diff --git a/.circleci/requirements.txt b/.circleci/requirements.txt index a5ec74424fe..2294c84813c 100644 --- a/.circleci/requirements.txt +++ b/.circleci/requirements.txt @@ -8,13 +8,12 @@ redis==5.2.1 redisvl==0.4.1 anthropic orjson==3.10.12 # fast /embedding responses -pydantic==2.11.0 +pydantic==2.10.2 google-cloud-aiplatform==1.43.0 google-cloud-iam==2.19.1 fastapi-sso==0.16.0 uvloop==0.21.0 -mcp==1.25.0 # for MCP server +mcp==1.10.1 # for MCP server semantic_router==0.1.10 # for auto-routing with litellm fastuuid==0.12.0 -responses==0.25.7 # for proxy client tests -pytest-retry==1.6.3 # for automatic test retries \ No newline at end of file +responses==0.25.7 # for proxy client tests \ No newline at end of file diff --git a/.github/workflows/create_daily_staging_branch.yml b/.github/workflows/create_daily_staging_branch.yml index 9d0093e8b16..a97cf6f9740 100644 --- a/.github/workflows/create_daily_staging_branch.yml +++ b/.github/workflows/create_daily_staging_branch.yml @@ -2,7 +2,7 @@ name: Create Daily Staging Branch on: schedule: - - cron: '0 0,12 * * *' # Runs every 12 hours at midnight and noon UTC + - cron: '0 0 * * *' # Runs daily at midnight UTC workflow_dispatch: # Allow manual trigger jobs: @@ -24,7 +24,7 @@ jobs: git config user.email "github-actions[bot]@users.noreply.github.com" # Generate branch name with MM_DD_YYYY format - BRANCH_NAME="litellm_oss_staging_$(date +'%m_%d_%Y')" + BRANCH_NAME="litellm_staging_$(date +'%m_%d_%Y')" echo "Creating branch: $BRANCH_NAME" # Fetch all branches diff --git a/.github/workflows/ghcr_deploy.yml b/.github/workflows/ghcr_deploy.yml index f67538a4272..aa032972b80 100644 --- a/.github/workflows/ghcr_deploy.yml +++ b/.github/workflows/ghcr_deploy.yml @@ -320,36 +320,72 @@ jobs: run: | echo "REPO_OWNER=`echo ${{github.repository_owner}} | tr '[:upper:]' '[:lower:]'`" >>${GITHUB_ENV} - # Sync Helm chart version with LiteLLM release version (1-1 versioning) - # This allows users to easily map Helm chart versions to LiteLLM versions - # See: https://codefresh.io/docs/docs/ci-cd-guides/helm-best-practices/ + - name: Get LiteLLM Latest Tag + id: current_app_tag + shell: bash + run: | + LATEST_TAG=$(git describe --tags --exclude "*dev*" --abbrev=0) + if [ -z "${LATEST_TAG}" ]; then + echo "latest_tag=latest" | tee -a $GITHUB_OUTPUT + else + echo "latest_tag=${LATEST_TAG}" | tee -a $GITHUB_OUTPUT + fi + + - name: Get last published chart version + id: current_version + shell: bash + run: | + CHART_LIST=$(helm show chart oci://${{ env.REGISTRY }}/${{ env.REPO_OWNER }}/${{ env.CHART_NAME }} 2>/dev/null || true) + if [ -z "${CHART_LIST}" ]; then + echo "current-version=1.0.0" | tee -a $GITHUB_OUTPUT + else + # Extract version and strip any prerelease suffix (e.g., 1.0.5-latest -> 1.0.5) + VERSION=$(printf '%s' "${CHART_LIST}" | grep '^version:' | awk 'BEGIN{FS=":"}{print $2}' | tr -d " " | cut -d'-' -f1) + echo "current-version=${VERSION}" | tee -a $GITHUB_OUTPUT + fi + env: + HELM_EXPERIMENTAL_OCI: '1' + + # Automatically update the helm chart version one "patch" level + - name: Bump release version + id: bump_version + uses: christian-draeger/increment-semantic-version@1.1.0 + with: + current-version: ${{ steps.current_version.outputs.current-version || '1.0.0' }} + version-fragment: 'bug' + + # Add suffix for non-stable releases (semantic versioning) - name: Calculate chart and app versions id: chart_version shell: bash run: | - INPUT_TAG="${{ github.event.inputs.tag }}" + BASE_VERSION="${{ steps.bump_version.outputs.next-version || '1.0.0' }}" RELEASE_TYPE="${{ github.event.inputs.release_type }}" + INPUT_TAG="${{ github.event.inputs.tag }}" - # Chart version = LiteLLM version without 'v' prefix (Helm semver convention) - # v1.81.0 -> 1.81.0, v1.81.0.rc.1 -> 1.81.0.rc.1 - CHART_VERSION="${INPUT_TAG#v}" - - # Add suffix for 'latest' releases (rc already has suffix in tag) - if [ "$RELEASE_TYPE" = "latest" ]; then - CHART_VERSION="${CHART_VERSION}-latest" + # Chart version (independent Helm chart versioning with release type suffix) + if [ "$RELEASE_TYPE" = "stable" ]; then + echo "version=${BASE_VERSION}" | tee -a $GITHUB_OUTPUT + else + echo "version=${BASE_VERSION}-${RELEASE_TYPE}" | tee -a $GITHUB_OUTPUT fi - # App version = Docker tag (keeps 'v' prefix to match Docker image tags) - APP_VERSION="${INPUT_TAG}" + # App version (must match Docker tags) + # stable/rc releases: Docker creates main-{tag}, so use the tag + # latest/dev releases: Docker only creates main-{release_type}, so use release_type + if [ "$RELEASE_TYPE" = "stable" ] || [ "$RELEASE_TYPE" = "rc" ]; then + APP_VERSION="${INPUT_TAG}" + else + APP_VERSION="${RELEASE_TYPE}" + fi - echo "version=${CHART_VERSION}" | tee -a $GITHUB_OUTPUT echo "app_version=${APP_VERSION}" | tee -a $GITHUB_OUTPUT - uses: ./.github/actions/helm-oci-chart-releaser with: name: ${{ env.CHART_NAME }} repository: ${{ env.REPO_OWNER }} - tag: ${{ steps.chart_version.outputs.version }} + tag: ${{ github.event.inputs.chartVersion || steps.chart_version.outputs.version || '1.0.0' }} app_version: ${{ steps.chart_version.outputs.app_version }} path: deploy/charts/${{ env.CHART_NAME }} registry: ${{ env.REGISTRY }} diff --git a/.github/workflows/ghcr_helm_deploy.yml b/.github/workflows/ghcr_helm_deploy.yml index 21b2eaafe19..f78dc6f0f3f 100644 --- a/.github/workflows/ghcr_helm_deploy.yml +++ b/.github/workflows/ghcr_helm_deploy.yml @@ -1,12 +1,10 @@ -# Standalone workflow to publish LiteLLM Helm Chart -# Note: The main ghcr_deploy.yml workflow also publishes the Helm chart as part of a full release +# this workflow is triggered by an API call when there is a new PyPI release of LiteLLM name: Build, Publish LiteLLM Helm Chart. New Release on: workflow_dispatch: inputs: - tag: - description: "LiteLLM version tag (e.g., v1.81.0)" - required: true + chartVersion: + description: "Update the helm chart's version to this" # Defines two custom environment variables for the workflow. Used for the Container registry domain, and a name for the Docker image that this workflow builds. env: @@ -33,22 +31,24 @@ jobs: run: | echo "REPO_OWNER=`echo ${{github.repository_owner}} | tr '[:upper:]' '[:lower:]'`" >>${GITHUB_ENV} - # Sync Helm chart version with LiteLLM release version (1-1 versioning) - - name: Calculate chart and app versions - id: chart_version + - name: Get LiteLLM Latest Tag + id: current_app_tag + uses: WyriHaximus/github-action-get-previous-tag@v1.3.0 + + - name: Get last published chart version + id: current_version shell: bash - run: | - INPUT_TAG="${{ github.event.inputs.tag }}" + run: helm show chart oci://${{ env.REGISTRY }}/${{ env.REPO_OWNER }}/litellm-helm | grep '^version:' | awk 'BEGIN{FS=":"}{print "current-version="$2}' | tr -d " " | tee -a $GITHUB_OUTPUT + env: + HELM_EXPERIMENTAL_OCI: '1' - # Chart version = LiteLLM version without 'v' prefix - # v1.81.0 -> 1.81.0 - CHART_VERSION="${INPUT_TAG#v}" - - # App version = Docker tag (keeps 'v' prefix) - APP_VERSION="${INPUT_TAG}" - - echo "version=${CHART_VERSION}" | tee -a $GITHUB_OUTPUT - echo "app_version=${APP_VERSION}" | tee -a $GITHUB_OUTPUT + # Automatically update the helm chart version one "patch" level + - name: Bump release version + id: bump_version + uses: christian-draeger/increment-semantic-version@1.1.0 + with: + current-version: ${{ steps.current_version.outputs.current-version || '0.1.0' }} + version-fragment: 'bug' - name: Lint helm chart run: helm lint deploy/charts/litellm-helm @@ -57,8 +57,8 @@ jobs: with: name: litellm-helm repository: ${{ env.REPO_OWNER }} - tag: ${{ steps.chart_version.outputs.version }} - app_version: ${{ steps.chart_version.outputs.app_version }} + tag: ${{ github.event.inputs.chartVersion || steps.bump_version.outputs.next-version || '0.1.0' }} + app_version: ${{ steps.current_app_tag.outputs.tag || 'latest' }} path: deploy/charts/litellm-helm registry: ${{ env.REGISTRY }} registry_username: ${{ github.actor }} diff --git a/.github/workflows/test-linting.yml b/.github/workflows/test-linting.yml index 7c5c269f899..35ebffeada3 100644 --- a/.github/workflows/test-linting.yml +++ b/.github/workflows/test-linting.yml @@ -73,4 +73,4 @@ jobs: - name: Check import safety run: | - poetry run python -c "from litellm import *" || (echo 'šØ import failed, this means you introduced unprotected imports! šØ'; exit 1) + poetry run python -c "from litellm import *" || (echo 'šØ import failed, this means you introduced unprotected imports! šØ'; exit 1) \ No newline at end of file diff --git a/.github/workflows/test-litellm.yml b/.github/workflows/test-litellm.yml index d9cf2e74a11..ba32dc1bf54 100644 --- a/.github/workflows/test-litellm.yml +++ b/.github/workflows/test-litellm.yml @@ -34,7 +34,7 @@ jobs: poetry run pip install "google-genai==1.22.0" poetry run pip install "google-cloud-aiplatform>=1.38" poetry run pip install "fastapi-offline==1.7.3" - poetry run pip install "python-multipart==0.0.22" + poetry run pip install "python-multipart==0.0.18" poetry run pip install "openapi-core" - name: Setup litellm-enterprise as local package run: | diff --git a/.github/workflows/test-mcp.yml b/.github/workflows/test-mcp.yml index e19e67c9c4f..64363c6f96d 100644 --- a/.github/workflows/test-mcp.yml +++ b/.github/workflows/test-mcp.yml @@ -34,8 +34,8 @@ jobs: poetry run pip install "pytest-cov==5.0.0" poetry run pip install "pytest-asyncio==0.21.1" poetry run pip install "respx==0.22.0" - poetry run pip install "pydantic==2.11.0" - poetry run pip install "mcp==1.25.0" + poetry run pip install "pydantic==2.10.2" + poetry run pip install "mcp==1.10.1" poetry run pip install pytest-xdist - name: Setup litellm-enterprise as local package diff --git a/.github/workflows/test-model-map.yaml b/.github/workflows/test-model-map.yaml deleted file mode 100644 index ae5ac402e23..00000000000 --- a/.github/workflows/test-model-map.yaml +++ /dev/null @@ -1,15 +0,0 @@ -name: Validate model_prices_and_context_window.json - -on: - pull_request: - branches: [ main ] - -jobs: - validate-model-prices-json: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Validate model_prices_and_context_window.json - run: | - jq empty model_prices_and_context_window.json diff --git a/.gitignore b/.gitignore index 32f1b6f8e1f..9d9e28dc466 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ .python-version .venv -.venv_policy_test .env .newenv newenv/* @@ -60,6 +59,10 @@ litellm/proxy/_super_secret_config.yaml litellm/proxy/myenv/bin/activate litellm/proxy/myenv/bin/Activate.ps1 myenv/* +litellm/proxy/_experimental/out/_next/ +litellm/proxy/_experimental/out/404/index.html +litellm/proxy/_experimental/out/model_hub/index.html +litellm/proxy/_experimental/out/onboarding/index.html litellm/tests/log.txt litellm/tests/langfuse.log litellm/tests/langfuse.log @@ -72,6 +75,9 @@ tests/local_testing/log.txt litellm/proxy/_new_new_secret_config.yaml litellm/proxy/custom_guardrail.py .mypy_cache/* +litellm/proxy/_experimental/out/404.html +litellm/proxy/_experimental/out/404.html +litellm/proxy/_experimental/out/model_hub.html .mypy_cache/* litellm/proxy/application.log tests/llm_translation/vertex_test_account.json @@ -93,6 +99,7 @@ litellm_config.yaml litellm/proxy/to_delete_loadtest_work/* update_model_cost_map.py tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py +litellm/proxy/_experimental/out/guardrails/index.html scripts/test_vertex_ai_search.py LAZY_LOADING_IMPROVEMENTS.md **/test-results diff --git a/AGENTS.md b/AGENTS.md index 5a48049ef45..61afbd035fe 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -51,14 +51,12 @@ LiteLLM is a unified interface for 100+ LLMs that: ### MAKING CODE CHANGES FOR THE UI (IGNORE FOR BACKEND) -1. **Tremor is DEPRECATED, do not use Tremor components in new features/changes** - - The only exception is the Tremor Table component and its required Tremor Table sub components. - -2. **Use Common Components as much as possible**: +1. **Use Common Components as much as possible**: - These are usually defined in the `common_components` directory - Use these components as much as possible and avoid building new components unless needed + - Tremor components are deprecated; prefer using Ant Design (AntD) as much as possible -3. **Testing**: +2. **Testing**: - The codebase uses **Vitest** and **React Testing Library** - **Query Priority Order**: Use query methods in this order: `getByRole`, `getByLabelText`, `getByPlaceholderText`, `getByText`, `getByTestId` - **Always use `screen`** instead of destructuring from `render()` (e.g., use `screen.getByText()` not `getByText`) diff --git a/Dockerfile b/Dockerfile index 2987a44b394..0e7a8412bbc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -46,8 +46,8 @@ FROM $LITELLM_RUNTIME_IMAGE AS runtime # Ensure runtime stage runs as root USER root -# Install runtime dependencies (libsndfile needed for audio processing on ARM64) -RUN apk add --no-cache bash openssl tzdata nodejs npm python3 py3-pip libsndfile +# Install runtime dependencies +RUN apk add --no-cache bash openssl tzdata nodejs npm python3 py3-pip WORKDIR /app # Copy the current directory contents into the container at /app @@ -69,8 +69,8 @@ RUN find /usr/lib -type f -path "*/tornado/test/*" -delete && \ # Convert Windows line endings to Unix and make executable RUN sed -i 's/\r$//' docker/install_auto_router.sh && chmod +x docker/install_auto_router.sh && ./docker/install_auto_router.sh -# Generate prisma client using the correct schema -RUN prisma generate --schema=./litellm/proxy/schema.prisma +# Generate prisma client +RUN prisma generate # Convert Windows line endings to Unix for entrypoint scripts RUN sed -i 's/\r$//' docker/entrypoint.sh && chmod +x docker/entrypoint.sh RUN sed -i 's/\r$//' docker/prod_entrypoint.sh && chmod +x docker/prod_entrypoint.sh diff --git a/README.md b/README.md index 77adddf8978..75a23faa5c1 100644 --- a/README.md +++ b/README.md @@ -258,19 +258,6 @@ LiteLLM Performance: **8ms P95 latency** at 1k RPS (See benchmarks [here](https: Support for more providers. Missing a provider or LLM Platform, raise a [feature request](https://github.com/BerriAI/litellm/issues/new?assignees=&labels=enhancement&projects=&template=feature_request.yml&title=%5BFeature%5D%3A+). -## OSS Adopters - -
Netflix |
-
=1e7/2&&++S;do f=0,(u=e(T,v,P,m))<0?(b=v[0],P!=m&&(b=1e7*b+(v[1]||0)),(f=b/S|0)>1?(f>=1e7&&(f=1e7-1),h=(p=t(T,f)).length,m=v.length,1==(u=e(p,v,h,m))&&(f--,r(p,P 0?i=i.charAt(0)+"."+i.slice(1)+j(n):a>1&&(i=i.charAt(0)+"."+i.slice(1)),i=i+(o<0?"e":"e+")+o):o<0?(i="0."+j(-o-1)+i,r&&(n=r-a)>0&&(i+=j(n))):o>=a?(i+=j(o+1-a),r&&(n=r-o-1)>0&&(i=i+"."+j(n))):((n=o+1)0&&(o+1===a&&(i+="."),i+=j(n))),t.s<0?"-"+i:i}function M(t,e){if(t.length>e)return t.length=e,!0}function _(t){if(!t||"object"!=typeof t)throw Error(c+"Object expected");var e,r,n,o=["precision",1,1e9,"rounding",0,8,"toExpNeg",-1/0,0,"toExpPos",0,1/0];for(e=0;e-1&&t%1==0&&t0&&360>Math.abs(g-x)?d({cx:a,cy:c,innerRadius:s,outerRadius:p,cornerRadius:Math.min(S,j/2),forceCornerRadius:m,cornerIsExternal:b,startAngle:g,endAngle:x}):h({cx:a,cy:c,innerRadius:s,outerRadius:p,startAngle:g,endAngle:x}),n.createElement("path",l({},(0,i.L6)(r,!0),{className:O,d:e,role:"img"}))}},14870:function(t,e,r){"use strict";r.d(e,{v:function(){return N}});var n=r(2265),o=r(75551),i=r.n(o);let a=Math.cos,u=Math.sin,c=Math.sqrt,l=Math.PI,s=2*l;var f={draw(t,e){let r=c(e/l);t.moveTo(r,0),t.arc(0,0,r,0,s)}};let p=c(1/3),h=2*p,d=u(l/10)/u(7*l/10),y=u(s/10)*d,v=-a(s/10)*d,m=c(3),b=c(3)/2,g=1/c(12),x=(g/2+1)*3;var w=r(76115),O=r(67790);c(3),c(3);var j=r(61994),S=r(82944);function P(t){return(P="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}var E=["type","size","sizeType"];function k(){return(k=Object.assign?Object.assign.bind():function(t){for(var e=1;e=s&&f<=l}return r?p(p({},e),{},{radius:o,angle:f+360*Math.min(Math.floor(e.startAngle/360),Math.floor(e.endAngle/360))}):null},j=function(t){return(0,i.isValidElement)(t)||u()(t)||"boolean"==typeof t?"":t.className}},82944:function(t,e,r){"use strict";r.d(e,{$R:function(){return R},Bh:function(){return B},Gf:function(){return j},L6:function(){return N},NN:function(){return k},TT:function(){return M},eu:function(){return L},jf:function(){return T},rL:function(){return D},sP:function(){return A}});var n=r(13735),o=r.n(n),i=r(77571),a=r.n(i),u=r(42715),c=r.n(u),l=r(86757),s=r.n(l),f=r(28302),p=r.n(f),h=r(2265),d=r(14326),y=r(16630),v=r(46485),m=r(41637),b=["children"],g=["children"];function x(t,e){if(null==t)return{};var r,n,o=function(t,e){if(null==t)return{};var r={};for(var n in t)if(Object.prototype.hasOwnProperty.call(t,n)){if(e.indexOf(n)>=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n