diff --git a/.circleci/AGENTS.md b/.circleci/AGENTS.md
deleted file mode 100644
index b3591145a3e..00000000000
--- a/.circleci/AGENTS.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# .circleci
-
-The `integration` workflow on `main` runs from a scheduled pipeline in the CircleCI project settings, cron `9 0-8,14-23 * * *` (UTC) on `main` with no pipeline parameters. It has no `triggers:` block in this file because a workflow with one runs only on its schedule, so the API pipelines that set `run_migration_tests` or `routing_parity_base` would stop running it. If main stops getting integration runs, check that the schedule still exists and recreate it with these values
-
-Jobs clone with the `checkout_over_https` command, never the built-in `checkout` step, because CircleCI falls back to the project's SSH checkout key without its GitHub App access, GitHub rejects that key, and the public repo needs no key. It fetches the job's commit with full history and no file contents (`--filter=blob:none`), like the built-in step did, so `scripts/path_filter.sh` can find a merge base
-
-A step that checks out files from another commit fetches that commit with `--no-filter`, as `routing_parity` does, because the clone has no file contents and git would otherwise download each missing file in its own request, which took 210 to 896 seconds for the routing parity base
diff --git a/.circleci/config.yml b/.circleci/config.yml
deleted file mode 100644
index 95ee0a6412c..00000000000
--- a/.circleci/config.yml
+++ /dev/null
@@ -1,1780 +0,0 @@
-version: 2.1
-parameters:
- run_migration_tests:
- type: boolean
- default: false
- migration_candidate_image:
- type: string
- default: ""
- migration_baseline_image:
- type: string
- default: "ghcr.io/berriai/litellm-database:v1.102.0"
- migration_source_sha:
- type: string
- default: ""
- routing_parity_base:
- type: string
- default: ""
-orbs:
- codecov: codecov/codecov@4.0.1
- node: circleci/node@5.1.0 # Add this line to declare the node orb
- win: circleci/windows@5.0 # Add Windows orb
-
-commands:
- checkout_over_https:
- parameters:
- revision:
- type: string
- default: ""
- shell:
- type: string
- default: /bin/bash -eo pipefail
- steps:
- - run:
- name: Check out the public repository over HTTPS
- shell: << parameters.shell >>
- environment:
- REQUESTED_REVISION: << parameters.revision >>
- command: |
- revision="${REQUESTED_REVISION:-$CIRCLE_SHA1}"
- [[ "$revision" =~ ^[0-9a-f]{40}$ ]] || exit 1
- git init
- git remote add origin https://github.com/BerriAI/litellm.git
- for attempt in 1 2 3; do
- git fetch --filter=blob:none origin "$revision" && git checkout --detach FETCH_HEAD && break
- [[ $attempt -lt 3 ]] || exit 1
- sleep 5
- done
- skip_if_unrelated_changes:
- parameters:
- category:
- type: enum
- enum: ["backend", "client"]
- default: "backend"
- steps:
- - run:
- name: "Skip job when no << parameters.category >>-relevant files changed"
- command: bash .circleci/scripts/path_filter.sh << parameters.category >>
- setup_google_dns:
- steps:
- - run:
- name: "Configure Google DNS"
- command: |
- # Backup original resolv.conf
- sudo cp /etc/resolv.conf /etc/resolv.conf.backup
- # Add both local and Google DNS servers
- echo "nameserver 127.0.0.11" | sudo tee /etc/resolv.conf
- echo "nameserver 8.8.8.8" | sudo tee -a /etc/resolv.conf
- echo "nameserver 8.8.4.4" | sudo tee -a /etc/resolv.conf
- wait_for_service:
- description: "Poll a TCP or HTTP endpoint until it responds (replaces dockerize -wait)"
- parameters:
- url:
- type: string
- timeout:
- type: string
- default: "60"
- steps:
- - run:
- name: "Wait for << parameters.url >>"
- command: |
- TIMEOUT=<< parameters.timeout >>
- URL="<< parameters.url >>"
- ELAPSED=0
- echo "Waiting up to ${TIMEOUT}s for ${URL} ..."
- if echo "$URL" | grep -q '^tcp://'; then
- HOST=$(echo "$URL" | sed 's|tcp://||' | cut -d: -f1)
- PORT=$(echo "$URL" | sed 's|tcp://||' | cut -d: -f2)
- while ! bash -c "echo > /dev/tcp/$HOST/$PORT" 2>/dev/null; do
- sleep 2; ELAPSED=$((ELAPSED+2))
- if [ "$ELAPSED" -ge "$TIMEOUT" ]; then echo "Timed out"; exit 1; fi
- done
- else
- while ! curl -sf --max-time 5 "$URL" > /dev/null 2>&1; do
- sleep 2; ELAPSED=$((ELAPSED+2))
- if [ "$ELAPSED" -ge "$TIMEOUT" ]; then echo "Timed out"; exit 1; fi
- done
- fi
- echo "Service ready after ${ELAPSED}s"
- install_helm:
- steps:
- - run:
- name: Install Helm v3.17.3
- command: |
- curl -sSLf -o /tmp/helm.tar.gz \
- https://get.helm.sh/helm-v3.17.3-linux-amd64.tar.gz
- echo "ee88b3c851ae6466a3de507f7be73fe94d54cbf2987cbaa3d1a3832ea331f2cd /tmp/helm.tar.gz" | sha256sum -c -
- sudo tar -C /usr/local/bin --strip-components=1 -xzf /tmp/helm.tar.gz linux-amd64/helm
- rm -f /tmp/helm.tar.gz
- install_kind:
- steps:
- - run:
- name: Install Kind v0.20.0
- command: |
- curl -sSLf -o /tmp/kind \
- https://kind.sigs.k8s.io/dl/v0.20.0/kind-linux-amd64
- echo "513a7213d6d3332dd9ef27c24dab35e5ef10a04fa27274fe1c14d8a246493ded /tmp/kind" | sha256sum -c -
- chmod +x /tmp/kind
- sudo mv /tmp/kind /usr/local/bin/kind
- install_uv:
- description: "Install pinned uv (0.10.9) with checksum verification. Adds ~/.local/bin to PATH."
- steps:
- - run:
- name: Install uv (pinned 0.10.9)
- command: |
- curl -LsSf -o /tmp/uv-install.sh https://astral.sh/uv/0.10.9/install.sh
- echo "7fc46e39cb97290b57169c0c813a17970585ac519139f19006453c99b5f2f45f /tmp/uv-install.sh" | sha256sum -c -
- env UV_NO_MODIFY_PATH=1 sh /tmp/uv-install.sh
- rm -f /tmp/uv-install.sh
- echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$BASH_ENV"
- export PATH="$HOME/.local/bin:$PATH"
- install_node:
- description: "Install the Node.js version pinned in ui/litellm-dashboard/.nvmrc (24.19.0, which bundles npm 11.17.0) with checksum verification, and prepend it to PATH. Run this on any executor whose image does not already ship that version, or `npm ci` in ui/litellm-dashboard fails EBADENGINE against the engines floor. Installs into /opt/node rather than over /usr/local on purpose: cimg/python:*-browsers ships its own node there, and unpacking the tarball on top of it leaves npm 11.17 files merged with the image's npm 11.9 tree, which reports the new version and then exits 1 on `npm ci` with no error text at all. Requires checkout, which the .nvmrc drift check reads."
- steps:
- - run:
- name: Install Node.js 24.19.0
- command: |
- NODE_VERSION="24.19.0"
- NODE_TARBALL="node-v${NODE_VERSION}-linux-x64.tar.xz"
- NODE_EXPECTED_SHA="14b342e71204f811bde6153be8e04b62aef63c236fef92b55f9c83154b409647"
- NVMRC_VERSION="$(tr -d '[:space:]' < ui/litellm-dashboard/.nvmrc)"
- if [ "$NVMRC_VERSION" != "$NODE_VERSION" ]; then
- echo "install_node: ui/litellm-dashboard/.nvmrc pins ${NVMRC_VERSION} but this command pins ${NODE_VERSION}; update NODE_VERSION and NODE_EXPECTED_SHA together" >&2
- exit 1
- fi
- curl -sSLf -o "/tmp/${NODE_TARBALL}" "https://nodejs.org/dist/v${NODE_VERSION}/${NODE_TARBALL}"
- echo "${NODE_EXPECTED_SHA} /tmp/${NODE_TARBALL}" | sha256sum -c -
- sudo mkdir -p /opt/node
- sudo tar -xJf "/tmp/${NODE_TARBALL}" -C /opt/node --strip-components=1
- rm -f "/tmp/${NODE_TARBALL}"
- echo 'export PATH="/opt/node/bin:$PATH"' >> "$BASH_ENV"
- export PATH="/opt/node/bin:$PATH"
- node --version
- npm --version
- install_rust:
- description: "Install pinned rustup (1.28.2) and Rust toolchain (1.98.0) with checksum verification. Adds ~/.cargo/bin to PATH. Run this before any `uv sync` or `uv build` of the workspace: the root package builds litellm-rust through maturin, and on an image without cargo maturin fetches an unpinned rustup and a floating toolchain by itself. Also restores the dev-profile cargo cache that save_cargo_target writes on main, minus the workspace crates' fingerprints so those always rebuild from the checked-out source."
- steps:
- - run:
- name: Install Rust (rustup 1.28.2, toolchain 1.98.0)
- command: |
- case "$(uname -m)" in
- x86_64)
- RUSTUP_TRIPLE=x86_64-unknown-linux-gnu
- RUSTUP_SHA256=20a06e644b0d9bd2fbdbfd52d42540bdde820ea7df86e92e533c073da0cdd43c
- ;;
- aarch64)
- RUSTUP_TRIPLE=aarch64-unknown-linux-gnu
- RUSTUP_SHA256=e3853c5a252fca15252d07cb23a1bdd9377a8c6f3efa01531109281ae47f841c
- ;;
- *)
- echo "install_rust: unsupported architecture $(uname -m)" >&2
- exit 1
- ;;
- esac
- curl -sSLf --retry 3 --retry-all-errors --retry-max-time 120 --connect-timeout 15 --max-time 60 -o /tmp/rustup-init \
- "https://static.rust-lang.org/rustup/archive/1.28.2/${RUSTUP_TRIPLE}/rustup-init"
- echo "${RUSTUP_SHA256} /tmp/rustup-init" | sha256sum -c -
- chmod +x /tmp/rustup-init
- /tmp/rustup-init -y --no-modify-path --profile minimal --default-toolchain 1.98.0
- rm -f /tmp/rustup-init
- echo 'export PATH="$HOME/.cargo/bin:$PATH"' >> "$BASH_ENV"
- echo 'export CARGO_INCREMENTAL=0' >> "$BASH_ENV"
- export PATH="$HOME/.cargo/bin:$PATH"
- rustc --version
- cargo --version
- { rustc -vV; cc --version; cat /etc/os-release; } > /tmp/cargo-build-env
- - restore_cache:
- keys:
- - v1-cargo-dev-{{ checksum "/tmp/cargo-build-env" }}-{{ checksum "litellm-rust/Cargo.lock" }}
- - v1-cargo-dev-{{ checksum "/tmp/cargo-build-env" }}-
- - run:
- name: Force a rebuild of the workspace crates restored from the cargo cache
- command: rm -rf litellm-rust/target/debug/.fingerprint/litellm-*
- save_cargo_target:
- steps:
- - when:
- condition:
- equal: [main, << pipeline.git.branch >>]
- steps:
- - save_cache:
- key: v1-cargo-dev-{{ checksum "/tmp/cargo-build-env" }}-{{ checksum "litellm-rust/Cargo.lock" }}
- paths:
- - ~/.cargo/registry
- - ~/project/litellm-rust/target/debug
- start_postgres:
- description: "Start a postgres-db container on port 5432 and wait until it accepts connections."
- parameters:
- db_name:
- type: string
- default: circle_test
- image:
- type: string
- default: postgres:14@sha256:6a70deda415ec296f977890e11aba04a0db9f632a362e3fce45e845e3db74f26
- server_args:
- type: string
- default: ""
- steps:
- - run:
- name: Start PostgreSQL
- command: |
- docker run -d \
- --name postgres-db \
- -e POSTGRES_USER=postgres \
- -e POSTGRES_PASSWORD=postgres \
- -e POSTGRES_DB=<< parameters.db_name >> \
- -p 5432:5432 \
- << parameters.image >> << parameters.server_args >>
- - wait_for_service:
- url: tcp://localhost:5432
- timeout: "60"
- start_redis:
- description: "Start a redis container on port 6379 and wait until it accepts connections. Use this to isolate a job from the shared remote Redis so concurrent CI pipelines don't contend for pod locks or buffer keys."
- steps:
- - run:
- name: Start Redis
- command: |
- docker run -d \
- --name redis-cache \
- -p 6379:6379 \
- redis:7-alpine@sha256:7aec734b2bb298a1d769fd8729f13b8514a41bf90fcdd1f38ec52267fbaa8ee6
- - wait_for_service:
- url: tcp://localhost:6379
- timeout: "60"
- install_codecov_cli:
- steps:
- - run:
- name: Install Codecov CLI (pinned v11.3.1)
- when: always
- command: |
- curl -sSLf -o /tmp/codecov https://cli.codecov.io/v11.3.1/linux/codecov
- curl -sSLf -o /tmp/codecov.SHA256SUM https://cli.codecov.io/v11.3.1/linux/codecov.SHA256SUM
- [ "$(cat /tmp/codecov.SHA256SUM)" = "ca1d64196d2d34771084afe76ea657d581bf628e31d993ff8e52ea09cc88a56d codecov" ]
- (cd /tmp && sha256sum -c codecov.SHA256SUM)
- chmod +x /tmp/codecov
- mkdir -p "$HOME/.local/bin"
- mv /tmp/codecov "$HOME/.local/bin/codecov"
- start_openai_record_replay_proxy:
- description: "Start the record/replay proxy (tests/_openai_record_replay_proxy.py) on host port 8090 and wait until healthy. Models whose api_base points here replay Redis cache hits. Cache misses call the live provider and store its response, so these legacy E2E jobs still require provider availability and may incur provider charges. The default upstream is OpenAI; a non-OpenAI model must point its api_base at /__recorder_upstream// so the recorder forwards there instead of defaulting to OpenAI. Run after uv deps are synced."
- steps:
- - run:
- name: Start record/replay proxy
- background: true
- command: |
- CASSETTE_REDIS_URL="$CASSETTE_REDIS_URL" \
- RECORDER_UPSTREAM_BASE_URL="https://api.openai.com" \
- uv run --no-sync python tests/_openai_record_replay_proxy.py --host 0.0.0.0 --port 8090
- - run:
- name: Wait for record/replay proxy
- command: |
- for i in $(seq 1 30); do
- if curl -sf http://localhost:8090/__recorder_health >/dev/null 2>&1; then
- echo "record/replay proxy is up"
- exit 0
- fi
- sleep 1
- done
- echo "record/replay proxy did not become ready" >&2
- exit 1
- start_fake_openai_endpoint:
- description: "Start the canned OpenAI mock (tests/_fake_openai_endpoint_server.py) on host port 8190 and wait until healthy. Models whose api_base points here (via FAKE_OPENAI_API_BASE) get well-formed chat/text/embedding responses with realistic usage, so the E2E run neither pays for nor depends on the live provider. A request whose model is '429' returns HTTP 429 for rate-limit/cooldown tests. Run after uv deps are synced."
- steps:
- - run:
- name: Start fake OpenAI endpoint
- background: true
- command: |
- uv run --no-sync python tests/_fake_openai_endpoint_server.py --host 0.0.0.0 --port 8190
- - run:
- name: Wait for fake OpenAI endpoint
- command: |
- for i in $(seq 1 30); do
- if curl -sf http://localhost:8190/health >/dev/null 2>&1; then
- echo "fake OpenAI endpoint is up"
- exit 0
- fi
- sleep 1
- done
- echo "fake OpenAI endpoint did not become ready" >&2
- exit 1
- setup_litellm_enterprise_pip:
- steps:
- - run:
- name: "Install local version of litellm-enterprise"
- command: |
- # litellm-enterprise is a uv workspace member and is already installed
- # by the main `uv sync --all-groups --all-extras`. Do NOT run
- # `uv sync --package litellm-enterprise` here — that overwrites the
- # shared .venv and strips out dev/test deps (pytest, prisma, etc.).
- uv run --no-sync python -c "import litellm_enterprise; print('litellm-enterprise OK:', litellm_enterprise.__file__)"
- install_windows_toolchain:
- steps:
- - run:
- name: Install Rust and uv
- no_output_timeout: 30m
- command: |
- $rustupInit = Join-Path $env:TEMP "rustup-init.exe"
- $rustupVersion = "1.28.2"
- $rustupUrl = "https://static.rust-lang.org/rustup/archive/$rustupVersion/x86_64-pc-windows-msvc/rustup-init.exe"
- Invoke-WebRequest -Uri $rustupUrl -OutFile $rustupInit
- $rustupExpected = "88d8258dcf6ae4f7a80c7d1088e1f36fa7025a1cfd1343731b4ee6f385121fc0"
- $rustupActual = (Get-FileHash -Path $rustupInit -Algorithm SHA256).Hash.ToLower()
- if ($rustupActual -ne $rustupExpected) {
- throw "rustup installer hash mismatch: expected $rustupExpected got $rustupActual"
- }
- & $rustupInit -y --profile minimal --default-toolchain 1.98.0
- if ($LASTEXITCODE -ne 0) {
- exit $LASTEXITCODE
- }
- Remove-Item $rustupInit
- $cargoBin = Join-Path $HOME ".cargo\bin"
- $env:Path = "$cargoBin;$env:Path"
- rustc --version
- cargo --version
- $installer = Join-Path $env:TEMP "uv-install.ps1"
- Invoke-WebRequest -Uri https://astral.sh/uv/0.10.9/install.ps1 -OutFile $installer
- $expected = "d43ffff8d28e7d1e7d1831a212465f12b24a43c7f87f386e95e2a5915aee5d7d"
- $actual = (Get-FileHash -Path $installer -Algorithm SHA256).Hash.ToLower()
- if ($actual -ne $expected) {
- throw "uv installer hash mismatch: expected $expected got $actual"
- }
- & $installer
- Remove-Item $installer
- $uvBin = Join-Path $HOME ".local\bin"
- $env:Path = "$uvBin;$env:Path"
- if (!(Test-Path $PROFILE)) {
- New-Item -ItemType File -Force -Path $PROFILE | Out-Null
- }
- if (-not (Select-String -Path $PROFILE -SimpleMatch $uvBin -Quiet)) {
- Add-Content -Path $PROFILE -Value "`$env:Path = `"$uvBin;`$env:Path`""
- }
- if (-not (Select-String -Path $PROFILE -SimpleMatch $cargoBin -Quiet)) {
- Add-Content -Path $PROFILE -Value "`$env:Path = `"$cargoBin;`$env:Path`""
- }
- setup_litellm_test_deps:
- parameters:
- checkout:
- type: boolean
- default: true
- steps:
- - when:
- condition: << parameters.checkout >>
- steps:
- - checkout_over_https
- - setup_google_dns
- - install_uv
- - install_rust
- - restore_cache:
- keys:
- - v3-integration-uv-cache-{{ checksum "uv.lock" }}
- - run:
- name: Install Dependencies
- command: |
- uv sync --frozen --all-groups --all-extras --python 3.12
- - setup_litellm_enterprise_pip
- - save_cache:
- paths:
- - ~/.cache/uv
- key: v3-integration-uv-cache-{{ checksum "uv.lock" }}
- - save_cargo_target
-
-jobs:
- windows_release_wheel:
- executor:
- name: win/default
- shell: powershell.exe
- size: xlarge
- working_directory: ~/project
- environment:
- UV_PYTHON: "3.11"
- CARGO_HTTP_MULTIPLEXING: "false"
- CARGO_NET_RETRY: "5"
- steps:
- - checkout_over_https:
- shell: bash.exe
- - run:
- name: Skip job when no windows-release-relevant files changed
- shell: bash.exe
- command: bash .circleci/scripts/path_filter.sh windows-release
- - run:
- name: Install Python 3.11.9 from python.org
- no_output_timeout: 6m
- command: |
- $ProgressPreference = "SilentlyContinue"
- $installer = Join-Path $env:TEMP "python-3.11.9-amd64.exe"
- Invoke-WebRequest -Uri https://www.python.org/ftp/python/3.11.9/python-3.11.9-amd64.exe -OutFile $installer -TimeoutSec 120
- $expected = "5ee42c4eee1e6b4464bb23722f90b45303f79442df63083f05322f1785f5fdde"
- $actual = (Get-FileHash -Path $installer -Algorithm SHA256).Hash.ToLower()
- if ($actual -ne $expected) {
- throw "Python installer hash mismatch: expected $expected got $actual"
- }
- $arguments = "/quiet InstallAllUsers=1 PrependPath=1 Include_test=0 Include_doc=0 Include_launcher=0 TargetDir=C:\Python311"
- $process = Start-Process -FilePath $installer -ArgumentList $arguments -PassThru
- if (-not $process.WaitForExit(300000)) {
- $process.Kill()
- throw "Python installer did not finish within 5 minutes"
- }
- if ($process.ExitCode -ne 0) {
- throw "Python installer exited with $($process.ExitCode)"
- }
- Remove-Item $installer
- & C:\Python311\python.exe --version
- - install_windows_toolchain
- - run:
- name: Record the Rust build environment for the release cargo cache key
- command: |
- & "$HOME\.cargo\bin\rustc.exe" -vV | Out-File -Encoding ascii .cargo-build-env
- - restore_cache:
- keys:
- - v1-cargo-release-windows-{{ checksum ".cargo-build-env" }}-{{ checksum "litellm-rust/Cargo.lock" }}
- - v1-cargo-release-windows-{{ checksum ".cargo-build-env" }}-
- - run:
- name: Force a rebuild of the workspace crates restored from the cargo cache
- command: |
- $fingerprints = "litellm-rust/target/release/.fingerprint"
- if (Test-Path $fingerprints) {
- Get-ChildItem -Path $fingerprints -Filter "litellm-*" | Remove-Item -Recurse -Force
- }
- - run:
- name: Build the release wheel and install it under a worst-case MAX_PATH prefix
- no_output_timeout: 30m
- environment:
- UV_HTTP_TIMEOUT: "300"
- command: |
- $env:Path = "C:\Python311;C:\Python311\Scripts;$HOME\.cargo\bin;$HOME\.local\bin;$env:Path"
- uv build --wheel --out-dir dist
- if ($LASTEXITCODE -ne 0) {
- exit $LASTEXITCODE
- }
- python tests/windows_tests/check_windows_wheel_install.py
- - when:
- condition:
- equal: [main, << pipeline.git.branch >>]
- steps:
- - save_cache:
- key: v1-cargo-release-windows-{{ checksum ".cargo-build-env" }}-{{ checksum "litellm-rust/Cargo.lock" }}
- paths:
- - ~/.cargo/registry
- - ~/project/litellm-rust/target/release
-
- base_sdk_install:
- docker:
- - image: cimg/python:3.12@sha256:9c796c23c84e84a66a964acb508d39dc5433c81a47e07efd56dccbbc2427e07c
- auth:
- username: ${DOCKERHUB_USERNAME}
- password: ${DOCKERHUB_PASSWORD}
- working_directory: ~/project
- steps:
- - checkout_over_https
- - setup_google_dns
- - install_uv
- - install_rust
- - run:
- name: Build the wheel
- no_output_timeout: 30m
- environment:
- UV_HTTP_TIMEOUT: "300"
- command: |
- uv build --wheel --out-dir dist
- - run:
- name: Install the wheel with no extras and smoke-check it
- environment:
- UV_HTTP_TIMEOUT: "300"
- command: |
- uv venv /tmp/base-sdk --python 3.12
- VIRTUAL_ENV=/tmp/base-sdk uv pip install dist/*.whl
- /tmp/base-sdk/bin/python tests/base_sdk_tests/check_base_sdk_install.py
- - run:
- name: Guard against MAX_PATH-busting packaged wheel paths
- command: |
- python3 tests/windows_tests/check_windows_wheel_install.py --lengths-only
-
- helm_chart_testing:
- machine:
- image: ubuntu-2204:2024.04.1 # Use machine executor instead of docker
- resource_class: medium
- working_directory: ~/project
-
- steps:
- - checkout_over_https
- - skip_if_unrelated_changes
- - attach_workspace:
- at: ~/project
- - setup_google_dns
- - install_helm
- - install_kind
-
- # Install kubectl (pinned version with hardcoded checksum)
- - run:
- name: Install kubectl v1.31.4
- command: |
- curl -sSLf -o /tmp/kubectl \
- https://dl.k8s.io/release/v1.31.4/bin/linux/amd64/kubectl
- echo "298e19e9c6c17199011404278f0ff8168a7eca4217edad9097af577023a5620f /tmp/kubectl" | sha256sum -c -
- chmod +x /tmp/kubectl
- sudo mv /tmp/kubectl /usr/local/bin/
-
- # Create kind cluster
- - run:
- name: Create Kind Cluster
- command: |
- kind create cluster --name litellm-test
-
- - run:
- name: Load Docker Database Image for helm tests
- command: |
- zstd -d litellm-docker-database.tar.zst --stdout | docker load
- IMAGE_TAG=${CIRCLE_SHA1:-ci}
- docker tag litellm-docker-database:ci litellm-ci:${IMAGE_TAG}
-
- - run:
- name: Load Docker image into Kind
- command: |
- IMAGE_TAG=${CIRCLE_SHA1:-ci}
- kind load docker-image litellm-ci:${IMAGE_TAG} --name litellm-test
-
- # Run helm lint
- - run:
- name: Run helm lint
- command: |
- helm lint ./helm/litellm-helm
-
- # Run helm tests
- - run:
- name: Run helm tests
- command: |
- IMAGE_TAG=${CIRCLE_SHA1:-ci}
- helm install litellm ./helm/litellm-helm -f ./helm/litellm-helm/ci/test-values.yaml \
- --set image.repository=litellm-ci \
- --set image.tag=${IMAGE_TAG} \
- --set image.pullPolicy=Never
- if ! kubectl rollout status deployment/litellm --timeout=180s; then
- kubectl describe pods -l app.kubernetes.io/name=litellm
- kubectl logs -l app.kubernetes.io/name=litellm --tail=200
- exit 1
- fi
-
- echo "Printing pod logs..."
- kubectl logs $(kubectl get pods -l app.kubernetes.io/name=litellm -o jsonpath="{.items[0].metadata.name}")
-
- # Run the helm tests
- helm test litellm --logs
-
- # Cleanup
- - run:
- name: Cleanup
- command: |
- kind delete cluster --name litellm-test
- when: always # This ensures cleanup runs even if previous steps fail
-
- db_migration_disable_update_check:
- machine:
- image: ubuntu-2204:2024.04.1
- resource_class: medium
- working_directory: ~/project
- steps:
- - checkout_over_https
- - run:
- name: Generate LiteLLM master key
- command: |
- key="$(openssl rand -hex 16)"
- printf 'export LITELLM_MASTER_KEY=sk-%s\n' "$key" >> "$BASH_ENV"
- - skip_if_unrelated_changes
- - setup_google_dns
- - install_uv
- - install_rust
- - run:
- name: Install Dependencies
- command: |
- uv sync --frozen --all-groups --all-extras --python 3.12
- - start_fake_openai_endpoint
- - start_postgres:
- db_name: litellm_test
- - attach_workspace:
- at: ~/project
- - run:
- name: Load Docker Database Image
- command: |
- zstd -d litellm-docker-database.tar.zst --stdout | docker load
- docker images | grep litellm-docker-database
- - run:
- name: Seed database with real schema
- command: |
- docker run -d \
- -p 4001:4000 \
- -e DATABASE_URL="postgresql://postgres:postgres@host.docker.internal:5432/litellm_test" \
- -e LITELLM_MASTER_KEY="$LITELLM_MASTER_KEY" \
- --name schema-seed \
- --add-host=host.docker.internal:host-gateway \
- -v $(pwd)/litellm/proxy/example_config_yaml/simple_config.yaml:/app/config.yaml \
- litellm-docker-database:ci \
- --config /app/config.yaml \
- --port 4000 \
- --use_prisma_db_push
- - wait_for_service:
- url: http://localhost:4001
- timeout: "300"
- - run:
- name: Stop schema seed container
- command: docker stop schema-seed && docker rm schema-seed
- - run:
- name: Run Docker container with bad schema and disabled updates
- command: |
- docker run -d \
- -p 4000:4000 \
- -e LITELLM_DANGEROUSLY_PERMIT_WEAK_OR_UNSET_MASTER_KEY=true \
- -e DATABASE_URL="postgresql://postgres:postgres@host.docker.internal:5432/litellm_test" \
- -e DEFAULT_NUM_WORKERS_LITELLM_PROXY=1 \
- -e DISABLE_SCHEMA_UPDATE="True" \
- -e FAKE_OPENAI_API_BASE=http://host.docker.internal:8190 \
- --name my-app \
- --add-host=host.docker.internal:host-gateway \
- -v $(pwd)/litellm/proxy/example_config_yaml/bad_schema.prisma:/app/schema.prisma \
- -v $(pwd)/litellm/proxy/example_config_yaml/bad_schema.prisma:/app/litellm/proxy/schema.prisma \
- -v $(pwd)/litellm/proxy/example_config_yaml/disable_schema_update.yaml:/app/config.yaml \
- litellm-docker-database:ci \
- --config /app/config.yaml \
- --port 4000
- - wait_for_service:
- url: http://localhost:4000
- timeout: "60"
- - run:
- name: Check container logs for expected message
- command: |
- echo "=== Printing Full Container Startup Logs ==="
- LOG_OUTPUT="$(docker logs my-app 2>&1)"
- printf '%s\n' "$LOG_OUTPUT"
- echo "=== End of Full Container Startup Logs ==="
-
- if printf '%s\n' "$LOG_OUTPUT" | grep -q "prisma schema out of sync with db. Consider running these sql_commands to sync the two"; then
- echo "Expected message found in logs. Test passed."
- else
- echo "Expected message not found in logs. Test failed."
- exit 1
- fi
- - run:
- name: Run Basic Proxy Startup Tests (Health Readiness and Chat Completion)
- command: |
- mkdir -p test-results
- TEST_FILES=$(circleci tests glob "tests/integration/configuration/test_basic_proxy_startup.py")
- echo "$TEST_FILES" | circleci tests run \
- --verbose \
- --command="tr ' ' '\\n' | awk '/\\.py/ {print; next} {sub(/\\.[A-Z][^.]*$/, \"\"); gsub(/\\./, \"/\"); print \$0 \".py\"}' | xargs uv run --no-sync python -m pytest --tb=short \
- -v \
- --junitxml=test-results/junit-2.xml \
- --durations=5"
- no_output_timeout: 15m
- - store_test_results:
- path: test-results
-
- proxy_logging_guardrails_model_info_tests:
- machine:
- image: ubuntu-2204:2024.04.1
- resource_class: large
- working_directory: ~/project
- steps:
- - checkout_over_https
- - run:
- name: Generate LiteLLM master key
- command: |
- key="$(openssl rand -hex 16)"
- printf 'export LITELLM_MASTER_KEY=sk-%s\n' "$key" >> "$BASH_ENV"
- - skip_if_unrelated_changes
- - setup_google_dns
- - install_uv
- - install_rust
- - run:
- name: Install Dependencies
- command: |
- uv sync --frozen --all-groups --all-extras --python 3.12
- - start_postgres
- - attach_workspace:
- at: ~/project
- - run:
- name: Load Docker Database Image
- command: |
- zstd -d litellm-docker-database.tar.zst --stdout | docker load
- docker images | grep litellm-docker-database
- - start_openai_record_replay_proxy
- - start_fake_openai_endpoint
- # Second Docker Container Run with Different Config
- # NOTE: We intentionally pass a "bad" license here. We need to ensure proxy starts and serves request even with bad license
- - run:
- name: Run Second Docker container
- command: |
- docker run -d \
- -p 4000:4000 \
- -e DATABASE_URL=postgresql://postgres:postgres@host.docker.internal:5432/circle_test \
- -e REDIS_HOST=$REDIS_HOST \
- -e REDIS_PASSWORD=$REDIS_PASSWORD \
- -e REDIS_PORT=$REDIS_PORT \
- -e LITELLM_MASTER_KEY="$LITELLM_MASTER_KEY" \
- -e OPENAI_API_KEY=$OPENAI_API_KEY \
- -e FAKE_OPENAI_API_BASE=http://host.docker.internal:8190 \
- -e LITELLM_LICENSE="bad-license" \
- -e LITELLM_LOG=ERROR \
- --add-host host.docker.internal:host-gateway \
- --name my-app-3 \
- -v $(pwd)/litellm/proxy/example_config_yaml/enterprise_config.yaml:/app/config.yaml \
- litellm-docker-database:ci \
- --config /app/config.yaml \
- --port 4000
-
- - run:
- name: Start outputting logs for second container
- command: docker logs -f my-app-3
- background: true
-
- - wait_for_service:
- url: http://localhost:4000
- timeout: "300"
-
- - run:
- name: Run second round of tests
- command: |
- mkdir -p test-results
- TEST_FILES=$(circleci tests glob "tests/integration/configuration/test_basic_proxy_startup.py")
- echo "$TEST_FILES" | circleci tests run \
- --verbose \
- --command="tr ' ' '\\n' | awk '/\\.py/ {print; next} {sub(/\\.[A-Z][^.]*$/, \"\"); gsub(/\\./, \"/\"); print \$0 \".py\"}' | xargs uv run --no-sync python -m pytest --tb=short \
- -v \
- --junitxml=test-results/junit-2.xml \
- --durations=5"
- no_output_timeout: 15m
-
- # Store test results
- - store_test_results:
- path: test-results
- e2e_ui_build:
- docker:
- - image: cimg/python:3.12-browsers@sha256:b432899af01c9a311bf74f4f22e9ada2e5306d4b1b4383f8d29e1228a5844ef2
- auth:
- username: ${DOCKERHUB_USERNAME}
- password: ${DOCKERHUB_PASSWORD}
- resource_class: large
- working_directory: ~/project
- steps:
- - checkout_over_https
- - skip_if_unrelated_changes:
- category: client
- - setup_google_dns
- - install_node
- - restore_cache:
- keys:
- - ui-dashboard-node-deps-v1-{{ checksum "ui/litellm-dashboard/package-lock.json" }}
- - run:
- name: Install dashboard dependencies
- command: |
- cd ui/litellm-dashboard
- npm ci
- - save_cache:
- key: ui-dashboard-node-deps-v1-{{ checksum "ui/litellm-dashboard/package-lock.json" }}
- paths:
- - ui/litellm-dashboard/node_modules
- - run:
- name: Build UI bundle
- command: |
- cd ui/litellm-dashboard
- npm run build
- mkdir -p ../../ui-bundle
- mv out ../../ui-bundle/out
- find ../../ui-bundle/out -name '*.html' ! -name 'index.html' | while read -r f; do
- d="${f%.html}"; mkdir -p "$d"; mv "$f" "$d/index.html"
- done
- - persist_to_workspace:
- root: ~/project
- paths:
- - ui-bundle/out
-
- e2e_ui_testing:
- docker:
- - image: cimg/python:3.12-browsers@sha256:b432899af01c9a311bf74f4f22e9ada2e5306d4b1b4383f8d29e1228a5844ef2
- auth:
- username: ${DOCKERHUB_USERNAME}
- password: ${DOCKERHUB_PASSWORD}
- - image: cimg/postgres:16.0@sha256:b125148bc76e8e8eee5eb3ad6020a3a14110a14e8192f1c645128afebe2e2f84
- environment:
- POSTGRES_USER: e2euser
- POSTGRES_PASSWORD: e2epassword
- POSTGRES_DB: litellm_e2e
- resource_class: xlarge
- working_directory: ~/project
- environment:
- DATABASE_URL: "postgresql://e2euser:e2epassword@localhost:5432/litellm_e2e"
- CI: "true"
- # Boot the proxy with an external logout URL so proxyLogoutUrl.spec.ts can
- # assert the redirect. Set at job level so both the proxy boot step and the
- # Playwright step (whose skip guard reads this) see the same value. Safe for
- # the rest of the suite: nothing else performs a logout.
- PROXY_LOGOUT_URL: "https://www.example.com"
- steps:
- - checkout_over_https
- - run:
- name: Generate LiteLLM master key
- command: |
- key="$(openssl rand -hex 16)"
- printf 'export LITELLM_MASTER_KEY=sk-%s\n' "$key" >> "$BASH_ENV"
- - skip_if_unrelated_changes:
- category: client
- - attach_workspace:
- at: ~/project
- - setup_google_dns
- - install_node
- - install_uv
- - install_rust
- - restore_cache:
- keys:
- - v1-uv-cache-{{ checksum "uv.lock" }}
- - run:
- name: Install Python dependencies
- command: |
- uv sync --frozen --all-groups --all-extras --python 3.12
- uv run --no-sync python -m prisma generate --schema litellm/proxy/schema.prisma
- - save_cache:
- key: v1-uv-cache-{{ checksum "uv.lock" }}
- paths:
- - ~/.cache/uv
- - restore_cache:
- keys:
- - ui-playwright-node-deps-v1-{{ checksum "tests/e2e/ui/package-lock.json" }}
- - run:
- name: Install Node dependencies and Playwright
- # The cimg/python:3.12-browsers image already ships the Chromium system
- # libraries Playwright needs (libnss3, libatk-bridge2.0-0, libcups2, etc.).
- # `--with-deps` triggers a redundant apt-get update + install that adds
- # 5-10 minutes to the job and frequently stalls on flaky Ubuntu mirrors,
- # so we install just the browser binary.
- command: |
- cd tests/e2e/ui
- npm ci
- npx playwright install chromium
- - save_cache:
- key: ui-playwright-node-deps-v1-{{ checksum "tests/e2e/ui/package-lock.json" }}
- paths:
- - tests/e2e/ui/node_modules
- - ~/.cache/ms-playwright
- - run:
- name: Use built UI bundle
- command: |
- rm -rf litellm/proxy/_experimental/out
- cp -r ui-bundle/out litellm/proxy/_experimental/out
- - wait_for_service:
- url: tcp://localhost:5432
- timeout: "30"
- - run:
- name: Push Prisma schema
- command: uv run --no-sync python -m prisma db push --schema litellm/proxy/schema.prisma --accept-data-loss
- - run:
- name: Seed database
- command: |
- PGPASSWORD=e2epassword psql -h localhost -p 5432 -U e2euser -d litellm_e2e \
- -f tests/e2e/ui/fixtures/seed.sql
- - run:
- name: Start mock LLM server
- command: uv run --no-sync python tests/e2e/ui/fixtures/mock_llm_server/server.py
- background: true
- - run:
- name: Start mock Presidio server
- command: uv run --no-sync python tests/e2e/ui/fixtures/mock_presidio_server/server.py
- background: true
- - run:
- name: Wait for mock Presidio server
- command: |
- for i in $(seq 1 30); do
- if curl -sf http://127.0.0.1:8091/health >/dev/null 2>&1; then exit 0; fi
- sleep 1
- done
- echo "Mock Presidio server never answered /health on port 8091" >&2
- exit 1
- - run:
- name: Start LiteLLM proxy
- environment:
- MOCK_LLM_URL: "http://127.0.0.1:8090/v1"
- DISABLE_SCHEMA_UPDATE: "true"
- SERVER_ROOT_PATH: ""
- # PROXY_LOGOUT_URL is inherited from the job-level environment so the
- # proxy and proxyLogoutUrl.spec.ts agree on the logout target.
- # LITELLM_LICENSE is forwarded from the project env so premium-gated
- # UI flows can be exercised. license.spec.ts asserts the resulting
- # JWT carries premium_user=true; if it ever stops being passed, that
- # test fails loudly rather than silently regressing premium coverage.
- command: |
- LITELLM_LICENSE="$LITELLM_LICENSE" \
- uv run --no-sync python -m litellm.proxy.proxy_cli \
- --config tests/e2e/ui/fixtures/config.yml \
- --port 4000
- background: true
- - run:
- name: Wait for proxy to be ready
- command: |
- for i in $(seq 1 60); do
- HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:4000/health -H "Authorization: Bearer $LITELLM_MASTER_KEY" 2>/dev/null || true)
- if [ "$HTTP_CODE" = "200" ]; then
- echo "Proxy is ready"
- exit 0
- fi
- sleep 2
- done
- echo "Proxy failed to start"
- exit 1
- - run:
- name: Run Playwright E2E tests
- # Forward LITELLM_LICENSE so license.spec.ts can detect that the
- # proxy was launched with a license and assert premium_user=true.
- command: |
- cd tests/e2e/ui
- LITELLM_LICENSE="$LITELLM_LICENSE" \
- npx playwright test --config playwright.config.ts --workers=3 --retries=0
- no_output_timeout: 10m
- - store_artifacts:
- path: tests/e2e/ui/test-results
- destination: e2e-test-results
- - store_artifacts:
- path: tests/e2e/ui/playwright-report
- destination: e2e-playwright-report
-
- e2e_ui_testing_server_root_path:
- docker:
- - image: cimg/python:3.12-browsers@sha256:b432899af01c9a311bf74f4f22e9ada2e5306d4b1b4383f8d29e1228a5844ef2
- auth:
- username: ${DOCKERHUB_USERNAME}
- password: ${DOCKERHUB_PASSWORD}
- - image: cimg/postgres:16.0@sha256:b125148bc76e8e8eee5eb3ad6020a3a14110a14e8192f1c645128afebe2e2f84
- environment:
- POSTGRES_USER: e2euser
- POSTGRES_PASSWORD: e2epassword
- POSTGRES_DB: litellm_e2e
- resource_class: large
- working_directory: ~/project
- environment:
- DATABASE_URL: "postgresql://e2euser:e2epassword@localhost:5432/litellm_e2e"
- CI: "true"
- # The whole job exercises the proxy mounted under a prefix. SERVER_ROOT_PATH
- # is read both by the proxy at boot (to rewrite the built UI bundle in place)
- # and by migration.serverRootPath.config.ts, which refuses to run without it.
- SERVER_ROOT_PATH: "/litellm"
- steps:
- - checkout_over_https
- - run:
- name: Generate LiteLLM master key
- command: |
- key="$(openssl rand -hex 16)"
- printf 'export LITELLM_MASTER_KEY=sk-%s\n' "$key" >> "$BASH_ENV"
- - skip_if_unrelated_changes:
- category: client
- - attach_workspace:
- at: ~/project
- - setup_google_dns
- - install_node
- - install_uv
- - install_rust
- - restore_cache:
- keys:
- - v1-uv-cache-{{ checksum "uv.lock" }}
- - run:
- name: Install Python dependencies
- command: |
- uv sync --frozen --all-groups --all-extras --python 3.12
- uv run --no-sync python -m prisma generate --schema litellm/proxy/schema.prisma
- - save_cache:
- key: v1-uv-cache-{{ checksum "uv.lock" }}
- paths:
- - ~/.cache/uv
- - restore_cache:
- keys:
- - ui-playwright-node-deps-v1-{{ checksum "tests/e2e/ui/package-lock.json" }}
- - run:
- name: Install Node dependencies and Playwright
- command: |
- cd tests/e2e/ui
- npm ci
- npx playwright install chromium
- - save_cache:
- key: ui-playwright-node-deps-v1-{{ checksum "tests/e2e/ui/package-lock.json" }}
- paths:
- - tests/e2e/ui/node_modules
- - ~/.cache/ms-playwright
- - run:
- name: Use built UI bundle
- command: |
- rm -rf litellm/proxy/_experimental/out
- cp -r ui-bundle/out litellm/proxy/_experimental/out
- - wait_for_service:
- url: tcp://localhost:5432
- timeout: "30"
- - run:
- name: Push Prisma schema
- command: uv run --no-sync python -m prisma db push --schema litellm/proxy/schema.prisma --accept-data-loss
- - run:
- name: Seed database
- command: |
- PGPASSWORD=e2epassword psql -h localhost -p 5432 -U e2euser -d litellm_e2e \
- -f tests/e2e/ui/fixtures/seed.sql
- - run:
- name: Start mock LLM server
- command: uv run --no-sync python tests/e2e/ui/fixtures/mock_llm_server/server.py
- background: true
- - run:
- name: Start mock Presidio server
- command: uv run --no-sync python tests/e2e/ui/fixtures/mock_presidio_server/server.py
- background: true
- - run:
- name: Wait for mock Presidio server
- command: |
- for i in $(seq 1 30); do
- if curl -sf http://127.0.0.1:8091/health >/dev/null 2>&1; then exit 0; fi
- sleep 1
- done
- echo "Mock Presidio server never answered /health on port 8091" >&2
- exit 1
- - run:
- name: Start LiteLLM proxy under a server root path
- environment:
- MOCK_LLM_URL: "http://127.0.0.1:8090/v1"
- DISABLE_SCHEMA_UPDATE: "true"
- # Output flows to this step's own log, so a boot crash is visible here
- # rather than swallowed by a downstream readiness probe.
- command: |
- LITELLM_LICENSE="$LITELLM_LICENSE" \
- uv run --no-sync python -m litellm.proxy.proxy_cli \
- --config tests/e2e/ui/fixtures/config.yml \
- --port 4000
- background: true
- - run:
- name: Wait for prefixed proxy to be ready
- command: |
- for i in $(seq 1 60); do
- HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 -H "Authorization: Bearer $LITELLM_MASTER_KEY" http://127.0.0.1:4000/litellm/health 2>/dev/null || true)
- if [ "$HTTP_CODE" = "200" ]; then
- echo "Prefixed proxy is ready"
- exit 0
- fi
- sleep 2
- done
- echo "Prefixed proxy failed to start; see the 'Start LiteLLM proxy under a server root path' step for the boot log"
- exit 1
- - run:
- name: Run migration smoke under SERVER_ROOT_PATH
- command: |
- cd tests/e2e/ui
- LITELLM_LICENSE="$LITELLM_LICENSE" \
- npx playwright test --config migration.serverRootPath.config.ts
- no_output_timeout: 10m
- - store_artifacts:
- path: tests/e2e/ui/test-results
- destination: e2e-server-root-path-test-results
- - store_artifacts:
- path: tests/e2e/ui/playwright-report
- destination: e2e-server-root-path-playwright-report
-
- build_docker_database_image:
- parameters:
- migration_qualification:
- type: boolean
- default: false
- machine:
- image: ubuntu-2204:2024.04.1
- resource_class: large
- working_directory: ~/project
- steps:
- - when:
- condition: << parameters.migration_qualification >>
- steps:
- - checkout_over_https:
- revision: << pipeline.parameters.migration_source_sha >>
- - unless:
- condition: << parameters.migration_qualification >>
- steps:
- - checkout_over_https
- - skip_if_unrelated_changes
-
- - run:
- name: Build Docker image
- environment:
- MIGRATION_CANDIDATE_IMAGE: << pipeline.parameters.migration_candidate_image >>
- command: |
- if [ -n "$MIGRATION_CANDIDATE_IMAGE" ]; then
- [[ "$MIGRATION_CANDIDATE_IMAGE" =~ ^ghcr.io/berriai/[a-z0-9._/-]+@sha256:[0-9a-f]{64}$ ]] || exit 1
- docker pull "$MIGRATION_CANDIDATE_IMAGE"
- docker tag "$MIGRATION_CANDIDATE_IMAGE" litellm-docker-database:ci
- else
- docker build \
- --label org.opencontainers.image.revision="$(git rev-parse HEAD)" \
- -t litellm-docker-database:ci \
- -f docker/Dockerfile.database .
- fi
- python3 .circleci/scripts/run_migration_tests.py record-image
-
- - run:
- name: Save Docker image to workspace root
- command: |
- docker save litellm-docker-database:ci | zstd -1 -T0 > litellm-docker-database.tar.zst
-
- - persist_to_workspace:
- root: .
- paths:
- - litellm-docker-database.tar.zst
- - migration-image.json
-
- migration_startup_tests:
- parameters:
- suite:
- type: enum
- enum: [startup, recovery, legacy, upgrade, shaped]
- baseline:
- type: boolean
- default: false
- machine:
- image: ubuntu-2204:2024.04.1
- resource_class: large
- working_directory: ~/project
- environment:
- LITELLM_MIGRATION_TESTS: "1"
- LITELLM_MIGRATION_TEST_IMAGE: litellm-docker-database:ci
- LITELLM_MIGRATION_BASELINE_IMAGE: << pipeline.parameters.migration_baseline_image >>
- MIGRATION_TEST_ADMIN_URL: postgresql://postgres:postgres@127.0.0.1:5432/postgres
- MIGRATION_TEST_CONTAINER_ADMIN_URL: postgresql://postgres:postgres@host.docker.internal:5432/postgres
- MIGRATION_TEST_OUTPUT: /tmp/migration-results
- PYTHONPATH: tests/e2e
- steps:
- - checkout_over_https:
- revision: << pipeline.parameters.migration_source_sha >>
- - run:
- name: Generate LiteLLM master key
- command: |
- key="$(openssl rand -hex 16)"
- printf 'export LITELLM_MASTER_KEY=sk-%s\n' "$key" >> "$BASH_ENV"
- - install_uv
- - install_rust
- - restore_cache:
- keys:
- - v1-uv-cache-{{ checksum "uv.lock" }}
- - run:
- name: Install test dependencies
- command: uv sync --frozen --all-groups --all-extras --python 3.12
- - attach_workspace:
- at: ~/project
- - run:
- name: Load the shared candidate and start PostgreSQL
- command: |
- zstd -d litellm-docker-database.tar.zst --stdout | docker load
- docker run -d --name migration-postgres \
- -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres \
- -p 5432:5432 \
- postgres:16@sha256:e17e86066e5ef83e0952a9347f5c792b7ece00972e2aa787a6986f471b3dd3d5
- - wait_for_service:
- url: tcp://localhost:5432
- timeout: "60"
- - when:
- condition: << parameters.baseline >>
- steps:
- - run:
- name: Pull the baseline release the upgrade starts from
- environment:
- BASELINE_IMAGE: << pipeline.parameters.migration_baseline_image >>
- command: |
- [[ "$BASELINE_IMAGE" =~ ^ghcr.io/berriai/[a-z0-9._/-]+(@sha256:[0-9a-f]{64}|:v[0-9][0-9a-z.-]*)$ ]] || exit 1
- docker pull "$BASELINE_IMAGE"
- - run:
- name: Run migration startup regressions
- environment:
- MIGRATION_TEST_SUITE: << parameters.suite >>
- MIGRATION_CANDIDATE_IMAGE: << pipeline.parameters.migration_candidate_image >>
- command: |
- mkdir -p /tmp/migration-results
- uv run --no-sync python .circleci/scripts/run_migration_tests.py
- no_output_timeout: 15m
- - store_test_results:
- path: /tmp/migration-results/junit
- - run:
- name: Package migration diagnostics
- when: always
- command: |
- mkdir -p /tmp/migration-artifacts
- if [ -d /tmp/migration-results ]; then
- tar -czf /tmp/migration-artifacts/diagnostics.tar.gz -C /tmp/migration-results .
- if [ -f /tmp/migration-results/verdict.json ]; then
- cp /tmp/migration-results/verdict.json /tmp/migration-artifacts/verdict.json
- fi
- fi
- - store_artifacts:
- path: /tmp/migration-artifacts
- destination: migration-results
- - run:
- name: Remove migration test containers
- when: always
- command: |
- docker ps -aq --filter label=litellm-migration-test=true | xargs -r docker rm -f
- docker rm -f migration-postgres || true
-
- test_bad_database_url:
- machine:
- image: ubuntu-2204:2024.04.1
- resource_class: medium
- working_directory: ~/project
- steps:
- - checkout_over_https
- - skip_if_unrelated_changes
- - attach_workspace:
- at: ~/project
- - setup_google_dns
- - start_postgres
- - run:
- name: Load Docker Database Image
- command: |
- zstd -d litellm-docker-database.tar.zst --stdout | docker load
- docker tag litellm-docker-database:ci myapp:latest
- - run:
- name: Run Docker container with bad DATABASE_URL
- command: |
- set +e
- docker run --name my-app \
- -p 4000:4000 \
- -e LITELLM_DANGEROUSLY_PERMIT_WEAK_OR_UNSET_MASTER_KEY=true \
- -e DEFAULT_NUM_WORKERS_LITELLM_PROXY=1 \
- -e DATABASE_URL="postgresql://wrong:wrong@wrong:5432/wrong" \
- myapp:latest \
- --port 4000 > docker_output.log 2>&1
- echo "$?" > docker_exit_code
- set -e
- - run:
- name: Display Docker logs
- command: cat docker_output.log
- - run:
- name: Proxy must refuse to serve on an unreachable database
- command: |
- fail() { echo "FAILED: $1"; cat docker_output.log; exit 1; }
- exit_code="$(cat docker_exit_code)"
- [ "$exit_code" -ne 0 ] || fail "proxy exited 0 with an unreachable database"
- grep -q "P1001" docker_output.log || fail "log does not name the unreachable database server"
- ! grep -q "Application startup complete" docker_output.log || fail "proxy reached serving state"
- ! docker exec my-app true 2>/dev/null || fail "container is still running"
- echo "Proxy refused to serve (exit $exit_code) and never reached startup. Test passed."
-
- integration_contracts:
- parameters:
- suite:
- type: string
- mode:
- type: enum
- enum: [standard, replica]
- default: standard
- parallelism:
- type: integer
- default: 1
- resource_class:
- type: enum
- enum: [large, xlarge]
- default: large
- machine:
- image: ubuntu-2204:2024.04.1
- resource_class: << parameters.resource_class >>
- working_directory: ~/project
- parallelism: << parameters.parallelism >>
- steps:
- - setup_litellm_test_deps
- - when:
- condition:
- equal: [browser, << parameters.suite >>]
- steps:
- - install_node
- - restore_cache:
- keys:
- - integration-ui-v3-{{ checksum "ui/litellm-dashboard/package-lock.json" }}-{{ checksum "tests/e2e/ui/package-lock.json" }}
- - run:
- name: Install locked browser dependencies
- command: |
- cd ui/litellm-dashboard
- npm ci
- cd ../../tests/e2e/ui
- npm ci
- sudo env PATH="$PATH" DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=l \
- timeout --signal=TERM --kill-after=20s 6m node node_modules/@playwright/test/cli.js install-deps chromium
- timeout --signal=TERM --kill-after=20s 3m node node_modules/@playwright/test/cli.js install chromium
- - save_cache:
- key: integration-ui-v3-{{ checksum "ui/litellm-dashboard/package-lock.json" }}-{{ checksum "tests/e2e/ui/package-lock.json" }}
- paths:
- - ~/.npm
- - ~/.cache/ms-playwright
- - run:
- name: Build the candidate dashboard
- command: cd ui/litellm-dashboard && NEXT_TELEMETRY_DISABLED=1 npm run build
- - when:
- condition:
- equal: [mcp, << parameters.suite >>]
- steps:
- - run:
- name: Install MCP SDK1 peer
- command: |
- uv venv --python 3.12 .venv-mcp-peer
- uv pip install --no-config --exclude-newer "3 days" --python .venv-mcp-peer 'mcp==1.28.1' 'langchain-mcp-adapters==0.2.1'
- echo "export MCP_TEST_PEER_PYTHON=$PWD/.venv-mcp-peer/bin/python" >> "$BASH_ENV"
- - when:
- condition:
- equal: [sdk, << parameters.suite >>]
- steps:
- - run:
- name: Install redis-server 7.2.16
- command: |
- mkdir -p /tmp/redis-bin
- cid="$(docker create redis:7.2.16@sha256:0637954999d01b7c9ce9167db2da50656e2590d3b884f1c600c5f63bb6e6773c)"
- docker cp "${cid}:/usr/local/bin/redis-server" /tmp/redis-bin/redis-server
- docker rm "$cid"
- /tmp/redis-bin/redis-server --version | grep -q 'v=7.2.16'
- echo 'export PATH="/tmp/redis-bin:$PATH"' >> "$BASH_ENV"
- - start_postgres:
- image: postgres:16@sha256:e17e86066e5ef83e0952a9347f5c792b7ece00972e2aa787a6986f471b3dd3d5
- server_args: "-c max_connections=300 -c shared_preload_libraries=pg_stat_statements -c pg_stat_statements.track=all -c pg_stat_statements.max=20000"
- - start_redis
- - run:
- name: Run owned integration contracts
- command: bash .circleci/scripts/run_integration.sh << parameters.suite >> << parameters.mode >>
- no_output_timeout: 15m
- - run:
- name: Stop owned database and Redis
- when: always
- command: |
- mkdir -p test-results/services-<< parameters.suite >>-<< parameters.mode >>
- docker logs postgres-db > test-results/services-<< parameters.suite >>-<< parameters.mode >>/postgres.log 2>&1 || true
- docker logs redis-cache > test-results/services-<< parameters.suite >>-<< parameters.mode >>/redis.log 2>&1 || true
- docker rm -f postgres-db redis-cache
- test -z "$(docker ps -aq --filter name=postgres-db --filter name=redis-cache)"
- - when:
- condition:
- not:
- equal: [1, << parameters.parallelism >>]
- steps:
- - run:
- name: Prepare this shard's execution record
- command: |
- results="test-results/integration-<< parameters.suite >>"
- if [ "<< parameters.mode >>" = replica ]; then
- results="$results-replica"
- fi
- mkdir -p "/tmp/integration-shards/<< parameters.suite >>-<< parameters.mode >>/$CIRCLE_NODE_INDEX"
- cp "$results/execution.json" "/tmp/integration-shards/<< parameters.suite >>-<< parameters.mode >>/$CIRCLE_NODE_INDEX/execution.json"
- if [ "<< parameters.suite >>" = mcp ]; then
- mkdir -p "/tmp/integration-shards/mcp-standard/$CIRCLE_NODE_INDEX/coverage"
- cp test-results/integration-mcp/coverage/data "/tmp/integration-shards/mcp-standard/$CIRCLE_NODE_INDEX/coverage/data.$CIRCLE_NODE_INDEX"
- fi
- - persist_to_workspace:
- root: /tmp/integration-shards
- paths:
- - << parameters.suite >>-<< parameters.mode >>
- - store_test_results:
- path: test-results
- - store_artifacts:
- path: test-results
-
- integration_shard_coverage:
- parameters:
- suite:
- type: enum
- enum: [management, accounting, database, extensions, providers, mcp]
- mode:
- type: enum
- enum: [standard, replica]
- default: standard
- shards:
- type: integer
- docker:
- - image: cimg/python:3.12@sha256:9c796c23c84e84a66a964acb508d39dc5433c81a47e07efd56dccbbc2427e07c
- auth:
- username: ${DOCKERHUB_USERNAME}
- password: ${DOCKERHUB_PASSWORD}
- resource_class: small
- working_directory: ~/project
- environment:
- UV_PYTHON_DOWNLOADS: never
- steps:
- - checkout_over_https
- - install_uv
- - run:
- name: Install the locked shard verifier and coverage dependencies
- command: |
- uv export --frozen --all-groups --all-extras --no-emit-project --no-emit-workspace --no-header --no-annotate \
- | awk '/^[^ ]/ { keep = $0 ~ /^(annotated-types|coverage|pydantic|pydantic-core|typing-extensions|typing-inspection)==/ } keep' \
- > /tmp/fan-in-requirements.txt
- uv venv .venv --python 3.12
- uv pip install --no-config --python .venv --require-hashes -r /tmp/fan-in-requirements.txt
- - attach_workspace:
- at: /tmp/integration-shards
- - run:
- name: Verify every << parameters.suite >> case ran exactly once
- command: |
- .venv/bin/python -m tests.integration._support.sharding /tmp/integration-shards/<< parameters.suite >>-<< parameters.mode >> \
- --shards << parameters.shards >>
- - when:
- condition:
- equal: [mcp, << parameters.suite >>]
- steps:
- - run:
- name: Combine coverage from every MCP shard
- command: |
- mkdir -p test-results/integration-mcp/coverage
- export COVERAGE_FILE="$PWD/test-results/integration-mcp/coverage/data"
- .venv/bin/python -m coverage combine --rcfile=tests/integration/mcp_coverage.toml /tmp/integration-shards/mcp-standard/*/coverage
- .venv/bin/python -m coverage report --rcfile=tests/integration/mcp_coverage.toml > test-results/integration-mcp/coverage/coverage.txt
- .venv/bin/python -m coverage html --rcfile=tests/integration/mcp_coverage.toml -d test-results/integration-mcp/coverage/html
- - store_artifacts:
- path: test-results/integration-mcp/coverage
-
- routing_parity:
- parameters:
- suite:
- type: string
- machine:
- image: ubuntu-2204:2024.04.1
- resource_class: large
- working_directory: ~/project
- steps:
- - setup_litellm_test_deps
- - run:
- name: Check out base product code
- environment:
- ROUTING_PARITY_BASE: << pipeline.parameters.routing_parity_base >>
- command: |
- [[ "$ROUTING_PARITY_BASE" =~ ^[0-9a-f]{40}$ ]] || exit 1
- git fetch --depth 1 --no-filter origin "$ROUTING_PARITY_BASE"
- git rm -r -f --quiet litellm enterprise litellm-proxy-extras
- git checkout "$ROUTING_PARITY_BASE" -- litellm enterprise litellm-proxy-extras
- git reset --quiet
- test -f litellm/rust_bridge/_native.abi3.so
- - start_postgres:
- image: postgres:16@sha256:e17e86066e5ef83e0952a9347f5c792b7ece00972e2aa787a6986f471b3dd3d5
- server_args: "-c shared_preload_libraries=pg_stat_statements -c pg_stat_statements.track=all -c pg_stat_statements.max=20000"
- - start_redis
- - run:
- name: Run base side
- command: bash .circleci/scripts/run_integration.sh << parameters.suite >> parity base
- no_output_timeout: 15m
- - run:
- name: Stop base database and Redis
- when: always
- command: |
- mkdir -p test-results/services-<< parameters.suite >>-parity-base
- docker logs postgres-db > test-results/services-<< parameters.suite >>-parity-base/postgres.log 2>&1 || true
- docker logs redis-cache > test-results/services-<< parameters.suite >>-parity-base/redis.log 2>&1 || true
- docker rm -f postgres-db redis-cache
- test -z "$(docker ps -aq --filter name=postgres-db --filter name=redis-cache)"
- - run:
- name: Check out head product code
- command: |
- git rm -r -f --quiet litellm enterprise litellm-proxy-extras
- git checkout "$CIRCLE_SHA1" -- litellm enterprise litellm-proxy-extras
- git reset --quiet
- test -f litellm/rust_bridge/_native.abi3.so
- - start_postgres:
- image: postgres:16@sha256:e17e86066e5ef83e0952a9347f5c792b7ece00972e2aa787a6986f471b3dd3d5
- server_args: "-c shared_preload_libraries=pg_stat_statements -c pg_stat_statements.track=all -c pg_stat_statements.max=20000"
- - start_redis
- - run:
- name: Run head side
- command: bash .circleci/scripts/run_integration.sh << parameters.suite >> parity head
- no_output_timeout: 15m
- - run:
- name: Stop head database and Redis
- when: always
- command: |
- mkdir -p test-results/services-<< parameters.suite >>-parity-head
- docker logs postgres-db > test-results/services-<< parameters.suite >>-parity-head/postgres.log 2>&1 || true
- docker logs redis-cache > test-results/services-<< parameters.suite >>-parity-head/redis.log 2>&1 || true
- docker rm -f postgres-db redis-cache
- test -z "$(docker ps -aq --filter name=postgres-db --filter name=redis-cache)"
- - run:
- name: Compare routing parity
- command: PYTHONPATH="$PWD/tests" .venv/bin/python -m integration._support.routing check test-results/parity-<< parameters.suite >>/base test-results/parity-<< parameters.suite >>/head
- - store_test_results:
- path: test-results
- - store_artifacts:
- path: test-results
-
- postgres_suite:
- parameters:
- test_path:
- type: string
- seed:
- type: boolean
- coverage_flag:
- type: string
- default: ""
- timeout_minutes:
- type: integer
- workers:
- type: integer
- default: 1
- machine:
- image: ubuntu-2204:2024.04.1
- resource_class: large
- working_directory: ~/project
- environment:
- DATABASE_URL: postgresql://postgres:postgres@localhost:5432/litellm_test
- steps:
- - checkout_over_https
- - skip_if_unrelated_changes
- - setup_litellm_test_deps:
- checkout: false
- - start_postgres:
- db_name: litellm_test
- image: postgres:16@sha256:e17e86066e5ef83e0952a9347f5c792b7ece00972e2aa787a6986f471b3dd3d5
- - run:
- name: Generate Prisma client
- command: uv run --no-sync prisma generate --schema litellm/proxy/schema.prisma
- - when:
- condition: << parameters.seed >>
- steps:
- - run:
- name: Seed database schema
- command: uv run --no-sync prisma db push --schema litellm/proxy/schema.prisma --accept-data-loss
- - run:
- name: Run << parameters.test_path >>
- command: |
- mkdir -p test-results
- coverage_args=()
- worker_args=()
- if [ "<< parameters.workers >>" -gt 1 ]; then
- worker_args=(-n << parameters.workers >> --dist=worksteal)
- fi
- if [ -n "<< parameters.coverage_flag >>" ]; then
- coverage_args=(--cov=./litellm --cov-report=xml:coverage.xml)
- fi
- timeout --signal=TERM << parameters.timeout_minutes >>m uv run --no-sync pytest \
- << parameters.test_path >> -vv --tb=short --durations=10 -o junit_family=xunit1 \
- --junitxml=test-results/junit.xml "${coverage_args[@]}" "${worker_args[@]}"
- - when:
- condition: << parameters.coverage_flag >>
- steps:
- - install_codecov_cli
- - run:
- name: Upload coverage
- when: always
- command: |
- [ -f coverage.xml ] || { echo "no coverage.xml produced"; exit 1; }
- codecov upload-process --disable-search --fail-on-error -f coverage.xml \
- -F << parameters.coverage_flag >> -C "$CIRCLE_SHA1" \
- -n "<< parameters.coverage_flag >>-${CIRCLE_BUILD_NUM}" --git-service github
- - store_test_results:
- path: test-results
- - store_artifacts:
- path: test-results
-
-workflows:
- migration_startup_scheduled:
- triggers:
- - schedule:
- cron: "17 0,6,12,18 * * *"
- filters:
- branches:
- only: main
- jobs:
- - build_docker_database_image:
- migration_qualification: true
- - migration_startup_tests:
- name: migration-startup
- suite: startup
- requires: [build_docker_database_image]
- - migration_startup_tests:
- name: migration-recovery
- suite: recovery
- requires: [build_docker_database_image]
- - migration_startup_tests:
- name: migration-legacy-and-pooling
- suite: legacy
- requires: [build_docker_database_image]
- - migration_startup_tests:
- name: migration-upgrade
- suite: upgrade
- baseline: true
- requires: [build_docker_database_image]
- - migration_startup_tests:
- name: migration-upgrade-shaped
- suite: shaped
- baseline: true
- requires: [build_docker_database_image]
- integration:
- jobs:
- - integration_contracts:
- name: integration-<< matrix.suite >>
- filters: &integration_default_run not pipeline.parameters.run_migration_tests and pipeline.parameters.routing_parity_base == ""
- matrix:
- parameters:
- suite: [sdk, cost, browser]
- parallelism: 1
- - integration_contracts:
- name: integration-security
- suite: security
- parallelism: 1
- resource_class: xlarge
- filters: *integration_default_run
- - integration_contracts:
- name: integration-management
- suite: management
- parallelism: 4
- resource_class: xlarge
- filters: *integration_default_run
- - integration_shard_coverage:
- name: integration-management-coverage
- suite: management
- shards: 4
- requires: [integration-management]
- filters: *integration_default_run
- - integration_contracts:
- name: integration-accounting
- suite: accounting
- parallelism: 4
- resource_class: xlarge
- filters: *integration_default_run
- - integration_shard_coverage:
- name: integration-accounting-coverage
- suite: accounting
- shards: 4
- requires: [integration-accounting]
- filters: *integration_default_run
- - integration_contracts:
- name: integration-providers
- suite: providers
- parallelism: 4
- resource_class: xlarge
- filters: *integration_default_run
- - integration_shard_coverage:
- name: integration-providers-coverage
- suite: providers
- shards: 4
- requires:
- - integration-providers
- filters: *integration_default_run
- - integration_contracts:
- name: integration-database
- suite: database
- parallelism: 2
- filters: *integration_default_run
- - integration_shard_coverage:
- name: integration-database-coverage
- suite: database
- shards: 2
- requires: [integration-database]
- filters: *integration_default_run
- - integration_contracts:
- name: integration-mcp
- suite: mcp
- parallelism: 2
- filters: *integration_default_run
- - integration_shard_coverage:
- name: integration-mcp-coverage
- suite: mcp
- shards: 2
- requires:
- - integration-mcp
- filters: *integration_default_run
- - integration_contracts:
- name: integration-extensions
- suite: extensions
- parallelism: 8
- resource_class: xlarge
- filters: *integration_default_run
- - integration_shard_coverage:
- name: integration-extensions-coverage
- suite: extensions
- shards: 8
- requires:
- - integration-extensions
- filters: *integration_default_run
- - integration_contracts:
- name: integration-management-replica
- suite: management
- mode: replica
- parallelism: 4
- resource_class: xlarge
- filters: *integration_default_run
- - integration_shard_coverage:
- name: integration-management-replica-coverage
- suite: management
- mode: replica
- shards: 4
- requires: [integration-management-replica]
- filters: *integration_default_run
- - integration_contracts:
- name: integration-database-replica
- suite: database
- mode: replica
- parallelism: 2
- filters: *integration_default_run
- - integration_shard_coverage:
- name: integration-database-replica-coverage
- suite: database
- mode: replica
- shards: 2
- requires: [integration-database-replica]
- filters: *integration_default_run
- - postgres_suite:
- name: roi-database
- test_path: tests/integration/database/test_roi_observed.py
- seed: false
- coverage_flag: roi-postgres
- timeout_minutes: 10
- filters: *integration_default_run
- - windows_release_wheel:
- filters: *integration_default_run
- - base_sdk_install:
- filters: *integration_default_run
- - build_docker_database_image:
- filters: *integration_default_run
- - proxy_logging_guardrails_model_info_tests:
- requires:
- - build_docker_database_image
- filters: *integration_default_run
- - db_migration_disable_update_check:
- requires:
- - build_docker_database_image
- filters: *integration_default_run
- - test_bad_database_url:
- requires:
- - build_docker_database_image
- filters: *integration_default_run
- - helm_chart_testing:
- requires:
- - build_docker_database_image
- filters: *integration_default_run
- - e2e_ui_build:
- filters: *integration_default_run
- - e2e_ui_testing:
- requires: [e2e_ui_build]
- filters: *integration_default_run
- - e2e_ui_testing_server_root_path:
- requires: [e2e_ui_build]
- filters: *integration_default_run
- - build_docker_database_image:
- name: build_docker_database_image-migration
- migration_qualification: true
- filters: pipeline.parameters.run_migration_tests
- - migration_startup_tests:
- name: migration-startup
- suite: startup
- requires: [build_docker_database_image-migration]
- filters: pipeline.parameters.run_migration_tests
- - migration_startup_tests:
- name: migration-recovery
- suite: recovery
- requires: [build_docker_database_image-migration]
- filters: pipeline.parameters.run_migration_tests
- - migration_startup_tests:
- name: migration-legacy-and-pooling
- suite: legacy
- requires: [build_docker_database_image-migration]
- filters: pipeline.parameters.run_migration_tests
- - migration_startup_tests:
- name: migration-upgrade
- suite: upgrade
- baseline: true
- requires: [build_docker_database_image-migration]
- filters: pipeline.parameters.run_migration_tests
- - migration_startup_tests:
- name: migration-upgrade-shaped
- suite: shaped
- baseline: true
- requires: [build_docker_database_image-migration]
- filters: pipeline.parameters.run_migration_tests
- - routing_parity:
- name: routing-parity-<< matrix.suite >>
- matrix:
- parameters:
- suite: [management, accounting, database, providers, extensions, cost, mcp]
- filters: pipeline.parameters.routing_parity_base != ""
diff --git a/.circleci/scripts/run_migration_tests.py b/.circleci/scripts/run_migration_tests.py
deleted file mode 100644
index 50d01f1d50b..00000000000
--- a/.circleci/scripts/run_migration_tests.py
+++ /dev/null
@@ -1,116 +0,0 @@
-from __future__ import annotations
-
-import json
-import os
-import re
-import subprocess
-import sys
-from pathlib import Path
-from typing import Final
-from xml.etree import ElementTree
-
-SUITES: Final = {
- "startup": (("test_startup.py",), 13),
- "recovery": (("test_recovery.py",), 15),
- "legacy": (("test_legacy.py", "test_pooling.py"), 11),
- "upgrade": (("test_upgrade.py", "test_rolling_upgrade.py"), 5),
- "shaped": (("test_shaped_database.py",), 1),
-}
-
-
-def successful_junit(path: Path, expected: int, exit_code: int) -> bool:
- if exit_code != 0 or not path.is_file():
- return False
- try:
- root: Final = ElementTree.parse(path).getroot()
- except ElementTree.ParseError:
- return False
- cases: Final = tuple(root.iter("testcase"))
- identities: Final = frozenset((case.get("classname"), case.get("name")) for case in cases)
- return len(cases) == len(identities) == expected and all(
- not any(case.find(tag) is not None for tag in ("failure", "error", "skipped")) for case in cases
- )
-
-
-def output(*command: str) -> str:
- return subprocess.check_output(command, text=True, timeout=90).strip()
-
-
-def record_image() -> None:
- source: Final = output("git", "rev-parse", "HEAD")
- image: Final = output("docker", "image", "inspect", "litellm-docker-database:ci", "--format", "{{.Id}}")
- revision: Final = output(
- "docker",
- "image",
- "inspect",
- "litellm-docker-database:ci",
- "--format",
- '{{index .Config.Labels "org.opencontainers.image.revision"}}',
- )
- assert re.fullmatch(r"[0-9a-f]{40}", source), "Invalid source revision"
- assert revision == source, "Candidate image revision differs from the tested source"
- Path("migration-image.json").write_text(
- json.dumps(
- {
- "source_sha": source,
- "image_id": image,
- "candidate_image": os.environ.get("MIGRATION_CANDIDATE_IMAGE", ""),
- }
- )
- )
-
-
-def main() -> int:
- suite: Final = os.environ["MIGRATION_TEST_SUITE"]
- files, expected = SUITES[suite]
- metadata: Final = json.loads(Path("migration-image.json").read_text())
- assert metadata["source_sha"] == output("git", "rev-parse", "HEAD"), "Image and test source revisions differ"
- assert metadata["image_id"] == output(
- "docker", "image", "inspect", os.environ["LITELLM_MIGRATION_TEST_IMAGE"], "--format", "{{.Id}}"
- ), "Loaded image differs from the build output"
- assert metadata["candidate_image"] == os.environ.get("MIGRATION_CANDIDATE_IMAGE", ""), "Wrong release candidate"
- destination: Final = Path(os.environ["MIGRATION_TEST_OUTPUT"])
- junit: Final = destination / "junit" / "results.xml"
- junit.parent.mkdir(parents=True, exist_ok=True)
- result: Final = subprocess.run(
- (
- sys.executable,
- "-m",
- "pytest",
- *(f"tests/e2e/migrations/{name}" for name in files),
- "-vv",
- "--tb=short",
- "--durations=10",
- f"--junitxml={junit}",
- "-o",
- "addopts=",
- "--reruns=0",
- ),
- check=False,
- timeout=1200,
- )
- passed: Final = successful_junit(junit, expected, result.returncode)
- (destination / "verdict.json").write_text(
- json.dumps(
- {
- **metadata,
- "suite": suite,
- "baseline_image": os.environ.get("LITELLM_MIGRATION_BASELINE_IMAGE", ""),
- "expected_cases": expected,
- "passed": passed,
- "pytest_exit_code": result.returncode,
- "test_revision": metadata["source_sha"],
- "workflow_id": os.environ.get("CIRCLE_WORKFLOW_ID", ""),
- "job_number": os.environ.get("CIRCLE_BUILD_NUM", ""),
- },
- indent=2,
- )
- )
- return 0 if passed else 1
-
-
-if __name__ == "__main__":
- if sys.argv[1:] == ["record-image"]:
- record_image()
- else:
- raise SystemExit(main())
diff --git a/.dockerignore b/.dockerignore
index f3a80fee3e4..d8eb72a057f 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,13 +1,11 @@
docs
-cookbook
-.circleci
+litellm/proxy/discovery_endpoints/skill_scanner_adapter
.github
tests
.git
.devcontainer
*.tgz
log.txt
-docker/Dockerfile.*
# Claude Flow generated files (must be excluded from Docker build)
.claude/
diff --git a/.github/AGENTS.md b/.github/AGENTS.md
new file mode 100644
index 00000000000..14bd461a8a0
--- /dev/null
+++ b/.github/AGENTS.md
@@ -0,0 +1,5 @@
+# CI
+
+Integration groups and replica runs remain in `workflows/test-integration.yml`. Do not duplicate them in another workflow.
+
+CI helpers used by existing workflows live in `scripts/ci/`. Jobs that need merge bases check out full history. Removing a CI job does not count its tests as covered. List tests that no remaining job invokes explicitly in `ci-coverage-allowlist.yml`.
diff --git a/.github/PULL_REQUEST_TEMPLATE/general.md b/.github/PULL_REQUEST_TEMPLATE/general.md
index 4f1a18e925e..f815601a29c 100644
--- a/.github/PULL_REQUEST_TEMPLATE/general.md
+++ b/.github/PULL_REQUEST_TEMPLATE/general.md
@@ -6,19 +6,12 @@
## Handwritten 1-3 sentence description
-
-``
-
-``
+[insert handwritten description here]
Details
@@ -100,6 +93,7 @@ If you're seeing a delay in your PR being merged, ping the LiteLLM Team on [Slac
Otherwise proof must be fully e2e, no mocks, with real provider calls where applicable. Pytest alone is insufficient. The following format applies to live proof
Show only the latest proof. Before uses the merge base and After the tested tip. Rerun affected QA after behavior changes. Verified neutral diffs may carry proof with the tested SHA, current tip, equivalence reason and focused checks, following AGENTS.md and /qa
Use matching Before/After case headings with numbered commands and observed output. Put shared setup above them. For one case, omit case headings
+ A reviewer must be able to rerun every case from the body alone, so the shared setup gives the proxy start command, the config.yaml, env var names (never values) and the client code or curl used, all exactly as run. Wrap any block longer than about 20 lines in a collapsed with a one-line summary
### Before ()
diff --git a/.github/actions/detect-changes/action.yml b/.github/actions/detect-changes/action.yml
index b0f9b72f0ee..59c6ab28c30 100644
--- a/.github/actions/detect-changes/action.yml
+++ b/.github/actions/detect-changes/action.yml
@@ -1,6 +1,6 @@
name: "Detect relevant changes"
description: >-
- Classify the pull request's changed files with .circleci/scripts/classify_changes.sh
+ Classify the pull request's changed files with .github/scripts/ci/classify_changes.sh
and expose decision=run|skip for one category. backend means anything outside ui/,
docs/ and markdown; ui means the dashboard sources alone. decision=skip lets callers
short-circuit expensive steps while the job still completes successfully and satisfies
diff --git a/.github/ci-coverage-allowlist.yml b/.github/ci-coverage-allowlist.yml
index 5dffa78776d..3a714187325 100644
--- a/.github/ci-coverage-allowlist.yml
+++ b/.github/ci-coverage-allowlist.yml
@@ -4,6 +4,16 @@ description: >-
by a job nor listed here, so every entry below is a decision on the record.
test_paths:
+ - reason: ran only in CircleCI, removed
+ paths:
+ - tests/e2e/migrations/test_legacy.py
+ - tests/e2e/migrations/test_pooling.py
+ - tests/e2e/migrations/test_recovery.py
+ - tests/e2e/migrations/test_rolling_upgrade.py
+ - tests/e2e/migrations/test_shaped_database.py
+ - tests/e2e/migrations/test_startup.py
+ - tests/e2e/migrations/test_upgrade.py
+ - tests/integration/database/test_roi_observed.py
- reason: >-
litellm.agent() end-to-end suite. It drives the real claude, codex, opencode and pi CLIs and
deepagents against a live LiteLLM AI Gateway, so it needs those binaries on PATH plus
@@ -19,7 +29,7 @@ test_paths:
- tests/rust-python-harness
- reason: >-
Owned by the open pull request that moves the Rust/Python parity checks (#45135). It ran in
- the CircleCI build_and_test job that globbed tests/test_*.py, which this migration deletes,
+ the retired build_and_test job that globbed tests/test_*.py,
and that pull request gives it a new home
paths:
- tests/test_rust_python_harness.py
@@ -57,15 +67,6 @@ test_paths:
- tests/integration/test_oci_proxy_integration.py
dockerfiles:
- - reason: >-
- The dashboard container is a static Next.js export served by nginx, and the dashboard build
- and lint workflows already exercise that output, so building the image adds no signal about it
- paths:
- - ui/Dockerfile
- - reason: >-
- An example image under cookbook/ that is documentation rather than a shipped artifact
- paths:
- - cookbook/litellm-ollama-docker-image/Dockerfile
- reason: >-
The Rust gateway image compiles the whole workspace in release mode, which is too slow for
a per-pull-request job while the gateway binary is still being assembled; the Rust lint,
diff --git a/.github/scripts/assert_ci_coverage.py b/.github/scripts/assert_ci_coverage.py
index b442948c3ca..b443fd9e0d9 100644
--- a/.github/scripts/assert_ci_coverage.py
+++ b/.github/scripts/assert_ci_coverage.py
@@ -10,6 +10,7 @@ import warnings
from collections.abc import Callable, Iterable, Mapping, Sequence
from dataclasses import dataclass
from functools import lru_cache
+from itertools import chain
from types import MappingProxyType
from typing import Final
@@ -17,7 +18,6 @@ import yaml
REPO_ROOT = pathlib.Path(__file__).resolve().parents[2]
WORKFLOW_DIR = REPO_ROOT / ".github" / "workflows"
-CIRCLECI_CONFIG = REPO_ROOT / ".circleci" / "config.yml"
ALLOWLIST_FILE = REPO_ROOT / ".github" / "ci-coverage-allowlist.yml"
TESTS_ROOT = REPO_ROOT / "tests"
@@ -25,14 +25,18 @@ ALLOWLIST_KEYS = frozenset({"description", "test_paths", "dockerfiles"})
PATH_FILTER_KEYS = frozenset({"paths", "paths-ignore"})
TEST_PATH_KEYS = frozenset({"test-path", "test-paths", "test_path"})
DOCKERFILE_INPUT_KEYS = frozenset({"file", "dockerfile"})
-TEST_RUNNER_RE = re.compile(r"\bpytest\b|\bcircleci tests\b|\bhelm unittest\b|\bplaywright test\b|\bpython[0-9.]*\s")
+TEST_RUNNER_RE = re.compile(r"\bpytest\b|\bhelm unittest\b|\bplaywright test\b|\bpython[0-9.]*\s")
IMAGE_BUILD_RE = re.compile(r"\bdocker\s+(?:buildx\s+)?build\b")
TEST_TOKEN_RE = re.compile(r"tests/[A-Za-z0-9_./*?-]+")
IGNORE_ARG_RE: Final = re.compile(r"--ignore(?:-glob)?[= ](\S+)")
DOCKERFILE_TOKEN_RE = re.compile(r"[A-Za-z0-9_./-]*Dockerfile[A-Za-z0-9_.-]*")
COMMENT_RE = re.compile(r"^\s*#.*$", re.MULTILINE)
GLOB_CHARS = frozenset("*?")
-DEDICATED_INTEGRATION_WORKFLOWS: Final = MappingProxyType({"image-scan.yml": "tests/integration/database/image/"})
+DEDICATED_INTEGRATION_WORKFLOWS: Final = MappingProxyType(
+ {
+ "image-scan.yml": "tests/integration/database/image/",
+ }
+)
# Trees whose jobs are sharded with no catch-all bucket, so every child that holds
# tests has to be named by some shard or it runs nowhere. A child listed here is
@@ -115,9 +119,7 @@ def _scalars(node: object, key: str) -> tuple[Scalar, ...]:
def _config_files() -> tuple[pathlib.Path, ...]:
- workflows = tuple(sorted(path for path in WORKFLOW_DIR.iterdir() if path.suffix in (".yml", ".yaml")))
- circleci = (CIRCLECI_CONFIG,) if CIRCLECI_CONFIG.is_file() else ()
- return workflows + circleci
+ return tuple(sorted(path for path in WORKFLOW_DIR.iterdir() if path.suffix in (".yml", ".yaml")))
def _all_scalars() -> tuple[Scalar, ...]:
@@ -243,7 +245,6 @@ def _describe(paths: tuple[str, ...]) -> str:
return f"{len(paths)} test file(s) invoked by no job: {names}{suffix}"
-GLOB_CALL_RE = re.compile(r'circleci tests glob "([^"]+)"')
KEYWORD_RE = re.compile(r"-k\s+\\?[\"']([^\"'\\]+)")
@@ -316,16 +317,14 @@ def _keyword_terms(
def _slices() -> tuple[Slice, ...]:
- if not CIRCLECI_CONFIG.exists():
- return ()
test_files: Final = frozenset(_test_files())
- jobs: Final = yaml.safe_load(CIRCLECI_CONFIG.read_text()).get("jobs", {})
return tuple(
- Slice(job=job, globs=globs, named=named, required=required, excluded=excluded, understood=understood)
- for job, body in jobs.items()
+ Slice(job=f"{path.name}:{job}", globs=globs, named=named, required=required, excluded=excluded, understood=understood)
+ for path in _config_files()
+ for job, body in yaml.safe_load(path.read_text()).get("jobs", {}).items()
for text in ("\n".join(_strings(body)),)
if "pytest" in text
- for globs in (tuple(GLOB_CALL_RE.findall(text)),)
+ for globs in (tuple(token for token in TEST_TOKEN_RE.findall(text) if GLOB_CHARS & set(token)),)
for named in (frozenset(TEST_TOKEN_RE.findall(text)) & test_files,)
for required, excluded, understood in (
_keyword_terms(tuple(KEYWORD_RE.findall(text)), attributable=len(globs) < 2),
@@ -352,16 +351,11 @@ def _matchable_names(relative_path: str) -> frozenset[str]:
def _workflow_named_tokens() -> frozenset[str]:
- """Test tokens a GitHub Actions job names directly.
-
- A CircleCI `-k` that deselects a file no longer means the file runs nowhere once a
- workflow names it, so the slice check has to credit those the same way the census does.
- """
return _invoked_test_tokens(
scalar
for path in _config_files()
- if path != CIRCLECI_CONFIG
for scalar in _scalars(yaml.safe_load(path.read_text(encoding="utf-8")), path.name)
+ if not KEYWORD_RE.search(scalar.value)
)
@@ -566,13 +560,28 @@ def _owns(workflow: str, relative_path: str) -> bool:
return owned_root is not None and relative_path.startswith(owned_root)
+def _expression_suites(value: str) -> tuple[str, ...]:
+ suites: Final = chain.from_iterable(map(json.loads, re.findall(r"'(\[[^']*\])'", value)))
+ return tuple(suite for suite in suites if isinstance(suite, str) and "{" not in suite)
+
+
+def _matrix_suites(value: object) -> tuple[str, ...]:
+ if isinstance(value, list):
+ return tuple(suite for suite in value if isinstance(suite, str))
+ if isinstance(value, str):
+ return _expression_suites(value)
+ return ()
+
+
def _tokens_outside_owner(relative_path: str, workflow_tokens: Mapping[str, frozenset[str]]) -> frozenset[str]:
return frozenset[str]().union(
*(tokens for workflow, tokens in workflow_tokens.items() if not _owns(workflow, relative_path))
)
-def _integration_ownership(repo_root: pathlib.Path = REPO_ROOT) -> tuple[frozenset[str], tuple[Finding, ...]]:
+def _integration_ownership(
+ repo_root: pathlib.Path = REPO_ROOT, *, allowlist: Allowlist | None = None
+) -> tuple[frozenset[str], tuple[Finding, ...]]:
runner: Final = repo_root / "tests/integration/run.py"
if not runner.exists():
return frozenset(), ()
@@ -589,26 +598,19 @@ def _integration_ownership(repo_root: pathlib.Path = REPO_ROOT) -> tuple[frozens
browser_manifest: Final = repo_root / "tests/e2e/ui/tests/integrationCritical/expected.json"
browser_nodes: Final = json.loads(browser_manifest.read_text()) if browser_manifest.exists() else ()
browser_paths: Final = frozenset(node.split("::", 1)[0] for node in browser_nodes)
- circle_path: Final = repo_root / ".circleci/config.yml"
- circle: Final = yaml.safe_load(circle_path.read_text()) if circle_path.exists() else {}
- circle_test_path_tokens: Final = _invoked_test_tokens(
- scalar for scalar in _scalars(circle, "config.yml") if scalar.key == "test_path"
- )
- steps: Final = circle.get("jobs", {}).get("integration_contracts", {}).get("steps", ())
+ integration_path: Final = repo_root / ".github/workflows/test-integration.yml"
+ integration: Final = yaml.safe_load(integration_path.read_text()) if integration_path.exists() else {}
+ integration_jobs: Final = integration.get("jobs", {})
invoked: Final = any(
- ".circleci/scripts/run_integration.sh" in scalar.value
- for scalar in _scalars(steps, "integration_contracts")
- if scalar.key == "command"
+ ".github/scripts/ci/run_integration.sh" in scalar.value
+ for scalar in _scalars(integration_jobs, "integration")
+ if scalar.key == "run"
)
scheduled: Final = frozenset(
suite
- for job in circle.get("workflows", {}).get("integration", {}).get("jobs", ())
- if isinstance(job, dict) and "integration_contracts" in job
- for suite in job["integration_contracts"]
- .get("matrix", {})
- .get("parameters", {})
- .get("suite", (job["integration_contracts"].get("suite"),))
- if isinstance(suite, str)
+ for body in integration_jobs.values()
+ if any(".github/scripts/ci/run_integration.sh" in scalar.value for scalar in _scalars(body, "integration"))
+ for suite in _matrix_suites(body.get("strategy", {}).get("matrix", {}).get("suite", ()))
)
required: Final = (frozenset({"browser"}) if browser_paths else frozenset()) | frozenset(
group
@@ -630,19 +632,27 @@ def _integration_ownership(repo_root: pathlib.Path = REPO_ROOT) -> tuple[frozens
}
)
gha_tokens: Final = frozenset[str]().union(*workflow_tokens.values())
- circle_owned: Final = (
+ invoked_github_files: Final = frozenset(
+ path for path in github_files if any(_token_covers(token, path) for token in gha_tokens)
+ )
+ grouped: Final = (
frozenset(str(path.relative_to(repo_root)) for path in integration_root.rglob("test_*.py")) - github_files
)
findings: Final = (
tuple(
Finding(path, "integration file outside GITHUB_FILES is selected by GitHub Actions")
- for path in sorted(circle_owned)
- if any(_token_covers(token, path) for token in gha_tokens)
+ for path in sorted(grouped)
+ if any(
+ _token_covers(token, path)
+ for workflow, tokens in workflow_tokens.items()
+ if workflow != "test-integration.yml"
+ for token in tokens
+ )
)
+ tuple(
Finding(path, "GitHub-owned integration contract has no invoking job")
for path in sorted(github_files)
- if not any(_token_covers(token, path) for token in gha_tokens | circle_test_path_tokens)
+ if path not in invoked_github_files and not (allowlist is not None and allowlist.covers_test(path))
)
+ tuple(
Finding(path, "GitHub-owned integration file is missing")
@@ -658,6 +668,7 @@ def _integration_ownership(repo_root: pathlib.Path = REPO_ROOT) -> tuple[frozens
browser_commands: Final = tuple(
scalar.value
for path in (repo_root / ".github/workflows").glob("*.y*ml")
+ if path.name != "test-integration.yml"
for scalar in _scalars(yaml.safe_load(path.read_text()), path.name)
if scalar.key in {"run", "command"}
)
@@ -690,14 +701,14 @@ def _integration_ownership(repo_root: pathlib.Path = REPO_ROOT) -> tuple[frozens
else ()
)
group_findings: Final = tuple(
- Finding(group, "canonical integration group is not scheduled by CircleCI")
+ Finding(group, "canonical integration group is not scheduled by GitHub Actions")
for group in sorted(required - scheduled)
) + tuple(Finding(path, "canonical node must have exactly one integration group") for path in sorted(ungrouped))
if not paths or not invoked or not scheduled:
return frozenset(), findings + (
- Finding(str(runner.relative_to(repo_root)), "dedicated CircleCI runner is missing"),
+ Finding(str(runner.relative_to(repo_root)), "dedicated GitHub Actions runner is missing"),
)
- return paths | browser_paths | github_files, findings + group_findings + browser_findings + exclusion_findings
+ return paths | browser_paths | invoked_github_files, findings + group_findings + browser_findings + exclusion_findings
def main() -> int:
@@ -709,7 +720,7 @@ def main() -> int:
allowlist = _load_allowlist()
scalars = _all_scalars()
- integration_paths, ownership_findings = _integration_ownership()
+ integration_paths, ownership_findings = _integration_ownership(allowlist=allowlist)
test_findings = _uncovered_tests(
allowlist,
_invoked_selections(scalars) + (Selection(included=integration_paths, ignored=frozenset()),),
@@ -731,8 +742,8 @@ def main() -> int:
)
if ownership_findings:
browser_remedy = (
- " CircleCI's browser group owns the integrationCritical Playwright contracts, so no GitHub workflow may "
- "select them and the default Playwright config must exclude them."
+ " The browser group in test-integration.yml owns the integrationCritical Playwright contracts. "
+ "Other workflows and the default Playwright config must exclude them."
if any("browser" in finding.detail or "Playwright" in finding.detail for finding in ownership_findings)
else ""
)
diff --git a/.circleci/scripts/classify_changes.sh b/.github/scripts/ci/classify_changes.sh
similarity index 86%
rename from .circleci/scripts/classify_changes.sh
rename to .github/scripts/ci/classify_changes.sh
index a33695d0dbe..82851fa0a82 100755
--- a/.circleci/scripts/classify_changes.sh
+++ b/.github/scripts/ci/classify_changes.sh
@@ -14,22 +14,22 @@ outside_cost_map_set=false
while IFS= read -r file || [ -n "$file" ]; do
[ -n "$file" ] || continue
case "$file" in
- litellm/_redis.py | litellm/_redis_credential_provider.py | litellm/caching/redis_cache.py | litellm/caching/evicted_client_closer.py | tests/unit/test_redis.py | tests/integration/sdk/test_redis_cluster_iam_auth.py | tests/unit/caching/test_redis_connection_pool.py | tests/unit/caching/test_redis_cluster_cache.py | tests/unit/caching/test_evicted_client_closer.py | .github/workflows/test-unit.yml | .github/actions/detect-changes/* | .github/scripts/detect_changes.sh | .circleci/scripts/classify_changes.sh | tests/integration/run.py | pyproject.toml | uv.lock)
+ litellm/_redis.py | litellm/_redis_credential_provider.py | litellm/caching/redis_cache.py | litellm/caching/evicted_client_closer.py | tests/unit/test_redis.py | tests/integration/sdk/test_redis_cluster_iam_auth.py | tests/unit/caching/test_redis_connection_pool.py | tests/unit/caching/test_redis_cluster_cache.py | tests/unit/caching/test_evicted_client_closer.py | .github/workflows/test-unit.yml | .github/actions/detect-changes/* | .github/scripts/detect_changes.sh | .github/scripts/ci/classify_changes.sh | tests/integration/run.py | pyproject.toml | uv.lock)
has_redis_compat=true ;;
esac
case "$file" in
*.md | *.mdx) : ;;
- pyproject.toml | */pyproject.toml | uv.lock | uv.toml | .python-version | rust-toolchain.toml | litellm-rust/* | litellm/__init__.py | litellm/proxy/proxy_server.py | litellm/*mcp* | tests/*mcp* | litellm/integrations/arize/* | tests/base_sdk_tests/* | scripts/check_mcp_sdk_install.py | .github/workflows/test-mcp-dependency-resolution.yml | .github/actions/detect-changes/* | .github/actions/setup-uv-with-retries/* | .github/actions/cache-cargo-build/* | .github/scripts/detect_changes.sh | .github/scripts/uv_sync_with_retries.sh | .circleci/scripts/classify_changes.sh | tests/unit/test_circleci_path_filter.py | tests/unit/test_detect_changes.py)
+ pyproject.toml | */pyproject.toml | uv.lock | uv.toml | .python-version | rust-toolchain.toml | litellm-rust/* | litellm/__init__.py | litellm/proxy/proxy_server.py | litellm/*mcp* | tests/*mcp* | litellm/integrations/arize/* | tests/base_sdk_tests/* | scripts/check_mcp_sdk_install.py | .github/workflows/test-mcp-dependency-resolution.yml | .github/actions/detect-changes/* | .github/actions/setup-uv-with-retries/* | .github/actions/cache-cargo-build/* | .github/scripts/detect_changes.sh | .github/scripts/uv_sync_with_retries.sh | .github/scripts/ci/classify_changes.sh | tests/unit/test_ci_path_filter.py | tests/unit/test_detect_changes.py)
has_mcp_dependencies=true ;;
esac
case "$file" in
- litellm-rust/* | litellm/rust_bridge/* | rust-toolchain.toml | pyproject.toml | uv.lock | tests/windows_tests/* | .circleci/*)
+ litellm-rust/* | litellm/rust_bridge/* | rust-toolchain.toml | pyproject.toml | uv.lock | tests/windows_tests/* | .github/scripts/ci/* | .github/actions/*)
has_windows_release=true ;;
esac
case "$file" in
ui/* | tests/e2e/ui/*) has_client=true ;;
docs/* | *.md | *.mdx) : ;;
- .github/* | .circleci/*) has_ci=true; has_backend=true ;;
+ .github/*) has_ci=true; has_backend=true ;;
*) has_backend=true ;;
esac
case "$file" in
diff --git a/.circleci/scripts/path_filter.sh b/.github/scripts/ci/path_filter.sh
similarity index 78%
rename from .circleci/scripts/path_filter.sh
rename to .github/scripts/ci/path_filter.sh
index c3cf6e566ff..e8ac6e5b918 100755
--- a/.circleci/scripts/path_filter.sh
+++ b/.github/scripts/ci/path_filter.sh
@@ -6,10 +6,12 @@ here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
run_full() {
echo "path-filter[$category]: running job ($1)"
+ echo "decision=run"
+ [ -z "${GITHUB_OUTPUT:-}" ] || echo "decision=run" >> "$GITHUB_OUTPUT"
exit 0
}
-[ -n "${CIRCLE_PULL_REQUEST:-}" ] || run_full "not a pull request"
+[ "${GITHUB_EVENT_NAME:-}" = pull_request ] || run_full "not a pull request"
candidate_bases="${PATH_FILTER_BASE_BRANCH:-main}"
merge_base=""
@@ -36,5 +38,6 @@ if [ "$decision" = run ]; then
run_full "$category-relevant changes detected"
fi
-echo "path-filter[$category]: only unrelated changes detected; halting job as successful"
-circleci-agent step halt
+echo "path-filter[$category]: only unrelated changes detected"
+echo "decision=skip"
+[ -z "${GITHUB_OUTPUT:-}" ] || echo "decision=skip" >> "$GITHUB_OUTPUT"
diff --git a/.circleci/scripts/prepare_replica_roles.py b/.github/scripts/ci/prepare_replica_roles.py
similarity index 100%
rename from .circleci/scripts/prepare_replica_roles.py
rename to .github/scripts/ci/prepare_replica_roles.py
diff --git a/.circleci/scripts/run_integration.sh b/.github/scripts/ci/run_integration.sh
similarity index 92%
rename from .circleci/scripts/run_integration.sh
rename to .github/scripts/ci/run_integration.sh
index fb91bcce77f..ed405634796 100644
--- a/.circleci/scripts/run_integration.sh
+++ b/.github/scripts/ci/run_integration.sh
@@ -30,7 +30,7 @@ egress_cgroup=litellm-integration
cleanup() {
original_status=$?
trap - EXIT INT TERM
- sudo .venv/bin/python .circleci/scripts/stop_integration_processes.py \
+ sudo .venv/bin/python .github/scripts/ci/stop_integration_processes.py \
"$integration_identity" "$(id -u)" "$proxy_pid" "$peer_pid" "$upstream_pid" \
> "$results/process-cleanup.txt" 2>&1 || original_status=1
for owned_pid in "$peer_pid" "$proxy_pid" "$upstream_pid"; do
@@ -84,7 +84,7 @@ if [ "$suite" = browser ]; then
export LITELLM_UI_PATH="$PWD/ui/litellm-dashboard/out"
test -f "$LITELLM_UI_PATH/index.html"
fi
-export INTEGRATION_SEED="$(.venv/bin/python -c 'import hashlib,os; print(int(hashlib.sha256(((os.environ.get("CIRCLE_SHA1") or os.environ.get("GITHUB_SHA") or "local") + (os.environ.get("CIRCLE_WORKFLOW_ID") or os.environ.get("GITHUB_RUN_ID") or "local")).encode()).hexdigest()[:8],16))')"
+export INTEGRATION_SEED="$(.venv/bin/python -c 'import hashlib,os; print(int(hashlib.sha256(((os.environ.get("GITHUB_SHA") or "local") + (os.environ.get("GITHUB_RUN_ID") or "local")).encode()).hexdigest()[:8],16))')"
export INTEGRATION_ORDER_SEED="$INTEGRATION_SEED"
uv run --no-sync prisma generate --schema litellm/proxy/schema.prisma > "$results/prisma-generate.log" 2>&1
@@ -93,7 +93,7 @@ export INTEGRATION_PROXY_DATABASE_URL=""
export INTEGRATION_PROXY_READ_REPLICA_URL=""
export INTEGRATION_ROUTING=""
if [ "$mode" = replica ] || [ "$mode" = parity ]; then
- .venv/bin/python .circleci/scripts/prepare_replica_roles.py > "$results/prepare-replica-roles.log" 2>&1
+ .venv/bin/python .github/scripts/ci/prepare_replica_roles.py > "$results/prepare-replica-roles.log" 2>&1
export INTEGRATION_PROXY_DATABASE_URL="postgresql://litellm_writer:litellm-writer@127.0.0.1:5432/circle_test"
export INTEGRATION_PROXY_READ_REPLICA_URL="postgresql://litellm_reader:litellm-reader@127.0.0.1:5432/circle_test"
fi
@@ -202,7 +202,7 @@ start_proxy() {
if [ "$isolated_workers" = false ]; then
start_proxy 4000 proxy.log
proxy_pid="$launched_pid"
- .venv/bin/python .circleci/scripts/wait_integration_services.py
+ .venv/bin/python .github/scripts/ci/wait_integration_services.py
curl --noproxy '*' -sSf -X POST "$INTEGRATION_PROXY_URL/config/update" \
-H "Authorization: Bearer $LITELLM_MASTER_KEY" -H 'Content-Type: application/json' \
-d '{"router_settings": {"num_retries": 0}}' > "$results/seed-router-settings.json"
@@ -210,7 +210,7 @@ if [ "$isolated_workers" = false ]; then
export INTEGRATION_PEER_URL=http://127.0.0.1:4001
start_proxy 4001 peer.log
peer_pid="$launched_pid"
- .venv/bin/python .circleci/scripts/wait_integration_services.py
+ .venv/bin/python .github/scripts/ci/wait_integration_services.py
fi
fi
@@ -233,14 +233,13 @@ if [ "$suite" = browser ]; then
E2E_UI_BASE_URL="$E2E_UI_BASE_URL" E2E_UI_ARTIFACT_DIR="$E2E_UI_ARTIFACT_DIR" \
LITELLM_MASTER_KEY="$LITELLM_MASTER_KEY" CI=true \
node tests/e2e/ui/node_modules/@playwright/test/cli.js test --config tests/e2e/ui/integration.config.ts
- .venv/bin/python .circleci/scripts/verify_integration_browser.py "$results/browser-results.json"
+ .venv/bin/python .github/scripts/ci/verify_integration_browser.py "$results/browser-results.json"
exit 0
fi
shard_arguments=()
-if [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ]; then
- .venv/bin/python tests/integration/run.py "$suite" --list \
- | circleci tests split --split-by=timings --timings-type=filename > "$results/node-files.txt"
+if [ -n "${INTEGRATION_SHARD_FILES:-}" ]; then
+ cp "$INTEGRATION_SHARD_FILES" "$results/node-files.txt"
shard_arguments=(--shard-files "$results/node-files.txt")
fi
@@ -265,7 +264,7 @@ env -i PATH="$PATH" HOME="$HOME" PYTHONPATH="$PYTHONPATH" \
"${license_env[@]}" \
INTEGRATION_SEED="$INTEGRATION_SEED" \
INTEGRATION_ORDER_SEED="$INTEGRATION_ORDER_SEED" \
- INTEGRATION_CASE_SHARDS="${CIRCLE_NODE_TOTAL:-1}" INTEGRATION_CASE_SHARD_INDEX="${CIRCLE_NODE_INDEX:-0}" \
+ INTEGRATION_CASE_SHARDS="${INTEGRATION_CASE_SHARDS:-1}" INTEGRATION_CASE_SHARD_INDEX="${INTEGRATION_CASE_SHARD_INDEX:-0}" \
LITELLM_LOCAL_MODEL_COST_MAP=True AWS_EC2_METADATA_DISABLED=true DO_NOT_TRACK=1 \
INTEGRATION_PROXY_DATABASE_URL="$INTEGRATION_PROXY_DATABASE_URL" \
INTEGRATION_PROXY_READ_REPLICA_URL="$INTEGRATION_PROXY_READ_REPLICA_URL" \
diff --git a/.circleci/scripts/stop_integration_processes.py b/.github/scripts/ci/stop_integration_processes.py
similarity index 100%
rename from .circleci/scripts/stop_integration_processes.py
rename to .github/scripts/ci/stop_integration_processes.py
diff --git a/.circleci/scripts/verify_integration_browser.py b/.github/scripts/ci/verify_integration_browser.py
similarity index 94%
rename from .circleci/scripts/verify_integration_browser.py
rename to .github/scripts/ci/verify_integration_browser.py
index 4468fadbcde..452a6cd43ae 100644
--- a/.circleci/scripts/verify_integration_browser.py
+++ b/.github/scripts/ci/verify_integration_browser.py
@@ -31,7 +31,7 @@ def main() -> None:
result: Final = json.loads(Path(sys.argv[1]).read_text())
assert not result.get("errors"), result.get("errors")
expected: Final = json.loads(
- (Path(__file__).resolve().parents[2] / "tests/e2e/ui/tests/integrationCritical/expected.json").read_text()
+ (Path(__file__).resolve().parents[3] / "tests/e2e/ui/tests/integrationCritical/expected.json").read_text()
)
assert expected and result["stats"]["expected"] == len(expected)
assert all(result["stats"][name] == 0 for name in ("unexpected", "flaky", "skipped"))
@@ -41,7 +41,7 @@ def main() -> None:
suites: Final = TypeAdapter(list[BrowserSuite]).validate_python(result["suites"], strict=True)
specs: Final = tuple(spec for suite in suites for spec in cases(suite))
- repository: Final = Path(__file__).resolve().parents[2]
+ repository: Final = Path(__file__).resolve().parents[3]
report_root: Final = Path(result["config"]["rootDir"])
assert report_root.is_absolute(), "Playwright rootDir must be explicit"
observed: Final = tuple(
diff --git a/.circleci/scripts/wait_integration_services.py b/.github/scripts/ci/wait_integration_services.py
similarity index 100%
rename from .circleci/scripts/wait_integration_services.py
rename to .github/scripts/ci/wait_integration_services.py
diff --git a/.github/scripts/detect_changes.sh b/.github/scripts/detect_changes.sh
index 2d427c92fb5..acc19be57f1 100755
--- a/.github/scripts/detect_changes.sh
+++ b/.github/scripts/detect_changes.sh
@@ -16,7 +16,7 @@ run_full() {
}
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-classify="${here}/../../.circleci/scripts/classify_changes.sh"
+classify="${here}/ci/classify_changes.sh"
[ -n "${PR_NUMBER:-}" ] || run_full "not a pull_request event"
[ -n "${REPO:-}" ] || run_full "no repository in the environment"
diff --git a/.github/scripts/verify_linux_native_wheel.py b/.github/scripts/verify_linux_native_wheel.py
index 4c4cb399dce..571092263ed 100644
--- a/.github/scripts/verify_linux_native_wheel.py
+++ b/.github/scripts/verify_linux_native_wheel.py
@@ -214,7 +214,7 @@ def main(
native_module: Final = load_native_module(native_path)
native_module_loads: Final = native_module is not None
panic_test_hook_absent: Final = native_module is not None and not hasattr(native_module, "_panic_for_test")
- native_size_limit: Final = 48_000_000
+ native_size_limit: Final = 55_000_000
native_size_within_limit: Final = native_member.file_size <= native_size_limit
validations: Final = (
(f"Python tag is {EXPECTED_PYTHON_TAG}", python_tag == EXPECTED_PYTHON_TAG),
diff --git a/.github/workflows/compat-matrix-image.yml b/.github/workflows/compat-matrix-image.yml
index ddd2aa1a50b..73a77c1e861 100644
--- a/.github/workflows/compat-matrix-image.yml
+++ b/.github/workflows/compat-matrix-image.yml
@@ -26,17 +26,24 @@ jobs:
with:
persist-credentials: false
- - name: Build the Render cron image
- run: docker build -f tests/e2e/claude_code/cron_vm/Dockerfile -t compat-matrix:${{ github.sha }} tests/e2e
-
- - name: Resolve and install the Claude Code CLI as the cron user
+ - name: Install the pinned uv the cron image ships
+ env:
+ UV_VERSION: 0.10.9
+ UV_SHA256: 20d79708222611fa540b5c9ed84f352bcd3937740e51aacc0f8b15b271c57594
run: |
- docker run --rm compat-matrix:${{ github.sha }} bash -c '
- set -euo pipefail
- whoami
- gh --version
- uv --version
- version="$(uv run --no-project --python 3.12 python /opt/litellm/tests/e2e/cli_harness/npm_version_resolver.py @anthropic-ai/claude-code)"
- /opt/litellm/tests/e2e/claude_code/cron_vm/install_claude_code.sh "${version}" /tmp/claude-cli
- /tmp/claude-cli/claude --version
- '
+ set -euo pipefail
+ curl -fsSLo "$RUNNER_TEMP/uv.tar.gz" "https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/uv-x86_64-unknown-linux-gnu.tar.gz"
+ echo "${UV_SHA256} $RUNNER_TEMP/uv.tar.gz" | sha256sum -c -
+ mkdir -p "$RUNNER_TEMP/bin"
+ tar -xzf "$RUNNER_TEMP/uv.tar.gz" -C "$RUNNER_TEMP/bin" --strip-components=1 uv-x86_64-unknown-linux-gnu/uv
+ echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH"
+
+ - name: Resolve and install the Claude Code CLI as an unprivileged user
+ run: |
+ set -euo pipefail
+ whoami
+ gh --version
+ uv --version
+ version="$(uv run --no-project --python 3.12 python tests/e2e/cli_harness/npm_version_resolver.py @anthropic-ai/claude-code)"
+ tests/e2e/claude_code/cron_vm/install_claude_code.sh "${version}" "$RUNNER_TEMP/claude-cli"
+ "$RUNNER_TEMP/claude-cli/claude" --version
diff --git a/.github/workflows/image-scan.yml b/.github/workflows/image-scan.yml
index 3e2bca8779d..54051104cb9 100644
--- a/.github/workflows/image-scan.yml
+++ b/.github/workflows/image-scan.yml
@@ -8,31 +8,25 @@ on:
- "litellm_**"
paths:
- Dockerfile
- - docker/Dockerfile.non_root
- - docker/Dockerfile.database
- - migrations/Dockerfile
+ - .dockerignore
+ - docker-entrypoint.sh
- migrations/run.py
- - gateway/Dockerfile
- - gateway/launch.py
- gateway/main.py
+ - gateway/launch.py
- gateway/routes/allowlist.py
- litellm_cli/**
- - backend/Dockerfile
- backend/main.py
+ - backend/routes/allowlist.py
- litellm-rust/**
- litellm/proxy/lens/**
- - docker/component_entrypoint.sh
- - docker/entrypoint.sh
- litellm/proxy/prisma_migration.py
- litellm/proxy/admin_mcp.py
- litellm/proxy/proxy_server.py
- - backend/routes/allowlist.py
- pyproject.toml
- litellm-proxy-extras/**
- tests/integration/database/image/**
- uv.lock
- ui/litellm-dashboard/package-lock.json
- - ui/Dockerfile
- ui/nginx.conf
- .github/workflows/image-scan.yml
- .grype.yaml
@@ -46,14 +40,18 @@ concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
+env:
+ IMAGE: litellm-image-scan:${{ github.sha }}
+ ADMIN_MCP_SCHEMA_IMAGE: litellm-admin-mcp-schema:${{ github.sha }}
+
jobs:
- image-scan:
- name: image-scan
+ build:
+ name: build
runs-on: ubuntu-latest
if: >-
github.event_name != 'pull_request' ||
github.event.pull_request.head.repo.full_name == github.repository
- timeout-minutes: 30
+ timeout-minutes: 60
permissions:
contents: read
steps:
@@ -61,7 +59,129 @@ jobs:
with:
persist-credentials: false
+ - name: Build the image
+ run: docker build --build-arg MATURIN_PEP517_ARGS="--profile ci" --build-arg LITELLM_RELEASE_TAG=v0.0.0-lens-ci -t "$IMAGE" .
+
+ - name: Save the image for the verify matrix
+ run: docker save "$IMAGE" | zstd -T0 -3 -o "$RUNNER_TEMP/litellm-image.tar.zst"
+
+ - uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
+ with:
+ name: litellm-image
+ path: ${{ runner.temp }}/litellm-image.tar.zst
+ retention-days: 1
+ compression-level: 0
+
+ - name: Build the Admin MCP schema image
+ run: docker build --build-arg MATURIN_PEP517_ARGS="--profile ci" --build-arg LITELLM_RELEASE_TAG=v0.0.0-lens-ci --target builder -t "$ADMIN_MCP_SCHEMA_IMAGE" .
+
+ - name: Save the Admin MCP schema image for the verify matrix
+ run: docker save "$ADMIN_MCP_SCHEMA_IMAGE" | zstd -T0 -3 -o "$RUNNER_TEMP/litellm-admin-mcp-schema.tar.zst"
+
+ - uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
+ with:
+ name: litellm-admin-mcp-schema
+ path: ${{ runner.temp }}/litellm-admin-mcp-schema.tar.zst
+ retention-days: 1
+ compression-level: 0
+
+ verify:
+ name: verify (${{ matrix.check }})
+ needs: build
+ runs-on: ubuntu-latest
+ timeout-minutes: 45
+ permissions:
+ contents: read
+ strategy:
+ fail-fast: false
+ matrix:
+ check: [scan, migrations, gateway, backend, ui, collector]
+ steps:
+ - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ with:
+ persist-credentials: false
+
+ - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
+ with:
+ name: litellm-image
+ path: ${{ runner.temp }}
+
+ - name: Load the image
+ run: zstd -d --stdout "$RUNNER_TEMP/litellm-image.tar.zst" | docker load
+
+ - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
+ if: matrix.check == 'migrations' || matrix.check == 'backend'
+ with:
+ name: litellm-admin-mcp-schema
+ path: ${{ runner.temp }}
+
+ - name: Load the Admin MCP schema image
+ if: matrix.check == 'migrations' || matrix.check == 'backend'
+ run: zstd -d --stdout "$RUNNER_TEMP/litellm-admin-mcp-schema.tar.zst" | docker load
+
+ - name: Set up Python
+ if: matrix.check != 'scan'
+ uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
+ with:
+ python-version: "3.12"
+
+ - name: Install pytest
+ if: matrix.check != 'scan'
+ run: python -m pip install "pytest==9.0.3"
+
+ # The prisma bake must migrate a fresh DB with no egress as an arbitrary
+ # non-root uid (OpenShift restricted-v2 / air-gapped / readOnlyFilesystem).
+ # `docker run` as the default uid with network hides a broken bake because
+ # the migration entrypoint exits 0 even when it applied nothing; asserting
+ # the schema was created is what catches it.
+ - name: Verify offline migration as a non-root uid
+ if: matrix.check == 'migrations'
+ env:
+ LITELLM_IMAGE: ${{ env.IMAGE }}
+ LITELLM_ADMIN_MCP_SCHEMA_IMAGE: ${{ env.ADMIN_MCP_SCHEMA_IMAGE }}
+ run: |
+ python -m pytest --noconftest -v \
+ tests/integration/database/image/test_offline_image_migration.py \
+ tests/integration/database/image/test_image_bedrock_realtime_extra.py \
+ tests/integration/database/image/test_image_admin_mcp.py
+
+ - name: Verify the gateway component serves offline as a non-root uid
+ if: matrix.check == 'gateway'
+ env:
+ LITELLM_IMAGE: ${{ env.IMAGE }}
+ LITELLM_COMPONENT: gateway
+ LITELLM_IMAGE_COMPONENT: gateway
+ run: |
+ python -m pytest --noconftest -v \
+ tests/integration/database/image/test_component_image_serves_offline.py \
+ tests/integration/database/image/test_image_admin_mcp.py
+
+ - name: Verify the backend component serves offline as a non-root uid
+ if: matrix.check == 'backend'
+ env:
+ LITELLM_IMAGE: ${{ env.IMAGE }}
+ LITELLM_COMPONENT: backend
+ LITELLM_IMAGE_COMPONENT: backend
+ LITELLM_ADMIN_MCP_SCHEMA_IMAGE: ${{ env.ADMIN_MCP_SCHEMA_IMAGE }}
+ run: |
+ python -m pytest --noconftest -v \
+ tests/integration/database/image/test_component_image_serves_offline.py \
+ tests/integration/database/image/test_image_admin_mcp.py
+
+ - name: Verify the ui component serves as an arbitrary uid with a read-only root fs
+ if: matrix.check == 'ui'
+ env:
+ LITELLM_IMAGE: ${{ env.IMAGE }}
+ run: python -m pytest --noconftest -v tests/integration/database/image/test_ui_image_serves_offline.py
+
+ - name: Verify the collector creates its socket as the image user
+ if: matrix.check == 'collector'
+ env:
+ LITELLM_IMAGE: ${{ env.IMAGE }}
+ run: python -m pytest --noconftest -v tests/integration/database/image/test_collector_image_serves_socket.py
+
- name: Download Grype v0.114.0
+ if: matrix.check == 'scan'
run: |
curl -fsSL --retry 3 -o "$RUNNER_TEMP/grype.tar.gz" \
https://github.com/anchore/grype/releases/download/v0.114.0/grype_0.114.0_linux_amd64.tar.gz
@@ -69,33 +189,6 @@ jobs:
tar xzf "$RUNNER_TEMP/grype.tar.gz" -C "$RUNNER_TEMP" grype
chmod +x "$RUNNER_TEMP/grype"
- # Dockerfile.non_root is the rootless variant we ship. The other
- # Dockerfiles share the same wolfi base and apk set, so OS-layer coverage
- # is the same; matrix-scan if those variants ever diverge.
- - name: Build runtime image
- run: docker build -f docker/Dockerfile.non_root -t litellm-image-scan:${{ github.sha }} .
-
- - name: Tag the cached builder for Admin MCP schema setup
- run: docker build --target builder -f docker/Dockerfile.non_root -t litellm-admin-mcp-schema:${{ github.sha }} .
-
- # The prisma bake must migrate a fresh DB with no egress as an arbitrary
- # non-root uid (OpenShift restricted-v2 / air-gapped / readOnlyRootFilesystem).
- # `docker run` as the default uid with network hides a broken bake because
- # the migration entrypoint exits 0 even when it applied nothing; asserting
- # the schema was created is what catches it.
- - name: Set up Python
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- with:
- python-version: "3.12"
-
- - name: Verify offline migration as a non-root uid
- env:
- LITELLM_IMAGE: litellm-image-scan:${{ github.sha }}
- LITELLM_ADMIN_MCP_SCHEMA_IMAGE: litellm-admin-mcp-schema:${{ github.sha }}
- run: |
- python -m pip install "pytest==9.0.3"
- python -m pytest --noconftest tests/integration/database/image/test_offline_image_migration.py tests/integration/database/image/test_image_bedrock_realtime_extra.py tests/integration/database/image/test_image_admin_mcp.py -v
-
# Scans the whole shipped artifact: OS/apk plus every language package
# baked into the image, including ones no lockfile declares (e.g. prisma's
# vendored node engine) that osv-scan cannot see. osv-scan stays the fast
@@ -103,175 +196,12 @@ jobs:
# free OSS, run as a pinned, checksum-verified binary; no GitHub Action
# dependency and no vendor SaaS callout.
- name: Scan image for fixable HIGH/CRITICAL CVEs
+ if: matrix.check == 'scan'
env:
GRYPE_MATCH_PYTHON_USING_CPES: "true"
run: |
- "$RUNNER_TEMP/grype" litellm-image-scan:${{ github.sha }} \
+ "$RUNNER_TEMP/grype" "$IMAGE" \
--config .grype.yaml \
--only-fixed \
--fail-on high \
--output table
-
- runtime-image:
- name: runtime-image (${{ matrix.dockerfile }})
- runs-on: ubuntu-latest
- if: >-
- github.event_name != 'pull_request' ||
- github.event.pull_request.head.repo.full_name == github.repository
- timeout-minutes: 45
- permissions:
- contents: read
- strategy:
- fail-fast: false
- matrix:
- dockerfile: [Dockerfile, docker/Dockerfile.database]
- steps:
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- with:
- persist-credentials: false
-
- - name: Build runtime image
- run: docker build --build-arg LITELLM_RELEASE_TAG=v0.0.0-lens-ci -f "${{ matrix.dockerfile }}" -t litellm-runtime-scan:${{ github.sha }} .
-
- - name: Tag the cached builder for Admin MCP schema setup
- run: docker build --target builder -f "${{ matrix.dockerfile }}" -t litellm-admin-mcp-schema:${{ github.sha }} .
-
- - name: Set up Python
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- with:
- python-version: "3.12"
-
- - name: Verify offline migration as a non-root uid
- env:
- LITELLM_IMAGE: litellm-runtime-scan:${{ github.sha }}
- LITELLM_ADMIN_MCP_SCHEMA_IMAGE: litellm-admin-mcp-schema:${{ github.sha }}
- run: |
- python -m pip install "pytest==9.0.3"
- python -m pytest --noconftest tests/integration/database/image/test_offline_image_migration.py tests/integration/database/image/test_image_bedrock_realtime_extra.py tests/integration/database/image/test_image_admin_mcp.py -v
-
-
- migrations-image:
- name: migrations-image
- runs-on: ubuntu-latest
- if: >-
- github.event_name != 'pull_request' ||
- github.event.pull_request.head.repo.full_name == github.repository
- timeout-minutes: 30
- permissions:
- contents: read
- steps:
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- with:
- persist-credentials: false
-
- - name: Build migrations image
- run: docker build -f migrations/Dockerfile -t litellm-migrations-scan:${{ github.sha }} .
-
- - name: Set up Python
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- with:
- python-version: "3.12"
-
- - name: Verify offline migration as a non-root uid
- env:
- LITELLM_IMAGE: litellm-migrations-scan:${{ github.sha }}
- LITELLM_MIGRATION_INTERPRETER: python3
- LITELLM_MIGRATION_SCRIPT: /app/run.py
- run: |
- python -m pip install "pytest==9.0.3"
- python -m pytest --noconftest tests/integration/database/image/test_offline_image_migration.py -v
-
- gateway-image:
- name: gateway-image
- runs-on: ubuntu-latest
- if: >-
- github.event_name != 'pull_request' ||
- github.event.pull_request.head.repo.full_name == github.repository
- timeout-minutes: 30
- permissions:
- contents: read
- steps:
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- with:
- persist-credentials: false
-
- - name: Build gateway image
- run: docker build -f gateway/Dockerfile -t litellm-gateway-scan:${{ github.sha }} .
-
- - name: Set up Python
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- with:
- python-version: "3.12"
-
- - name: Verify the gateway serves offline as a non-root uid
- env:
- LITELLM_IMAGE: litellm-gateway-scan:${{ github.sha }}
- LITELLM_COMPONENT_PORT: "4000"
- LITELLM_IMAGE_COMPONENT: gateway
- run: |
- python -m pip install "pytest==9.0.3"
- python -m pytest --noconftest tests/integration/database/image/test_component_image_serves_offline.py tests/integration/database/image/test_image_bedrock_realtime_extra.py tests/integration/database/image/test_image_admin_mcp.py -v
-
- ui-image:
- name: ui-image
- runs-on: ubuntu-latest
- if: >-
- github.event_name != 'pull_request' ||
- github.event.pull_request.head.repo.full_name == github.repository
- timeout-minutes: 30
- permissions:
- contents: read
- steps:
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- with:
- persist-credentials: false
-
- - name: Build UI image
- run: docker build -f ui/Dockerfile -t litellm-ui-scan:${{ github.sha }} .
-
- - name: Set up Python
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- with:
- python-version: "3.12"
-
- - name: Verify the UI serves offline as an arbitrary uid with a read-only root fs
- env:
- LITELLM_IMAGE: litellm-ui-scan:${{ github.sha }}
- run: |
- python -m pip install "pytest==9.0.3"
- python -m pytest --noconftest tests/integration/database/image/test_ui_image_serves_offline.py -v
-
- backend-image:
- name: backend-image
- runs-on: ubuntu-latest
- if: >-
- github.event_name != 'pull_request' ||
- github.event.pull_request.head.repo.full_name == github.repository
- timeout-minutes: 30
- permissions:
- contents: read
- steps:
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- with:
- persist-credentials: false
-
- - name: Build backend image
- run: docker build -f backend/Dockerfile -t litellm-backend-scan:${{ github.sha }} .
-
- - name: Tag the cached builder for Admin MCP schema setup
- run: docker build --target builder -f backend/Dockerfile -t litellm-admin-mcp-schema:${{ github.sha }} .
-
- - name: Set up Python
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- with:
- python-version: "3.12"
-
- - name: Verify the backend serves offline as a non-root uid
- env:
- LITELLM_IMAGE: litellm-backend-scan:${{ github.sha }}
- LITELLM_ADMIN_MCP_SCHEMA_IMAGE: litellm-admin-mcp-schema:${{ github.sha }}
- LITELLM_COMPONENT_PORT: "4001"
- LITELLM_IMAGE_COMPONENT: backend
- run: |
- python -m pip install "pytest==9.0.3"
- python -m pytest --noconftest tests/integration/database/image/test_component_image_serves_offline.py tests/integration/database/image/test_image_admin_mcp.py -v
diff --git a/.github/workflows/interactions-spec-canary.yml b/.github/workflows/interactions-spec-canary.yml
index a2e4646dce5..89ebdd87a7c 100644
--- a/.github/workflows/interactions-spec-canary.yml
+++ b/.github/workflows/interactions-spec-canary.yml
@@ -49,9 +49,9 @@ jobs:
id: compliance
run: |
uv run --no-sync pytest tests/unit/interactions/test_openapi_compliance.py \
- --interactions-spec-url=https://ai.google.dev/static/api/interactions.openapi.json \
+ --interactions-spec-url=https://aistudio.google.com/api-reference/interactions.openapi.json \
--tb=short -vv -p no:rerunfailures -p no:pytest-retry \
- --allow-hosts=127.0.0.1,::1,localhost,ai.google.dev
+ --allow-hosts=127.0.0.1,::1,localhost,ai.google.dev,aistudio.google.com
tracking-issue:
name: tracking-issue
diff --git a/.github/workflows/test-integration.yml b/.github/workflows/test-integration.yml
index 73159bd64d2..8be7b89e076 100644
--- a/.github/workflows/test-integration.yml
+++ b/.github/workflows/test-integration.yml
@@ -55,7 +55,6 @@ jobs:
services:
postgres:
image: postgres:16@sha256:e17e86066e5ef83e0952a9347f5c792b7ece00972e2aa787a6986f471b3dd3d5
- command: -c max_connections=1000 -c shared_preload_libraries=pg_stat_statements -c pg_stat_statements.track=all -c pg_stat_statements.max=20000
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
@@ -81,6 +80,17 @@ jobs:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
with:
persist-credentials: false
+ - name: Configure PostgreSQL for integration workers
+ env:
+ POSTGRES_CONTAINER: ${{ job.services.postgres.id }}
+ run: |
+ docker exec "$POSTGRES_CONTAINER" sh -c 'printf "%s\n" "max_connections = 1000" "shared_preload_libraries = pg_stat_statements" "pg_stat_statements.track = all" "pg_stat_statements.max = 20000" >> "$PGDATA/postgresql.conf"'
+ docker restart "$POSTGRES_CONTAINER"
+ for attempt in $(seq 1 30); do
+ if docker exec "$POSTGRES_CONTAINER" pg_isready -U postgres; then exit 0; fi
+ sleep 1
+ done
+ exit 1
- name: Set up Python
timeout-minutes: 3
@@ -155,8 +165,8 @@ jobs:
LITELLM_LICENSE: ${{ secrets.LITELLM_LICENSE }}
run: |
case "$SUITE" in
- *-replica) bash .circleci/scripts/run_integration.sh "${SUITE%-replica}" replica ;;
- *) bash .circleci/scripts/run_integration.sh "$SUITE" ;;
+ *-replica) bash .github/scripts/ci/run_integration.sh "${SUITE%-replica}" replica ;;
+ *) bash .github/scripts/ci/run_integration.sh "$SUITE" ;;
esac
- name: Verify every case ran exactly once
diff --git a/.github/workflows/test-linting.yml b/.github/workflows/test-linting.yml
index cbeea71ad0f..fd1d2973951 100644
--- a/.github/workflows/test-linting.yml
+++ b/.github/workflows/test-linting.yml
@@ -21,7 +21,7 @@ jobs:
pull-requests: read
actions: read
runs-on: ubuntu-latest
- timeout-minutes: 20
+ timeout-minutes: 30
services:
redis:
image: redis@sha256:e2debfb7956fa12c7ddc79d7e645c8cf26b30c99a6e9161ea9bf4171e1668a5f
diff --git a/.github/workflows/test-mcp-dependency-resolution.yml b/.github/workflows/test-mcp-dependency-resolution.yml
index 20efb351985..90589cf1e6f 100644
--- a/.github/workflows/test-mcp-dependency-resolution.yml
+++ b/.github/workflows/test-mcp-dependency-resolution.yml
@@ -28,7 +28,7 @@ on:
- ".github/actions/cache-cargo-build/**"
- ".github/scripts/detect_changes.sh"
- ".github/scripts/uv_sync_with_retries.sh"
- - ".circleci/scripts/classify_changes.sh"
+ - ".github/scripts/ci/classify_changes.sh"
permissions:
contents: read
diff --git a/.github/workflows/test-merge-smoke.yml b/.github/workflows/test-merge-smoke.yml
index 99dbd1c101b..912fc90fbe5 100644
--- a/.github/workflows/test-merge-smoke.yml
+++ b/.github/workflows/test-merge-smoke.yml
@@ -200,8 +200,36 @@ jobs:
- name: Install the test client dependencies
run: .github/scripts/uv_sync_with_retries.sh --frozen --no-install-workspace --extra proxy --group dev --python 3.12
- - name: Build the image from the published package
- run: docker build -t litellm-build-from-pip:smoke -f docker/build_from_pip/Dockerfile.build_from_pip .
+ - name: Cache Prisma binaries
+ timeout-minutes: 3
+ uses: ./.github/actions/cache-prisma-binaries
+
+ - name: Install the published proxy into its own venv
+ run: |
+ uv --no-config venv --python 3.13 /tmp/litellm-pip
+ uv --no-config pip install --python /tmp/litellm-pip/bin/python \
+ "litellm[proxy,proxy-runtime]==1.83.0" \
+ "google-cloud-aiplatform==1.133.0" \
+ "google-genai==1.37.0" \
+ "anthropic[vertex]==0.84.0" \
+ "grpcio==1.78.0" \
+ "prometheus-client==0.20.0" \
+ "langfuse==2.59.7" \
+ "opentelemetry-api==1.28.0" \
+ "opentelemetry-sdk==1.28.0" \
+ "opentelemetry-exporter-otlp==1.28.0" \
+ "ddtrace==4.11.0" \
+ "sentry-sdk==2.21.0" \
+ "mangum==0.17.0" \
+ "azure-ai-contentsafety==1.0.0" \
+ "azure-storage-file-datalake==12.20.0" \
+ "pypdf==6.7.5" \
+ "llm-sandbox==0.3.31" \
+ "detect-secrets==1.5.0" \
+ "prisma==0.11.0" \
+ "openai==2.24.0"
+ export PATH="/tmp/litellm-pip/bin:$PATH"
+ /tmp/litellm-pip/bin/prisma generate --schema=schema.prisma
- name: Start the fake OpenAI endpoint
run: |
@@ -219,23 +247,21 @@ jobs:
echo "::add-mask::${key}"
echo "LITELLM_MASTER_KEY=${key}" >> "${GITHUB_ENV}"
- - name: Start the proxy container
+ - name: Start the published proxy
+ env:
+ DATABASE_URL: postgresql://postgres:postgres@localhost:5432/circle_test
+ LITELLM_LICENSE: bad-license
+ FAKE_OPENAI_API_BASE: http://localhost:8190
+ OTEL_EXPORTER: in_memory
+ USE_DDTRACE: "True"
+ DD_TRACE_OPENAI_ENABLED: "False"
+ GCS_FLUSH_INTERVAL: "1"
+ LITELLM_LOG: ERROR
run: |
- docker run -d \
- -p 4000:4000 \
- -e DATABASE_URL=postgresql://postgres:postgres@host.docker.internal:5432/circle_test \
- -e LITELLM_MASTER_KEY \
- -e FAKE_OPENAI_API_BASE=http://host.docker.internal:8190 \
- -e OTEL_EXPORTER=in_memory \
- -e USE_DDTRACE=True \
- -e GCS_FLUSH_INTERVAL=1 \
- -e LITELLM_LOG=ERROR \
- --add-host host.docker.internal:host-gateway \
- --name litellm-build-from-pip \
- -v "${GITHUB_WORKSPACE}/docker/build_from_pip/litellm_config.yaml:/app/config.yaml" \
- litellm-build-from-pip:smoke \
- --config /app/config.yaml \
- --port 4000
+ cd /tmp/litellm-pip
+ nohup /tmp/litellm-pip/bin/ddtrace-run /tmp/litellm-pip/bin/litellm \
+ --config "${GITHUB_WORKSPACE}/tests/basic_proxy_startup_tests/build_from_pip_config.yaml" \
+ --port 4000 > /tmp/litellm-pip.log 2>&1 &
for _ in $(seq 1 150); do
curl -sf --max-time 5 -o /dev/null http://localhost:4000 && exit 0
sleep 2
@@ -270,7 +296,7 @@ jobs:
- name: Show the proxy logs
if: always()
- run: docker logs litellm-build-from-pip || true
+ run: cat /tmp/litellm-pip.log || true
smoke-passed:
name: smoke passed
diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml
index a090ec7bdb1..6f980b458fc 100644
--- a/.github/workflows/test-unit.yml
+++ b/.github/workflows/test-unit.yml
@@ -45,7 +45,7 @@ jobs:
id: rust-bridge-key
if: steps.changes.outputs.decision != 'skip'
env:
- RUST_BRIDGE_CACHE_KEY: ${{ runner.os }}-rust-bridge-editable-${{ hashFiles('litellm-rust/Cargo.lock', 'litellm-rust/Cargo.toml', 'litellm-rust/crates/**', 'model_prices_and_context_window.json', 'pyproject.toml') }}
+ RUST_BRIDGE_CACHE_KEY: ${{ runner.os }}-rust-bridge-editable-${{ hashFiles('litellm-rust/Cargo.lock', 'litellm-rust/Cargo.toml', 'litellm-rust/crates/**', 'model_prices_and_context_window.json', 'pyproject.toml', 'packaging/litellm-proxy/pyproject.toml') }}
run: echo "key=$RUST_BRIDGE_CACHE_KEY" >> "$GITHUB_OUTPUT"
- name: Set up Python
@@ -98,6 +98,18 @@ jobs:
mkdir -p rust-bridge-dist/litellm/rust_bridge
cp litellm/rust_bridge/_native.abi3.so rust-bridge-dist/litellm/rust_bridge/_native.abi3.so
+ - name: Build the gateway wheel once for test shards
+ if: steps.changes.outputs.decision != 'skip' && steps.rust-bridge-cache.outputs.cache-hit != 'true'
+ timeout-minutes: 15
+ working-directory: packaging/litellm-proxy
+ env:
+ RUSTC_WRAPPER: sccache
+ SCCACHE_GHA_ENABLED: "true"
+ SCCACHE_GHA_VERSION: litellm-ci-v1
+ SCCACHE_GHA_RW_MODE: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && 'READ_WRITE' || 'READ_ONLY' }}
+ CARGO_INCREMENTAL: "0"
+ run: uv run --no-project --with maturin==1.15.0 maturin build --profile dev --strip --out ../../rust-bridge-dist
+
- name: Upload Rust bridge artifact
if: steps.changes.outputs.decision != 'skip'
timeout-minutes: 10
@@ -170,6 +182,7 @@ jobs:
tests/unit/google_genai
tests/unit/router_strategy
tests/unit/router_utils
+ tests/unit/redis
tests/unit/proxy/common_utils/test_cache_aware_routing.py
tests/unit/enterprise/enterprise_callbacks/send_emails
tests/unit/enterprise/proxy/test_afile_retrieve_returns_unified_id.py
@@ -384,6 +397,7 @@ jobs:
tests/unit/llms/voyage
tests/unit/llms/wandb
tests/unit/llms/watsonx
+ tests/unit/llms/webiq
tests/unit/llms/xai
tests/unit/llms/xinference
tests/unit/llms/you_com
@@ -419,6 +433,7 @@ jobs:
- shard: router
test-path: >-
+ tests/unit/router_rust
tests/unit/test_router
python-version: "3.12"
workers: 4
@@ -445,7 +460,6 @@ jobs:
tests/unit/ocr
tests/unit/passthrough
tests/unit/rag
- tests/unit/cookbook
tests/unit/realtime_api
tests/unit/rerank_api
tests/unit/search
@@ -576,6 +590,7 @@ jobs:
tests/unit/proxy/credential_endpoints
tests/unit/proxy/discovery_endpoints
tests/unit/proxy/shutdown
+ tests/unit/proxy/rust_forward
tests/unit/proxy/public_endpoints
tests/unit/proxy/prompts
tests/unit/proxy/rag_endpoints
@@ -618,23 +633,32 @@ jobs:
job-timeout-minutes: 60
dist: loadscope
- - shard: proxy-infra
+ - shard: proxy-infra-db-spend
test-path: |-
tests/unit/proxy/db
--ignore=tests/unit/proxy/db/db_transaction_queue/test_e2e_pod_lock_manager.py
--ignore=tests/unit/proxy/db/test_update_daily_tag_spend.py
- tests/unit/proxy/middleware
- --ignore=tests/unit/proxy/middleware/test_request_size_limit_middleware.py
tests/unit/proxy/spend_tracking
--ignore=tests/unit/proxy/spend_tracking/test_search_api_logging.py
- tests/unit/proxy/pass_through_endpoints
- tests/unit/proxy/mcp_server/test_tool_registry.py
tests/unit/proxy/common_utils
--ignore=tests/unit/proxy/common_utils/test_cache_aware_routing.py
--ignore=tests/unit/proxy/common_utils/test_check_batch_cost.py
--ignore=tests/unit/proxy/common_utils/test_check_responses_cost.py
--ignore=tests/unit/proxy/common_utils/test_proxy_encrypt_decrypt.py
--ignore=tests/unit/proxy/common_utils/test_realtime_cache.py
+ python-version: "3.12"
+ workers: 4
+ reruns: 0
+ timeout-minutes: 20
+ job-timeout-minutes: 60
+ dist: loadscope
+
+ - shard: proxy-infra-routing
+ test-path: |-
+ tests/unit/proxy/middleware
+ --ignore=tests/unit/proxy/middleware/test_request_size_limit_middleware.py
+ tests/unit/proxy/pass_through_endpoints
+ tests/unit/proxy/mcp_server/test_tool_registry.py
tests/unit/proxy/enterprise_billing
tests/unit/proxy/types_utils
tests/unit/proxy/logging_endpoints
@@ -977,10 +1001,10 @@ jobs:
dist: loadscope
steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- timeout-minutes: ${{ matrix.shard == 'db-and-spend' && 10 || 3 }}
+ timeout-minutes: 3
with:
persist-credentials: false
- fetch-depth: ${{ matrix.shard == 'db-and-spend' && '0' || '1' }}
+ fetch-depth: 1
- name: Detect relevant changes
id: changes
@@ -1013,27 +1037,37 @@ jobs:
with:
artifact: ${{ needs.rust-bridge.outputs.artifact }}
- - name: Install dependencies and check schema
+ - name: Install dependencies
if: steps.changes.outputs.decision != 'skip'
- timeout-minutes: 8
+ timeout-minutes: 5
env:
RUST_BRIDGE_ARTIFACT: ${{ needs.rust-bridge.outputs.artifact }}
- UNIT_SHARD: ${{ matrix.shard }}
- SCHEMA_BASE_REF: origin/${{ github.event.repository.default_branch }}
run: |
diff -u model_prices_and_context_window.json litellm/model_prices_and_context_window_backup.json
if [ -z "$RUST_BRIDGE_ARTIFACT" ]; then
- .github/scripts/uv_sync_with_retries.sh --frozen --group ci --group proxy-dev --extra google --extra proxy --extra semantic-router --extra saml --extra extra_proxy --extra proxy-runtime --extra utils
+ .github/scripts/uv_sync_with_retries.sh --frozen --group ci --group proxy-dev --extra google --extra proxy --extra semantic-router --extra switchyard --extra saml --extra extra_proxy --extra proxy-runtime --extra utils
else
- .github/scripts/uv_sync_with_retries.sh --frozen --group ci --group proxy-dev --extra google --extra proxy --extra semantic-router --extra saml --extra extra_proxy --extra proxy-runtime --extra utils --no-install-project
+ .github/scripts/uv_sync_with_retries.sh --frozen --group ci --group proxy-dev --extra google --extra proxy --extra semantic-router --extra switchyard --extra saml --extra extra_proxy --extra proxy-runtime --extra utils --no-install-project --no-install-package litellm-proxy
uv pip install --no-deps --python .venv/bin/python rust-bridge-dist/*.whl
cp rust-bridge-dist/litellm/rust_bridge/_native.abi3.so litellm/rust_bridge/_native.abi3.so
uv run --no-sync python -c "import importlib.metadata; import litellm.rust_bridge._native; print(importlib.metadata.version('litellm'))"
fi
uv run --no-sync python -c 'import os, sys; print(sys.version); assert f"{sys.version_info.major}.{sys.version_info.minor}" == os.environ["UV_PYTHON"]'
- if [ "$UNIT_SHARD" = "db-and-spend" ]; then
- uv run --no-sync python scripts/check_schema_compatibility.py --base-ref "$SCHEMA_BASE_REF"
- fi
+
+ - name: Check schema.prisma against the merge base
+ if: steps.changes.outputs.decision != 'skip' && matrix.shard == 'db-and-spend' && github.event_name == 'pull_request'
+ timeout-minutes: 3
+ env:
+ GH_TOKEN: ${{ github.token }}
+ PR_NUMBER: ${{ github.event.pull_request.number }}
+ EVENT_BASE_REF: ${{ github.event.pull_request.base.ref }}
+ HEAD_SHA: ${{ github.event.pull_request.head.sha }}
+ run: |
+ base_ref=$(gh api "repos/${GITHUB_REPOSITORY}/branches/${EVENT_BASE_REF}" --jq .name) || base_ref=$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" --jq .base.ref)
+ merge_base=$(gh api "repos/${GITHUB_REPOSITORY}/compare/${base_ref}...${HEAD_SHA}?per_page=1" --jq '.merge_base_commit.sha')
+ test -n "$merge_base"
+ git fetch --no-tags --depth=1 origin "$merge_base"
+ uv run --no-sync python scripts/check_schema_compatibility.py --base-commit "$merge_base"
- name: Cache Prisma binaries
if: steps.changes.outputs.decision != 'skip'
@@ -1270,7 +1304,7 @@ jobs:
- name: Install dependencies
if: steps.changes.outputs.decision != 'skip'
run: |
- .github/scripts/uv_sync_with_retries.sh --frozen --group ci --group proxy-dev --extra google --extra proxy --extra semantic-router
+ .github/scripts/uv_sync_with_retries.sh --frozen --group ci --group proxy-dev --extra google --extra proxy --extra semantic-router --extra switchyard
- name: Cache Prisma binaries
if: steps.changes.outputs.decision != 'skip'
@@ -1319,17 +1353,16 @@ jobs:
- name: Run unit tests
run: |
- for chart in helm/litellm-helm helm/litellm; do
- declared="$(grep -h '^suite:' "$chart"/tests/*.yaml | wc -l | tr -d '[:space:]')"
- output="$(mktemp)"
- helm unittest -f 'tests/*.yaml' "$chart" | tee "$output"
- executed="$(sed -n 's/^Test Suites:.*[[:space:]]\([0-9][0-9]*\) total$/\1/p' "$output")"
- if [ "$declared" != "$executed" ]; then
- echo "::error::$chart declares $declared test suites but helm-unittest ran $executed. Suites are being skipped silently, so their assertions never execute."
- exit 1
- fi
- echo "$chart: all $declared declared test suites ran"
- done
+ chart=helm/litellm
+ declared="$(grep -h '^suite:' "$chart"/tests/*.yaml | wc -l | tr -d '[:space:]')"
+ output="$(mktemp)"
+ helm unittest -f 'tests/*.yaml' "$chart" | tee "$output"
+ executed="$(sed -n 's/^Test Suites:.*[[:space:]]\([0-9][0-9]*\) total$/\1/p' "$output")"
+ if [ "$declared" != "$executed" ]; then
+ echo "::error::$chart declares $declared test suites but helm-unittest ran $executed. Suites are being skipped silently, so their assertions never execute."
+ exit 1
+ fi
+ echo "$chart: all $declared declared test suites ran"
openai-sdk-compat:
name: OpenAI ${{ matrix.openai-version }} compatibility
needs: rust-bridge
@@ -1473,9 +1506,9 @@ jobs:
RUST_BRIDGE_ARTIFACT: ${{ needs.rust-bridge.outputs.artifact }}
run: |
if [ -z "$RUST_BRIDGE_ARTIFACT" ]; then
- .github/scripts/uv_sync_with_retries.sh --frozen --group ci --group proxy-dev --extra google --extra proxy --extra semantic-router --extra saml --extra extra_proxy --extra proxy-runtime --extra utils
+ .github/scripts/uv_sync_with_retries.sh --frozen --group ci --group proxy-dev --extra google --extra proxy --extra semantic-router --extra switchyard --extra saml --extra extra_proxy --extra proxy-runtime --extra utils
else
- .github/scripts/uv_sync_with_retries.sh --frozen --group ci --group proxy-dev --extra google --extra proxy --extra semantic-router --extra saml --extra extra_proxy --extra proxy-runtime --extra utils --no-install-project
+ .github/scripts/uv_sync_with_retries.sh --frozen --group ci --group proxy-dev --extra google --extra proxy --extra semantic-router --extra switchyard --extra saml --extra extra_proxy --extra proxy-runtime --extra utils --no-install-project --no-install-package litellm-proxy
uv pip install --no-deps --python .venv/bin/python rust-bridge-dist/*.whl
cp rust-bridge-dist/litellm/rust_bridge/_native.abi3.so litellm/rust_bridge/_native.abi3.so
fi
diff --git a/.gitignore b/.gitignore
index b08ee92d6af..1b4785d193a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -61,7 +61,7 @@ ui/litellm-dashboard/next-env.d.ts
*.tsbuildinfo
ui/litellm-dashboard/package.json
ui/litellm-dashboard/package-lock.json
-helm/litellm-helm/*.tgz
+helm/litellm/charts/*.tgz
helm/*.tgz
litellm/proxy/vertex_key.json
**/.vim/
diff --git a/AGENTS.md b/AGENTS.md
index 9aa9ab32528..d8de447be90 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -45,14 +45,13 @@ Run QA proxies with `python litellm/proxy/proxy_cli.py --config litellm/proxy/de
Simple PRs may skip before/after e2e when the full diff is mechanical and focused checks completely prove it, such as #49030 restoring an existing helper name. Trace affected callers and run relevant tests/static checks at the tip. For a bug fix, establish that the regression catches the original defect. Size alone never qualifies. New behavior, API/provider semantics, auth, billing, persistence, concurrency or uncertain integration risk need live e2e. In Screenshots / Proof of Fix, record `E2E exception at `, scope, rationale, commands and results. Tests count as proof only for this exception. Reassess changed diffs, retain CI/bot gates, and honor explicit e2e requests (2026-10-28, unnecessary live QA for #49030)
-For other PRs, proof must show the end user's real flow before and after, with commands and output against a live proxy and real provider calls where applicable. Pytest alone is insufficient. For UI or interactive coding-tool flows, drive that surface and embed before/after screenshots, with URLs, clicks and input fields a reviewer can reproduce
+For other PRs, proof must show the end user's real flow before and after, with commands and output against a live proxy and real provider calls where applicable. The body alone must let a reviewer rerun it, with the proxy command, config, env var names and client code exactly as run, and long blocks collapsed in . Pytest alone is insufficient. For UI or interactive coding-tool flows, drive that surface and embed before/after screenshots, with URLs, clicks and input fields a reviewer can reproduce
If you ever write any human-facing text (pull requests, issues, commit messages, discussion posts, github comments, release notes, docs, etc.), always follow these guidelines to sound less AI-y:
- don't use emojis
- don't use "—". Don't use ";" or ":" unless it would be nonsensical not to. Instead, reach for ",", ".", conjunction words, etc. in descending order of preference: vary among them, weighted toward the front of the list, and skip "," where it would cause a comma splice or the sentence is getting long. Overusing any one of them also feels AI-y. A word cap does not penalize you for adding more sentences
- don't use the pattern "It's not X, it's Y", "You're not X, you're Y", etc.
- unless explicitly asked, don't use bulleted or numbered lists unless it would be nonsensical not to. Instead, prefer prose
-- don't add a trailing "." at the end of paragraphs (just like this file). That means every paragraph, not just the last one (of the markdown file, PR description, GitHub comment, etc.). Rule of thumb: if you're adding new line(s) before the next sentence, don't add a ".". Exception is that docs (i.e., litellm-docs) should have trailing "."
- don't use →. Instead, prefer not to use arrows, and if need be, use -> instead
- use plain, simple, everyday engineering language: the common phrase engineers actually say over rare compact phrasing, in grammatically complete sentences. When explicitly asked to use bullets or ordered lists and structure legitimately helps the reader, prefer nested bullets (any depth is fine) over dense lines in a flat structure
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e956b27d5da..426cf986d4c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -305,8 +305,8 @@ uv run litellm --config your_config.yaml
If you want to build the Docker image yourself:
```bash
-# Build using the non-root Dockerfile
-docker build -f docker/Dockerfile.non_root -t litellm_dev .
+# Build the image (runs as a non-root user by default)
+docker build -t litellm_dev .
# Generate a master key. Requests send it as the bearer token
export LITELLM_MASTER_KEY="sk-$(openssl rand -hex 32)"
diff --git a/Dockerfile b/Dockerfile
index 4cef63c8d71..0e9337e658b 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -76,6 +76,7 @@ ENV UV_PROJECT_ENVIRONMENT=/app/.venv \
COPY pyproject.toml uv.lock ./
COPY enterprise/pyproject.toml enterprise/
COPY litellm-proxy-extras/pyproject.toml litellm-proxy-extras/
+COPY packaging/litellm-proxy/pyproject.toml packaging/litellm-proxy/
# Install third-party dependencies (cached unless pyproject.toml/uv.lock change)
RUN uv sync --frozen --no-install-project --no-install-workspace --no-default-groups --no-editable \
@@ -101,7 +102,9 @@ COPY --from=ui-builder /ui/out/. litellm/proxy/_experimental/out/
RUN sed -i 's/\r$//' docker/build_admin_ui.sh && chmod +x docker/build_admin_ui.sh && ./docker/build_admin_ui.sh
# Install project and workspace packages (fast - deps already cached)
-RUN uv sync --frozen --no-default-groups --no-editable \
+ARG MATURIN_PEP517_ARGS=""
+RUN uv build --package litellm-proxy --wheel --out-dir /wheels
+RUN uv sync --frozen --no-install-package litellm-proxy --no-default-groups --no-editable \
--extra proxy \
--extra proxy-runtime \
--group admin-mcp \
@@ -111,12 +114,18 @@ RUN uv sync --frozen --no-default-groups --no-editable \
--extra bedrock-realtime \
--python python3.13
+RUN uv pip install --python /app/.venv/bin/python --no-deps /wheels/litellm_proxy-*.whl
+
RUN HOME=/opt/prisma XDG_CACHE_HOME=/opt/prisma/.cache PRISMA_BINARY_CACHE_DIR=/opt/prisma/binaries \
npm_config_cache=/root/.npm \
prisma generate --schema=./schema.prisma
-RUN sed -i 's/\r$//' docker/entrypoint.sh && chmod +x docker/entrypoint.sh && \
- sed -i 's/\r$//' docker/prod_entrypoint.sh && chmod +x docker/prod_entrypoint.sh
+RUN sed -i 's/\r$//' docker-entrypoint.sh && chmod +x docker-entrypoint.sh
+
+RUN mkdir -p /var/lib/litellm/ui /var/lib/litellm/assets && \
+ cp -r litellm/proxy/_experimental/out/. /var/lib/litellm/ui/ && \
+ cp litellm/proxy/logo.png /var/lib/litellm/assets/logo.png && \
+ touch /var/lib/litellm/ui/.litellm_ui_ready
FROM $LITELLM_BUILD_IMAGE AS liteadmin-builder
COPY --from=uvbin /uv /usr/local/bin/uv
@@ -141,24 +150,35 @@ USER root
# https://github.com/BerriAI/litellm/issues/33518
RUN echo "https://packages.wolfi.dev/os" >> /etc/apk/repositories
-# node (without npm) is required by the prisma CLI at runtime
-RUN apk add --no-cache bash openssl libssl3 tzdata nodejs python-3.13 libsndfile libevent
+RUN for i in 1 2 3; do \
+ apk add --no-cache bash openssl libssl3 tzdata nodejs python-3.13 libsndfile libatomic libevent nginx && break; \
+ [ "$i" = 3 ] && { echo "apk add failed after 3 retries" >&2; exit 1; }; \
+ sleep 5; \
+ done
COPY --from=pgbouncer-builder /usr/local/bin/pgbouncer /usr/local/bin/pgbouncer
WORKDIR /app
+
ENV PATH="/app/.venv/bin:${PATH}" \
+ PYTHONPATH="/app" \
+ PYTHONDONTWRITEBYTECODE=1 \
+ PYTHONUNBUFFERED=1 \
+ HOME=/app \
+ LITELLM_NON_ROOT=true \
PRISMA_BINARY_CACHE_DIR=/opt/prisma/binaries \
PRISMA_CLI_PATH=/opt/prisma/binaries/node_modules/.bin/prisma \
PRISMA_CLI_QUERY_ENGINE_TYPE=binary \
+ PRISMA_SKIP_POSTINSTALL_GENERATE=1 \
+ PRISMA_HIDE_UPDATE_MESSAGE=1 \
+ PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING=1 \
PRISMA_OFFLINE_MODE=true
# Copy only what runtime needs. The application is installed inside the venv;
# the rest of the builder's /app is source and build metadata that must not
# ship (manifest-scanning tools attribute everything in it to this image).
-# entrypoint.sh invokes litellm/proxy/prisma_migration.py by source path.
COPY --from=builder /app/.venv /app/.venv
COPY --from=liteadmin-builder /opt/liteadmin /opt/liteadmin
-COPY --from=builder /app/docker /app/docker
+COPY --from=builder /app/docker-entrypoint.sh /app/docker-entrypoint.sh
COPY --from=builder /app/schema.prisma /app/schema.prisma
COPY --from=builder /app/litellm/proxy/prisma_migration.py /app/litellm/proxy/prisma_migration.py
# enterprise/ is imported by source path at runtime (proxy_cli puts the
@@ -166,21 +186,33 @@ COPY --from=builder /app/litellm/proxy/prisma_migration.py /app/litellm/proxy/pr
# enterprise.enterprise_hooks from it)
COPY --from=builder /app/enterprise /app/enterprise
COPY --from=builder /app/litellm-proxy-extras /app/litellm-proxy-extras
-# Prisma CLI + engines are baked under /opt/prisma, a fixed path every
-# runtime uid can read and that no cache volume mount shadows. The paths are
-# pinned via PRISMA_BINARY_CACHE_DIR / PRISMA_CLI_PATH and recorded into the
-# generated client at build time, so `prisma migrate deploy` on a fresh
-# database needs no npm and no network access (#33650, #24554).
+COPY --from=builder /app/gateway /app/gateway
+COPY --from=builder /app/backend /app/backend
+COPY --from=builder /app/migrations /app/migrations
COPY --from=builder /opt/prisma /opt/prisma
+COPY --from=builder /var/lib/litellm /var/lib/litellm
+COPY ui/nginx.conf /etc/nginx/nginx.conf
+COPY ui/docker-entrypoint.d/40-litellm-runtime-config.sh /app/ui/docker-entrypoint.d/40-litellm-runtime-config.sh
-RUN find /app/.venv -type f -path "*/tornado/test/*" -delete && \
- find /app/.venv -type d -path "*/tornado/test" -delete && \
+RUN find /app/.venv -depth -type d -path "*/tornado/test" -exec rm -rf {} + && \
+ mkdir -p /app/.cache /var/run/litellm && \
+ chown -R 65532:0 /app /var/lib/litellm /var/run/litellm && \
+ chmod -R g=u,g+w /app/.cache /var/lib/litellm /var/run/litellm && \
+ PRISMA_PATH="$(python -c 'import os, prisma; print(os.path.dirname(prisma.__file__))')" && \
+ PROXY_EXTRAS_PATH="$(python -c 'import os, litellm_proxy_extras; print(os.path.dirname(litellm_proxy_extras.__file__))')" && \
+ chmod -R g=u,g+w "$PRISMA_PATH" "$PROXY_EXTRAS_PATH" && \
chmod -R a+rX /opt/prisma && \
test -x /opt/prisma/binaries/node_modules/.bin/prisma && \
test -f /opt/prisma/binaries/node_modules/prisma/build/index.js && \
- python -c "from prisma.client import BINARY_PATHS; paths = list(BINARY_PATHS.query_engine.values()); assert paths and all(p.startswith('/opt/prisma/') for p in paths), paths"
+ ls /opt/prisma/binaries/node_modules/@prisma/engines/query-engine-* >/dev/null && \
+ python -c "from prisma.client import BINARY_PATHS; paths = list(BINARY_PATHS.query_engine.values()); assert paths and all(p.startswith('/opt/prisma/') for p in paths), paths" && \
+ python -c "from litellm.rust_bridge import _native" && \
+ python -c "import gateway.launch, backend.main"
-EXPOSE 4000/tcp
+USER 65532:65532
-ENTRYPOINT ["docker/prod_entrypoint.sh"]
-CMD ["--port", "4000"]
+RUN nginx -t
+
+EXPOSE 4000/tcp 4001/tcp 3000/tcp
+
+ENTRYPOINT ["/app/docker-entrypoint.sh"]
diff --git a/GEMINI.md b/GEMINI.md
deleted file mode 100644
index 5fc00e0b5ae..00000000000
--- a/GEMINI.md
+++ /dev/null
@@ -1 +0,0 @@
-Read @AGENTS.md for coding guidelines
diff --git a/Makefile b/Makefile
index 48df3c8e215..c256d273d8a 100644
--- a/Makefile
+++ b/Makefile
@@ -341,7 +341,7 @@ test-integration: install-test-deps
$(UV_RUN) pytest tests/
test-unit-helm: install-helm-unittest
- helm unittest -f 'tests/*.yaml' helm/litellm-helm
+ helm unittest -f 'tests/*.yaml' helm/litellm
# LLM Translation testing targets
test-llm-translation: install-test-deps
diff --git a/SECURITY.md b/SECURITY.md
index d3603448d46..6d0a9a6414d 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -4,23 +4,19 @@
LiteLLM maintains a security announcements mailing list that is open to anyone. Subscribers receive advance notice, typically one to two days, before we release a fix for a particularly severe vulnerability or for any vulnerability exploitable by an unauthenticated attacker. This notice is provided on a best-effort basis
-To subscribe, visit [https://berriai.github.io/security-announce-signup/](https://berriai.github.io/security-announce-signup/)
+To subscribe, visit [https://security.litellm.ai/mailinglist/](https://security.litellm.ai/mailinglist/)
## Security Vulnerability Reporting Guidelines
-> [!WARNING]
-> Reports that do not include a video demonstrating the exploit will be closed without review. See [Reproduction Video Requirement](#reproduction-video-requirement) below.
-We value the security community's role in protecting our systems and users. To report a security vulnerability:
+We value the security community's role in protecting our systems and users.
+To report a security vulnerability, please submit a report through our vulnerability disclosure form:
-- File a private vulnerability report on GitHub: [Report a vulnerability](https://github.com/BerriAI/litellm/security/advisories/new)
-- Include steps to reproduce the issue
-- Include a video or screen recording demonstrating the full exploit against a live LiteLLM instance, from initial access through to impact. A terminal recording (for example asciinema) is fine for CLI-only exploits.
-- Provide any relevant additional information
+[LiteLLM VDP](https://security.litellm.ai/vdp/)
-### Reproduction Video Requirement
+These reports are submitted to HackerOne and are triaged there, though no account is needed.
-A video demonstrating the exploit is required for every report. AI tools have made it easy to produce plausible-sounding vulnerability reports that do not reproduce in practice, and triaging them takes time away from real issues. Reports submitted without a working reproduction video will be closed without review. If you add a video to a closed report, we will reopen and triage it.
+**Please do not submit vulnerabilities as draft GHSAs. As of 2026-11-11 new GHSA submissions will be closed.**
### Vulnerability Categories
@@ -55,7 +51,9 @@ To qualify for a bounty, reports must include clear reproduction steps, a reprod
### Known Non-Issues
-- Attacks that require a misconfiguration on setup (e.g not setting a `master_key` on the proxy configuration), are **explicitly not in scope** and are not considered vulnerable.
+- **Misconfigurations:** attacks that require misconfiguration of the application (e.g. not setting a `master_key` on the proxy configuration).
+- **Insecure configurations:** attacks that require deliberate configuration changes to disable security functionality. (e.g. IDOR on response IDs requiring configuration of `disable_responses_id_security` to `true`)
+- **Social engineering:** attacks that rely on social engineering as their primary mechanism (e.g. convincing a user's agent to install a malicious package we did not publish).
## Security Measures
diff --git a/backend/Dockerfile b/backend/Dockerfile
deleted file mode 100644
index aad59ba17c6..00000000000
--- a/backend/Dockerfile
+++ /dev/null
@@ -1,146 +0,0 @@
-ARG LITELLM_BUILD_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:360896efb0753a5701c48f860e17130c205eae5a264797aa3e7ea126ee2923a7
-ARG LITELLM_RUNTIME_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:360896efb0753a5701c48f860e17130c205eae5a264797aa3e7ea126ee2923a7
-ARG UV_IMAGE=ghcr.io/astral-sh/uv:0.11.7@sha256:240fb85ab0f263ef12f492d8476aa3a2e4e1e333f7d67fbdd923d00a506a516a
-
-FROM $UV_IMAGE AS uvbin
-
-# ---------- Rust extension ----------
-# Copies only the native build's inputs, so the fat-LTO compile stays cached across Python-only changes
-FROM $LITELLM_BUILD_IMAGE AS rust-native
-
-WORKDIR /app
-USER root
-
-COPY --from=uvbin /uv /uvx /usr/local/bin/
-
-RUN for i in 1 2 3; do \
- apk add --no-cache bash gcc python-3.13 python-3.13-dev openssl openssl-dev && break; \
- [ $i = 3 ] && { echo "apk add failed after 3 retries" >&2; exit 1; }; \
- sleep 5; \
- done
-
-ENV UV_PYTHON_DOWNLOADS=0
-
-COPY pyproject.toml README.md rust-toolchain.toml ./
-COPY .cargo ./.cargo
-COPY litellm-rust ./litellm-rust
-COPY model_prices_and_context_window.json ./
-
-RUN --mount=type=cache,target=/root/.cache/uv \
- mkdir -p litellm/rust_bridge /wheels && \
- touch litellm/__init__.py litellm/rust_bridge/__init__.py && \
- uvx --python python3.13 --from maturin==1.15.0 --with puccinialin==0.1.14 \
- python -c "import maturin; maturin.build_wheel('/wheels')"
-
-# ---------- Builder ----------
-FROM $LITELLM_BUILD_IMAGE AS builder
-
-WORKDIR /app
-USER root
-
-COPY --from=uvbin /uv /uvx /usr/local/bin/
-
-# nodejs/npm so `prisma generate` uses Wolfi's Node via PRISMA_USE_GLOBAL_NODE
-# instead of nodeenv downloading one whose dynamic deps may not be in Wolfi
-# (e.g. Node 26.2.0 needs libatomic). Retry for transient apk.cgr.dev flakes.
-RUN for i in 1 2 3; do \
- apk add --no-cache bash gcc python-3.13 python-3.13-dev openssl libssl3 openssl-dev libsndfile nodejs npm && break; \
- [ $i = 3 ] && { echo "apk add failed after 3 retries" >&2; exit 1; }; \
- sleep 5; \
- done
-
-# UV_COMPILE_BYTECODE=1 precompiles .pyc at install time → faster cold start.
-# UV_LINK_MODE=copy avoids hardlink warnings when uv installs from a
-# BuildKit cache mount (different filesystem).
-# UV_PYTHON_DOWNLOADS=0 force uv to use the apk-installed CPython instead of
-# silently pulling a managed interpreter.
-# PRISMA_USE_GLOBAL_NODE explicit (matches default) so an env override can't
-# silently re-enable nodeenv's Node download.
-ENV UV_PROJECT_ENVIRONMENT=/app/.venv \
- UV_LINK_MODE=copy \
- UV_COMPILE_BYTECODE=1 \
- UV_PYTHON_DOWNLOADS=0 \
- PRISMA_USE_GLOBAL_NODE=true \
- PATH="/app/.venv/bin:${PATH}"
-
-# Stage 1 — install dependencies only.
-RUN --mount=type=cache,target=/root/.cache/uv \
- --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
- --mount=type=bind,source=uv.lock,target=uv.lock \
- --mount=type=bind,source=enterprise/pyproject.toml,target=enterprise/pyproject.toml \
- --mount=type=bind,source=litellm-proxy-extras/pyproject.toml,target=litellm-proxy-extras/pyproject.toml \
- uv sync --frozen --no-install-project --no-install-workspace --no-default-groups --no-editable \
- --extra proxy \
- --extra proxy-runtime \
- --group admin-mcp \
- --extra extra_proxy \
- --extra semantic-router \
- --extra saml \
- --python python3.13
-
-# Stage 2 — copy source and install the project + workspace members.
-COPY . .
-
-RUN --mount=type=cache,target=/root/.cache/uv \
- uv sync --frozen --no-install-project --no-default-groups --no-editable \
- --extra proxy \
- --extra proxy-runtime \
- --group admin-mcp \
- --extra extra_proxy \
- --extra semantic-router \
- --extra saml \
- --python python3.13
-
-RUN --mount=type=bind,from=rust-native,source=/wheels,target=/wheels \
- uv pip install --python /app/.venv/bin/python --no-deps /wheels/litellm-*.whl
-
-RUN cp "$(python -c 'import sysconfig; print(sysconfig.get_paths()["purelib"])')"/litellm/rust_bridge/_native*.so litellm/rust_bridge/
-
-RUN HOME=/opt/prisma XDG_CACHE_HOME=/opt/prisma/.cache PRISMA_BINARY_CACHE_DIR=/opt/prisma/binaries \
- npm_config_cache=/root/.npm \
- prisma generate --schema=./schema.prisma
-
-RUN sed -i 's/\r$//' docker/component_entrypoint.sh && chmod +x docker/component_entrypoint.sh
-
-# ---------- Runtime ----------
-FROM $LITELLM_RUNTIME_IMAGE AS runtime
-ARG LITELLM_RELEASE_TAG=""
-ENV LITELLM_RELEASE_TAG=${LITELLM_RELEASE_TAG}
-
-USER root
-
-RUN for i in 1 2 3; do \
- apk add --no-cache bash openssl libssl3 tzdata python-3.13 libsndfile libatomic && break; \
- [ $i = 3 ] && { echo "apk add failed after 3 retries" >&2; exit 1; }; \
- sleep 5; \
- done
-
-# wolfi-base ships an unprivileged `nonroot` account (UID/GID 65532) with
-# /home/nonroot. We run the backend as that user
-WORKDIR /app
-ENV HOME=/home/nonroot \
- PATH="/app/.venv/bin:${PATH}" \
- PYTHONPATH="/app" \
- PYTHONDONTWRITEBYTECODE=1 \
- PYTHONUNBUFFERED=1 \
- PRISMA_BINARY_CACHE_DIR=/opt/prisma/binaries
-
-COPY --from=builder --chown=nonroot:nonroot /app/.venv /app/.venv
-COPY --from=builder --chown=nonroot:nonroot /app/litellm /app/litellm
-COPY --from=builder --chown=nonroot:nonroot /app/enterprise /app/enterprise
-COPY --from=builder --chown=nonroot:nonroot /app/backend /app/backend
-COPY --from=builder --chown=nonroot:nonroot /app/docker/component_entrypoint.sh /app/docker/component_entrypoint.sh
-COPY --from=builder --chown=nonroot:nonroot /app/schema.prisma /app/schema.prisma
-COPY --from=builder /opt/prisma /opt/prisma
-
-RUN find /app/.venv -type f -path "*/tornado/test/*" -delete && \
- find /app/.venv -type d -path "*/tornado/test" -delete && \
- chmod -R a+rX /opt/prisma && \
- python -c "from prisma.client import BINARY_PATHS; paths = list(BINARY_PATHS.query_engine.values()); assert paths and all(p.startswith('/opt/prisma/') for p in paths), paths"
-
-USER nonroot
-
-EXPOSE 4001/tcp
-
-ENTRYPOINT ["/app/docker/component_entrypoint.sh", "uvicorn", "backend.main:app"]
-CMD ["--host", "0.0.0.0", "--port", "4001"]
diff --git a/ci_cd/cost_map_guard.py b/ci_cd/cost_map_guard.py
index d5fd755ba18..09138bd6c14 100644
--- a/ci_cd/cost_map_guard.py
+++ b/ci_cd/cost_map_guard.py
@@ -20,7 +20,7 @@ import json
import os
import subprocess
import sys
-from collections.abc import Iterator, Mapping, Sequence
+from collections.abc import Callable, Iterator, Mapping, Sequence
from dataclasses import dataclass
from types import MappingProxyType
from typing import Final
@@ -28,8 +28,13 @@ from typing import Final
COST_MAP_PATH: Final = "model_prices_and_context_window.json"
BACKUP_PATH: Final = "litellm/model_prices_and_context_window_backup.json"
PINS_PATH: Final = "ci_cd/cost_map_pins.json"
+PINS_PATHS: Final = (
+ "litellm/scripts/ci_cd/cost_map_pins.json",
+ "scripts/ci_cd/cost_map_pins.json",
+ "ci_cd/cost_map_pins.json",
+)
GUARDED_PATHS: Final = (COST_MAP_PATH, BACKUP_PATH)
-CHECKED_PATHS: Final = (*GUARDED_PATHS, PINS_PATH)
+CHECKED_PATHS: Final = (*GUARDED_PATHS, *PINS_PATHS)
BOT_BRANCH_PREFIX: Final = "litellm_cost_map_sync_"
PRICE_SYNC_BRANCH_PREFIX: Final = "litellm-providers/price-sync"
TARGET_ENV: Final = "COST_MAP_GUARD_TARGET"
@@ -193,7 +198,7 @@ def _bot_failures(base: Snapshot, head_map: CostMap, changed_files: Sequence[str
def pin_author_failures(head_ref: str, author_type: str, changed_files: Sequence[str]) -> tuple[str, ...]:
- if PINS_PATH not in changed_files:
+ if not any(path in changed_files for path in PINS_PATHS):
return ()
if head_ref.startswith(PRICE_SYNC_BRANCH_PREFIX):
return (f"price sync PRs may not change {PINS_PATH}; a pin moves only in a human PR with new evidence",)
@@ -251,8 +256,11 @@ def _git(*args: str) -> str | None:
return result.stdout if result.returncode == 0 else None
-def snapshot(revision: str) -> Snapshot:
- return Snapshot(*(_git("show", f"{revision}:{path}") or "" for path in CHECKED_PATHS))
+def snapshot(revision: str, *, read_git: Callable[[str, str], str | None] = _git) -> Snapshot:
+ return Snapshot(
+ *(read_git("show", f"{revision}:{path}") or "" for path in GUARDED_PATHS),
+ next((text for path in PINS_PATHS if (text := read_git("show", f"{revision}:{path}")) is not None), ""),
+ )
def merged_tree(merge_base: str, target: str, head: str) -> str | MergeFailed | None:
diff --git a/ci_cd/security_scans_readme.md b/ci_cd/security_scans_readme.md
index dd64b01c296..3003ea1d105 100644
--- a/ci_cd/security_scans_readme.md
+++ b/ci_cd/security_scans_readme.md
@@ -4,6 +4,5 @@
- Trivy scan on `./docs/` (HIGH/CRITICAL/MEDIUM)
- Trivy scan on `./ui/` (HIGH/CRITICAL/MEDIUM)
-- Grype scan on `Dockerfile.database` (fails on CRITICAL)
- Grype scan on main `Dockerfile` (fails on CRITICAL)
- Grype CVSS ≥ 4.0 scan on main `Dockerfile` (fails any vulnerabilities with CVSS ≥ 4.0)
diff --git a/codecov.yaml b/codecov.yaml
index 521088868bd..419a28ce6c6 100644
--- a/codecov.yaml
+++ b/codecov.yaml
@@ -6,7 +6,7 @@ codecov:
ignore:
- "litellm-rust/**"
-# Uploads are flagged per workflow tier (GHA) or "circleci". carryforward makes
+# Uploads are flagged per workflow tier. carryforward makes
# a re-upload of a flag replace its prior session instead of accumulating a
# conflicting one, and lets a commit reuse a flag from its parent when that flag
# was not re-uploaded. Required because the same commit can receive the
diff --git a/cookbook/Benchmarking_LLMs_by_use_case.ipynb b/cookbook/Benchmarking_LLMs_by_use_case.ipynb
deleted file mode 100644
index 6ea6211bfb6..00000000000
--- a/cookbook/Benchmarking_LLMs_by_use_case.ipynb
+++ /dev/null
@@ -1,753 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "4Cq-_Y-TKf0r"
- },
- "source": [
- "# LiteLLM - Benchmark Llama2, Claude1.2 and GPT3.5 for a use case\n",
- "In this notebook for a given use case we run the same question and view:\n",
- "* LLM Response\n",
- "* Response Time\n",
- "* Response Cost\n",
- "\n",
- "## Sample output for a question\n",
- ""
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "O3ENsWYB27Mb"
- },
- "outputs": [],
- "source": [
- "!pip install litellm"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "Pk55Mjq_3DiR"
- },
- "source": [
- "## Example Use Case 1 - Code Generator\n",
- "### For this use case enter your system prompt and questions\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 21,
- "metadata": {
- "id": "_1SZYJFB3HmQ"
- },
- "outputs": [],
- "source": [
- "# enter your system prompt if you have one\n",
- "system_prompt = \"\"\"\n",
- "You are a coding assistant helping users using litellm.\n",
- "litellm is a light package to simplify calling OpenAI, Azure, Cohere, Anthropic, Huggingface API Endpoints\n",
- "--\n",
- "Sample Usage:\n",
- "```\n",
- "pip install litellm\n",
- "from litellm import completion\n",
- "## set ENV variables\n",
- "os.environ[\"OPENAI_API_KEY\"] = \"openai key\"\n",
- "os.environ[\"COHERE_API_KEY\"] = \"cohere key\"\n",
- "messages = [{ \"content\": \"Hello, how are you?\",\"role\": \"user\"}]\n",
- "# openai call\n",
- "response = completion(model=\"gpt-3.5-turbo\", messages=messages)\n",
- "# cohere call\n",
- "response = completion(\"command-nightly\", messages)\n",
- "```\n",
- "\n",
- "\"\"\"\n",
- "\n",
- "\n",
- "# qustions/logs you want to run the LLM on\n",
- "questions = [\n",
- " \"what is litellm?\",\n",
- " \"why should I use LiteLLM\",\n",
- " \"does litellm support Anthropic LLMs\",\n",
- " \"write code to make a litellm completion call\",\n",
- "]"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "AHH3cqeU3_ZT"
- },
- "source": [
- "## Running questions\n",
- "### Select from 100+ LLMs here: https://docs.litellm.ai/docs/providers"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "BpQD4A5339L3"
- },
- "outputs": [],
- "source": [
- "from litellm import completion, completion_cost\n",
- "import os\n",
- "import time\n",
- "\n",
- "# optional use litellm dashboard to view logs\n",
- "# litellm.use_client = True\n",
- "# litellm.token = \"ishaan_2@berri.ai\" # set your email\n",
- "\n",
- "\n",
- "# set API keys\n",
- "os.environ['TOGETHERAI_API_KEY'] = \"\"\n",
- "os.environ['OPENAI_API_KEY'] = \"\"\n",
- "os.environ['ANTHROPIC_API_KEY'] = \"\"\n",
- "\n",
- "\n",
- "# select LLMs to benchmark\n",
- "# using https://api.together.xyz/playground for llama2\n",
- "# try any supported LLM here: https://docs.litellm.ai/docs/providers\n",
- "\n",
- "models = ['togethercomputer/llama-2-70b-chat', 'gpt-3.5-turbo', 'claude-instant-1.2']\n",
- "data = []\n",
- "\n",
- "for question in questions: # group by question\n",
- " for model in models:\n",
- " print(f\"running question: {question} for model: {model}\")\n",
- " start_time = time.time()\n",
- " # show response, response time, cost for each question\n",
- " response = completion(\n",
- " model=model,\n",
- " max_tokens=500,\n",
- " messages = [\n",
- " {\n",
- " \"role\": \"system\", \"content\": system_prompt\n",
- " },\n",
- " {\n",
- " \"role\": \"user\", \"content\": question\n",
- " }\n",
- " ],\n",
- " )\n",
- " end = time.time()\n",
- " total_time = end-start_time # response time\n",
- " # print(response)\n",
- " cost = completion_cost(response) # cost for completion\n",
- " raw_response = response['choices'][0]['message']['content'] # response string\n",
- "\n",
- "\n",
- " # add log to pandas df\n",
- " data.append(\n",
- " {\n",
- " 'Model': model,\n",
- " 'Question': question,\n",
- " 'Response': raw_response,\n",
- " 'ResponseTime': total_time,\n",
- " 'Cost': cost\n",
- " })"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "apOSV3PBLa5Y"
- },
- "source": [
- "## View Benchmarks for LLMs"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 22,
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/",
- "height": 1000
- },
- "id": "CJqBlqUh_8Ws",
- "outputId": "e02c3427-d8c6-4614-ff07-6aab64247ff6"
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Question: does litellm support Anthropic LLMs\n"
- ]
- },
- {
- "data": {
- "text/html": [
- "
\n",
- " \n",
- "
\n",
- "
\n",
- "
Model
\n",
- "
Question
\n",
- "
Response
\n",
- "
ResponseTime
\n",
- "
Cost
\n",
- "
\n",
- " \n",
- " \n",
- "
\n",
- "
6
\n",
- "
togethercomputer/llama-2-70b-chat
\n",
- "
does litellm support Anthropic LLMs
\n",
- "
Yes, litellm supports Anthropic LLMs.\\n\\nIn the example usage you provided, the `completion` function is called with the `model` parameter set to `\"gpt-3.5-turbo\"` for OpenAI and `\"command-nightly\"` for Cohere.\\n\\nTo use an Anthropic LLM with litellm, you would set the `model` parameter to the name of the Anthropic model you want to use, followed by the version number, if applicable. For example:\\n```\\nresponse = completion(model=\"anthropic-gpt-2\", messages=messages)\\n```\\nThis would call the Anthropic GPT-2 model to generate a completion for the given input messages.\\n\\nNote that you will need to set the `ANTHROPIC_API_KEY` environment variable to your Anthropic API key before making the call. You can do this by running the following command in your terminal:\\n```\\nos.environ[\"ANTHROPIC_API_KEY\"] = \"your-anthropic-api-key\"\\n```\\nReplace `\"your-anthropic-api-key\"` with your actual Anthropic API key.\\n\\nOnce you've set the environment variable, you can use the `completion` function with the `model` parameter set to an Anthropic model name to call the Anthropic API and generate a completion.
\n",
- "
21.513009
\n",
- "
0.001347
\n",
- "
\n",
- "
\n",
- "
7
\n",
- "
gpt-3.5-turbo
\n",
- "
does litellm support Anthropic LLMs
\n",
- "
No, currently litellm does not support Anthropic LLMs. It mainly focuses on simplifying the usage of OpenAI, Azure, Cohere, and Huggingface API endpoints.
\n",
- "
8.656510
\n",
- "
0.000342
\n",
- "
\n",
- "
\n",
- "
8
\n",
- "
claude-instant-1.2
\n",
- "
does litellm support Anthropic LLMs
\n",
- "
Yes, litellm supports calling Anthropic LLMs through the completion function.\\n\\nTo use an Anthropic model with litellm:\\n\\n1. Set the ANTHROPIC_API_KEY environment variable with your Anthropic API key\\n\\n2. Pass the model name as the 'model' argument to completion(). Anthropic model names follow the format 'anthropic/<model_name>'\\n\\nFor example:\\n\\n```python \\nimport os\\nfrom litellm import completion\\n\\nos.environ[\"ANTHROPIC_API_KEY\"] = \"your_anthropic_api_key\"\\n\\nmessages = [{\"content\": \"Hello\", \"role\": \"user\"}]\\n\\nresponse = completion(model=\"anthropic/constitutional\", messages=messages)\\n```\\n\\nThis would call the Constitutional AI model from Anthropic.\\n\\nSo in summary, litellm provides a simple interface to call any Anthropic models as long as you specify the model name correctly and set the ANTHROPIC_API_KEY env variable.
Litellm is a lightweight Python package that simplifies calling various AI API endpoints, including OpenAI, Azure, Cohere, Anthropic, and Hugging Face. It provides a convenient interface for making requests to these APIs, allowing developers to easily integrate them into their applications. With Litellm, developers can quickly and easily interact with multiple AI models and services, without having to handle the details of authentication, API calls, and response parsing. This makes it easier to build and deploy AI-powered applications, and can help developers save time and effort.
\n",
- "
13.479644
\n",
- "
0.000870
\n",
- "
\n",
- "
\n",
- "
1
\n",
- "
gpt-3.5-turbo
\n",
- "
what is litellm?
\n",
- "
litellm is a light package that provides a simplified interface for making API calls to various language models and APIs. It abstracts away the complexities of handling network requests, authentication, and response parsing, making it easier for developers to integrate powerful language models into their applications.\\n\\nWith litellm, you can quickly make API calls to models like OpenAI's GPT-3.5 Turbo, Azure's Text Analytics, Cohere's Command API, Anthropic's API, and Huggingface's models. It also supports additional functionality like conversational AI, summarization, translation, and more.\\n\\nBy using litellm, you can focus on your application logic without getting tangled in the details of API integration, allowing you to quickly build intelligent and conversational applications.
\n",
- "
8.324332
\n",
- "
0.000566
\n",
- "
\n",
- "
\n",
- "
2
\n",
- "
claude-instant-1.2
\n",
- "
what is litellm?
\n",
- "
litellm is a Python library that simplifies calling various AI API endpoints like OpenAI, Azure, Cohere, Anthropic, and Huggingface. \\n\\nSome key things to know about litellm:\\n\\n- It provides a consistent interface for completing prompts and generating responses from different AI models through a single method called completion().\\n\\n- You specify the API (e.g. OpenAI, Cohere etc.) and model either by name or by setting environment variables before making the completion call.\\n\\n- This avoids having to use different SDKs or APIs for each provider and standardizes the call structure. \\n\\n- It handles things like setting headers, encoding inputs, parsing responses so the user doesn't have to deal with those details.\\n\\n- The goal is to make it easy to try different AI APIs and models without having to change code or learn different interfaces.\\n\\n- It's lightweight with no other dependencies required besides what's needed for each API (e.g. openai, azure SDKs etc.).\\n\\nSo in summary, litellm is a small library that provides a common way to interact with multiple conversational AI APIs through a single Python method, avoiding the need to directly use each provider's specific SDK.
\\nThere are several reasons why you might want to use LiteLLM:\\n\\n1. Simplified API calls: LiteLLM provides a simple and consistent API for calling various language models, making it easier to use multiple models and switch between them.\\n2. Environment variable configuration: LiteLLM allows you to set environment variables for API keys and model names, making it easier to manage and switch between different models and APIs.\\n3. Support for multiple models and APIs: LiteLLM supports a wide range of language models and APIs, including OpenAI, Azure, Cohere, Anthropic, and Hugging Face.\\n4. Easy integration with popular frameworks: LiteLLM can be easily integrated with popular frameworks such as PyTorch and TensorFlow, making it easy to use with your existing codebase.\\n5. Lightweight: LiteLLM is a lightweight package, making it easy to install and use, even on resource-constrained devices.\\n6. Flexible: LiteLLM allows you to define your own models and APIs, making it easy to use with custom models and APIs.\\n7. Extensive documentation: LiteLLM has extensive documentation, making it easy to get started and learn how to use the package.\\n8. Active community: LiteLLM has an active community of developers and users, making it easy to get help and feedback on your projects.\\n\\nOverall, LiteLLM can help you to simplify your workflow, improve your productivity, and make it easier to work with multiple language models and APIs.
\n",
- "
23.777885
\n",
- "
0.001443
\n",
- "
\n",
- "
\n",
- "
4
\n",
- "
gpt-3.5-turbo
\n",
- "
why should I use LiteLLM
\n",
- "
LiteLLM is a lightweight Python package that simplifies the process of making API calls to various language models. Here are some reasons why you should use LiteLLM:\\n\\n1. **Simplified API Calls**: LiteLLM abstracts away the complexity of making API calls to different language models. It provides a unified interface for invoking models from OpenAI, Azure, Cohere, Anthropic, Huggingface, and more.\\n\\n2. **Easy Integration**: LiteLLM seamlessly integrates with your existing codebase. You can import the package and start making API calls with just a few lines of code.\\n\\n3. **Flexibility**: LiteLLM supports a variety of language models, including GPT-3, GPT-Neo, chatGPT, and more. You can choose the model that suits your requirements and easily switch between them.\\n\\n4. **Convenience**: LiteLLM handles the authentication and connection details for you. You just need to set the relevant environment variables, and the package takes care of the rest.\\n\\n5. **Quick Prototyping**: LiteLLM is ideal for rapid prototyping and experimentation. With its simple API, you can quickly generate text, chat with models, and build interactive applications.\\n\\n6. **Community Support**: LiteLLM is actively maintained and supported by a community of developers. You can find help, share ideas, and collaborate with others to enhance your projects.\\n\\nOverall, LiteLLM simplifies the process of making API calls to language models, saving you time and effort while providing flexibility and convenience.
\n",
- "
12.109881
\n",
- "
0.000881
\n",
- "
\n",
- "
\n",
- "
5
\n",
- "
claude-instant-1.2
\n",
- "
why should I use LiteLLM
\n",
- "
Here are some key reasons why you may want to consider using LiteLLM:\\n\\n- Simplifies calling multiple large language models - LiteLLM provides a unified API to call models from different providers like OpenAI, Azure, HuggingFace, Anthropic etc. This avoids having to deal with different APIs from each provider.\\n\\n- Easy to get started - LiteLLM is very lightweight and simple to install with just one pip install. It has minimal configuration needed with just API keys. This lowers the barrier to start experimenting with LLMs.\\n\\n- Standard interface - The completion API provides a standard way to send messages and get responses back regardless of underlying model. This keeps the code interface consistent. \\n\\n- Model agnostic - Your code doesn't need to change to switch between different models. You can easily compare performance of models from different providers.\\n\\n- Open source - As it is open source, the library can be expanded to support more models and providers over time via community contributions.\\n\\n- Cost effective - LiteLLM helps avoid vendor lock-in. You can optimize costs by testing multiple models and choosing the best suitable and cost-effective option for your needs.\\n\\nSo in summary, L
\\nTo make a completion call using litellm, you can use the `completion` function from the litellm package. Here's an example of how to use it:\\n```\\nfrom litellm import completion\\n\\n# Set ENV variables\\nos.environ[\"OPENAI_API_KEY\"] = \"your_openai_api_key\"\\nos.environ[\"COHERE_API_KEY\"] = \"your_cohere_api_key\"\\n\\n# Define the messages to be completed\\nmessages = [\\n {\\n \"content\": \"Hello, how are you?\",\\n \"role\": \"user\"\\n }\\n]\\n\\n# Make a completion call using OpenAI\\nresponse = completion(model=\"gpt-3.5-turbo\", messages=messages)\\n\\n# Make a completion call using Cohere\\nresponse = completion(\"command-nightly\", messages)\\n```\\nIn this example, we first set the ENV variables for the OpenAI and Cohere API keys. Then, we define a list of messages to be completed, which in this case contains a single message with the content \"Hello, how are you?\" and the role \"user\".\\n\\nNext, we make two completion calls using the `completion` function from litellm. The first call uses the OpenAI model `gpt-3.5-turbo` and passes in the list of messages. The second call uses the Cohere model `command-nightly` and passes in the same list of messages.\\n\\nThe `completion` function returns a response object that contains the completed messages. You can then use the `response.messages` attribute to access the completed messages.\\n\\nHere's an example of how to access the completed messages:\\n```\\n# Print the completed messages\\nprint(response.messages)\\n```\\nThis will print the completed messages, which should look something like this:\\n```\\n[{'content': 'Hello, how are you?', 'role': 'user', 'completion': 'I am doing well, thanks for asking.'}]\\n```\\nNote that the `completion` attribute contains the completed message. You can use this attribute to retrieve the completed message and use it in your application.
\n",
- "
32.192453
\n",
- "
0.001821
\n",
- "
\n",
- "
\n",
- "
10
\n",
- "
gpt-3.5-turbo
\n",
- "
write code to make a litellm completion call
\n",
- "
Sure! Here's an example code to make a completion call using the litellm package:\\n\\n```python\\nfrom litellm import completion\\n\\n# Set the model and messages\\nmodel = \"gpt-3.5-turbo\"\\nmessages = [\\n {\"role\": \"system\", \"content\": \"You are a helpful assistant.\"},\\n {\"role\": \"user\", \"content\": \"Who won the world series in 2020?\"},\\n {\"role\": \"assistant\", \"content\": \"The Los Angeles Dodgers won the World Series in 2020.\"},\\n {\"role\": \"user\", \"content\": \"Where was it played?\"}\\n]\\n\\n# Make the completion call\\nresponse = completion(model=model, messages=messages)\\n\\n# Print the assistant's reply\\nassistant_reply = response[\"choices\"][0][\"message\"][\"content\"]\\nprint(\"Assistant: \", assistant_reply)\\n```\\n\\nMake sure you have the litellm package installed (`pip install litellm`) and set the necessary environment variables for the API keys before running this code.
\n",
- "
9.377155
\n",
- "
0.000686
\n",
- "
\n",
- "
\n",
- "
11
\n",
- "
claude-instant-1.2
\n",
- "
write code to make a litellm completion call
\n",
- "
Here is an example of making a completion call using litellm:\\n\\n```python\\nimport os\\nfrom litellm import completion\\n\\n# Set API keys as environment variables\\nos.environ[\"OPENAI_API_KEY\"] = \"your openai api key\" \\n\\n# Conversation context \\nmessages = [{\\n \"content\": \"Hello, how can I help you today?\",\\n \"role\": \"assistant\"\\n}]\\n\\n# Make completion call with GPT-3 model\\nresponse = completion(\\n model=\"gpt-3.5-turbo\", \\n messages=messages\\n)\\n\\nprint(response)\\n```\\n\\nTo break it down:\\n\\n- Import completion from litellm\\n- Set the OPENAI_API_KEY env var \\n- Define a messages list with the conversation context\\n- Call completion(), specifying the model (\"gpt-3.5-turbo\") and messages\\n- It will return the response from the API\\n- Print the response\\n\\nThis makes a simple completion call to OpenAI GPT-3 using litellm to handle the API details. You can also call other models like Cohere or Anthropic by specifying their name instead of the OpenAI
\n",
- "
9.839988
\n",
- "
0.001578
\n",
- "
\n",
- " \n",
- "
"
- ],
- "text/plain": [
- ""
- ]
- },
- "execution_count": 22,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "from IPython.core.interactiveshell import InteractiveShell\n",
- "InteractiveShell.ast_node_interactivity = \"all\"\n",
- "from IPython.display import HTML\n",
- "import pandas as pd\n",
- "\n",
- "df = pd.DataFrame(data)\n",
- "grouped_by_question = df.groupby('Question')\n",
- "\n",
- "for question, group_data in grouped_by_question:\n",
- " print(f\"Question: {question}\")\n",
- " HTML(group_data.to_html())\n"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "bmtAbC1rGVAm"
- },
- "source": [
- "## Use Case 2 - Rewrite user input concisely"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 23,
- "metadata": {
- "id": "boiHO1PhGXSL"
- },
- "outputs": [],
- "source": [
- "# enter your system prompt if you have one\n",
- "system_prompt = \"\"\"\n",
- "For a given user input, rewrite the input to make be more concise.\n",
- "\"\"\"\n",
- "\n",
- "# user input for re-writing questions\n",
- "questions = [\n",
- " \"LiteLLM is a lightweight Python package that simplifies the process of making API calls to various language models. Here are some reasons why you should use LiteLLM:\\n\\n1. **Simplified API Calls**: LiteLLM abstracts away the complexity of making API calls to different language models. It provides a unified interface for invoking models from OpenAI, Azure, Cohere, Anthropic, Huggingface, and more.\\n\\n2. **Easy Integration**: LiteLLM seamlessly integrates with your existing codebase. You can import the package and start making API calls with just a few lines of code.\\n\\n3. **Flexibility**: LiteLLM supports a variety of language models, including GPT-3, GPT-Neo, chatGPT, and more. You can choose the model that suits your requirements and easily switch between them.\\n\\n4. **Convenience**: LiteLLM handles the authentication and connection details for you. You just need to set the relevant environment variables, and the package takes care of the rest.\\n\\n5. **Quick Prototyping**: LiteLLM is ideal for rapid prototyping and experimentation. With its simple API, you can quickly generate text, chat with models, and build interactive applications.\\n\\n6. **Community Support**: LiteLLM is actively maintained and supported by a community of developers. You can find help, share ideas, and collaborate with others to enhance your projects.\\n\\nOverall, LiteLLM simplifies the process of making API calls to language models, saving you time and effort while providing flexibility and convenience\",\n",
- " \"Hi everyone! I'm [your name] and I'm currently working on [your project/role involving LLMs]. I came across LiteLLM and was really excited by how it simplifies working with different LLM providers. I'm hoping to use LiteLLM to [build an app/simplify my code/test different models etc]. Before finding LiteLLM, I was struggling with [describe any issues you faced working with multiple LLMs]. With LiteLLM's unified API and automatic translation between providers, I think it will really help me to [goals you have for using LiteLLM]. Looking forward to being part of this community and learning more about how I can build impactful applications powered by LLMs!Let me know if you would like me to modify or expand on any part of this suggested intro. I'm happy to provide any clarification or additional details you need!\",\n",
- " \"Traceloop is a platform for monitoring and debugging the quality of your LLM outputs. It provides you with a way to track the performance of your LLM application; rollout changes with confidence; and debug issues in production. It is based on OpenTelemetry, so it can provide full visibility to your LLM requests, as well vector DB usage, and other infra in your stack.\"\n",
- "]"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "fwNcC_obICUc"
- },
- "source": [
- "## Run Questions"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "KtBjZ1mUIBiJ"
- },
- "outputs": [],
- "source": [
- "from litellm import completion, completion_cost\n",
- "import os\n",
- "import time\n",
- "\n",
- "# optional use litellm dashboard to view logs\n",
- "# litellm.use_client = True\n",
- "# litellm.token = \"ishaan_2@berri.ai\" # set your email\n",
- "\n",
- "os.environ['TOGETHERAI_API_KEY'] = \"\"\n",
- "os.environ['OPENAI_API_KEY'] = \"\"\n",
- "os.environ['ANTHROPIC_API_KEY'] = \"\"\n",
- "\n",
- "models = ['togethercomputer/llama-2-70b-chat', 'gpt-3.5-turbo', 'claude-instant-1.2'] # enter llms to benchmark\n",
- "data_2 = []\n",
- "\n",
- "for question in questions: # group by question\n",
- " for model in models:\n",
- " print(f\"running question: {question} for model: {model}\")\n",
- " start_time = time.time()\n",
- " # show response, response time, cost for each question\n",
- " response = completion(\n",
- " model=model,\n",
- " max_tokens=500,\n",
- " messages = [\n",
- " {\n",
- " \"role\": \"system\", \"content\": system_prompt\n",
- " },\n",
- " {\n",
- " \"role\": \"user\", \"content\": \"User input:\" + question\n",
- " }\n",
- " ],\n",
- " )\n",
- " end = time.time()\n",
- " total_time = end-start_time # response time\n",
- " # print(response)\n",
- " cost = completion_cost(response) # cost for completion\n",
- " raw_response = response['choices'][0]['message']['content'] # response string\n",
- " #print(raw_response, total_time, cost)\n",
- "\n",
- " # add to pandas df\n",
- " data_2.append(\n",
- " {\n",
- " 'Model': model,\n",
- " 'Question': question,\n",
- " 'Response': raw_response,\n",
- " 'ResponseTime': total_time,\n",
- " 'Cost': cost\n",
- " })\n",
- "\n",
- "\n"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "-PCYIzG5M0II"
- },
- "source": [
- "## View Logs - Group by Question"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 20,
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/",
- "height": 1000
- },
- "id": "-3R5-2q8IiL2",
- "outputId": "c4a0d9e5-bb21-4de0-fc4c-9f5e71d0f177"
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Question: Hi everyone! I'm [your name] and I'm currently working on [your project/role involving LLMs]. I came across LiteLLM and was really excited by how it simplifies working with different LLM providers. I'm hoping to use LiteLLM to [build an app/simplify my code/test different models etc]. Before finding LiteLLM, I was struggling with [describe any issues you faced working with multiple LLMs]. With LiteLLM's unified API and automatic translation between providers, I think it will really help me to [goals you have for using LiteLLM]. Looking forward to being part of this community and learning more about how I can build impactful applications powered by LLMs!Let me know if you would like me to modify or expand on any part of this suggested intro. I'm happy to provide any clarification or additional details you need!\n"
- ]
- },
- {
- "data": {
- "text/html": [
- "
\n",
- " \n",
- "
\n",
- "
\n",
- "
Model
\n",
- "
Question
\n",
- "
Response
\n",
- "
ResponseTime
\n",
- "
Cost
\n",
- "
\n",
- " \n",
- " \n",
- "
\n",
- "
3
\n",
- "
togethercomputer/llama-2-70b-chat
\n",
- "
Hi everyone! I'm [your name] and I'm currently working on [your project/role involving LLMs]. I came across LiteLLM and was really excited by how it simplifies working with different LLM providers. I'm hoping to use LiteLLM to [build an app/simplify my code/test different models etc]. Before finding LiteLLM, I was struggling with [describe any issues you faced working with multiple LLMs]. With LiteLLM's unified API and automatic translation between providers, I think it will really help me to [goals you have for using LiteLLM]. Looking forward to being part of this community and learning more about how I can build impactful applications powered by LLMs!Let me know if you would like me to modify or expand on any part of this suggested intro. I'm happy to provide any clarification or additional details you need!
\n",
- "
\\nHere's a more concise version of the user input:\\n\\n\"Hi everyone! I'm [your name] and I'm working on [your project/role involving LLMs]. I recently discovered LiteLLM and I'm excited to use it to [build an app/simplify my code/test different models etc]. Before LiteLLM, I struggled with [describe any issues you faced working with multiple LLMs]. I'm looking forward to using LiteLLM's unified API and automatic translation to achieve my goals. I'm eager to learn more about building impactful applications powered by LLMs and to be part of this community. Let me know if you have any questions or need further clarification.\"\\n\\nIn this revised version, we've kept the essential information and removed some of the extraneous language. We've also rephrased some of the sentences to make them more concise and easier to read.
\n",
- "
18.300620
\n",
- "
0.001200
\n",
- "
\n",
- "
\n",
- "
4
\n",
- "
gpt-3.5-turbo
\n",
- "
Hi everyone! I'm [your name] and I'm currently working on [your project/role involving LLMs]. I came across LiteLLM and was really excited by how it simplifies working with different LLM providers. I'm hoping to use LiteLLM to [build an app/simplify my code/test different models etc]. Before finding LiteLLM, I was struggling with [describe any issues you faced working with multiple LLMs]. With LiteLLM's unified API and automatic translation between providers, I think it will really help me to [goals you have for using LiteLLM]. Looking forward to being part of this community and learning more about how I can build impactful applications powered by LLMs!Let me know if you would like me to modify or expand on any part of this suggested intro. I'm happy to provide any clarification or additional details you need!
\n",
- "
User input: Hi, I'm [your name] and I'm excited about using LiteLLM to simplify working with different LLM providers. Before finding LiteLLM, I faced challenges working with multiple LLMs. With LiteLLM's unified API and automatic translation, I believe it will help me achieve my goals of [state your goals]. I look forward to being part of this community and learning how to build impactful applications with LLMs. Let me know if you need any further clarification or details.
\n",
- "
7.385472
\n",
- "
0.000525
\n",
- "
\n",
- "
\n",
- "
5
\n",
- "
claude-instant-1.2
\n",
- "
Hi everyone! I'm [your name] and I'm currently working on [your project/role involving LLMs]. I came across LiteLLM and was really excited by how it simplifies working with different LLM providers. I'm hoping to use LiteLLM to [build an app/simplify my code/test different models etc]. Before finding LiteLLM, I was struggling with [describe any issues you faced working with multiple LLMs]. With LiteLLM's unified API and automatic translation between providers, I think it will really help me to [goals you have for using LiteLLM]. Looking forward to being part of this community and learning more about how I can build impactful applications powered by LLMs!Let me know if you would like me to modify or expand on any part of this suggested intro. I'm happy to provide any clarification or additional details you need!
\n",
- "
Here is a more concise rewrite of the user input:\\n\\nHi everyone, I'm [your name]. I'm currently [your project/role] and came across LiteLLM, which simplifies working with different LLMs through its unified API. I hope to [build an app/simplify code/test models] with LiteLLM since I previously struggled with [issues]. LiteLLM's automatic translation between providers will help me [goals] and build impactful LLM applications. Looking forward to learning more as part of this community. Let me know if you need any clarification on my plans to use LiteLLM.
\n",
- "
8.628217
\n",
- "
0.001022
\n",
- "
\n",
- " \n",
- "
"
- ],
- "text/plain": [
- ""
- ]
- },
- "execution_count": 20,
- "metadata": {},
- "output_type": "execute_result"
- },
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Question: LiteLLM is a lightweight Python package that simplifies the process of making API calls to various language models. Here are some reasons why you should use LiteLLM:\n",
- "\n",
- "1. **Simplified API Calls**: LiteLLM abstracts away the complexity of making API calls to different language models. It provides a unified interface for invoking models from OpenAI, Azure, Cohere, Anthropic, Huggingface, and more.\n",
- "\n",
- "2. **Easy Integration**: LiteLLM seamlessly integrates with your existing codebase. You can import the package and start making API calls with just a few lines of code.\n",
- "\n",
- "3. **Flexibility**: LiteLLM supports a variety of language models, including GPT-3, GPT-Neo, chatGPT, and more. You can choose the model that suits your requirements and easily switch between them.\n",
- "\n",
- "4. **Convenience**: LiteLLM handles the authentication and connection details for you. You just need to set the relevant environment variables, and the package takes care of the rest.\n",
- "\n",
- "5. **Quick Prototyping**: LiteLLM is ideal for rapid prototyping and experimentation. With its simple API, you can quickly generate text, chat with models, and build interactive applications.\n",
- "\n",
- "6. **Community Support**: LiteLLM is actively maintained and supported by a community of developers. You can find help, share ideas, and collaborate with others to enhance your projects.\n",
- "\n",
- "Overall, LiteLLM simplifies the process of making API calls to language models, saving you time and effort while providing flexibility and convenience\n"
- ]
- },
- {
- "data": {
- "text/html": [
- "
\n",
- " \n",
- "
\n",
- "
\n",
- "
Model
\n",
- "
Question
\n",
- "
Response
\n",
- "
ResponseTime
\n",
- "
Cost
\n",
- "
\n",
- " \n",
- " \n",
- "
\n",
- "
0
\n",
- "
togethercomputer/llama-2-70b-chat
\n",
- "
LiteLLM is a lightweight Python package that simplifies the process of making API calls to various language models. Here are some reasons why you should use LiteLLM:\\n\\n1. **Simplified API Calls**: LiteLLM abstracts away the complexity of making API calls to different language models. It provides a unified interface for invoking models from OpenAI, Azure, Cohere, Anthropic, Huggingface, and more.\\n\\n2. **Easy Integration**: LiteLLM seamlessly integrates with your existing codebase. You can import the package and start making API calls with just a few lines of code.\\n\\n3. **Flexibility**: LiteLLM supports a variety of language models, including GPT-3, GPT-Neo, chatGPT, and more. You can choose the model that suits your requirements and easily switch between them.\\n\\n4. **Convenience**: LiteLLM handles the authentication and connection details for you. You just need to set the relevant environment variables, and the package takes care of the rest.\\n\\n5. **Quick Prototyping**: LiteLLM is ideal for rapid prototyping and experimentation. With its simple API, you can quickly generate text, chat with models, and build interactive applications.\\n\\n6. **Community Support**: LiteLLM is actively maintained and supported by a community of developers. You can find help, share ideas, and collaborate with others to enhance your projects.\\n\\nOverall, LiteLLM simplifies the process of making API calls to language models, saving you time and effort while providing flexibility and convenience
\n",
- "
Here's a more concise version of the user input:\\n\\nLiteLLM is a lightweight Python package that simplifies API calls to various language models. It abstracts away complexity, integrates seamlessly, supports multiple models, and handles authentication. It's ideal for rapid prototyping and has community support. It saves time and effort while providing flexibility and convenience.
\n",
- "
11.294250
\n",
- "
0.001251
\n",
- "
\n",
- "
\n",
- "
1
\n",
- "
gpt-3.5-turbo
\n",
- "
LiteLLM is a lightweight Python package that simplifies the process of making API calls to various language models. Here are some reasons why you should use LiteLLM:\\n\\n1. **Simplified API Calls**: LiteLLM abstracts away the complexity of making API calls to different language models. It provides a unified interface for invoking models from OpenAI, Azure, Cohere, Anthropic, Huggingface, and more.\\n\\n2. **Easy Integration**: LiteLLM seamlessly integrates with your existing codebase. You can import the package and start making API calls with just a few lines of code.\\n\\n3. **Flexibility**: LiteLLM supports a variety of language models, including GPT-3, GPT-Neo, chatGPT, and more. You can choose the model that suits your requirements and easily switch between them.\\n\\n4. **Convenience**: LiteLLM handles the authentication and connection details for you. You just need to set the relevant environment variables, and the package takes care of the rest.\\n\\n5. **Quick Prototyping**: LiteLLM is ideal for rapid prototyping and experimentation. With its simple API, you can quickly generate text, chat with models, and build interactive applications.\\n\\n6. **Community Support**: LiteLLM is actively maintained and supported by a community of developers. You can find help, share ideas, and collaborate with others to enhance your projects.\\n\\nOverall, LiteLLM simplifies the process of making API calls to language models, saving you time and effort while providing flexibility and convenience
\n",
- "
LiteLLM is a lightweight Python package that simplifies API calls to various language models. Here's why you should use it:\\n1. Simplified API Calls: Works with multiple models (OpenAI, Azure, Cohere, Anthropic, Huggingface).\\n2. Easy Integration: Import and start using it quickly in your codebase.\\n3. Flexibility: Supports GPT-3, GPT-Neo, chatGPT, etc. easily switch between models.\\n4. Convenience: Handles authentication and connection details, just set environment variables.\\n5. Quick Prototyping: Great for rapid prototyping and building interactive applications.\\n6. Community Support: Actively maintained and supported by a developer community.
\n",
- "
9.778315
\n",
- "
0.000795
\n",
- "
\n",
- "
\n",
- "
2
\n",
- "
claude-instant-1.2
\n",
- "
LiteLLM is a lightweight Python package that simplifies the process of making API calls to various language models. Here are some reasons why you should use LiteLLM:\\n\\n1. **Simplified API Calls**: LiteLLM abstracts away the complexity of making API calls to different language models. It provides a unified interface for invoking models from OpenAI, Azure, Cohere, Anthropic, Huggingface, and more.\\n\\n2. **Easy Integration**: LiteLLM seamlessly integrates with your existing codebase. You can import the package and start making API calls with just a few lines of code.\\n\\n3. **Flexibility**: LiteLLM supports a variety of language models, including GPT-3, GPT-Neo, chatGPT, and more. You can choose the model that suits your requirements and easily switch between them.\\n\\n4. **Convenience**: LiteLLM handles the authentication and connection details for you. You just need to set the relevant environment variables, and the package takes care of the rest.\\n\\n5. **Quick Prototyping**: LiteLLM is ideal for rapid prototyping and experimentation. With its simple API, you can quickly generate text, chat with models, and build interactive applications.\\n\\n6. **Community Support**: LiteLLM is actively maintained and supported by a community of developers. You can find help, share ideas, and collaborate with others to enhance your projects.\\n\\nOverall, LiteLLM simplifies the process of making API calls to language models, saving you time and effort while providing flexibility and convenience
\n",
- "
Here is a concise rewrite of the user input:\\n\\nLiteLLM is a lightweight Python package that simplifies accessing various language models. It provides a unified interface for models from OpenAI, Azure, Cohere, Anthropic, Huggingface, and more. Key benefits include simplified API calls, easy integration, flexibility to use different models, automated handling of authentication, and support for quick prototyping. The actively maintained package saves time by abstracting away complexity while offering convenience and a collaborative community.
\n",
- "
7.697520
\n",
- "
0.001098
\n",
- "
\n",
- " \n",
- "
"
- ],
- "text/plain": [
- ""
- ]
- },
- "execution_count": 20,
- "metadata": {},
- "output_type": "execute_result"
- },
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Question: Traceloop is a platform for monitoring and debugging the quality of your LLM outputs. It provides you with a way to track the performance of your LLM application; rollout changes with confidence; and debug issues in production. It is based on OpenTelemetry, so it can provide full visibility to your LLM requests, as well vector DB usage, and other infra in your stack.\n"
- ]
- },
- {
- "data": {
- "text/html": [
- "
\n",
- " \n",
- "
\n",
- "
\n",
- "
Model
\n",
- "
Question
\n",
- "
Response
\n",
- "
ResponseTime
\n",
- "
Cost
\n",
- "
\n",
- " \n",
- " \n",
- "
\n",
- "
6
\n",
- "
togethercomputer/llama-2-70b-chat
\n",
- "
Traceloop is a platform for monitoring and debugging the quality of your LLM outputs. It provides you with a way to track the performance of your LLM application; rollout changes with confidence; and debug issues in production. It is based on OpenTelemetry, so it can provide full visibility to your LLM requests, as well vector DB usage, and other infra in your stack.
\n",
- "
\\nRewritten input: Traceloop is a platform for monitoring and debugging LLM outputs. It allows users to track performance, rollout changes confidently, and debug issues in production. It uses OpenTelemetry for full visibility into LLM requests, vector DB usage, and other infrastructure.
\n",
- "
9.060444
\n",
- "
0.000525
\n",
- "
\n",
- "
\n",
- "
7
\n",
- "
gpt-3.5-turbo
\n",
- "
Traceloop is a platform for monitoring and debugging the quality of your LLM outputs. It provides you with a way to track the performance of your LLM application; rollout changes with confidence; and debug issues in production. It is based on OpenTelemetry, so it can provide full visibility to your LLM requests, as well vector DB usage, and other infra in your stack.
\n",
- "
Traceloop is a platform for monitoring and debugging the quality of your LLM outputs. It helps track performance, rollout changes, and debug issues in production. It is based on OpenTelemetry, providing visibility to LLM requests, vector DB usage, and other infrastructure in your stack.
\n",
- "
7.304661
\n",
- "
0.000283
\n",
- "
\n",
- "
\n",
- "
8
\n",
- "
claude-instant-1.2
\n",
- "
Traceloop is a platform for monitoring and debugging the quality of your LLM outputs. It provides you with a way to track the performance of your LLM application; rollout changes with confidence; and debug issues in production. It is based on OpenTelemetry, so it can provide full visibility to your LLM requests, as well vector DB usage, and other infra in your stack.
\n",
- "
Here is a more concise rewrite of the user input:\\n\\nTraceloop monitors and debugs LLM quality. It tracks LLM performance, enables confident changes, and debugs production issues. Based on OpenTelemetry, Traceloop provides full visibility into LLM requests, vector DB usage, and other stack infrastructure.
\n"
- ],
- "text/plain": [
- "Model Name claude-instant-1 \\\n",
- "Prompt \n",
- "\\nIs paul graham a writer? Yes, Paul Graham is considered a writer in ad... \n",
- "\\nWhat has Paul Graham done? Paul Graham has made significant contribution... \n",
- "\\nWhat is Paul Graham known for? Paul Graham is known for several things:\\n\\n-... \n",
- "\\nWhere does Paul Graham live? Based on the information provided:\\n\\n- Paul ... \n",
- "\\nWho is Paul Graham? Paul Graham is an influential computer scient... \n",
- "\n",
- "Model Name gpt-3.5-turbo-0613 \\\n",
- "Prompt \n",
- "\\nIs paul graham a writer? Yes, Paul Graham is a writer. He has written s... \n",
- "\\nWhat has Paul Graham done? Paul Graham has achieved several notable accom... \n",
- "\\nWhat is Paul Graham known for? Paul Graham is known for his work on the progr... \n",
- "\\nWhere does Paul Graham live? According to the given information, Paul Graha... \n",
- "\\nWho is Paul Graham? Paul Graham is an English computer scientist, ... \n",
- "\n",
- "Model Name gpt-3.5-turbo-16k-0613 \\\n",
- "Prompt \n",
- "\\nIs paul graham a writer? Yes, Paul Graham is a writer. He has authored ... \n",
- "\\nWhat has Paul Graham done? Paul Graham has made significant contributions... \n",
- "\\nWhat is Paul Graham known for? Paul Graham is known for his work on the progr... \n",
- "\\nWhere does Paul Graham live? Paul Graham currently lives in England, where ... \n",
- "\\nWho is Paul Graham? Paul Graham is an English computer scientist, ... \n",
- "\n",
- "Model Name gpt-4-0613 \\\n",
- "Prompt \n",
- "\\nIs paul graham a writer? Yes, Paul Graham is a writer. He is an essayis... \n",
- "\\nWhat has Paul Graham done? Paul Graham is known for his work on the progr... \n",
- "\\nWhat is Paul Graham known for? Paul Graham is known for his work on the progr... \n",
- "\\nWhere does Paul Graham live? The text does not provide a current place of r... \n",
- "\\nWho is Paul Graham? Paul Graham is an English computer scientist, ... \n",
- "\n",
- "Model Name replicate/llama-2-70b-chat:58d078176e02c219e11eb4da5a02a7830a283b14cf8f94537af893ccff5ee781 \n",
- "Prompt \n",
- "\\nIs paul graham a writer? Yes, Paul Graham is an author. According to t... \n",
- "\\nWhat has Paul Graham done? Paul Graham has had a diverse career in compu... \n",
- "\\nWhat is Paul Graham known for? Paul Graham is known for many things, includi... \n",
- "\\nWhere does Paul Graham live? Based on the information provided, Paul Graha... \n",
- "\\nWho is Paul Graham? Paul Graham is an English computer scientist,... "
- ]
- },
- "execution_count": 17,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "import pandas as pd\n",
- "\n",
- "# Create an empty list to store the row data\n",
- "table_data = []\n",
- "\n",
- "# Iterate through the list and extract the required data\n",
- "for item in result:\n",
- " prompt = item['prompt'][0]['content'].replace(context, \"\") # clean the prompt for easy comparison\n",
- " model = item['response']['model']\n",
- " response = item['response']['choices'][0]['message']['content']\n",
- " table_data.append([prompt, model, response])\n",
- "\n",
- "# Create a DataFrame from the table data\n",
- "df = pd.DataFrame(table_data, columns=['Prompt', 'Model Name', 'Response'])\n",
- "\n",
- "# Pivot the DataFrame to get the desired table format\n",
- "table = df.pivot(index='Prompt', columns='Model Name', values='Response')\n",
- "table"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {
- "id": "zOxUM40PINDC"
- },
- "source": [
- "# Load Test endpoint\n",
- "\n",
- "Run 100+ simultaneous queries across multiple providers to see when they fail + impact on latency"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "ZkQf_wbcIRQ9"
- },
- "outputs": [],
- "source": [
- "models=[\"gpt-3.5-turbo\", \"replicate/llama-2-70b-chat:58d078176e02c219e11eb4da5a02a7830a283b14cf8f94537af893ccff5ee781\", \"claude-instant-1\"]\n",
- "context = \"\"\"Paul Graham (/ɡræm/; born 1964)[3] is an English computer scientist, essayist, entrepreneur, venture capitalist, and author. He is best known for his work on the programming language Lisp, his former startup Viaweb (later renamed Yahoo! Store), cofounding the influential startup accelerator and seed capital firm Y Combinator, his essays, and Hacker News. He is the author of several computer programming books, including: On Lisp,[4] ANSI Common Lisp,[5] and Hackers & Painters.[6] Technology journalist Steven Levy has described Graham as a \"hacker philosopher\".[7] Graham was born in England, where he and his family maintain permanent residence. However he is also a citizen of the United States, where he was educated, lived, and worked until 2016.\"\"\"\n",
- "prompt = \"Where does Paul Graham live?\"\n",
- "final_prompt = context + prompt\n",
- "result = load_test_model(models=models, prompt=final_prompt, num_calls=5)"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {
- "id": "8vSNBFC06aXY"
- },
- "source": [
- "## Visualize the data"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 19,
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/",
- "height": 552
- },
- "id": "SZfiKjLV3-n8",
- "outputId": "00f7f589-b3da-43ed-e982-f9420f074b8d"
- },
- "outputs": [
- {
- "data": {
- "image/png": "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WwwqREREJFsMKkRERCRbDCpEREQkWwwqREREJFsMKkRERCRbDCpEREQkWwwqREREJFsMKkRERCRbDCpEREQkWwwqREREJFsMKkRERCRbDCpEREQkWwwqREREJFsMKkRERCRbDCpEREQkWwwqREREJFsMKkRERCRbDCpEREQkWwwqREREJFsMKkRERCRbDCpEREQkWwwqREREJFsMKkRERCRbDCpEREQkWwwqREREJFsMKkRERCRbDCpEREQkWwwqREREJFsMKkRERCRbDCpEREQkWwwqREREJFsMKkRERCRbDCpEREQkWwwqREREJFsMKkRERCRbDCpEREQkWwwqREREJFsMKkRERCRbDCpEREQkWz8UVB4+fIhx48ahXbt2CA8PBwAcOHAAt2/f1mhxRERElLmpHVSOHz+O4sWL4/z58wgICEBsbCwA4Pr165g4caLGCyQiIqLMS+2gMmrUKEybNg1BQUEwMDCQ2mvWrIlz585ptDgiIiLK3NQOKjdv3kSLFi3StNva2uLt27caKYqIiIgI+IGgYmFhgZcvX6Zpv3r1KnLmzKmRooiIiIiAHwgqbdu2xciRI/Hq1SsoFAoolUqcPn0aw4YNQ+fOndOjRiIiIsqk1A4qM2bMQKFCheDo6IjY2FgUKVIEVatWRcWKFTFu3Lj0qJGIiIgyKT11b2BgYICVK1di/PjxuHXrFmJjY1GqVCnkz58/PeojIiKiTEztoJIqV65cyJUrlyZrISIiIlKhdlARQmD79u0IDg5GeHg4lEqlyvKAgACNFUdERESZm9pBZdCgQVixYgVq1KiB7NmzQ6FQpEddREREROoHFT8/PwQEBKBhw4bpUQ8RERGRRO1ZP+bm5sibN2961EJERESkQu2gMmnSJEyePBnx8fHpUQ8RERGRRO1dP25ubti0aRNsbW3h5OQEfX19leVXrlzRWHFERESUuakdVNzd3XH58mV07NiRg2mJiIgoXakdVPbv34/AwEBUrlw5PeohIiIikqg9RsXR0RFmZmbpUQsRERGRCrWDyvz58zFixAiEhoamQzlERERE/6P2rp+OHTviw4cPyJcvH0xMTNIMpo2IiNBYcUSZndOo/dougbQsdFYjbZdApFVqBxUvL690KIOIiIgorR+a9UNERET0K3xXUImOjpYG0EZHR//ruhxoS0RERJryXUHF0tISL1++hK2tLSwsLL567BQhBBQKBVJSUjReJBEREWVO3xVUjh49CisrKwBAcHBwuhZERERElOq7gkq1atWQN29eXLx4EdWqVUvvmoiIiIgAqHEcldDQUO7WISIiol9K7QO+aZK3tzdcXFxgZmYGMzMzVKhQAQcOHNBmSURERCQjak1PDgwMhLm5+b+u07Rp0+++PwcHB8yaNQv58+eHEALr169Hs2bNcPXqVRQtWlSd0oiIiCgDUiuo/NcxVNSd9dOkSROV69OnT4e3tzfOnTvHoEJERETqBZVXr17B1tY2XQpJSUnBtm3bEBcXhwoVKnx1ncTERCQmJkrX/+uYLkRERPR7++4xKl87doom3Lx5E1myZIGhoSF69+6NnTt3okiRIl9dd+bMmTA3N5cujo6O6VITERERycN3BxUhRLoUULBgQVy7dg3nz59Hnz594O7ujn/++eer644ePRpRUVHS5enTp+lSExEREcnDd+/6cXd3h7GxscYLMDAwgLOzMwCgTJkyuHjxIhYtWoQVK1akWdfQ0BCGhoYar4GIiIjk6buDytq1a9OzDolSqVQZh0JERESZl9pnT9ak0aNHo0GDBsiVKxdiYmKwceNGHDt2DIGBgdosi4iIiGRCq0ElPDwcnTt3xsuXL2Fubg4XFxcEBgaiTp062iyLiIiIZEKrQWX16tXafHgiIiKSuR8+hH5ISAgCAwMRHx8PIP1mBREREVHmpXZQeffuHWrXro0CBQqgYcOGePnyJQCge/fuGDp0qMYLJCIiosxL7aAyePBg6Onp4cmTJzAxMZHa27Rpg4MHD2q0OCIiIsrc1B6jcujQIQQGBsLBwUGlPX/+/AgLC9NYYURERERq96jExcWp9KSkioiI4MHYiIiISKPUDipVqlTBhg0bpOsKhQJKpRJz5sxBjRo1NFocERERZW5q7/qZM2cOatWqhUuXLiEpKQkjRozA7du3ERERgdOnT6dHjURERJRJqd2jUqxYMdy/fx+VK1dGs2bNEBcXh5YtW+Lq1avIly9fetRIREREmdQPHfDN3NwcY8eO1XQtRERERCrU7lE5ePAgTp06JV1ftmwZSpYsifbt2+P9+/caLY6IiIgyN7WDyvDhwxEdHQ0AuHnzJoYMGYKGDRvi8ePHGDJkiMYLJCIiosxL7V0/jx8/RpEiRQAAO3bsQJMmTTBjxgxcuXIFDRs21HiBRERElHmp3aNiYGCADx8+AAAOHz6MunXrAgCsrKyknhYiIiIiTVC7R6Vy5coYMmQIKlWqhAsXLmDLli0AgPv376c5Wi0RERHRz1C7R2Xp0qXQ09PD9u3b4e3tjZw5cwIADhw4gPr162u8QCIiIsq81O5RyZUrF/bt25emfeHChRopiIiIiCjVDx1HRalUIiQkBOHh4VAqlSrLqlatqpHCiIiIiNQOKufOnUP79u0RFhYGIYTKMoVCgZSUFI0VR0RERJmb2kGld+/eKFu2LPbv348cOXJAoVCkR11ERERE6geVBw8eYPv27XB2dk6PeoiIiIgkas/6KV++PEJCQtKjFiIiIiIVaveo9O/fH0OHDsWrV69QvHhx6Ovrqyx3cXHRWHFERESUuakdVFq1agUA6Natm9SmUCgghOBgWiIiItKoHzrXDxEREdGvoHZQyZ07d3rUQURERJTGDx3w7eHDh/Dy8sKdO3cAAEWKFMHAgQORL18+jRZHREREmZvaQSUwMBBNmzZFyZIlUalSJQDA6dOnUbRoUezduxd16tTReJHa4jRqv7ZLIC0LndVI2yUQEWVqageVUaNGYfDgwZg1a1aa9pEjR2aooEJERETapfZxVO7cuYPu3bunae/WrRv++ecfjRRFREREBPxAULGxscG1a9fStF+7dg22traaqImIiIgIwA/s+unZsyd69eqFR48eoWLFigA+jVGZPXs2hgwZovECiYiIKPNSO6iMHz8eWbNmxfz58zF69GgAgL29PSZNmoQBAwZovEAiIiLKvNQOKgqFAoMHD8bgwYMRExMDAMiaNavGCyMiIiL6oeOoAEB4eDju3bsHAChUqBBsbGw0VhQRERER8AODaWNiYtCpUyfY29ujWrVqqFatGuzt7dGxY0dERUWlR41ERESUSakdVHr06IHz589j//79iIyMRGRkJPbt24dLly7Bw8MjPWokIiKiTErtXT/79u1DYGAgKleuLLXVq1cPK1euRP369TVaHBEREWVuaveoZMuWDebm5mnazc3NYWlpqZGiiIiIiIAfCCrjxo3DkCFD8OrVK6nt1atXGD58OMaPH6/R4oiIiChzU3vXj7e3N0JCQpArVy7kypULAPDkyRMYGhrizZs3WLFihbTulStXNFcpERERZTpqB5XmzZunQxlEREREaakdVCZOnJgedRARERGlofYYladPn+LZs2fS9QsXLmDQoEHw9fXVaGFEREREageV9u3bIzg4GMCnQbS1a9fGhQsXMHbsWEyZMkXjBRIREVHmpXZQuXXrFv744w8AwNatW1G8eHGcOXMG/v7+WLdunabrIyIiokxM7aCSnJwMQ0NDAMDhw4fRtGlTAJ/O9/Py5UvNVkdERESZmtpBpWjRovDx8cHJkycRFBQkHY32xYsXyJYtm8YLJCIiosxL7aAye/ZsrFixAtWrV0e7du1QokQJAMCePXukXUJEREREmqD29OTq1avj7du3iI6OVjlkfq9evWBiYqLR4oiIiChzU7tHBQCEELh8+TJWrFiBmJgYAICBgQGDChEREWmU2j0qYWFhqF+/Pp48eYLExETUqVMHWbNmxezZs5GYmAgfH5/0qJOIiIgyIbV7VAYOHIiyZcvi/fv3MDY2ltpbtGiBI0eOaLQ4IiIiytzU7lE5efIkzpw5AwMDA5V2JycnPH/+XGOFEREREando6JUKpGSkpKm/dmzZ8iaNatGiiIiIiICfiCo1K1bF15eXtJ1hUKB2NhYTJw4EQ0bNtRkbURERJTJqb3rZ/78+ahXrx6KFCmChIQEtG/fHg8ePIC1tTU2bdqUHjUSERFRJqV2UHFwcMD169exZcsWXL9+HbGxsejevTs6dOigMriWiIiI6GepHVQAQE9PDx06dECHDh2ktpcvX2L48OFYunSpxoojIiKizE2toHL79m0EBwfDwMAAbm5usLCwwNu3bzF9+nT4+Pggb9686VUnERERZULfPZh2z549KFWqFAYMGIDevXujbNmyCA4ORuHChXHnzh3s3LkTt2/fTs9aiYiIKJP57qAybdo0eHp6Ijo6GgsWLMCjR48wYMAA/P333zh48KB0FmUiIiIiTfnuoHLv3j14enoiS5Ys6N+/P3R0dLBw4UKUK1cuPesjIiKiTOy7g0pMTAzMzMwAALq6ujA2NuaYFCIiIkpXag2mDQwMhLm5OYBPR6g9cuQIbt26pbJO06ZNNVcdERERZWpqBRV3d3eV6x4eHirXFQrFVw+vT0RERPQjvjuoKJXK9KyDiIiIKA21z/VDRERE9KtoNajMnDkT5cqVQ9asWWFra4vmzZvj3r172iyJiIiIZESrQeX48ePw9PTEuXPnEBQUhOTkZNStWxdxcXHaLIuIiIhk4ofO9aMpBw8eVLm+bt062Nra4vLly6hataqWqiIiIiK50GpQ+VJUVBQAwMrK6qvLExMTkZiYKF2Pjo7+JXURERGRdvzQrp/IyEisWrUKo0ePRkREBADgypUreP78+Q8XolQqMWjQIFSqVAnFihX76jozZ86Eubm5dHF0dPzhxyMiIiL5Uzuo3LhxAwUKFMDs2bMxb948REZGAgACAgIwevToHy7E09MTt27dwubNm7+5zujRoxEVFSVdnj59+sOPR0RERPKndlAZMmQIunTpggcPHsDIyEhqb9iwIU6cOPFDRfTr1w/79u1DcHAwHBwcvrmeoaEhzMzMVC5ERESUcak9RuXixYtYsWJFmvacOXPi1atXat2XEAL9+/fHzp07cezYMeTJk0fdcoiIiCgDUzuoGBoafnUQ6/3792FjY6PWfXl6emLjxo3YvXs3smbNKgUdc3NzGBsbq1saERERZTBq7/pp2rQppkyZguTkZACfzu/z5MkTjBw5Eq1atVLrvry9vREVFYXq1asjR44c0mXLli3qlkVEREQZkNpBZf78+YiNjYWtrS3i4+NRrVo1ODs7I2vWrJg+fbpa9yWE+OqlS5cu6pZFREREGZDau37Mzc0RFBSEU6dO4caNG4iNjUXp0qVRu3bt9KiPiIiIMrEfPuBb5cqVUblyZU3WQkRERKRC7aCyePHir7YrFAoYGRnB2dkZVatWha6u7k8XR0RERJmb2kFl4cKFePPmDT58+ABLS0sAwPv372FiYoIsWbIgPDwcefPmRXBwMI8cS0RERD9F7cG0M2bMQLly5fDgwQO8e/cO7969w/3791G+fHksWrQIT548gZ2dHQYPHpwe9RIREVEmonaPyrhx47Bjxw7ky5dPanN2dsa8efPQqlUrPHr0CHPmzFF7qjIRERHRl9TuUXn58iU+fvyYpv3jx4/SAdvs7e0RExPz89URERFRpqZ2UKlRowY8PDxw9epVqe3q1avo06cPatasCQC4efMmD4dPREREP03toLJ69WpYWVmhTJkyMDQ0hKGhIcqWLQsrKyusXr0aAJAlSxbMnz9f48USERFR5qL2GBU7OzsEBQXh7t27uH//PgCgYMGCKFiwoLROjRo1NFchERERZVo/fMC3QoUKoVChQpqshYiIiEjFDwWVZ8+eYc+ePXjy5AmSkpJUli1YsEAjhRERERGpHVSOHDmCpk2b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",
- "text/plain": [
- "
"
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- }
- ],
- "source": [
- "import matplotlib.pyplot as plt\n",
- "\n",
- "## calculate avg response time\n",
- "unique_models = set(result[\"response\"]['model'] for result in result[\"results\"])\n",
- "model_dict = {model: {\"response_time\": []} for model in unique_models}\n",
- "for completion_result in result[\"results\"]:\n",
- " model_dict[completion_result[\"response\"][\"model\"]][\"response_time\"].append(completion_result[\"response_time\"])\n",
- "\n",
- "avg_response_time = {}\n",
- "for model, data in model_dict.items():\n",
- " avg_response_time[model] = sum(data[\"response_time\"]) / len(data[\"response_time\"])\n",
- "\n",
- "models = list(avg_response_time.keys())\n",
- "response_times = list(avg_response_time.values())\n",
- "\n",
- "plt.bar(models, response_times)\n",
- "plt.xlabel('Model', fontsize=10)\n",
- "plt.ylabel('Average Response Time')\n",
- "plt.title('Average Response Times for each Model')\n",
- "\n",
- "plt.xticks(models, [model[:15]+'...' if len(model) > 15 else model for model in models], rotation=45)\n",
- "plt.show()"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {
- "id": "inSDIE3_IRds"
- },
- "source": [
- "# Duration Test endpoint\n",
- "\n",
- "Run load testing for 2 mins. Hitting endpoints with 100+ queries every 15 seconds."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 20,
- "metadata": {
- "id": "ePIqDx2EIURH"
- },
- "outputs": [],
- "source": [
- "models=[\"gpt-3.5-turbo\", \"replicate/llama-2-70b-chat:58d078176e02c219e11eb4da5a02a7830a283b14cf8f94537af893ccff5ee781\", \"claude-instant-1\"]\n",
- "context = \"\"\"Paul Graham (/ɡræm/; born 1964)[3] is an English computer scientist, essayist, entrepreneur, venture capitalist, and author. He is best known for his work on the programming language Lisp, his former startup Viaweb (later renamed Yahoo! Store), cofounding the influential startup accelerator and seed capital firm Y Combinator, his essays, and Hacker News. He is the author of several computer programming books, including: On Lisp,[4] ANSI Common Lisp,[5] and Hackers & Painters.[6] Technology journalist Steven Levy has described Graham as a \"hacker philosopher\".[7] Graham was born in England, where he and his family maintain permanent residence. However he is also a citizen of the United States, where he was educated, lived, and worked until 2016.\"\"\"\n",
- "prompt = \"Where does Paul Graham live?\"\n",
- "final_prompt = context + prompt\n",
- "result = load_test_model(models=models, prompt=final_prompt, num_calls=100, interval=15, duration=120)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 27,
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/",
- "height": 552
- },
- "id": "k6rJoELM6t1K",
- "outputId": "f4968b59-3bca-4f78-a88b-149ad55e3cf7"
- },
- "outputs": [
- {
- "data": {
- "image/png": "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DaxZswYFCxaU/ua9fPkS4eHhcHNzU3mMd+/eyTrgcLcU5VkZuTsuLg5JSUlQKBQ4cOAAwsPDoaurCz09PURERMDV1RX//POPFGwSExOhr68PPz8/vH79msGGNEIIIQWbX3/9Fd27d8fcuXPRt29fjBgxAufPnwfwftdASkoKJkyYgMqVK+PVq1cqj8NgQ5/r/PnzaN68Obp374727dtj1KhRCAkJwbVr19ClSxcA708zkJaWhnXr1qFGjRo4ceKEymPIOdgADDeUx0VGRqJixYo4evQoNmzYgCZNmuDvv/8G8H5XU/ny5dGmTRtMnz5dWidfvnwYNWoU7t27B0tLSwYb0oiM7Wju3Ln4448/sHHjRty4cQNt27bFn3/+ifHjx+P06dMAAAMDA7i6uqJhw4YwNzfXYtUkRw8fPsTz589V2r7//nvs3r0bFy9eRJ8+fQC8P/VFixYtMH369EwjN7IniPK4Hj16CFNTU6GjoyOWL18utaemporNmzeL9PR0qU2pVGqjRPpGREVFibZt24pVq1YJIYTYvXu3MDU1Ff369ROurq6iYcOG4syZM0II1W3x3bt3WqmX5CkkJETY2NiITZs2SW0Z29v69euFk5OTOH/+fKb10tLSvliN2saRG8qzMk4J7uPjg/j4eBgYGMDW1hbJyckA3n8rad++vcrETY7SUG6ytrbGqFGj0KRJE1y+fBk+Pj6YNm0ali5dip9++glnzpyBj48PLl68qLItclcUaVLZsmVRr149rFu3DocOHQLwv799Li4uiI6Oli4z8yG574r6EMMN5VkZocXe3h4nTpyAl5cXOnbsiF27diEpKSlTfzleH4W051Pbk6urKwoXLox9+/bB2dkZffv2BQBYWlrCzc0NLVu2hKur65cslb4x9vb26NevH2JiYuDn54fdu3dLywoXLgxHR0ctVpc3fDsxjr4a4v8nAD9//hxpaWkoVqwYrK2tUaNGDSQnJ6NXr14ICgpCixYtYGRkhICAADRq1AhOTk7aLp1kQnwweXjFihWIjo6GgYEBRowYIV26IyUlBc+ePcOjR49QunRpHDhwAK1atcLAgQP/9bQERJ8j42i7evXqYcmSJRg7dixGjx6NkJAQODs7Y8uWLVAoFPjxxx+1XapW8VBwypOCg4MxadIkREVFoXnz5mjTpg1atmwJAOjRowd27NiB4cOH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",
- "text/plain": [
- "
\n"
- ]
- },
- "metadata": {},
- "execution_count": 6
- }
- ]
- },
- {
- "cell_type": "code",
- "source": [
- "input_texts = data['Input'].values"
- ],
- "metadata": {
- "id": "0DbL-kirWUyn"
- },
- "execution_count": 7,
- "outputs": []
- },
- {
- "cell_type": "code",
- "source": [
- "messages = [[{\"role\": \"user\", \"content\": input_text}] for input_text in input_texts]"
- ],
- "metadata": {
- "id": "cqpAvy8hWXyC"
- },
- "execution_count": 8,
- "outputs": []
- },
- {
- "cell_type": "markdown",
- "source": [
- "# Running Inference"
- ],
- "metadata": {
- "id": "SugCyom0Xy8U"
- }
- },
- {
- "cell_type": "code",
- "source": [
- "from litellm import batch_completion\n",
- "model_name = \"facebook/opt-125m\"\n",
- "provider = \"vllm\"\n",
- "response_list = batch_completion(\n",
- " model=model_name,\n",
- " custom_llm_provider=provider, # can easily switch to huggingface, replicate, together ai, sagemaker, etc.\n",
- " messages=messages,\n",
- " temperature=0.2,\n",
- " max_tokens=80,\n",
- " )"
- ],
- "metadata": {
- "id": "qpikx3uxHns3"
- },
- "execution_count": null,
- "outputs": []
- },
- {
- "cell_type": "code",
- "source": [
- "response_list"
- ],
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/"
- },
- "id": "QDPikHtwKJJ2",
- "outputId": "06f47c44-e258-452a-f9db-232a5b6d2810"
- },
- "execution_count": 10,
- "outputs": [
- {
- "output_type": "execute_result",
- "data": {
- "text/plain": [
- "[ JSON: {\n",
- " \"choices\": [\n",
- " {\n",
- " \"finish_reason\": \"stop\",\n",
- " \"index\": 0,\n",
- " \"message\": {\n",
- " \"content\": \".\\n\\nThe query input is the query input that is used to query the data.\\n\\nThe query input is the query input that is used to query the data.\\n\\nThe query input is the query input that is used to query the data.\\n\\nThe query input is the query input that is used to query the data.\\n\\nThe query input is the query input that is\",\n",
- " \"role\": \"assistant\",\n",
- " \"logprobs\": null\n",
- " }\n",
- " }\n",
- " ],\n",
- " \"created\": 1694053363.6139505,\n",
- " \"model\": \"facebook/opt-125m\",\n",
- " \"usage\": {\n",
- " \"prompt_tokens\": 9,\n",
- " \"completion_tokens\": 80,\n",
- " \"total_tokens\": 89\n",
- " }\n",
- " }]"
- ]
- },
- "metadata": {},
- "execution_count": 10
- }
- ]
- },
- {
- "cell_type": "code",
- "source": [
- "response_values = [response['choices'][0]['message']['content'] for response in response_list]"
- ],
- "metadata": {
- "id": "SYqTcCiJbQDF"
- },
- "execution_count": 11,
- "outputs": []
- },
- {
- "cell_type": "code",
- "source": [
- "response_values"
- ],
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/"
- },
- "id": "wqs-Oy9FbiPo",
- "outputId": "16a6a7b7-97c8-4b5b-eff8-09ea5eb5ad06"
- },
- "execution_count": 12,
- "outputs": [
- {
- "output_type": "execute_result",
- "data": {
- "text/plain": [
- "['.\\n\\nThe query input is the query input that is used to query the data.\\n\\nThe query input is the query input that is used to query the data.\\n\\nThe query input is the query input that is used to query the data.\\n\\nThe query input is the query input that is used to query the data.\\n\\nThe query input is the query input that is']"
- ]
- },
- "metadata": {},
- "execution_count": 12
- }
- ]
- },
- {
- "cell_type": "code",
- "source": [
- "data[f\"{model_name}_output\"] = response_values"
- ],
- "metadata": {
- "id": "mElNbBehbkrz"
- },
- "execution_count": 13,
- "outputs": []
- },
- {
- "cell_type": "code",
- "source": [
- "data.to_csv('model_responses.csv', index=False)"
- ],
- "metadata": {
- "id": "F06NXssDc45k"
- },
- "execution_count": 14,
- "outputs": []
- }
- ]
-}
\ No newline at end of file
diff --git a/cookbook/ai_coding_tool_guides/chatgpt_subscription_passthrough.md b/cookbook/ai_coding_tool_guides/chatgpt_subscription_passthrough.md
deleted file mode 100644
index e1f606a2eb7..00000000000
--- a/cookbook/ai_coding_tool_guides/chatgpt_subscription_passthrough.md
+++ /dev/null
@@ -1,78 +0,0 @@
-# Use your ChatGPT subscription through LiteLLM
-
-Sign into ChatGPT in Codex or Pi and send model requests through your existing LiteLLM proxy. The client keeps your login and refreshes it. Each request uses that client's ChatGPT account, while a separate LiteLLM virtual key controls gateway access and usage attribution.
-
-An administrator adds a `chatgpt/` model using the usual model configuration. No ChatGPT login needs to be added to the proxy. This uses the same separate-key convention as provider passthrough, including Claude Code subscription forwarding.
-
-## Add a model to the proxy
-
-Reuse your existing proxy. If you need one, follow the [proxy quickstart](https://docs.litellm.ai/docs/proxy/docker_quick_start). Merge this entry into its model list and restart the proxy:
-
-```yaml
-model_list:
- - model_name: gpt-6-luna
- litellm_params:
- model: chatgpt/gpt-6-luna
- model_info:
- mode: responses
-```
-
-Use a model available to the caller's subscription. The virtual key must allow the exposed model name. Leave saved ChatGPT logins and API credentials unset for a client-only deployment. Explicit cross-provider fallbacks can incur separate API charges; do not configure them for subscription-only traffic.
-
-## Configure Codex
-
-Use your existing LiteLLM URL and a virtual key assigned to your gateway user. Export that key as `LITELLM_API_KEY` in the environment that launches Codex. Keep the value out of committed configuration.
-
-Add this provider to `~/.codex/config.toml`, preserving your other settings:
-
-```toml
-model = "gpt-6-luna"
-model_provider = "litellm_subscription"
-
-[model_providers.litellm_subscription]
-name = "LiteLLM subscription"
-base_url = "https://litellm.example.com/v1"
-wire_api = "responses"
-supports_websockets = false
-requires_openai_auth = true
-env_http_headers = { "x-litellm-api-key" = "LITELLM_API_KEY" }
-```
-
-Replace `https://litellm.example.com` with your proxy URL. A local proxy normally uses `http://localhost:4000/v1`. Launch Codex, sign into ChatGPT if needed, and send a prompt with a model available to your subscription. The selected model must match the name exposed by the gateway.
-
-The `lite` CLI can set up this provider each time it starts Codex, with no `config.toml` edit. Export `LITELLM_PROXY_URL` and `LITELLM_PROXY_API_KEY`, then run `lite codex --subscription`. Run `lite config set codex_subscription true` to make it the default.
-
-For a managed workspace rollout, use OpenAI's [gateway configuration guide](https://learn.chatgpt.com/docs/enterprise/sign-in-with-chatgpt-through-a-gateway).
-
-## Native compatibility and Pi
-
-For a native Codex model catalog and request format, change Codex's `base_url` to `https://litellm.example.com/backend-api/codex`. This route uses the same credentials but does not need a model deployment or resolve model aliases. Restricted keys must allow the upstream model name.
-
-### Configure Pi
-
-Sign into the built-in `openai-codex` provider in Pi. Merge this override into `~/.pi/agent/models.json` and export your LiteLLM virtual key as `LITELLM_API_KEY` before launching Pi:
-
-```json
-{
- "providers": {
- "openai-codex": {
- "baseUrl": "https://litellm.example.com/backend-api/codex",
- "headers": {
- "x-litellm-api-key": "LITELLM_API_KEY"
- }
- }
- }
-}
-```
-
-Keep Pi's subscription login as the provider credential. The header value names an environment variable, not a key to paste into the file.
-
-## Verify account usage
-
-Send a prompt and check the completed request in LiteLLM's Logs page. Its gateway user/key and token counts should match the caller. Give each person their own LiteLLM virtual key for separate attribution. The gateway credential and ChatGPT login are independently authenticated; LiteLLM does not establish that both identities belong to the same person.
-
-The native routes are `GET /backend-api/codex/models` and `POST /backend-api/codex/responses`. Model requests use the upstream model name, so a restricted LiteLLM key must allow that name. OpenAI's model catalog is returned in its native format; seeing a model there does not override gateway permissions.
-
-The proxy forwards the ChatGPT bearer and account header to the fixed OpenAI Codex endpoint. It removes gateway credentials and cookies from the upstream request and redacts credential headers in logging callbacks. The client remains responsible for refresh and reauthentication. Native passthrough returns authentication and quota errors without switching subscriptions. Standard model routes retain the caller login across ChatGPT retries; explicitly configured cross-provider fallbacks use their own credentials. An explicit workspace header is preserved for OpenAI to validate.
-
-This flow does not connect accounts through the dashboard or pool subscription allowances. Use a Codex build that supports ChatGPT authentication with a custom model provider. The integration forwards HTTP and SSE requests; it does not add native WebSocket or other Codex backend routes.
diff --git a/cookbook/ai_coding_tool_guides/claude_code_quickstart/guide.md b/cookbook/ai_coding_tool_guides/claude_code_quickstart/guide.md
deleted file mode 100644
index cd27eed18a3..00000000000
--- a/cookbook/ai_coding_tool_guides/claude_code_quickstart/guide.md
+++ /dev/null
@@ -1,294 +0,0 @@
-# Claude Code with LiteLLM Quickstart
-
-This guide shows how to call Claude models (and any LiteLLM-supported model) through LiteLLM proxy from Claude Code.
-
-> **Note:** This integration is based on [Anthropic's official LiteLLM configuration documentation](https://docs.anthropic.com/en/docs/claude-code/llm-gateway#litellm-configuration). It allows you to use any LiteLLM supported model through Claude Code with centralized authentication, usage tracking, and cost controls.
-
-## Video Walkthrough
-
-Watch the full tutorial: https://www.loom.com/embed/3c17d683cdb74d36a3698763cc558f56
-
-## Prerequisites
-
-- [Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview) installed
-- API keys for your chosen providers
-
-## Installation
-
-First, install LiteLLM with proxy support:
-
-```bash
-pip install 'litellm[proxy]'
-```
-
-## Step 1: Setup config.yaml
-
-Create a secure configuration using environment variables:
-
-```yaml
-model_list:
- # Claude models
- - model_name: claude-3-5-sonnet-20241022
- litellm_params:
- model: anthropic/claude-3-5-sonnet-20241022
- api_key: os.environ/ANTHROPIC_API_KEY
-
- - model_name: claude-3-5-haiku-20241022
- litellm_params:
- model: anthropic/claude-3-5-haiku-20241022
- api_key: os.environ/ANTHROPIC_API_KEY
-
-
-litellm_settings:
- master_key: os.environ/LITELLM_MASTER_KEY
-```
-
-Set your environment variables:
-
-```bash
-export ANTHROPIC_API_KEY="your-anthropic-api-key"
-export LITELLM_MASTER_KEY="sk-$(openssl rand -hex 32)"
-```
-
-## Step 2: Start Proxy
-
-```bash
-litellm --config /path/to/config.yaml
-
-# RUNNING on http://0.0.0.0:4000
-```
-
-## Step 3: Verify Setup
-
-Test that your proxy is working correctly:
-
-```bash
-curl -X POST http://0.0.0.0:4000/v1/messages \
--H "Authorization: Bearer $LITELLM_MASTER_KEY" \
--H "Content-Type: application/json" \
--d '{
- "model": "claude-3-5-sonnet-20241022",
- "max_tokens": 1000,
- "messages": [{"role": "user", "content": "What is the capital of France?"}]
-}'
-```
-
-## Step 4: Configure Claude Code
-
-### Method 1: Unified Endpoint (Recommended)
-
-Configure Claude Code to use LiteLLM's unified endpoint. Either a virtual key or master key can be used here:
-
-```bash
-export ANTHROPIC_BASE_URL="http://0.0.0.0:4000"
-export ANTHROPIC_AUTH_TOKEN="$LITELLM_MASTER_KEY"
-```
-
-> **Tip:** LITELLM_MASTER_KEY gives Claude access to all proxy models, whereas a virtual key would be limited to the models set in the UI.
-
-### Method 2: Provider-specific Pass-through Endpoint
-
-Alternatively, use the Anthropic pass-through endpoint:
-
-```bash
-export ANTHROPIC_BASE_URL="http://0.0.0.0:4000/anthropic"
-export ANTHROPIC_AUTH_TOKEN="$LITELLM_MASTER_KEY"
-```
-
-## Step 5: Use Claude Code
-
-### Choosing Your Model
-
-You have two options for specifying which model Claude Code uses:
-
-#### Option 1: Command Line / Session Model Selection
-
-Specify the model directly when starting Claude Code or during a session:
-
-```bash
-# Specify model at startup
-claude --model claude-3-5-sonnet-20241022
-
-# Or change model during a session
-/model claude-3-5-haiku-20241022
-```
-
-This method uses the exact model you specify.
-
-#### Option 2: Environment Variables
-
-Configure default models using environment variables:
-
-```bash
-# Tell Claude Code which models to use by default
-export ANTHROPIC_DEFAULT_SONNET_MODEL=claude-3-5-sonnet-20241022
-export ANTHROPIC_DEFAULT_HAIKU_MODEL=claude-3-5-haiku-20241022
-export ANTHROPIC_DEFAULT_OPUS_MODEL=claude-opus-3-5-20240229
-
-claude # Will use the models specified above
-```
-
-**Note:** Claude Code may cache the model from a previous session. If environment variables don't take effect, use Option 1 to explicitly set the model.
-
-**Important:** The `model_name` in your LiteLLM config must match what Claude Code requests (either from env vars or command line).
-
-### Using 1M Context Window
-
-Claude Code supports extended context (1 million tokens) using the `[1m]` suffix with Claude 4+ models:
-
-```bash
-# Use Sonnet 4.5 with 1M context (requires quotes for shell)
-claude --model 'claude-sonnet-4-5-20250929[1m]'
-
-# Inside a Claude Code session (no quotes needed)
-/model claude-sonnet-4-5-20250929[1m]
-```
-
-**Important:** When using `--model` with `[1m]` in the shell, you must use quotes to prevent the shell from interpreting the brackets.
-
-Alternatively, set as default with environment variables:
-
-```bash
-export ANTHROPIC_DEFAULT_SONNET_MODEL='claude-sonnet-4-5-20250929[1m]'
-claude
-```
-
-**How it works:**
-- Claude Code strips the `[1m]` suffix before sending to LiteLLM
-- Claude Code automatically adds the header `anthropic-beta: context-1m-2025-08-07`
-- Your LiteLLM config should **NOT** include `[1m]` in model names
-
-**Verify 1M context is active:**
-```bash
-/context
-# Should show: 21k/1000k tokens (2%)
-```
-
-**Pricing:** Models using 1M context have different pricing. Input tokens above 200k are charged at a higher rate.
-
-## Troubleshooting
-
-Common issues and solutions:
-
-**Claude Code not connecting:**
-- Verify your proxy is running: `curl http://0.0.0.0:4000/health`
-- Check that `ANTHROPIC_BASE_URL` is set correctly
-- Ensure your `ANTHROPIC_AUTH_TOKEN` matches your LiteLLM master key
-
-**Authentication errors:**
-- Verify your environment variables are set: `echo $LITELLM_MASTER_KEY`
-- Check that your API keys are valid and have sufficient credits
-- Ensure the `ANTHROPIC_AUTH_TOKEN` matches your LiteLLM master key
-
-**Model not found:**
-- Check what model Claude Code is requesting in LiteLLM logs
-- Ensure your `config.yaml` has a matching `model_name` entry
-- If using environment variables, verify they're set: `echo $ANTHROPIC_DEFAULT_SONNET_MODEL`
-
-**1M context not working (showing 200k instead of 1000k):**
-- Verify you're using the `[1m]` suffix: `/model your-model-name[1m]`
-- Check LiteLLM logs for the header `context-1m-2025-08-07` in the request
-- Ensure your model supports 1M context (only certain Claude models do)
-- Your LiteLLM config should **NOT** include `[1m]` in the `model_name`
-
-## Using Multiple Models and Providers
-
-You can configure LiteLLM to route to any supported provider. Here's an example with multiple providers:
-
-```yaml
-model_list:
- # OpenAI models
- - model_name: codex-mini
- litellm_params:
- model: openai/codex-mini
- api_key: os.environ/OPENAI_API_KEY
- api_base: https://api.openai.com/v1
-
- - model_name: o3-pro
- litellm_params:
- model: openai/o3-pro
- api_key: os.environ/OPENAI_API_KEY
- api_base: https://api.openai.com/v1
-
- - model_name: gpt-4o
- litellm_params:
- model: openai/gpt-4o
- api_key: os.environ/OPENAI_API_KEY
- api_base: https://api.openai.com/v1
-
- # Anthropic models
- - model_name: claude-3-5-sonnet-20241022
- litellm_params:
- model: anthropic/claude-3-5-sonnet-20241022
- api_key: os.environ/ANTHROPIC_API_KEY
-
- - model_name: claude-3-5-haiku-20241022
- litellm_params:
- model: anthropic/claude-3-5-haiku-20241022
- api_key: os.environ/ANTHROPIC_API_KEY
-
- # AWS Bedrock
- - model_name: claude-bedrock
- litellm_params:
- model: bedrock/us.anthropic.claude-haiku-4-5-20251001-v1:0
- aws_access_key_id: os.environ/AWS_ACCESS_KEY_ID
- aws_secret_access_key: os.environ/AWS_SECRET_ACCESS_KEY
- aws_region_name: us-east-1
-
-litellm_settings:
- master_key: os.environ/LITELLM_MASTER_KEY
-```
-
-**Note:** The `model_name` can be anything you choose. Claude Code will request whatever model you specify (via env vars or command line), and LiteLLM will route to the `model` configured in `litellm_params`.
-
-Switch between models seamlessly:
-
-```bash
-# Use environment variables to set defaults
-export ANTHROPIC_DEFAULT_SONNET_MODEL=claude-3-5-sonnet-20241022
-export ANTHROPIC_DEFAULT_HAIKU_MODEL=claude-3-5-haiku-20241022
-
-# Or specify directly
-claude --model claude-3-5-sonnet-20241022 # Complex reasoning
-claude --model claude-3-5-haiku-20241022 # Fast responses
-claude --model claude-bedrock # Bedrock deployment
-```
-
-## Default Models Used by Claude Code
-
-If you **don't** set environment variables, Claude Code uses these default model names:
-
-| Purpose | Default Model Name (v2.1.14) |
-|---------|------------------------------|
-| Main model | `claude-sonnet-4-5-20250929` |
-| Light tasks (subagents, summaries) | `claude-haiku-4-5-20251001` |
-| Planning mode | `claude-opus-4-5-20251101` |
-
-Your LiteLLM config should include these model names if you want Claude Code to work without setting environment variables:
-
-```yaml
-model_list:
- - model_name: claude-sonnet-4-5-20250929
- litellm_params:
- # Can be any provider - Anthropic, Bedrock, Vertex AI, etc.
- model: anthropic/claude-sonnet-4-5-20250929
- api_key: os.environ/ANTHROPIC_API_KEY
-
- - model_name: claude-haiku-4-5-20251001
- litellm_params:
- model: anthropic/claude-haiku-4-5-20251001
- api_key: os.environ/ANTHROPIC_API_KEY
-
- - model_name: claude-opus-4-5-20251101
- litellm_params:
- model: anthropic/claude-opus-4-5-20251101
- api_key: os.environ/ANTHROPIC_API_KEY
-```
-
-**Warning:** These default model names may change with new Claude Code versions. Check LiteLLM proxy logs for "model not found" errors to identify what Claude Code is requesting.
-
-## Additional Resources
-
-- [LiteLLM Documentation](https://docs.litellm.ai/)
-- [Claude Code Documentation](https://docs.anthropic.com/en/docs/claude-code/overview)
-- [Anthropic's LiteLLM Configuration Guide](https://docs.anthropic.com/en/docs/claude-code/llm-gateway#litellm-configuration)
diff --git a/cookbook/ai_coding_tool_guides/index.json b/cookbook/ai_coding_tool_guides/index.json
deleted file mode 100644
index 3e71670d623..00000000000
--- a/cookbook/ai_coding_tool_guides/index.json
+++ /dev/null
@@ -1,134 +0,0 @@
-[{
- "title": "Claude Code Quickstart",
- "description": "This is a quickstart guide to using Claude Code with LiteLLM.",
- "url": "https://docs.litellm.ai/docs/tutorials/claude_responses_api",
- "date": "2026-01-15",
- "version": "1.0.0",
- "tags": [
- "Claude Code",
- "LiteLLM"
- ]
-},
-{
- "title": "Claude Code with MCPs",
- "description": "This is a guide to using Claude Code with MCPs via LiteLLM Proxy.",
- "url": "https://docs.litellm.ai/docs/tutorials/claude_mcp",
- "date": "2026-01-15",
- "version": "1.0.0",
- "tags": [
- "Claude Code",
- "LiteLLM",
- "MCP"
- ]
-},
-{
- "title": "Claude Code with Non-Anthropic Models",
- "description": "This is a guide to using Claude Code with non-Anthropic models via LiteLLM Proxy.",
- "url": "https://docs.litellm.ai/docs/tutorials/claude_non_anthropic_models",
- "date": "2026-01-16",
- "version": "1.0.0",
- "tags": [
- "Claude Code",
- "LiteLLM",
- "OpenAI",
- "Gemini"
- ]
-},
-{
- "title": "Cursor Quickstart",
- "description": "This is a quickstart guide to using Cursor with LiteLLM.",
- "url": "https://docs.litellm.ai/docs/tutorials/cursor_integration",
- "date": "2026-01-16",
- "version": "1.0.0",
- "tags": [
- "Cursor",
- "LiteLLM",
- "Quickstart"
- ]
-},
-{
- "title": "Github Copilot Quickstart",
- "description": "This is a quickstart guide to using Github Copilot with LiteLLM.",
- "url": "https://docs.litellm.ai/docs/tutorials/github_copilot_integration",
- "date": "2026-01-16",
- "version": "1.0.0",
- "tags": [
- "Github Copilot",
- "LiteLLM",
- "Quickstart"
- ]
-},
-{
- "title": "LiteLLM Gemini CLI Quickstart",
- "description": "This is a quickstart guide to using LiteLLM Gemini CLI.",
- "url": "https://docs.litellm.ai/docs/tutorials/litellm_gemini_cli",
- "date": "2026-01-16",
- "version": "1.0.0",
- "tags": [
- "Gemini CLI",
- "Gemini",
- "LiteLLM",
- "Quickstart"
- ]
-},
-{
- "title": "OpenAI Codex CLI Quickstart",
- "description": "This is a quickstart guide to using OpenAI Codex CLI.",
- "url": "https://docs.litellm.ai/docs/tutorials/openai_codex",
- "date": "2026-01-16",
- "version": "1.0.0",
- "tags": [
- "OpenAI Codex CLI",
- "OpenAI",
- "LiteLLM",
- "Quickstart"
- ]
-},
-{
- "title": "OpenWebUI Quickstart",
- "description": "This is a quickstart guide to using OpenWebUI with LiteLLM.",
- "url": "https://docs.litellm.ai/docs/tutorials/openweb_ui",
- "date": "2026-01-16",
- "version": "1.0.0",
- "tags": [
- "OpenWebUI",
- "LiteLLM",
- "Quickstart"
- ]
-},
-{
- "title": "AI Coding Tool Usage Tracking",
- "description": "This is a guide to tracking usage for AI coding tools monitor the use of Claude Code , Google Antigravity, OpenAI Codex, Roo Code etc. through LiteLLM.",
- "url": "https://docs.litellm.ai/docs/tutorials/cost_tracking_coding",
- "date": "2026-01-17",
- "version": "1.0.0",
- "tags": [
- "Claude Code",
- "Gemini CLI",
- "OpenAI Codex",
- "LiteLLM"
- ]
-},
-{
- "title": "Use Web Search with Claude Code (across Bedrock/OpenAI/Gemini/etc.)",
- "description": "This is a guide for using Web Search with Claude Code via LiteLLM.",
- "url": "https://docs.litellm.ai/docs/tutorials/claude_code_websearch",
- "date": "2026-01-17",
- "version": "1.0.0",
- "tags": [
- "Claude Code",
- "LiteLLM",
- "Web Search"
- ]
-},
-{
- "title": "Track Claude Code Usage per user via Custom Headers",
- "description": "This is a guide for tracking claude code user usage by passing a customer ID header.",
- "url": "https://docs.litellm.ai/docs/tutorials/claude_code_customer_tracking",
- "date": "2026-01-17",
- "version": "1.0.0",
- "tags": [
- "Claude Code",
- "LiteLLM"
- ]
-}]
\ No newline at end of file
diff --git a/cookbook/anthropic_agent_sdk/README.md b/cookbook/anthropic_agent_sdk/README.md
deleted file mode 100644
index 4521aee310a..00000000000
--- a/cookbook/anthropic_agent_sdk/README.md
+++ /dev/null
@@ -1,150 +0,0 @@
-# Claude Agent SDK with LiteLLM Gateway
-
-A simple example showing how to use Claude's Agent SDK with LiteLLM as a proxy. This lets you use any LLM provider (OpenAI, Bedrock, Azure, etc.) through the Agent SDK.
-
-## Quick Start
-
-### 1. Install dependencies
-
-```bash
-pip install anthropic claude-agent-sdk litellm
-```
-
-### 2. Start LiteLLM proxy
-
-```bash
-# Simple start with Claude
-litellm --model claude-sonnet-4-20250514
-
-# Or with a config file
-litellm --config config.yaml
-```
-
-Generate a master key before starting the proxy:
-
-```bash
-export LITELLM_MASTER_KEY="sk-$(openssl rand -hex 32)"
-```
-
-### 3. Run the chat
-
-**Basic Agent (no MCP):**
-
-```bash
-python main.py
-```
-
-**Agent with MCP (DeepWiki2 for research):**
-
-```bash
-python agent_with_mcp.py
-```
-
-If MCP connection fails, you can disable it:
-
-```bash
-USE_MCP=false python agent_with_mcp.py
-```
-
-That's it! You can now chat with the agent in your terminal.
-
-### Chat Commands
-
-While chatting, you can use these commands:
-- `models` - List all available models (fetched from your LiteLLM proxy)
-- `model` - Switch to a different model
-- `clear` - Start a new conversation
-- `quit` or `exit` - End the chat
-
-The chat automatically fetches available models from your LiteLLM proxy's `/models` endpoint, so you'll always see what's currently configured.
-
-## Configuration
-
-Set these environment variables if needed:
-
-```bash
-export LITELLM_PROXY_URL="http://localhost:4000"
-export LITELLM_API_KEY="$LITELLM_MASTER_KEY"
-export LITELLM_MODEL="bedrock-claude-sonnet-4.5"
-```
-
-Or just use the defaults - it'll connect to `http://localhost:4000` by default.
-
-## Files
-
-- `main.py` - Basic interactive agent without MCP
-- `agent_with_mcp.py` - Agent with MCP server integration (DeepWiki2)
-- `common.py` - Shared utilities and functions
-- `config.example.yaml` - Example LiteLLM configuration
-- `requirements.txt` - Python dependencies
-
-## Example Config File
-
-If you want to use multiple models, create a `config.yaml` (see `config.example.yaml`):
-
-```yaml
-model_list:
- - model_name: bedrock-claude-sonnet-4
- litellm_params:
- model: "bedrock/us.anthropic.claude-sonnet-4-20250514-v1:0"
- aws_region_name: "us-east-1"
-
- - model_name: bedrock-claude-sonnet-4.5
- litellm_params:
- model: "bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0"
- aws_region_name: "us-east-1"
-```
-
-Then start LiteLLM with: `litellm --config config.yaml`
-
-## How It Works
-
-The key is pointing the Agent SDK to LiteLLM instead of directly to Anthropic:
-
-```python
-# Point to LiteLLM gateway (not Anthropic)
-os.environ["ANTHROPIC_BASE_URL"] = "http://localhost:4000"
-os.environ["ANTHROPIC_API_KEY"] = os.environ["LITELLM_API_KEY"]
-
-# Use any model configured in LiteLLM
-options = ClaudeAgentOptions(
- model="bedrock-claude-sonnet-4", # or gpt-4, or anything else
- system_prompt="You are a helpful assistant.",
- max_turns=50,
-)
-```
-
-Note: Don't add `/anthropic` to the base URL - LiteLLM handles the routing automatically.
-
-## Why Use This?
-
-- **Switch providers easily**: Use the same code with OpenAI, Bedrock, Azure, etc.
-- **Cost tracking**: LiteLLM tracks spending across all your agent conversations
-- **Rate limiting**: Set budgets and limits on your agent usage
-- **Load balancing**: Distribute requests across multiple API keys or regions
-- **Fallbacks**: Automatically retry with a different model if one fails
-
-## Troubleshooting
-
-**Connection errors?**
-- Make sure LiteLLM is running: `litellm --model your-model`
-- Check the URL is correct (default: `http://localhost:4000`)
-
-**Authentication errors?**
-- Verify your LiteLLM API key is correct
-- Make sure the model is configured in your LiteLLM setup
-
-**Model not found?**
-- Check the model name matches what's in your LiteLLM config
-- Run `litellm --model your-model` to test it works
-
-**Agent with MCP stuck or failing?**
-- The MCP server might not be available at `http://localhost:4000/mcp/deepwiki2`
-- Try disabling MCP: `USE_MCP=false python agent_with_mcp.py`
-- Or use the basic agent: `python main.py`
-
-## Learn More
-
-- [LiteLLM Docs](https://docs.litellm.ai/)
-- [Claude Agent SDK](https://github.com/anthropics/anthropic-agent-sdk)
-- [LiteLLM Proxy Guide](https://docs.litellm.ai/docs/proxy/quick_start)
diff --git a/cookbook/anthropic_agent_sdk/agent_with_mcp.py b/cookbook/anthropic_agent_sdk/agent_with_mcp.py
deleted file mode 100644
index 8a7513c786c..00000000000
--- a/cookbook/anthropic_agent_sdk/agent_with_mcp.py
+++ /dev/null
@@ -1,144 +0,0 @@
-"""
-Interactive Claude Agent SDK CLI with MCP Support
-
-This example demonstrates an interactive CLI chat with the Anthropic Agent SDK using LiteLLM as a proxy,
-with MCP (Model Context Protocol) server integration for enhanced capabilities.
-"""
-
-import asyncio
-import os
-from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions
-from common import (
- Config,
- fetch_available_models,
- setup_litellm_env,
- print_header,
- handle_model_list,
- handle_model_switch,
- stream_response,
-)
-
-
-async def interactive_chat_with_mcp():
- """
- Interactive CLI chat with the agent and MCP server
- """
- config = Config()
-
- # Configure Anthropic SDK to point to LiteLLM gateway
- litellm_base_url = setup_litellm_env(config)
-
- # Fetch available models from proxy
- available_models = await fetch_available_models(
- litellm_base_url, config.LITELLM_API_KEY
- )
-
- current_model = config.LITELLM_MODEL
-
- # MCP server configuration
- mcp_server_url = f"{litellm_base_url}/mcp/deepwiki2"
- use_mcp = os.getenv("USE_MCP", "true").lower() == "true"
-
- if not use_mcp:
- print("⚠️ MCP disabled via USE_MCP=false")
-
- print_header(litellm_base_url, current_model, has_mcp=use_mcp)
-
- while True:
- # Configure agent options
- if use_mcp:
- try:
- # Try with MCP server (HTTP transport)
- # Using McpHttpServerConfig format from Agent SDK
- options = ClaudeAgentOptions(
- system_prompt="You are a helpful AI assistant with access to DeepWiki for research. Be concise, accurate, and friendly.",
- model=current_model,
- max_turns=50,
- mcp_servers={
- "deepwiki2": {
- "type": "http",
- "url": mcp_server_url,
- "headers": {
- "Authorization": f"Bearer {config.LITELLM_API_KEY}"
- },
- }
- },
- )
- except Exception as e:
- print(f"⚠️ Warning: Could not configure MCP server: {e}")
- print("Continuing without MCP...\n")
- use_mcp = False
- options = ClaudeAgentOptions(
- system_prompt="You are a helpful AI assistant. Be concise, accurate, and friendly.",
- model=current_model,
- max_turns=50,
- )
- else:
- # Without MCP
- options = ClaudeAgentOptions(
- system_prompt="You are a helpful AI assistant. Be concise, accurate, and friendly.",
- model=current_model,
- max_turns=50,
- )
-
- # Create agent client
- try:
- async with ClaudeSDKClient(options=options) as client:
- conversation_active = True
-
- while conversation_active:
- # Get user input
- try:
- user_input = input("\n👤 You: ").strip()
- except (EOFError, KeyboardInterrupt):
- print("\n\n👋 Goodbye!")
- return
-
- # Handle commands
- if user_input.lower() in ["quit", "exit"]:
- print("\n👋 Goodbye!")
- return
-
- if user_input.lower() == "clear":
- print("\n🔄 Starting new conversation...\n")
- conversation_active = False
- continue
-
- if user_input.lower() == "models":
- handle_model_list(available_models, current_model)
- continue
-
- if user_input.lower() == "model":
- new_model, should_restart = handle_model_switch(
- available_models, current_model
- )
- if should_restart:
- current_model = new_model
- conversation_active = False
- continue
-
- if not user_input:
- continue
-
- # Stream response from agent
- await stream_response(client, user_input)
-
- except Exception as e:
- print(f"\n❌ Error creating agent client: {e}")
- print("This might be an MCP configuration issue. Try running without MCP:")
- print(" USE_MCP=false python agent_with_mcp.py")
- print("\nOr use the basic agent:")
- print(" python main.py")
- return
-
-
-def main():
- """Run interactive chat with MCP"""
- try:
- asyncio.run(interactive_chat_with_mcp())
- except KeyboardInterrupt:
- print("\n\n👋 Goodbye!")
-
-
-if __name__ == "__main__":
- main()
diff --git a/cookbook/anthropic_agent_sdk/common.py b/cookbook/anthropic_agent_sdk/common.py
deleted file mode 100644
index 505b5ecd91c..00000000000
--- a/cookbook/anthropic_agent_sdk/common.py
+++ /dev/null
@@ -1,164 +0,0 @@
-"""
-Common utilities for Claude Agent SDK examples
-"""
-
-import os
-import httpx
-
-
-class Config:
- """Configuration for LiteLLM Gateway connection"""
-
- # LiteLLM proxy URL (default to local instance)
- LITELLM_PROXY_URL = os.getenv("LITELLM_PROXY_URL", "http://localhost:4000")
-
- # LiteLLM API key (master key or virtual key)
- LITELLM_API_KEY = os.environ["LITELLM_API_KEY"]
-
- # Model name as configured in LiteLLM (e.g., "bedrock-claude-sonnet-4", "gpt-4", etc.)
- LITELLM_MODEL = os.getenv("LITELLM_MODEL", "bedrock-claude-sonnet-4.5")
-
-
-async def fetch_available_models(base_url: str, api_key: str) -> list[str]:
- """
- Fetch available models from LiteLLM proxy /models endpoint
- """
- try:
- async with httpx.AsyncClient() as client:
- response = await client.get(
- f"{base_url}/models",
- headers={"Authorization": f"Bearer {api_key}"},
- timeout=10.0,
- )
- response.raise_for_status()
- data = response.json()
- return [model["id"] for model in data.get("data", [])]
- except Exception as e:
- print(f"⚠️ Warning: Could not fetch models from proxy: {e}")
- print("Using default model list...")
- # Fallback to default models
- return [
- "bedrock-claude-sonnet-3.5",
- "bedrock-claude-sonnet-4",
- "bedrock-claude-sonnet-4.5",
- "bedrock-claude-opus-4.5",
- "bedrock-nova-premier",
- ]
-
-
-def setup_litellm_env(config: Config):
- """
- Configure environment variables to point Agent SDK to LiteLLM
- """
- litellm_base_url = config.LITELLM_PROXY_URL.rstrip("/")
- os.environ["ANTHROPIC_BASE_URL"] = litellm_base_url
- os.environ["ANTHROPIC_API_KEY"] = config.LITELLM_API_KEY
- return litellm_base_url
-
-
-def print_header(base_url: str, current_model: str, has_mcp: bool = False):
- """
- Print the chat header
- """
- mcp_indicator = " + MCP" if has_mcp else ""
- print("=" * 70)
- print(f"🤖 Claude Agent SDK with LiteLLM Gateway{mcp_indicator} - Interactive Chat")
- print("=" * 70)
- print(f"🚀 Connected to: {base_url}")
- print(f"📦 Current model: {current_model}")
- if has_mcp:
- print("🔌 MCP: deepwiki2 enabled")
- print("\nType your messages below. Commands:")
- print(" - 'quit' or 'exit' to end the conversation")
- print(" - 'clear' to start a new conversation")
- print(" - 'model' to switch models")
- print(" - 'models' to list available models")
- print("=" * 70)
- print()
-
-
-def handle_model_list(available_models: list[str], current_model: str):
- """
- Display available models
- """
- print("\n📋 Available models:")
- for i, model in enumerate(available_models, 1):
- marker = "✓" if model == current_model else " "
- print(f" {marker} {i}. {model}")
-
-
-def handle_model_switch(
- available_models: list[str], current_model: str
-) -> tuple[str, bool]:
- """
- Handle model switching
-
- Returns:
- tuple: (new_model, should_restart_conversation)
- """
- print("\n📋 Select a model:")
- for i, model in enumerate(available_models, 1):
- marker = "✓" if model == current_model else " "
- print(f" {marker} {i}. {model}")
-
- try:
- choice = input("\nEnter number (or press Enter to cancel): ").strip()
- if choice:
- idx = int(choice) - 1
- if 0 <= idx < len(available_models):
- new_model = available_models[idx]
- print(f"\n✅ Switched to: {new_model}")
- print("🔄 Starting new conversation with new model...\n")
- return new_model, True
- else:
- print("❌ Invalid choice")
- except (ValueError, IndexError):
- print("❌ Invalid input")
-
- return current_model, False
-
-
-async def stream_response(client, user_input: str):
- """
- Stream response from the agent
- """
- print("\n🤖 Assistant: ", end="", flush=True)
-
- try:
- await client.query(user_input)
-
- # Show loading indicator
- print("⏳ thinking...", end="", flush=True)
-
- # Stream the response
- first_chunk = True
- async for msg in client.receive_response():
- # Clear loading indicator on first message
- if first_chunk:
- print("\r🤖 Assistant: ", end="", flush=True)
- first_chunk = False
-
- # Handle different message types
- if hasattr(msg, "type"):
- if msg.type == "content_block_delta":
- # Streaming text delta
- if hasattr(msg, "delta") and hasattr(msg.delta, "text"):
- print(msg.delta.text, end="", flush=True)
- elif msg.type == "content_block_start":
- # Start of content block
- if hasattr(msg, "content_block") and hasattr(
- msg.content_block, "text"
- ):
- print(msg.content_block.text, end="", flush=True)
-
- # Fallback to original content handling
- if hasattr(msg, "content"):
- for content_block in msg.content:
- if hasattr(content_block, "text"):
- print(content_block.text, end="", flush=True)
-
- print() # New line after response
-
- except Exception as e:
- print(f"\r\n❌ Error: {e}")
- print("Please check your LiteLLM gateway is running and configured correctly.")
diff --git a/cookbook/anthropic_agent_sdk/config.example.yaml b/cookbook/anthropic_agent_sdk/config.example.yaml
deleted file mode 100644
index eb1984fc4ea..00000000000
--- a/cookbook/anthropic_agent_sdk/config.example.yaml
+++ /dev/null
@@ -1,25 +0,0 @@
-model_list:
- - model_name: bedrock-claude-sonnet-3.5
- litellm_params:
- model: "bedrock/us.anthropic.claude-3-5-sonnet-20240620-v1:0"
- aws_region_name: "us-east-1"
-
- - model_name: bedrock-claude-sonnet-4
- litellm_params:
- model: "bedrock/us.anthropic.claude-sonnet-4-20250514-v1:0"
- aws_region_name: "us-east-1"
-
- - model_name: bedrock-claude-sonnet-4.5
- litellm_params:
- model: "bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0"
- aws_region_name: "us-east-1"
-
- - model_name: bedrock-claude-opus-4.5
- litellm_params:
- model: "bedrock/us.anthropic.claude-opus-4-5-20251101-v1:0"
- aws_region_name: "us-east-1"
-
- - model_name: bedrock-nova-premier
- litellm_params:
- model: "bedrock/amazon.nova-premier-v1:0"
- aws_region_name: "us-east-1"
diff --git a/cookbook/anthropic_agent_sdk/main.py b/cookbook/anthropic_agent_sdk/main.py
deleted file mode 100644
index 506c6fa07b0..00000000000
--- a/cookbook/anthropic_agent_sdk/main.py
+++ /dev/null
@@ -1,99 +0,0 @@
-"""
-Simple Interactive Claude Agent SDK CLI using LiteLLM Gateway
-
-This example demonstrates an interactive CLI chat with the Anthropic Agent SDK using LiteLLM as a proxy.
-LiteLLM acts as a unified interface, allowing you to use any LLM provider (OpenAI, Azure, Bedrock, etc.)
-through the Claude Agent SDK by pointing it to the LiteLLM gateway.
-"""
-
-import asyncio
-from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions
-from common import (
- Config,
- fetch_available_models,
- setup_litellm_env,
- print_header,
- handle_model_list,
- handle_model_switch,
- stream_response,
-)
-
-
-async def interactive_chat():
- """
- Interactive CLI chat with the agent
- """
- config = Config()
-
- # Configure Anthropic SDK to point to LiteLLM gateway
- litellm_base_url = setup_litellm_env(config)
-
- # Fetch available models from proxy
- available_models = await fetch_available_models(
- litellm_base_url, config.LITELLM_API_KEY
- )
-
- current_model = config.LITELLM_MODEL
-
- print_header(litellm_base_url, current_model)
-
- while True:
- # Configure agent options for each conversation
- options = ClaudeAgentOptions(
- system_prompt="You are a helpful AI assistant. Be concise, accurate, and friendly.",
- model=current_model,
- max_turns=50,
- )
-
- # Create agent client
- async with ClaudeSDKClient(options=options) as client:
- conversation_active = True
-
- while conversation_active:
- # Get user input
- try:
- user_input = input("\n👤 You: ").strip()
- except (EOFError, KeyboardInterrupt):
- print("\n\n👋 Goodbye!")
- return
-
- # Handle commands
- if user_input.lower() in ["quit", "exit"]:
- print("\n👋 Goodbye!")
- return
-
- if user_input.lower() == "clear":
- print("\n🔄 Starting new conversation...\n")
- conversation_active = False
- continue
-
- if user_input.lower() == "models":
- handle_model_list(available_models, current_model)
- continue
-
- if user_input.lower() == "model":
- new_model, should_restart = handle_model_switch(
- available_models, current_model
- )
- if should_restart:
- current_model = new_model
- conversation_active = False
- continue
-
- if not user_input:
- continue
-
- # Stream response from agent
- await stream_response(client, user_input)
-
-
-def main():
- """Run interactive chat"""
- try:
- asyncio.run(interactive_chat())
- except KeyboardInterrupt:
- print("\n\n👋 Goodbye!")
-
-
-if __name__ == "__main__":
- main()
diff --git a/cookbook/anthropic_agent_sdk/requirements.txt b/cookbook/anthropic_agent_sdk/requirements.txt
deleted file mode 100644
index 1e810bb7d99..00000000000
--- a/cookbook/anthropic_agent_sdk/requirements.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-claude-agent-sdk
-httpx>=0.27.0
diff --git a/cookbook/benchmark/benchmark.py b/cookbook/benchmark/benchmark.py
deleted file mode 100644
index b38d185a166..00000000000
--- a/cookbook/benchmark/benchmark.py
+++ /dev/null
@@ -1,90 +0,0 @@
-from litellm import completion, completion_cost
-import time
-import click
-from tqdm import tqdm
-from tabulate import tabulate
-from termcolor import colored
-import os
-
-
-# Define the list of models to benchmark
-# select any LLM listed here: https://docs.litellm.ai/docs/providers
-models = ["gpt-3.5-turbo", "claude-2"]
-
-# Enter LLM API keys
-# https://docs.litellm.ai/docs/providers
-os.environ["OPENAI_API_KEY"] = ""
-os.environ["ANTHROPIC_API_KEY"] = ""
-
-# List of questions to benchmark (replace with your questions)
-questions = ["When will BerriAI IPO?", "When will LiteLLM hit $100M ARR?"]
-
-# Enter your system prompt here
-system_prompt = """
-You are LiteLLMs helpful assistant
-"""
-
-
-@click.command()
-@click.option(
- "--system-prompt",
- default="You are a helpful assistant that can answer questions.",
- help="System prompt for the conversation.",
-)
-def main(system_prompt):
- for question in questions:
- data = [] # Data for the current question
-
- with tqdm(total=len(models)) as pbar:
- for model in models:
- colored_description = colored(
- f"Running question: {question} for model: {model}", "green"
- )
- pbar.set_description(colored_description)
- start_time = time.time()
-
- response = completion(
- model=model,
- max_tokens=500,
- messages=[
- {"role": "system", "content": system_prompt},
- {"role": "user", "content": question},
- ],
- )
-
- end = time.time()
- total_time = end - start_time
- cost = completion_cost(completion_response=response)
- raw_response = response["choices"][0]["message"]["content"]
-
- data.append(
- {
- "Model": colored(model, "light_blue"),
- "Response": raw_response, # Colorize the response
- "ResponseTime": colored(f"{total_time:.2f} seconds", "red"),
- "Cost": colored(f"${cost:.6f}", "green"), # Colorize the cost
- }
- )
-
- pbar.update(1)
-
- # Separate headers from the data
- headers = ["Model", "Response", "Response Time (seconds)", "Cost ($)"]
- colwidths = [15, 80, 15, 10]
-
- # Create a nicely formatted table for the current question
- table = tabulate(
- [list(d.values()) for d in data],
- headers,
- tablefmt="grid",
- maxcolwidths=colwidths,
- )
-
- # Print the table for the current question
- colored_question = colored(question, "green")
- click.echo(f"\nBenchmark Results for '{colored_question}':")
- click.echo(table) # Display the formatted table
-
-
-if __name__ == "__main__":
- main()
diff --git a/cookbook/benchmark/eval_suites_mlflow_autoevals/auto_evals.py b/cookbook/benchmark/eval_suites_mlflow_autoevals/auto_evals.py
deleted file mode 100644
index daa38dda585..00000000000
--- a/cookbook/benchmark/eval_suites_mlflow_autoevals/auto_evals.py
+++ /dev/null
@@ -1,30 +0,0 @@
-from dotenv import load_dotenv
-
-load_dotenv()
-
-import litellm
-
-from autoevals.llm import *
-
-###################
-
-# litellm completion call
-question = "which country has the highest population"
-response = litellm.completion(
- model="gpt-3.5-turbo",
- messages=[{"role": "user", "content": question}],
-)
-print(response)
-# use the auto eval Factuality() evaluator
-
-print("calling evaluator")
-evaluator = Factuality()
-result = evaluator(
- output=response.choices[0]["message"][
- "content"
- ], # response from litellm.completion()
- expected="India", # expected output
- input=question, # question passed to litellm.completion
-)
-
-print(result)
diff --git a/cookbook/benchmark/readme.md b/cookbook/benchmark/readme.md
deleted file mode 100644
index afa59aa91ee..00000000000
--- a/cookbook/benchmark/readme.md
+++ /dev/null
@@ -1,181 +0,0 @@
-
- LLM-Bench
-
-
-
Benchmark LLMs response, cost and response time
-
LLM vs Cost per input + output token ($)
-
-
-
- Bar Graph Excel Sheet here
-
-
-| Model | Provider | Cost per input + output token ($)|
-| --- | --- | --- |
-| openrouter/mistralai/mistral-7b-instruct | openrouter | 0.0 |
-| ollama/llama2 | ollama | 0.0 |
-| ollama/llama2:13b | ollama | 0.0 |
-| ollama/llama2:70b | ollama | 0.0 |
-| ollama/llama2-uncensored | ollama | 0.0 |
-| ollama/mistral | ollama | 0.0 |
-| ollama/codellama | ollama | 0.0 |
-| ollama/orca-mini | ollama | 0.0 |
-| ollama/vicuna | ollama | 0.0 |
-| perplexity/codellama-34b-instruct | perplexity | 0.0 |
-| perplexity/llama-2-13b-chat | perplexity | 0.0 |
-| perplexity/llama-2-70b-chat | perplexity | 0.0 |
-| perplexity/mistral-7b-instruct | perplexity | 0.0 |
-| perplexity/replit-code-v1.5-3b | perplexity | 0.0 |
-| text-bison | vertex_ai-text-models | 0.00000025 |
-| text-bison@001 | vertex_ai-text-models | 0.00000025 |
-| chat-bison | vertex_ai-chat-models | 0.00000025 |
-| chat-bison@001 | vertex_ai-chat-models | 0.00000025 |
-| chat-bison-32k | vertex_ai-chat-models | 0.00000025 |
-| code-bison | vertex_ai-code-text-models | 0.00000025 |
-| code-bison@001 | vertex_ai-code-text-models | 0.00000025 |
-| code-gecko@001 | vertex_ai-chat-models | 0.00000025 |
-| code-gecko@latest | vertex_ai-chat-models | 0.00000025 |
-| codechat-bison | vertex_ai-code-chat-models | 0.00000025 |
-| codechat-bison@001 | vertex_ai-code-chat-models | 0.00000025 |
-| codechat-bison-32k | vertex_ai-code-chat-models | 0.00000025 |
-| palm/chat-bison | palm | 0.00000025 |
-| palm/chat-bison-001 | palm | 0.00000025 |
-| palm/text-bison | palm | 0.00000025 |
-| palm/text-bison-001 | palm | 0.00000025 |
-| palm/text-bison-safety-off | palm | 0.00000025 |
-| palm/text-bison-safety-recitation-off | palm | 0.00000025 |
-| anyscale/meta-llama/Llama-2-7b-chat-hf | anyscale | 0.0000003 |
-| anyscale/mistralai/Mistral-7B-Instruct-v0.1 | anyscale | 0.0000003 |
-| openrouter/meta-llama/llama-2-13b-chat | openrouter | 0.0000004 |
-| openrouter/nousresearch/nous-hermes-llama2-13b | openrouter | 0.0000004 |
-| deepinfra/meta-llama/Llama-2-7b-chat-hf | deepinfra | 0.0000004 |
-| deepinfra/mistralai/Mistral-7B-Instruct-v0.1 | deepinfra | 0.0000004 |
-| anyscale/meta-llama/Llama-2-13b-chat-hf | anyscale | 0.0000005 |
-| amazon.titan-text-lite-v1 | bedrock | 0.0000007 |
-| deepinfra/meta-llama/Llama-2-13b-chat-hf | deepinfra | 0.0000007 |
-| text-babbage-001 | text-completion-openai | 0.0000008 |
-| text-ada-001 | text-completion-openai | 0.0000008 |
-| babbage-002 | text-completion-openai | 0.0000008 |
-| openrouter/google/palm-2-chat-bison | openrouter | 0.000001 |
-| openrouter/google/palm-2-codechat-bison | openrouter | 0.000001 |
-| openrouter/meta-llama/codellama-34b-instruct | openrouter | 0.000001 |
-| deepinfra/codellama/CodeLlama-34b-Instruct-hf | deepinfra | 0.0000012 |
-| deepinfra/meta-llama/Llama-2-70b-chat-hf | deepinfra | 0.0000016499999999999999 |
-| deepinfra/jondurbin/airoboros-l2-70b-gpt4-1.4.1 | deepinfra | 0.0000016499999999999999 |
-| anyscale/meta-llama/Llama-2-70b-chat-hf | anyscale | 0.000002 |
-| anyscale/codellama/CodeLlama-34b-Instruct-hf | anyscale | 0.000002 |
-| gpt-3.5-turbo-1106 | openai | 0.000003 |
-| openrouter/meta-llama/llama-2-70b-chat | openrouter | 0.000003 |
-| amazon.titan-text-express-v1 | bedrock | 0.000003 |
-| gpt-3.5-turbo | openai | 0.0000035 |
-| gpt-3.5-turbo-0301 | openai | 0.0000035 |
-| gpt-3.5-turbo-0613 | openai | 0.0000035 |
-| gpt-3.5-turbo-instruct | text-completion-openai | 0.0000035 |
-| openrouter/openai/gpt-3.5-turbo | openrouter | 0.0000035 |
-| cohere.command-text-v14 | bedrock | 0.0000035 |
-| gpt-3.5-turbo-0613 | openai | 0.0000035 |
-| claude-instant-1 | anthropic | 0.00000714 |
-| claude-instant-1.2 | anthropic | 0.00000714 |
-| openrouter/anthropic/claude-instant-v1 | openrouter | 0.00000714 |
-| anthropic.claude-instant-v1 | bedrock | 0.00000714 |
-| openrouter/mancer/weaver | openrouter | 0.00001125 |
-| j2-mid | ai21 | 0.00002 |
-| ai21.j2-mid-v1 | bedrock | 0.000025 |
-| openrouter/jondurbin/airoboros-l2-70b-2.1 | openrouter | 0.00002775 |
-| command-nightly | cohere | 0.00003 |
-| command | cohere | 0.00003 |
-| command-light | cohere | 0.00003 |
-| command-medium-beta | cohere | 0.00003 |
-| command-xlarge-beta | cohere | 0.00003 |
-| command-r-plus| cohere | 0.000018 |
-| j2-ultra | ai21 | 0.00003 |
-| ai21.j2-ultra-v1 | bedrock | 0.0000376 |
-| gpt-4-1106-preview | openai | 0.00004 |
-| gpt-4-vision-preview | openai | 0.00004 |
-| claude-2 | anthropic | 0.0000437 |
-| openrouter/anthropic/claude-2 | openrouter | 0.0000437 |
-| anthropic.claude-v1 | bedrock | 0.0000437 |
-| anthropic.claude-v2 | bedrock | 0.0000437 |
-| gpt-4 | openai | 0.00009 |
-| gpt-4-0314 | openai | 0.00009 |
-| gpt-4-0613 | openai | 0.00009 |
-| openrouter/openai/gpt-4 | openrouter | 0.00009 |
-| gpt-4-32k | openai | 0.00018 |
-| gpt-4-32k-0314 | openai | 0.00018 |
-| gpt-4-32k-0613 | openai | 0.00018 |
-
-
-
-## Setup:
-```
-git clone https://github.com/BerriAI/litellm
-```
-cd to `benchmark` dir
-```
-cd litellm/cookbook/benchmark
-```
-
-### Install Dependencies
-```
-pip install litellm click tqdm tabulate termcolor
-```
-
-### Configuration
-In `benchmark/benchmark.py` select your LLMs, LLM API Key and questions
-
-Supported LLMs: https://docs.litellm.ai/docs/providers
-
-```python
-# Define the list of models to benchmark
-models = ['gpt-3.5-turbo', 'togethercomputer/llama-2-70b-chat', 'claude-2']
-
-# Enter LLM API keys
-os.environ['OPENAI_API_KEY'] = ""
-os.environ['ANTHROPIC_API_KEY'] = ""
-os.environ['TOGETHERAI_API_KEY'] = ""
-
-# List of questions to benchmark (replace with your questions)
-questions = [
- "When will BerriAI IPO?",
- "When will LiteLLM hit $100M ARR?"
-]
-
-```
-
-## Run LLM-Bench
-```
-python3 benchmark.py
-```
-
-## Expected Output
-```
-Running question: When will BerriAI IPO? for model: claude-2: 100%|████████████████████████████████████████████████████████████████████████████████████| 3/3 [00:13<00:00, 4.41s/it]
-
-Benchmark Results for 'When will BerriAI IPO?':
-+-----------------+----------------------------------------------------------------------------------+---------------------------+------------+
-| Model | Response | Response Time (seconds) | Cost ($) |
-+=================+==================================================================================+===========================+============+
-| gpt-3.5-turbo | As an AI language model, I cannot provide up-to-date information or predict | 1.55 seconds | $0.000122 |
-| | future events. It is best to consult a reliable financial source or contact | | |
-| | BerriAI directly for information regarding their IPO plans. | | |
-+-----------------+----------------------------------------------------------------------------------+---------------------------+------------+
-| togethercompute | I'm not able to provide information about future IPO plans or dates for BerriAI | 8.52 seconds | $0.000531 |
-| r/llama-2-70b-c | or any other company. IPO (Initial Public Offering) plans and timelines are | | |
-| hat | typically kept private by companies until they are ready to make a public | | |
-| | announcement. It's important to note that IPO plans can change and are subject | | |
-| | to various factors, such as market conditions, financial performance, and | | |
-| | regulatory approvals. Therefore, it's difficult to predict with certainty when | | |
-| | BerriAI or any other company will go public. If you're interested in staying | | |
-| | up-to-date with BerriAI's latest news and developments, you may want to follow | | |
-| | their official social media accounts, subscribe to their newsletter, or visit | | |
-| | their website periodically for updates. | | |
-+-----------------+----------------------------------------------------------------------------------+---------------------------+------------+
-| claude-2 | I do not have any information about when or if BerriAI will have an initial | 3.17 seconds | $0.002084 |
-| | public offering (IPO). As an AI assistant created by Anthropic to be helpful, | | |
-| | harmless, and honest, I do not have insider knowledge about Anthropic's business | | |
-| | plans or strategies. | | |
-+-----------------+----------------------------------------------------------------------------------+---------------------------+------------+
-```
-
-## Support
-**🤝 Schedule a 1-on-1 Session:** Book a [1-on-1 session](https://enterprise.litellm.ai/demo) with Krrish and Ishaan, the founders, to discuss any issues, provide feedback, or explore how we can improve LiteLLM for you.
diff --git a/cookbook/codellama-server/README.MD b/cookbook/codellama-server/README.MD
deleted file mode 100644
index 82a7e62f40a..00000000000
--- a/cookbook/codellama-server/README.MD
+++ /dev/null
@@ -1,153 +0,0 @@
-# CodeLlama Server: Streaming, Caching, Model Fallbacks (OpenAI + Anthropic), Prompt-tracking
-
-Works with: Anthropic, Huggingface, Cohere, TogetherAI, Azure, OpenAI, etc.
-
-[](https://pypi.org/project/litellm/)
-[](https://pypi.org/project/litellm/0.1.1/)
-
-
-[](https://railway.app/template/HuDPw-?referralCode=jch2ME)
-
-**LIVE DEMO** - https://litellm.ai/playground
-
-## What does CodeLlama Server do
-
-- Uses Together AI's CodeLlama to answer coding questions, with GPT-4 + Claude-2 as backups (you can easily switch this to any model from Huggingface, Replicate, Cohere, AI21, Azure, OpenAI, etc.)
-- Sets default system prompt for guardrails `system_prompt = "Only respond to questions about code. Say 'I don't know' to anything outside of that."`
-- Integrates with Promptlayer for model + prompt tracking
-- Example output
-
-
-
-- **Consistent Input/Output** Format
- - Call all models using the OpenAI format - `completion(model, messages)`
- - Text responses will always be available at `['choices'][0]['message']['content']`
- - Stream responses will always be available at `['choices'][0]['delta']['content']`
-- **Error Handling** Using Model Fallbacks (if `CodeLlama` fails, try `GPT-4`) with cooldowns, and retries
-- **Prompt Logging** - Log successful completions to promptlayer for testing + iterating on your prompts in production! (Learn more: https://litellm.readthedocs.io/en/latest/advanced/
-
- **Example: Logs sent to PromptLayer**
-
-
-
-
-- **Token Usage & Spend** - Track Input + Completion tokens used + Spend/model - https://docs.litellm.ai/docs/token_usage
-- **Caching** - Provides in-memory cache + GPT-Cache integration for more advanced usage - https://docs.litellm.ai/docs/caching/gpt_cache
-
-- **Streaming & Async Support** - Return generators to stream text responses - TEST IT 👉 https://litellm.ai/
-
-## API Endpoints
-
-### `/chat/completions` (POST)
-
-This endpoint is used to generate chat completions for 50+ support LLM API Models. Use llama2, GPT-4, Claude2 etc
-
-#### Input
-
-This API endpoint accepts all inputs in raw JSON and expects the following inputs
-
-- `prompt` (string, required): The user's coding related question
-- Additional Optional parameters: `temperature`, `functions`, `function_call`, `top_p`, `n`, `stream`. See the full list of supported inputs here: https://litellm.readthedocs.io/en/latest/input/
-
-#### Example JSON body
-
-For claude-2
-
-```json
-{
- "prompt": "write me a function to print hello world"
-}
-```
-
-### Making an API request to the Code-Gen Server
-
-```python
-import requests
-import json
-
-url = "localhost:4000/chat/completions"
-
-payload = json.dumps({
- "prompt": "write me a function to print hello world"
-})
-headers = {
- 'Content-Type': 'application/json'
-}
-
-response = requests.request("POST", url, headers=headers, data=payload)
-
-print(response.text)
-
-```
-
-### Output [Response Format]
-
-Responses from the server are given in the following format.
-All responses from the server are returned in the following format (for all LLM models). More info on output here: https://litellm.readthedocs.io/en/latest/output/
-
-```json
-{
- "choices": [
- {
- "finish_reason": "stop",
- "index": 0,
- "message": {
- "content": ".\n\n```\ndef print_hello_world():\n print(\"hello world\")\n",
- "role": "assistant"
- }
- }
- ],
- "created": 1693279694.6474009,
- "model": "togethercomputer/CodeLlama-34b-Instruct",
- "usage": {
- "completion_tokens": 14,
- "prompt_tokens": 28,
- "total_tokens": 42
- }
-}
-```
-
-## Installation & Usage
-
-### Running Locally
-
-1. Clone liteLLM repository to your local machine:
- ```
- git clone https://github.com/BerriAI/litellm-CodeLlama-server
- ```
-2. Install the required dependencies using pip
- ```
- pip install requirements.txt
- ```
-3. Set your LLM API keys
- ```
- os.environ['OPENAI_API_KEY]` = "YOUR_API_KEY"
- or
- set OPENAI_API_KEY in your .env file
- ```
-4. Run the server:
- ```
- python main.py
- ```
-
-## Deploying
-
-1. Quick Start: Deploy on Railway
-
- [](https://railway.app/template/HuDPw-?referralCode=jch2ME)
-
-2. `GCP`, `AWS`, `Azure`
- This project includes a `Dockerfile` allowing you to build and deploy a Docker Project on your providers
-
-# Support / Talk with founders
-
-- [Our calendar 👋](https://calendly.com/d/4mp-gd3-k5k/berriai-1-1-onboarding-litellm-hosted-version)
-- [Community Discord 💭](https://discord.gg/wuPM9dRgDw)
-- Our emails ✉️ ishaan@berri.ai / krrish@berri.ai
-
-## Roadmap
-
-- [ ] Implement user-based rate-limiting
-- [ ] Spending controls per project - expose key creation endpoint
-- [ ] Need to store a keys db -> mapping created keys to their alias (i.e. project name)
-- [ ] Easily add new models as backups / as the entry-point (add this to the available model list)
diff --git a/cookbook/codellama-server/imgs/code-output.png b/cookbook/codellama-server/imgs/code-output.png
deleted file mode 100644
index 67e298bd3be..00000000000
Binary files a/cookbook/codellama-server/imgs/code-output.png and /dev/null differ
diff --git a/cookbook/codellama-server/imgs/promptlayer_logging.png b/cookbook/codellama-server/imgs/promptlayer_logging.png
deleted file mode 100644
index 26b046ac448..00000000000
Binary files a/cookbook/codellama-server/imgs/promptlayer_logging.png and /dev/null differ
diff --git a/cookbook/codellama-server/main.py b/cookbook/codellama-server/main.py
deleted file mode 100644
index d05d6752300..00000000000
--- a/cookbook/codellama-server/main.py
+++ /dev/null
@@ -1,102 +0,0 @@
-import traceback
-from flask import Flask, request, Response
-from flask_cors import CORS
-import litellm
-from util import handle_error
-from litellm import completion
-import os
-import dotenv
-import time
-import json
-
-dotenv.load_dotenv()
-
-# TODO: set your keys in .env or here:
-# os.environ["OPENAI_API_KEY"] = "" # set your openai key here
-# os.environ["ANTHROPIC_API_KEY"] = "" # set your anthropic key here
-# os.environ["TOGETHER_AI_API_KEY"] = "" # set your together ai key here
-# see supported models / keys here: https://litellm.readthedocs.io/en/latest/supported/
-######### ENVIRONMENT VARIABLES ##########
-verbose = True
-
-# litellm.caching_with_models = True # CACHING: caching_with_models Keys in the cache are messages + model. - to learn more: https://docs.litellm.ai/docs/caching/
-######### PROMPT LOGGING ##########
-os.environ["PROMPTLAYER_API_KEY"] = (
- "" # set your promptlayer key here - https://promptlayer.com/
-)
-
-# set callbacks
-litellm.success_callback = ["promptlayer"]
-############ HELPER FUNCTIONS ###################################
-
-
-def print_verbose(print_statement):
- if verbose:
- print(print_statement)
-
-
-app = Flask(__name__)
-CORS(app)
-
-
-@app.route("/")
-def index():
- return "received!", 200
-
-
-def data_generator(response):
- for chunk in response:
- yield f"data: {json.dumps(chunk)}\n\n"
-
-
-@app.route("/chat/completions", methods=["POST"])
-def api_completion():
- data = request.json
- start_time = time.time()
- if data.get("stream") == "True":
- data["stream"] = True # convert to boolean
- try:
- if "prompt" not in data:
- raise ValueError("data needs to have prompt")
- data["model"] = (
- "togethercomputer/CodeLlama-34b-Instruct" # by default use Together AI's CodeLlama model - https://api.together.xyz/playground/chat?model=togethercomputer%2FCodeLlama-34b-Instruct
- )
- # COMPLETION CALL
- system_prompt = "Only respond to questions about code. Say 'I don't know' to anything outside of that."
- messages = [
- {"role": "system", "content": system_prompt},
- {"role": "user", "content": data.pop("prompt")},
- ]
- data["messages"] = messages
- print(f"data: {data}")
- response = completion(**data)
- ## LOG SUCCESS
- end_time = time.time()
- if (
- "stream" in data and data["stream"] == True
- ): # use generate_responses to stream responses
- return Response(data_generator(response), mimetype="text/event-stream")
- except Exception:
- # call handle_error function
- print_verbose(f"Got Error api_completion(): {traceback.format_exc()}")
- ## LOG FAILURE
- end_time = time.time()
- traceback_exception = traceback.format_exc()
- return handle_error(data=data)
- return response
-
-
-@app.route("/get_models", methods=["POST"])
-def get_models():
- try:
- return litellm.model_list
- except Exception as e:
- traceback.print_exc()
- response = {"error": str(e)}
- return response, 200
-
-
-if __name__ == "__main__":
- from waitress import serve
-
- serve(app, host="0.0.0.0", port=4000, threads=500)
diff --git a/cookbook/community-resources/get_hf_models.py b/cookbook/community-resources/get_hf_models.py
deleted file mode 100644
index 8c75a241227..00000000000
--- a/cookbook/community-resources/get_hf_models.py
+++ /dev/null
@@ -1,89 +0,0 @@
-import requests
-
-
-def get_next_url(response):
- """
- Function to get 'next' url from Link header
- :param response: response from requests
- :return: next url or None
- """
- if "link" not in response.headers:
- return None
- headers = response.headers
-
- next_url = headers["Link"]
- print(next_url)
- start_index = next_url.find("<")
- end_index = next_url.find(">")
-
- return next_url[1:end_index]
-
-
-def get_models(url):
- """
- Function to retrieve all models from paginated endpoint
- :param url: base url to make GET request
- :return: list of all models
- """
- models = []
- while url:
- response = requests.get(url)
- if response.status_code != 200:
- print(f"Failed to retrieve data. Status code: {response.status_code}")
- return models
- payload = response.json()
- url = get_next_url(response)
- models.extend(payload)
- return models
-
-
-def get_cleaned_models(models):
- """
- Function to clean retrieved models
- :param models: list of retrieved models
- :return: list of cleaned models
- """
- cleaned_models = []
- for model in models:
- cleaned_models.append(model["id"])
- return cleaned_models
-
-
-# Get text-generation models
-url = "https://huggingface.co/api/models?filter=text-generation-inference"
-text_generation_models = get_models(url)
-cleaned_text_generation_models = get_cleaned_models(text_generation_models)
-
-print(cleaned_text_generation_models)
-
-
-# Get conversational models
-url = "https://huggingface.co/api/models?filter=conversational"
-conversational_models = get_models(url)
-cleaned_conversational_models = get_cleaned_models(conversational_models)
-
-print(cleaned_conversational_models)
-
-
-def write_to_txt(cleaned_models, filename):
- """
- Function to write the contents of a list to a text file
- :param cleaned_models: list of cleaned models
- :param filename: name of the text file
- """
- with open(filename, "w") as f:
- for item in cleaned_models:
- f.write("%s\n" % item)
-
-
-# Write contents of cleaned_text_generation_models to text_generation_models.txt
-write_to_txt(
- cleaned_text_generation_models,
- "huggingface_llms_metadata/hf_text_generation_models.txt",
-)
-
-# Write contents of cleaned_conversational_models to conversational_models.txt
-write_to_txt(
- cleaned_conversational_models,
- "huggingface_llms_metadata/hf_conversational_models.txt",
-)
diff --git a/cookbook/community-resources/max_tokens.json b/cookbook/community-resources/max_tokens.json
deleted file mode 100644
index 289f8faf9e3..00000000000
--- a/cookbook/community-resources/max_tokens.json
+++ /dev/null
@@ -1,93 +0,0 @@
-{
- "gpt-3.5-turbo": {
- "max_tokens": 4000,
- "input_cost_per_token": 0.0000015,
- "output_cost_per_token": 0.000002
- },
- "gpt-3.5-turbo-0613": {
- "max_tokens": 4000,
- "input_cost_per_token": 0.0000015,
- "output_cost_per_token": 0.000002
- },
- "gpt-3.5-turbo-0301": {
- "max_tokens": 4000,
- "input_cost_per_token": 0.0000015,
- "output_cost_per_token": 0.000002
- },
- "gpt-3.5-turbo-16k": {
- "max_tokens": 16000,
- "input_cost_per_token": 0.000003,
- "output_cost_per_token": 0.000004
- },
- "gpt-3.5-turbo-16k-0613": {
- "max_tokens": 16000,
- "input_cost_per_token": 0.000003,
- "output_cost_per_token": 0.000004
- },
- "gpt-4": {
- "max_tokens": 8000,
- "input_cost_per_token": 0.000003,
- "output_cost_per_token": 0.00006
- },
- "gpt-4-0613": {
- "max_tokens": 8000,
- "input_cost_per_token": 0.000003,
- "output_cost_per_token": 0.00006
- },
- "gpt-4-32k": {
- "max_tokens": 8000,
- "input_cost_per_token": 0.00006,
- "output_cost_per_token": 0.00012
- },
- "claude-instant-1": {
- "max_tokens": 100000,
- "input_cost_per_token": 0.00000163,
- "output_cost_per_token": 0.00000551
- },
- "claude-2": {
- "max_tokens": 100000,
- "input_cost_per_token": 0.00001102,
- "output_cost_per_token": 0.00003268
- },
- "text-bison-001": {
- "max_tokens": 8192,
- "input_cost_per_token": 0.000004,
- "output_cost_per_token": 0.000004
- },
- "chat-bison-001": {
- "max_tokens": 4096,
- "input_cost_per_token": 0.000002,
- "output_cost_per_token": 0.000002
- },
- "command-nightly": {
- "max_tokens": 4096,
- "input_cost_per_token": 0.000015,
- "output_cost_per_token": 0.000015
- },
- "replicate/llama-2-70b-chat:2c1608e18606fad2812020dc541930f2d0495ce32eee50074220b87300bc16e1": {
- "max_tokens": 4096,
- "input_cost_per_token": 0.00000608,
- "output_cost_per_token": 0.00000608
- },
- "together-ai-up-to-3b": {
- "input_cost_per_token": 0.0000001,
- "output_cost_per_token": 0.0000001
- },
- "together-ai-3.1b-7b": {
- "input_cost_per_token": 0.0000002,
- "output_cost_per_token": 0.0000002
- },
- "together-ai-7.1b-20b": {
- "max_tokens": 1000,
- "input_cost_per_token": 0.0000004,
- "output_cost_per_token": 0.0000004
- },
- "together-ai-20.1b-40b": {
- "input_cost_per_token": 0.000001,
- "output_cost_per_token": 0.000001
- },
- "together-ai-40.1b-70b": {
- "input_cost_per_token": 0.000003,
- "output_cost_per_token": 0.000003
- }
-}
diff --git a/cookbook/gollem_go_agent_framework/README.md b/cookbook/gollem_go_agent_framework/README.md
deleted file mode 100644
index 729f985d086..00000000000
--- a/cookbook/gollem_go_agent_framework/README.md
+++ /dev/null
@@ -1,119 +0,0 @@
-# Gollem Go Agent Framework with LiteLLM
-
-A working example showing how to use [gollem](https://github.com/fugue-labs/gollem), a production-grade Go agent framework, with LiteLLM as a proxy gateway. This lets Go developers access 100+ LLM providers through a single proxy while keeping compile-time type safety for tools and structured output.
-
-## Quick Start
-
-### 1. Start LiteLLM Proxy
-
-```bash
-# Simple start with a single model
-litellm --model gpt-4o
-
-# Or with the example config for multi-provider access
-litellm --config proxy_config.yaml
-```
-
-### 2. Run the examples
-
-```bash
-# Install Go dependencies
-go mod tidy
-
-# Basic agent
-go run ./basic
-
-# Agent with type-safe tools
-go run ./tools
-
-# Streaming responses
-go run ./streaming
-```
-
-## Configuration
-
-The included `proxy_config.yaml` sets up three providers through LiteLLM:
-
-```yaml
-model_list:
- - model_name: gpt-4o # OpenAI
- - model_name: claude-sonnet # Anthropic
- - model_name: gemini-pro # Google Vertex AI
-```
-
-Switch providers in Go by changing a single string — no code changes needed:
-
-```go
-model := openai.NewLiteLLM("http://localhost:4000",
- openai.WithModel("gpt-4o"), // OpenAI
- // openai.WithModel("claude-sonnet"), // Anthropic
- // openai.WithModel("gemini-pro"), // Google
-)
-```
-
-## Examples
-
-### `basic/` — Basic Agent
-
-Connects gollem to LiteLLM and runs a simple prompt. Demonstrates the `NewLiteLLM` constructor and basic agent creation.
-
-### `tools/` — Type-Safe Tools
-
-Shows gollem's compile-time type-safe tool framework working through LiteLLM's tool-use passthrough. The tool parameters are Go structs with JSON tags — the schema is generated automatically at compile time.
-
-### `streaming/` — Streaming Responses
-
-Real-time token streaming using Go 1.23+ range-over-function iterators, proxied through LiteLLM's SSE passthrough.
-
-## How It Works
-
-Gollem's `openai.NewLiteLLM()` constructor creates an OpenAI-compatible provider pointed at your LiteLLM proxy. Since LiteLLM speaks the OpenAI API protocol, everything works out of the box:
-
-- **Chat completions** — standard request/response
-- **Tool use** — LiteLLM passes tool definitions and calls through transparently
-- **Streaming** — Server-Sent Events proxied through LiteLLM
-- **Structured output** — JSON schema response format works with supporting models
-
-```
-Go App (gollem) → LiteLLM Proxy → OpenAI / Anthropic / Google / ...
-```
-
-## Why Use This?
-
-- **Type-safe Go**: Compile-time type checking for tools, structured output, and agent configuration — no runtime surprises
-- **Single proxy, many models**: Switch between OpenAI, Anthropic, Google, and 100+ other providers by changing a model name string
-- **Zero-dependency core**: gollem's core has no external dependencies — just stdlib
-- **Single binary deployment**: `go build` produces one binary, no pip/venv/Docker needed
-- **Cost tracking & rate limiting**: LiteLLM handles cost tracking, rate limits, and fallbacks at the proxy layer
-
-## Environment Variables
-
-```bash
-# Required for providers you want to use (set in LiteLLM config or env)
-export OPENAI_API_KEY="sk-..."
-export ANTHROPIC_API_KEY="sk-ant-..."
-
-# Optional: point to a non-default LiteLLM proxy
-export LITELLM_PROXY_URL="http://localhost:4000"
-```
-
-## Troubleshooting
-
-**Connection errors?**
-- Make sure LiteLLM is running: `litellm --model gpt-4o`
-- Check the URL is correct (default: `http://localhost:4000`)
-
-**Model not found?**
-- Verify the model name matches what's configured in LiteLLM
-- Run `curl http://localhost:4000/models` to see available models
-
-**Tool calls not working?**
-- Ensure the underlying model supports tool use (GPT-4o, Claude, Gemini)
-- Check LiteLLM logs for any provider-specific errors
-
-## Learn More
-
-- [gollem GitHub](https://github.com/fugue-labs/gollem)
-- [gollem API Reference](https://pkg.go.dev/github.com/fugue-labs/gollem/core)
-- [LiteLLM Proxy Docs](https://docs.litellm.ai/docs/simple_proxy)
-- [LiteLLM Supported Models](https://docs.litellm.ai/docs/providers)
diff --git a/cookbook/gollem_go_agent_framework/basic/main.go b/cookbook/gollem_go_agent_framework/basic/main.go
deleted file mode 100644
index 838149a8ff9..00000000000
--- a/cookbook/gollem_go_agent_framework/basic/main.go
+++ /dev/null
@@ -1,41 +0,0 @@
-// Basic gollem agent connected to a LiteLLM proxy.
-//
-// Usage:
-//
-// litellm --model gpt-4o # start proxy in another terminal
-// go run ./basic
-package main
-
-import (
- "context"
- "fmt"
- "log"
- "os"
-
- "github.com/fugue-labs/gollem/core"
- "github.com/fugue-labs/gollem/provider/openai"
-)
-
-func main() {
- proxyURL := "http://localhost:4000"
- if u := os.Getenv("LITELLM_PROXY_URL"); u != "" {
- proxyURL = u
- }
-
- // Connect to LiteLLM proxy. NewLiteLLM creates an OpenAI-compatible
- // provider pointed at the given URL.
- model := openai.NewLiteLLM(proxyURL,
- openai.WithModel("gpt-4o"), // any model name configured in LiteLLM
- )
-
- // Create and run a simple agent.
- agent := core.NewAgent[string](model,
- core.WithSystemPrompt[string]("You are a helpful assistant. Be concise."),
- )
-
- result, err := agent.Run(context.Background(), "Explain quantum computing in two sentences.")
- if err != nil {
- log.Fatal(err)
- }
- fmt.Println(result.Output)
-}
diff --git a/cookbook/gollem_go_agent_framework/go.mod b/cookbook/gollem_go_agent_framework/go.mod
deleted file mode 100644
index a8dc9365d7f..00000000000
--- a/cookbook/gollem_go_agent_framework/go.mod
+++ /dev/null
@@ -1,5 +0,0 @@
-module github.com/BerriAI/litellm/cookbook/gollem_go_agent_framework
-
-go 1.26.3
-
-require github.com/fugue-labs/gollem v0.1.0
diff --git a/cookbook/gollem_go_agent_framework/go.sum b/cookbook/gollem_go_agent_framework/go.sum
deleted file mode 100644
index 1eb6c5ac9fc..00000000000
--- a/cookbook/gollem_go_agent_framework/go.sum
+++ /dev/null
@@ -1,2 +0,0 @@
-github.com/fugue-labs/gollem v0.1.0 h1:QexYnvkb44QZFEljgAePqMIGZjgsbk0Y5GJ2jYYgfa8=
-github.com/fugue-labs/gollem v0.1.0/go.mod h1:htW1YO81uysSKVOkYJtxhGCFrzm+36HBFxEWuECoHKQ=
diff --git a/cookbook/gollem_go_agent_framework/proxy_config.yaml b/cookbook/gollem_go_agent_framework/proxy_config.yaml
deleted file mode 100644
index 18265a002bc..00000000000
--- a/cookbook/gollem_go_agent_framework/proxy_config.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-model_list:
- - model_name: gpt-4o
- litellm_params:
- model: openai/gpt-4o
- api_key: os.environ/OPENAI_API_KEY
-
- - model_name: claude-sonnet
- litellm_params:
- model: anthropic/claude-sonnet-4-20250514
- api_key: os.environ/ANTHROPIC_API_KEY
-
- - model_name: gemini-pro
- litellm_params:
- model: vertex_ai/gemini-2.0-flash
- vertex_project: my-project
- vertex_location: us-central1
diff --git a/cookbook/gollem_go_agent_framework/streaming/main.go b/cookbook/gollem_go_agent_framework/streaming/main.go
deleted file mode 100644
index 42bc9bbe34a..00000000000
--- a/cookbook/gollem_go_agent_framework/streaming/main.go
+++ /dev/null
@@ -1,56 +0,0 @@
-// Streaming responses from gollem through LiteLLM.
-//
-// Uses Go 1.23+ range-over-function iterators for real-time token
-// streaming via LiteLLM's SSE passthrough.
-//
-// Usage:
-//
-// litellm --model gpt-4o
-// go run ./streaming
-package main
-
-import (
- "context"
- "fmt"
- "log"
- "os"
-
- "github.com/fugue-labs/gollem/core"
- "github.com/fugue-labs/gollem/provider/openai"
-)
-
-func main() {
- proxyURL := "http://localhost:4000"
- if u := os.Getenv("LITELLM_PROXY_URL"); u != "" {
- proxyURL = u
- }
-
- model := openai.NewLiteLLM(proxyURL,
- openai.WithModel("gpt-4o"),
- )
-
- agent := core.NewAgent[string](model)
-
- // RunStream returns a streaming result that yields tokens as they arrive.
- stream, err := agent.RunStream(context.Background(), "Write a haiku about distributed systems")
- if err != nil {
- log.Fatal(err)
- }
-
- // StreamText yields text chunks in real-time.
- // The boolean argument controls whether deltas (true) or accumulated
- // text (false) is returned.
- fmt.Print("Response: ")
- for text, err := range stream.StreamText(true) {
- if err != nil {
- log.Fatal(err)
- }
- fmt.Print(text)
- }
- fmt.Println()
-
- // After streaming completes, the final response is available.
- resp := stream.Response()
- fmt.Printf("\nTokens used: input=%d, output=%d\n",
- resp.Usage.InputTokens, resp.Usage.OutputTokens)
-}
diff --git a/cookbook/gollem_go_agent_framework/tools/main.go b/cookbook/gollem_go_agent_framework/tools/main.go
deleted file mode 100644
index ed41a95ffef..00000000000
--- a/cookbook/gollem_go_agent_framework/tools/main.go
+++ /dev/null
@@ -1,64 +0,0 @@
-// Gollem agent with type-safe tools through LiteLLM.
-//
-// The tool parameters are Go structs — gollem generates the JSON schema
-// automatically at compile time. LiteLLM passes tool definitions through
-// transparently to the underlying provider.
-//
-// Usage:
-//
-// litellm --model gpt-4o
-// go run ./tools
-package main
-
-import (
- "context"
- "fmt"
- "log"
- "os"
-
- "github.com/fugue-labs/gollem/core"
- "github.com/fugue-labs/gollem/provider/openai"
-)
-
-// WeatherParams defines the tool's input schema via struct tags.
-// The JSON schema is generated at compile time — no runtime reflection needed.
-type WeatherParams struct {
- City string `json:"city" description:"City name to get weather for"`
- Unit string `json:"unit,omitempty" description:"Temperature unit: celsius or fahrenheit"`
-}
-
-func main() {
- proxyURL := "http://localhost:4000"
- if u := os.Getenv("LITELLM_PROXY_URL"); u != "" {
- proxyURL = u
- }
-
- model := openai.NewLiteLLM(proxyURL,
- openai.WithModel("gpt-4o"),
- )
-
- // Define a type-safe tool. The function signature enforces correct types.
- weatherTool := core.FuncTool[WeatherParams](
- "get_weather",
- "Get current weather for a city",
- func(ctx context.Context, p WeatherParams) (string, error) {
- unit := p.Unit
- if unit == "" {
- unit = "fahrenheit"
- }
- // In production, call a real weather API here.
- return fmt.Sprintf("Weather in %s: 72°F (22°C), sunny", p.City), nil
- },
- )
-
- agent := core.NewAgent[string](model,
- core.WithTools[string](weatherTool),
- core.WithSystemPrompt[string]("You are a helpful weather assistant. Use the get_weather tool to answer weather questions."),
- )
-
- result, err := agent.Run(context.Background(), "What's the weather like in San Francisco and Tokyo?")
- if err != nil {
- log.Fatal(err)
- }
- fmt.Println(result.Output)
-}
diff --git a/cookbook/google_adk_litellm_tutorial.ipynb b/cookbook/google_adk_litellm_tutorial.ipynb
deleted file mode 100644
index 27914edbba8..00000000000
--- a/cookbook/google_adk_litellm_tutorial.ipynb
+++ /dev/null
@@ -1,412 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "id": "7aa8875d",
- "metadata": {},
- "source": [
- "# Google ADK with LiteLLM\n",
- "\n",
- "Use Google ADK with LiteLLM Python SDK, LiteLLM Proxy.\n",
- "\n",
- "This tutorial shows you how to create intelligent agents using Agent Development Kit (ADK) with support for multiple Large Language Model (LLM) providers through LiteLLM."
- ]
- },
- {
- "cell_type": "markdown",
- "id": "a4d249c3",
- "metadata": {},
- "source": [
- "## Overview\n",
- "\n",
- "ADK (Agent Development Kit) allows you to build intelligent agents powered by LLMs. By integrating with LiteLLM, you can:\n",
- "\n",
- "- Use multiple LLM providers (OpenAI, Anthropic, Google, etc.)\n",
- "- Switch easily between models from different providers\n",
- "- Connect to a LiteLLM proxy for centralized model management"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "a0bbb56b",
- "metadata": {},
- "source": [
- "## Prerequisites\n",
- "\n",
- "- Python environment setup\n",
- "- API keys for model providers (OpenAI, Anthropic, Google AI Studio)\n",
- "- Basic understanding of LLMs and agent concepts"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "7fee50a8",
- "metadata": {},
- "source": [
- "## Installation"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "44106a23",
- "metadata": {},
- "outputs": [],
- "source": [
- "# Install dependencies\n",
- "!pip install google-adk litellm"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "2171740a",
- "metadata": {},
- "source": [
- "## 1. Setting Up Environment"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "6695807e",
- "metadata": {},
- "outputs": [],
- "source": [
- "# Setup environment and API keys\n",
- "import os\n",
- "import asyncio\n",
- "from google.adk.agents import Agent\n",
- "from google.adk.models.lite_llm import LiteLlm # For multi-model support\n",
- "from google.adk.sessions import InMemorySessionService\n",
- "from google.adk.runners import Runner\n",
- "from google.genai import types\n",
- "import litellm # Import for proxy configuration\n",
- "\n",
- "# Set your API keys\n",
- "os.environ['GOOGLE_API_KEY'] = 'your-google-api-key' # For Gemini models\n",
- "os.environ['OPENAI_API_KEY'] = 'your-openai-api-key' # For OpenAI models\n",
- "os.environ['ANTHROPIC_API_KEY'] = 'your-anthropic-api-key' # For Claude models\n",
- "\n",
- "# Define model constants for cleaner code\n",
- "MODEL_GEMINI_PRO = 'gemini-1.5-pro'\n",
- "MODEL_GPT_4O = 'openai/gpt-4o'\n",
- "MODEL_CLAUDE_SONNET = 'anthropic/claude-3-sonnet-20240229'"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "d2b1ed59",
- "metadata": {},
- "source": [
- "## 2. Define a Simple Tool"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "04b3ef5b",
- "metadata": {},
- "outputs": [],
- "source": [
- "# Weather tool implementation\n",
- "def get_weather(city: str) -> dict:\n",
- " \"\"\"Retrieves the current weather report for a specified city.\"\"\"\n",
- " print(f'Tool: get_weather called for city: {city}')\n",
- "\n",
- " # Mock weather data\n",
- " mock_weather_db = {\n",
- " 'newyork': {\n",
- " 'status': 'success',\n",
- " 'report': 'The weather in New York is sunny with a temperature of 25°C.'\n",
- " },\n",
- " 'london': {\n",
- " 'status': 'success',\n",
- " 'report': \"It's cloudy in London with a temperature of 15°C.\"\n",
- " },\n",
- " 'tokyo': {\n",
- " 'status': 'success',\n",
- " 'report': 'Tokyo is experiencing light rain and a temperature of 18°C.'\n",
- " },\n",
- " }\n",
- "\n",
- " city_normalized = city.lower().replace(' ', '')\n",
- "\n",
- " if city_normalized in mock_weather_db:\n",
- " return mock_weather_db[city_normalized]\n",
- " else:\n",
- " return {\n",
- " 'status': 'error',\n",
- " 'error_message': f\"Sorry, I don't have weather information for '{city}'.\"\n",
- " }"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "727b15c9",
- "metadata": {},
- "source": [
- "## 3. Helper Function for Agent Interaction"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "f77449bf",
- "metadata": {},
- "outputs": [],
- "source": [
- "# Agent interaction helper function\n",
- "async def call_agent_async(query: str, runner, user_id, session_id):\n",
- " \"\"\"Sends a query to the agent and prints the final response.\"\"\"\n",
- " print(f'\\n>>> User Query: {query}')\n",
- "\n",
- " content = types.Content(role='user', parts=[types.Part(text=query)])\n",
- " final_response_text = 'Agent did not produce a final response.'\n",
- "\n",
- " async for event in runner.run_async(\n",
- " user_id=user_id,\n",
- " session_id=session_id,\n",
- " new_message=content\n",
- " ):\n",
- " if event.is_final_response():\n",
- " if event.content and event.content.parts:\n",
- " final_response_text = event.content.parts[0].text\n",
- " break\n",
- " print(f'<<< Agent Response: {final_response_text}')"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "0ac87987",
- "metadata": {},
- "source": [
- "## 4. Using Different Model Providers with ADK\n",
- "\n",
- "### 4.1 Using OpenAI Models"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "e167d557",
- "metadata": {},
- "outputs": [],
- "source": [
- "# OpenAI model implementation\n",
- "weather_agent_gpt = Agent(\n",
- " name='weather_agent_gpt',\n",
- " model=LiteLlm(model=MODEL_GPT_4O),\n",
- " description='Provides weather information using OpenAI\\'s GPT.',\n",
- " instruction=(\n",
- " 'You are a helpful weather assistant powered by GPT-4o. '\n",
- " \"Use the 'get_weather' tool for city weather requests. \"\n",
- " 'Present information clearly.'\n",
- " ),\n",
- " tools=[get_weather],\n",
- ")\n",
- "\n",
- "session_service_gpt = InMemorySessionService()\n",
- "session_gpt = session_service_gpt.create_session(\n",
- " app_name='weather_app', user_id='user_1', session_id='session_gpt'\n",
- ")\n",
- "\n",
- "runner_gpt = Runner(\n",
- " agent=weather_agent_gpt,\n",
- " app_name='weather_app',\n",
- " session_service=session_service_gpt,\n",
- ")\n",
- "\n",
- "async def test_gpt_agent():\n",
- " print('\\n--- Testing GPT Agent ---')\n",
- " await call_agent_async(\n",
- " \"What's the weather in London?\",\n",
- " runner=runner_gpt,\n",
- " user_id='user_1',\n",
- " session_id='session_gpt',\n",
- " )\n",
- "\n",
- "# To execute in a notebook cell:\n",
- "# await test_gpt_agent()"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "f9cb0613",
- "metadata": {},
- "source": [
- "### 4.2 Using Anthropic Models"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "1c653665",
- "metadata": {},
- "outputs": [],
- "source": [
- "# Anthropic model implementation\n",
- "weather_agent_claude = Agent(\n",
- " name='weather_agent_claude',\n",
- " model=LiteLlm(model=MODEL_CLAUDE_SONNET),\n",
- " description='Provides weather information using Anthropic\\'s Claude.',\n",
- " instruction=(\n",
- " 'You are a helpful weather assistant powered by Claude Sonnet. '\n",
- " \"Use the 'get_weather' tool for city weather requests. \"\n",
- " 'Present information clearly.'\n",
- " ),\n",
- " tools=[get_weather],\n",
- ")\n",
- "\n",
- "session_service_claude = InMemorySessionService()\n",
- "session_claude = session_service_claude.create_session(\n",
- " app_name='weather_app', user_id='user_1', session_id='session_claude'\n",
- ")\n",
- "\n",
- "runner_claude = Runner(\n",
- " agent=weather_agent_claude,\n",
- " app_name='weather_app',\n",
- " session_service=session_service_claude,\n",
- ")\n",
- "\n",
- "async def test_claude_agent():\n",
- " print('\\n--- Testing Claude Agent ---')\n",
- " await call_agent_async(\n",
- " \"What's the weather in Tokyo?\",\n",
- " runner=runner_claude,\n",
- " user_id='user_1',\n",
- " session_id='session_claude',\n",
- " )\n",
- "\n",
- "# To execute in a notebook cell:\n",
- "# await test_claude_agent()"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "bf9d863b",
- "metadata": {},
- "source": [
- "### 4.3 Using Google's Gemini Models"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "83f49d0a",
- "metadata": {},
- "outputs": [],
- "source": [
- "# Gemini model implementation\n",
- "weather_agent_gemini = Agent(\n",
- " name='weather_agent_gemini',\n",
- " model=MODEL_GEMINI_PRO,\n",
- " description='Provides weather information using Google\\'s Gemini.',\n",
- " instruction=(\n",
- " 'You are a helpful weather assistant powered by Gemini Pro. '\n",
- " \"Use the 'get_weather' tool for city weather requests. \"\n",
- " 'Present information clearly.'\n",
- " ),\n",
- " tools=[get_weather],\n",
- ")\n",
- "\n",
- "session_service_gemini = InMemorySessionService()\n",
- "session_gemini = session_service_gemini.create_session(\n",
- " app_name='weather_app', user_id='user_1', session_id='session_gemini'\n",
- ")\n",
- "\n",
- "runner_gemini = Runner(\n",
- " agent=weather_agent_gemini,\n",
- " app_name='weather_app',\n",
- " session_service=session_service_gemini,\n",
- ")\n",
- "\n",
- "async def test_gemini_agent():\n",
- " print('\\n--- Testing Gemini Agent ---')\n",
- " await call_agent_async(\n",
- " \"What's the weather in New York?\",\n",
- " runner=runner_gemini,\n",
- " user_id='user_1',\n",
- " session_id='session_gemini',\n",
- " )\n",
- "\n",
- "# To execute in a notebook cell:\n",
- "# await test_gemini_agent()"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "93bc5fd0",
- "metadata": {},
- "source": [
- "## 5. Using LiteLLM Proxy with ADK"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "b4275151",
- "metadata": {},
- "source": [
- "| Variable | Description |\n",
- "|----------|-------------|\n",
- "| `LITELLM_PROXY_API_KEY` | The API key for the LiteLLM proxy |\n",
- "| `LITELLM_PROXY_API_BASE` | The base URL for the LiteLLM proxy |\n",
- "| `USE_LITELLM_PROXY` or `litellm.use_litellm_proxy` | When set to True, your request will be sent to LiteLLM proxy. |"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "256530a6",
- "metadata": {},
- "outputs": [],
- "source": [
- "# LiteLLM proxy integration\n",
- "os.environ['LITELLM_PROXY_API_KEY'] = 'your-litellm-proxy-api-key'\n",
- "os.environ['LITELLM_PROXY_API_BASE'] = 'your-litellm-proxy-url' # e.g., 'http://localhost:4000'\n",
- "litellm.use_litellm_proxy = True\n",
- "\n",
- "weather_agent_proxy_env = Agent(\n",
- " name='weather_agent_proxy_env',\n",
- " model=LiteLlm(model='gpt-4o'),\n",
- " description='Provides weather information using a model from LiteLLM proxy.',\n",
- " instruction=(\n",
- " 'You are a helpful weather assistant. '\n",
- " \"Use the 'get_weather' tool for city weather requests. \"\n",
- " 'Present information clearly.'\n",
- " ),\n",
- " tools=[get_weather],\n",
- ")\n",
- "\n",
- "session_service_proxy_env = InMemorySessionService()\n",
- "session_proxy_env = session_service_proxy_env.create_session(\n",
- " app_name='weather_app', user_id='user_1', session_id='session_proxy_env'\n",
- ")\n",
- "\n",
- "runner_proxy_env = Runner(\n",
- " agent=weather_agent_proxy_env,\n",
- " app_name='weather_app',\n",
- " session_service=session_service_proxy_env,\n",
- ")\n",
- "\n",
- "async def test_proxy_env_agent():\n",
- " print('\\n--- Testing Proxy-enabled Agent (Environment Variables) ---')\n",
- " await call_agent_async(\n",
- " \"What's the weather in London?\",\n",
- " runner=runner_proxy_env,\n",
- " user_id='user_1',\n",
- " session_id='session_proxy_env',\n",
- " )\n",
- "\n",
- "# To execute in a notebook cell:\n",
- "# await test_proxy_env_agent()"
- ]
- }
- ],
- "metadata": {
- "language_info": {
- "name": "python"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 5
-}
diff --git a/cookbook/gpt_realtime_translate.py b/cookbook/gpt_realtime_translate.py
deleted file mode 100644
index 827c65cdbfe..00000000000
--- a/cookbook/gpt_realtime_translate.py
+++ /dev/null
@@ -1,245 +0,0 @@
-#!/usr/bin/env python3
-
-import argparse
-import asyncio
-import base64
-import json
-import os
-import sys
-import wave
-from collections.abc import Iterator, Sequence
-from dataclasses import dataclass
-from pathlib import Path
-from urllib.parse import urlencode, urlsplit, urlunsplit
-
-import websockets
-from websockets.asyncio.client import ClientConnection
-
-SAMPLE_RATE = 24_000
-CHANNELS = 1
-SAMPLE_WIDTH = 2
-CHUNK_DURATION_SECONDS = 0.1
-CHUNK_BYTES = int(SAMPLE_RATE * CHANNELS * SAMPLE_WIDTH * CHUNK_DURATION_SECONDS)
-RESPONSE_TIMEOUT_SECONDS = 30.0
-AUDIO_EVENT_TYPES = frozenset(
- {
- "session.output_audio.delta",
- "response.audio.delta",
- "response.output_audio.delta",
- }
-)
-TRANSCRIPT_EVENT_TYPES = frozenset(
- {
- "session.output_transcript.delta",
- "response.text.delta",
- "response.output_audio_transcript.delta",
- }
-)
-
-
-@dataclass(frozen=True, slots=True)
-class Settings:
- input_wav: Path
- output_wav: Path
- base_url: str
- model: str
- target_language: str
- trailing_silence_seconds: float
- api_key: str
-
-
-def write_stdout(message: str = "", *, end: str = "\n", flush: bool = False) -> None:
- sys.stdout.write(f"{message}{end}")
- if flush:
- sys.stdout.flush()
-
-
-def write_stderr(message: str) -> None:
- sys.stderr.write(f"{message}\n")
-
-
-def parse_args(argv: Sequence[str] | None = None) -> Settings | str:
- parser = argparse.ArgumentParser(
- description="Stream a 24 kHz PCM16 WAV through gpt-realtime-translate and save the translated audio",
- )
- parser.add_argument("input_wav", type=Path)
- parser.add_argument("--output", type=Path, default=Path("translated.wav"))
- parser.add_argument("--base-url", default=os.getenv("LITELLM_BASE_URL", "http://localhost:4000"))
- parser.add_argument("--model", default=os.getenv("REALTIME_TRANSLATE_MODEL", "gpt-realtime-translate"))
- parser.add_argument("--target-language", default="fr")
- parser.add_argument("--trailing-silence", type=float, default=1.5)
- parsed = parser.parse_args(argv)
- api_key = os.getenv("LITELLM_API_KEY") or os.getenv("OPENAI_API_KEY")
- if not api_key:
- return "Set LITELLM_API_KEY or OPENAI_API_KEY before running the script"
- if parsed.trailing_silence < 0:
- return "--trailing-silence must be zero or greater"
- return Settings(
- input_wav=parsed.input_wav,
- output_wav=parsed.output,
- base_url=parsed.base_url,
- model=parsed.model,
- target_language=parsed.target_language,
- trailing_silence_seconds=parsed.trailing_silence,
- api_key=api_key,
- )
-
-
-def translation_url(base_url: str, model: str) -> str | None:
- parsed = urlsplit(base_url.rstrip("/"))
- scheme = {"http": "ws", "https": "wss", "ws": "ws", "wss": "wss"}.get(parsed.scheme)
- if not scheme or not parsed.netloc:
- return None
- base_path = parsed.path.rstrip("/")
- realtime_path = (
- f"{base_path}/realtime/translations" if base_path.endswith("/v1") else f"{base_path}/v1/realtime/translations"
- )
- return urlunsplit((scheme, parsed.netloc, realtime_path, urlencode({"model": model}), ""))
-
-
-def read_pcm16_wav(path: Path) -> bytes | str:
- try:
- with wave.open(str(path), "rb") as source:
- actual_format = (
- source.getnchannels(),
- source.getsampwidth(),
- source.getframerate(),
- source.getcomptype(),
- )
- expected_format = (CHANNELS, SAMPLE_WIDTH, SAMPLE_RATE, "NONE")
- if actual_format != expected_format:
- return (
- f"{path} must be mono, 16-bit PCM, 24 kHz WAV; received "
- f"channels={actual_format[0]}, sample_width={actual_format[1]}, "
- f"sample_rate={actual_format[2]}, compression={actual_format[3]}"
- )
- return source.readframes(source.getnframes())
- except (OSError, EOFError, wave.Error) as exc:
- return f"Unable to read {path}: {exc}"
-
-
-def audio_chunks(audio: bytes) -> Iterator[bytes]:
- return (audio[offset : offset + CHUNK_BYTES] for offset in range(0, len(audio), CHUNK_BYTES))
-
-
-def audio_message(audio: bytes) -> str:
- return json.dumps(
- {
- "type": "session.input_audio_buffer.append",
- "audio": base64.b64encode(audio).decode("ascii"),
- }
- )
-
-
-async def configure_session(connection: ClientConnection, target_language: str) -> str | None:
- await connection.send(
- json.dumps(
- {
- "type": "session.update",
- "session": {"audio": {"output": {"language": target_language}}},
- }
- )
- )
- while True:
- raw_event = await asyncio.wait_for(connection.recv(), timeout=20)
- event = json.loads(raw_event)
- event_type = event.get("type")
- if event_type == "session.created":
- write_stdout(f"Session: {event.get('session', {}).get('id', 'created')}")
- if event_type == "session.updated":
- return None
- if event_type == "error":
- return f"Realtime API error: {json.dumps(event.get('error', event), ensure_ascii=False)}"
-
-
-async def send_audio(
- connection: ClientConnection, pcm: bytes, trailing_silence_seconds: float, finished: asyncio.Event
-) -> None:
- silence = bytes(round(trailing_silence_seconds * SAMPLE_RATE * CHANNELS * SAMPLE_WIDTH))
- try:
- for chunk in audio_chunks(pcm + silence):
- await connection.send(audio_message(chunk))
- await asyncio.sleep(len(chunk) / (SAMPLE_RATE * CHANNELS * SAMPLE_WIDTH))
- await connection.send(json.dumps({"type": "session.close"}))
- finally:
- finished.set()
-
-
-async def receive_translation(
- connection: ClientConnection, output_path: Path, sender_finished: asyncio.Event
-) -> str | None:
- audio_received = asyncio.Event()
- try:
- with wave.open(str(output_path), "wb") as output:
- output.setnchannels(CHANNELS)
- output.setsampwidth(SAMPLE_WIDTH)
- output.setframerate(SAMPLE_RATE)
- write_stdout("Translation: ", end="", flush=True)
- while True:
- try:
- raw_event = await asyncio.wait_for(connection.recv(), timeout=RESPONSE_TIMEOUT_SECONDS)
- except TimeoutError:
- if not sender_finished.is_set():
- continue
- return (
- f"The translation stream went quiet for {RESPONSE_TIMEOUT_SECONDS:g} s without closing the session"
- )
- event = json.loads(raw_event)
- event_type = event.get("type")
- if event_type in AUDIO_EVENT_TYPES:
- output.writeframes(base64.b64decode(event.get("delta", ""), validate=True))
- audio_received.set()
- elif event_type in TRANSCRIPT_EVENT_TYPES:
- write_stdout(event.get("delta", event.get("text", "")), end="", flush=True)
- elif event_type == "error":
- return f"Realtime API error: {json.dumps(event.get('error', event), ensure_ascii=False)}"
- elif event_type == "session.closed":
- write_stdout()
- return None if audio_received.is_set() else "The translation stream ended without translated audio"
- except (OSError, wave.Error) as exc:
- return f"Unable to write {output_path}: {exc}"
-
-
-async def translate(settings: Settings, pcm: bytes) -> str | None:
- url = translation_url(settings.base_url, settings.model)
- if not url:
- return f"Invalid --base-url: {settings.base_url}"
- sender_finished = asyncio.Event()
- try:
- async with websockets.connect(
- url,
- additional_headers={"Authorization": f"Bearer {settings.api_key}"},
- proxy=None,
- open_timeout=20,
- close_timeout=5,
- ) as connection:
- configuration_error = await configure_session(connection, settings.target_language)
- if configuration_error:
- return configuration_error
- async with asyncio.TaskGroup() as tasks:
- receiver = tasks.create_task(receive_translation(connection, settings.output_wav, sender_finished))
- tasks.create_task(send_audio(connection, pcm, settings.trailing_silence_seconds, sender_finished))
- return receiver.result()
- except Exception as exc:
- return f"Translation failed: {type(exc).__name__}: {exc}"
-
-
-def main(argv: Sequence[str] | None = None) -> int:
- settings = parse_args(argv)
- if isinstance(settings, str):
- write_stderr(settings)
- return 2
- pcm = read_pcm16_wav(settings.input_wav)
- if isinstance(pcm, str):
- write_stderr(pcm)
- return 2
- error = asyncio.run(translate(settings, pcm))
- if error:
- write_stderr(error)
- return 1
- write_stdout(f"Translated audio: {settings.output_wav}")
- return 0
-
-
-if __name__ == "__main__":
- raise SystemExit(main())
diff --git a/cookbook/liteLLM_A121_Jurrasic_example.ipynb b/cookbook/liteLLM_A121_Jurrasic_example.ipynb
deleted file mode 100644
index f975b97e911..00000000000
--- a/cookbook/liteLLM_A121_Jurrasic_example.ipynb
+++ /dev/null
@@ -1,251 +0,0 @@
-{
- "nbformat": 4,
- "nbformat_minor": 0,
- "metadata": {
- "colab": {
- "provenance": []
- },
- "kernelspec": {
- "name": "python3",
- "display_name": "Python 3"
- },
- "language_info": {
- "name": "python"
- }
- },
- "cells": [
- {
- "cell_type": "markdown",
- "source": [
- "# LiteLLM A121 Tutorial\n",
- "\n",
- "This walks through using A121 Jurassic models\n",
- "* j2-light\n",
- "* j2-mid\n",
- "* j2-ultra"
- ],
- "metadata": {
- "id": "LeFYo8iqcn5g"
- }
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "GslPQFmaZsp-"
- },
- "outputs": [],
- "source": [
- "!pip install litellm"
- ]
- },
- {
- "cell_type": "code",
- "source": [
- "from litellm import completion\n",
- "import os"
- ],
- "metadata": {
- "id": "P3cKiqURZx7P"
- },
- "execution_count": 2,
- "outputs": []
- },
- {
- "cell_type": "markdown",
- "source": [
- "## Set A121 Keys\n",
- "You can get a free key from https://studio.ai21.com/account/api-key"
- ],
- "metadata": {
- "id": "tmTvA1_GaNU4"
- }
- },
- {
- "cell_type": "code",
- "source": [
- "os.environ[\"AI21_API_KEY\"] = \"\""
- ],
- "metadata": {
- "id": "_xX8LmxAZ2vp"
- },
- "execution_count": 5,
- "outputs": []
- },
- {
- "cell_type": "markdown",
- "source": [
- "# A121 Supported Models:\n",
- "https://studio.ai21.com/foundation-models"
- ],
- "metadata": {
- "id": "Fx5ZfJTLbF0A"
- }
- },
- {
- "cell_type": "markdown",
- "source": [
- "## J2-light Call"
- ],
- "metadata": {
- "id": "H0tl-0Z3bDaL"
- }
- },
- {
- "cell_type": "code",
- "source": [
- "messages = [{ \"content\": \"Hello, how are you?\",\"role\": \"user\"}]\n",
- "response = completion(model=\"j2-light\", messages=messages)\n",
- "response"
- ],
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/"
- },
- "id": "DZnApsJUZ_I2",
- "outputId": "b5707cbe-f67c-47f7-bac5-a7b8af1ba815"
- },
- "execution_count": 6,
- "outputs": [
- {
- "output_type": "execute_result",
- "data": {
- "text/plain": [
- " JSON: {\n",
- " \"choices\": [\n",
- " {\n",
- " \"finish_reason\": \"stop\",\n",
- " \"index\": 0,\n",
- " \"message\": {\n",
- " \"content\": \" However, I have an important question to ask you\\nMy name is X, and I was wondering if you would be willing to help me.\",\n",
- " \"role\": \"assistant\"\n",
- " }\n",
- " }\n",
- " ],\n",
- " \"created\": 1692761063.5189915,\n",
- " \"model\": \"j2-light\",\n",
- " \"usage\": {\n",
- " \"prompt_tokens\": null,\n",
- " \"completion_tokens\": null,\n",
- " \"total_tokens\": null\n",
- " }\n",
- "}"
- ]
- },
- "metadata": {},
- "execution_count": 6
- }
- ]
- },
- {
- "cell_type": "markdown",
- "source": [
- "# J2-Mid"
- ],
- "metadata": {
- "id": "wCcnrYnnbMQA"
- }
- },
- {
- "cell_type": "code",
- "source": [
- "messages = [{ \"content\": \"what model are you\",\"role\": \"user\"}]\n",
- "response = completion(model=\"j2-mid\", messages=messages)\n",
- "response"
- ],
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/"
- },
- "id": "-5Sxf4blaeEl",
- "outputId": "6264a5e8-16d6-44a3-e167-9e0c59b6dbc4"
- },
- "execution_count": 7,
- "outputs": [
- {
- "output_type": "execute_result",
- "data": {
- "text/plain": [
- " JSON: {\n",
- " \"choices\": [\n",
- " {\n",
- " \"finish_reason\": \"stop\",\n",
- " \"index\": 0,\n",
- " \"message\": {\n",
- " \"content\": \"\\nplease choose the model from the list below\\nModel view in Tekla Structures\",\n",
- " \"role\": \"assistant\"\n",
- " }\n",
- " }\n",
- " ],\n",
- " \"created\": 1692761140.0017524,\n",
- " \"model\": \"j2-mid\",\n",
- " \"usage\": {\n",
- " \"prompt_tokens\": null,\n",
- " \"completion_tokens\": null,\n",
- " \"total_tokens\": null\n",
- " }\n",
- "}"
- ]
- },
- "metadata": {},
- "execution_count": 7
- }
- ]
- },
- {
- "cell_type": "markdown",
- "source": [
- "# J2-Ultra"
- ],
- "metadata": {
- "id": "wDARpjxtbUcg"
- }
- },
- {
- "cell_type": "code",
- "source": [
- "messages = [{ \"content\": \"what model are you\",\"role\": \"user\"}]\n",
- "response = completion(model=\"j2-ultra\", messages=messages)\n",
- "response"
- ],
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/"
- },
- "id": "i228xwsYbSYo",
- "outputId": "3765ac56-5a9b-442e-b357-2e346d02e1df"
- },
- "execution_count": 8,
- "outputs": [
- {
- "output_type": "execute_result",
- "data": {
- "text/plain": [
- " JSON: {\n",
- " \"choices\": [\n",
- " {\n",
- " \"finish_reason\": \"stop\",\n",
- " \"index\": 0,\n",
- " \"message\": {\n",
- " \"content\": \"\\nI am not a specific model, but I can provide information and assistance based on my training data. Please let me know if there is anything you\",\n",
- " \"role\": \"assistant\"\n",
- " }\n",
- " }\n",
- " ],\n",
- " \"created\": 1692761157.8675153,\n",
- " \"model\": \"j2-ultra\",\n",
- " \"usage\": {\n",
- " \"prompt_tokens\": null,\n",
- " \"completion_tokens\": null,\n",
- " \"total_tokens\": null\n",
- " }\n",
- "}"
- ]
- },
- "metadata": {},
- "execution_count": 8
- }
- ]
- }
- ]
-}
\ No newline at end of file
diff --git a/cookbook/liteLLM_Baseten.ipynb b/cookbook/liteLLM_Baseten.ipynb
deleted file mode 100644
index 0a5bc5f1df7..00000000000
--- a/cookbook/liteLLM_Baseten.ipynb
+++ /dev/null
@@ -1,146 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "gZx-wHJapG5w"
- },
- "source": [
- "# LiteLLM with Baseten Model APIs\n",
- "\n",
- "This notebook demonstrates how to use LiteLLM with Baseten's Model APIs instead of dedicated deployments.\n",
- "\n",
- "## Example Usage\n",
- "```python\n",
- "response = completion(\n",
- " model=\"baseten/openai/gpt-oss-120b\",\n",
- " messages=[{\"role\": \"user\", \"content\": \"Hello!\"}],\n",
- " max_tokens=1000,\n",
- " temperature=0.7\n",
- ")\n",
- "```\n",
- "\n",
- "## Setup"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "4JSRa0QVogPo"
- },
- "outputs": [],
- "source": [
- "%pip install litellm"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "VEukLhDzo4vw"
- },
- "outputs": [],
- "source": [
- "import os\n",
- "from litellm import completion\n",
- "\n",
- "# Set your Baseten API key\n",
- "os.environ['BASETEN_API_KEY'] = \"\" #@param {type:\"string\"}\n",
- "\n",
- "# Test message\n",
- "messages = [{\"role\": \"user\", \"content\": \"What is AGI?\"}]"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "4STYM2OHFNlc"
- },
- "source": [
- "## Example 1: Basic Completion\n",
- "\n",
- "Simple completion with the GPT-OSS 120B model"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "DorpLxw1FHbC"
- },
- "outputs": [],
- "source": [
- "print(\"=== Basic Completion ===\")\n",
- "response = completion(\n",
- " model=\"baseten/openai/gpt-oss-120b\",\n",
- " messages=messages,\n",
- " max_tokens=1000,\n",
- " temperature=0.7,\n",
- " top_p=0.9,\n",
- " presence_penalty=0.1,\n",
- " frequency_penalty=0.1,\n",
- ")\n",
- "print(f\"Response: {response.choices[0].message.content}\")\n",
- "print(f\"Usage: {response.usage}\")"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "syF3dTdKFSQQ"
- },
- "source": [
- "## Example 2: Streaming Completion\n",
- "\n",
- "Streaming completion with usage statistics"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/"
- },
- "id": "rPgSoMlsojz0",
- "outputId": "81d6dc7b-1681-4ae4-e4c8-5684eb1bd050"
- },
- "outputs": [],
- "source": [
- "print(\"=== Streaming Completion ===\")\n",
- "response = completion(\n",
- " model=\"baseten/openai/gpt-oss-120b\",\n",
- " messages=[{\"role\": \"user\", \"content\": \"Write a short poem about AI\"}],\n",
- " stream=True,\n",
- " max_tokens=500,\n",
- " temperature=0.8,\n",
- " stream_options={\n",
- " \"include_usage\": True,\n",
- " \"continuous_usage_stats\": True\n",
- " },\n",
- ")\n",
- "\n",
- "print(\"Streaming response:\")\n",
- "for chunk in response:\n",
- " if chunk.choices and chunk.choices[0].delta.content:\n",
- " print(chunk.choices[0].delta.content, end=\"\", flush=True)\n",
- "print(\"\\n\")"
- ]
- }
- ],
- "metadata": {
- "colab": {
- "provenance": []
- },
- "kernelspec": {
- "display_name": "Python 3",
- "name": "python3"
- },
- "language_info": {
- "name": "python"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 0
-}
diff --git a/cookbook/liteLLM_Getting_Started.ipynb b/cookbook/liteLLM_Getting_Started.ipynb
deleted file mode 100644
index b43c51dceeb..00000000000
--- a/cookbook/liteLLM_Getting_Started.ipynb
+++ /dev/null
@@ -1,411 +0,0 @@
-{
- "cells": [
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {
- "id": "MZ01up0p7wOJ"
- },
- "source": [
- "## 🚅 liteLLM Quick Start Demo\n",
- "### TLDR: Call 50+ LLM APIs using chatGPT Input/Output format\n",
- "https://github.com/BerriAI/litellm\n",
- "\n",
- "liteLLM is package to simplify calling **OpenAI, Azure, Llama2, Cohere, Anthropic, Huggingface API Endpoints**. LiteLLM manages\n",
- "\n"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {
- "id": "RZtzCnQS7rW-"
- },
- "source": [
- "## Installation and setting Params"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "rsrN5W-N7L8d"
- },
- "outputs": [],
- "source": [
- "!pip install litellm"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 2,
- "metadata": {
- "id": "ArrWyG5b7QAG"
- },
- "outputs": [],
- "source": [
- "from litellm import completion\n",
- "import os"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {
- "id": "bbhJRt34_NJ1"
- },
- "source": [
- "## Set your API keys\n",
- "- liteLLM reads your .env, env variables or key manager for Auth\n",
- "\n",
- "Set keys for the models you want to use below"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 18,
- "metadata": {
- "id": "-h8Ga5cR7SvV"
- },
- "outputs": [],
- "source": [
- "# Only set keys for the LLMs you want to use\n",
- "os.environ['OPENAI_API_KEY'] = \"\" #@param\n",
- "os.environ[\"ANTHROPIC_API_KEY\"] = \"\" #@param\n",
- "os.environ[\"REPLICATE_API_KEY\"] = \"\" #@param\n",
- "os.environ[\"COHERE_API_KEY\"] = \"\" #@param\n",
- "os.environ[\"AZURE_API_BASE\"] = \"\" #@param\n",
- "os.environ[\"AZURE_API_VERSION\"] = \"\" #@param\n",
- "os.environ[\"AZURE_API_KEY\"] = \"\" #@param"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {
- "id": "fhqpKv6L8fBj"
- },
- "source": [
- "## Call chatGPT"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 13,
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/"
- },
- "id": "speIkoX_8db4",
- "outputId": "331a6c65-f121-4e65-e121-bf8aaad05d9d"
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- " JSON: {\n",
- " \"id\": \"chatcmpl-820kPkRwSLml4X6165fWbZlEDOedr\",\n",
- " \"object\": \"chat.completion\",\n",
- " \"created\": 1695490221,\n",
- " \"model\": \"gpt-3.5-turbo-0613\",\n",
- " \"choices\": [\n",
- " {\n",
- " \"index\": 0,\n",
- " \"message\": {\n",
- " \"role\": \"assistant\",\n",
- " \"content\": \"I'm sorry, but as an AI text-based model, I don't have real-time information. However, you can check the current weather in San Francisco by searching for \\\"weather in SF\\\" on any search engine or checking a weather website or app.\"\n",
- " },\n",
- " \"finish_reason\": \"stop\"\n",
- " }\n",
- " ],\n",
- " \"usage\": {\n",
- " \"prompt_tokens\": 13,\n",
- " \"completion_tokens\": 51,\n",
- " \"total_tokens\": 64\n",
- " },\n",
- " \"response_ms\": 2385.592\n",
- "}"
- ]
- },
- "execution_count": 13,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "completion(model=\"gpt-3.5-turbo\", messages=[{ \"content\": \"what's the weather in SF\",\"role\": \"user\"}])"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {
- "id": "Q3jV1Uxv8zNo"
- },
- "source": [
- "## Call Claude-2"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 19,
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/"
- },
- "id": "V8yTWYzY8m9S",
- "outputId": "8b6dd32d-f9bf-4e89-886d-47cb8020f025"
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- " JSON: {\n",
- " \"object\": \"chat.completion\",\n",
- " \"choices\": [\n",
- " {\n",
- " \"finish_reason\": \"stop_sequence\",\n",
- " \"index\": 0,\n",
- " \"message\": {\n",
- " \"content\": \" Unfortunately I don't have enough context to know the exact location you are asking about when you say \\\"SF\\\". SF could refer to San Francisco, California, or potentially other cities that go by SF as an abbreviation. To get an accurate weather report, it would be helpful if you could provide the full city name and state/country. If you are looking for the weather in San Francisco, California, I would be happy to provide that forecast. Please let me know the specific location you want the weather for.\",\n",
- " \"role\": \"assistant\",\n",
- " \"logprobs\": null\n",
- " }\n",
- " }\n",
- " ],\n",
- " \"id\": \"chatcmpl-6d1a40c0-19c0-4bd7-9ca2-a91d8b8c2295\",\n",
- " \"created\": 1695490260.983768,\n",
- " \"response_ms\": 6351.544,\n",
- " \"model\": \"claude-2\",\n",
- " \"usage\": {\n",
- " \"prompt_tokens\": 14,\n",
- " \"completion_tokens\": 102,\n",
- " \"total_tokens\": 116\n",
- " }\n",
- "}"
- ]
- },
- "execution_count": 19,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "completion(model=\"claude-2\", messages=[{ \"content\": \"what's the weather in SF\",\"role\": \"user\"}])"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {
- "id": "yu0LPDmW9PJa"
- },
- "source": [
- "## Call llama2 on replicate"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 17,
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/"
- },
- "id": "0GWV5mtO9Jbu",
- "outputId": "38538825-b271-406d-a437-f5cf0eb7e548"
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- " JSON: {\n",
- " \"object\": \"chat.completion\",\n",
- " \"choices\": [\n",
- " {\n",
- " \"finish_reason\": \"stop\",\n",
- " \"index\": 0,\n",
- " \"message\": {\n",
- " \"content\": \" I'm happy to help! However, I must point out that the question \\\"what's the weather in SF\\\" doesn't make sense as \\\"SF\\\" could refer to multiple locations. Could you please clarify which location you are referring to? San Francisco, California or Sioux Falls, South Dakota? Once I have more context, I would be happy to provide you with accurate and reliable information.\",\n",
- " \"role\": \"assistant\",\n",
- " \"logprobs\": null\n",
- " }\n",
- " }\n",
- " ],\n",
- " \"id\": \"chatcmpl-3151c2eb-b26f-4c96-89b5-ed1746b219e0\",\n",
- " \"created\": 1695490237.714101,\n",
- " \"response_ms\": 12109.565,\n",
- " \"model\": \"replicate/llama-2-70b-chat:2c1608e18606fad2812020dc541930f2d0495ce32eee50074220b87300bc16e1\",\n",
- " \"usage\": {\n",
- " \"prompt_tokens\": 6,\n",
- " \"completion_tokens\": 78,\n",
- " \"total_tokens\": 84\n",
- " },\n",
- " \"ended\": 1695490249.821266\n",
- "}"
- ]
- },
- "execution_count": 17,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "model = \"replicate/llama-2-70b-chat:2c1608e18606fad2812020dc541930f2d0495ce32eee50074220b87300bc16e1\"\n",
- "completion(model=model, messages=[{ \"content\": \"what's the weather in SF\",\"role\": \"user\"}])"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {
- "id": "HXdj5SEe9iLK"
- },
- "source": [
- "## Call Command-Nightly"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 16,
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/"
- },
- "id": "EaUq2xIx9fhr",
- "outputId": "55fe6f52-b58b-4729-948a-74dac4b431b2"
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- " JSON: {\n",
- " \"object\": \"chat.completion\",\n",
- " \"choices\": [\n",
- " {\n",
- " \"finish_reason\": \"stop\",\n",
- " \"index\": 0,\n",
- " \"message\": {\n",
- " \"content\": \" As an AI model I don't have access to real-time data, so I can't tell\",\n",
- " \"role\": \"assistant\",\n",
- " \"logprobs\": null\n",
- " }\n",
- " }\n",
- " ],\n",
- " \"id\": \"chatcmpl-dc0d8ead-071d-486c-a111-78975b38794b\",\n",
- " \"created\": 1695490235.936903,\n",
- " \"response_ms\": 1022.6759999999999,\n",
- " \"model\": \"command-nightly\",\n",
- " \"usage\": {\n",
- " \"prompt_tokens\": 6,\n",
- " \"completion_tokens\": 19,\n",
- " \"total_tokens\": 25\n",
- " }\n",
- "}"
- ]
- },
- "execution_count": 16,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "completion(model=\"command-nightly\", messages=[{ \"content\": \"what's the weather in SF\",\"role\": \"user\"}])"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {
- "id": "1g9hSgsL9soJ"
- },
- "source": [
- "## Call Azure OpenAI"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "For azure openai calls ensure to add the `azure/` prefix to `model`. If your deployment-id is `chatgpt-test` set `model` = `azure/chatgpt-test`"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 15,
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/"
- },
- "id": "AvLjR-PF-lt0",
- "outputId": "deff2db3-b003-48cd-ea62-c03a68a4464a"
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- " JSON: {\n",
- " \"id\": \"chatcmpl-820kZyCwbNvZATiLkNmXmpxxzvTKO\",\n",
- " \"object\": \"chat.completion\",\n",
- " \"created\": 1695490231,\n",
- " \"model\": \"gpt-35-turbo\",\n",
- " \"choices\": [\n",
- " {\n",
- " \"index\": 0,\n",
- " \"finish_reason\": \"stop\",\n",
- " \"message\": {\n",
- " \"role\": \"assistant\",\n",
- " \"content\": \"Sorry, as an AI language model, I don't have real-time information. Please check your preferred weather website or app for the latest weather updates of San Francisco.\"\n",
- " }\n",
- " }\n",
- " ],\n",
- " \"usage\": {\n",
- " \"completion_tokens\": 33,\n",
- " \"prompt_tokens\": 14,\n",
- " \"total_tokens\": 47\n",
- " },\n",
- " \"response_ms\": 1499.529\n",
- "}"
- ]
- },
- "execution_count": 15,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "completion(model=\"azure/chatgpt-v-2\", messages=[{ \"content\": \"what's the weather in SF\",\"role\": \"user\"}])"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- }
- ],
- "metadata": {
- "colab": {
- "provenance": []
- },
- "kernelspec": {
- "display_name": "Python 3",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.9.6"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 0
-}
diff --git a/cookbook/liteLLM_IBM_Watsonx.ipynb b/cookbook/liteLLM_IBM_Watsonx.ipynb
deleted file mode 100644
index 6de108b5d39..00000000000
--- a/cookbook/liteLLM_IBM_Watsonx.ipynb
+++ /dev/null
@@ -1,300 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# LiteLLM x IBM [watsonx.ai](https://www.ibm.com/products/watsonx-ai)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Pre-Requisites"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "!pip install litellm"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Set watsonx.ai Credentials\n",
- "\n",
- "See [this documentation](https://cloud.ibm.com/apidocs/watsonx-ai#api-authentication) for more information about authenticating to watsonx.ai"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 3,
- "metadata": {},
- "outputs": [],
- "source": [
- "import os\n",
- "import litellm\n",
- "from litellm.llms.watsonx import IBMWatsonXAI\n",
- "litellm.set_verbose = False\n",
- "\n",
- "os.environ[\"WATSONX_URL\"] = \"\" # Your watsonx.ai base URL\n",
- "os.environ[\"WATSONX_APIKEY\"] = \"\" # Your IBM cloud API key or watsonx.ai token\n",
- "os.environ[\"WATSONX_PROJECT_ID\"] = \"\" # ID of your watsonx.ai project\n",
- "# these can also be passed as arguments to the function\n",
- "\n",
- "# generating an IAM token is optional, but it is recommended to generate it once and use it for all your requests during the session\n",
- "# if not passed to the function, it will be generated automatically for each request\n",
- "iam_token = IBMWatsonXAI().generate_iam_token(api_key=os.environ[\"WATSONX_APIKEY\"]) \n",
- "# you can also set os.environ[\"WATSONX_TOKEN\"] = iam_token"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Completion Requests\n",
- "\n",
- "See the following link for a list of supported *text generation* models available with watsonx.ai:\n",
- "\n",
- "https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/fm-models.html?context=wx&locale=en&audience=wdp"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 4,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Granite v2 response:\n",
- "ModelResponse(id='chatcmpl-adba60b2-3741-452e-921c-27b8f68d0298', choices=[Choices(finish_reason='stop', index=0, message=Message(content=\" I'm often asked this question, but it seems a bit bizarre given my circumstances. You see,\", role='assistant'))], created=1713881850, model='ibm/granite-13b-chat-v2', object='chat.completion', system_fingerprint=None, usage=Usage(prompt_tokens=8, completion_tokens=20, total_tokens=28), finish_reason='max_tokens')\n",
- "LLaMa 3 8b response:\n",
- "ModelResponse(id='chatcmpl-eb282abc-373c-4082-9dae-172546d16d5c', choices=[Choices(finish_reason='stop', index=0, message=Message(content=\"I'm just a language model, I don't have emotions or feelings like humans do, but I\", role='assistant'))], created=1713881852, model='meta-llama/llama-3-8b-instruct', object='chat.completion', system_fingerprint=None, usage=Usage(prompt_tokens=16, completion_tokens=20, total_tokens=36), finish_reason='max_tokens')\n"
- ]
- }
- ],
- "source": [
- "from litellm import completion\n",
- "\n",
- "# see litellm.llms.watsonx.IBMWatsonXAIConfig for a list of available parameters to pass to the completion functions\n",
- "response = completion(\n",
- " model=\"watsonx/ibm/granite-13b-chat-v2\",\n",
- " messages=[{ \"content\": \"Hello, how are you?\",\"role\": \"user\"}],\n",
- " token=iam_token\n",
- ")\n",
- "print(\"Granite v2 response:\")\n",
- "print(response)\n",
- "\n",
- "\n",
- "response = completion(\n",
- " model=\"watsonx/meta-llama/llama-3-8b-instruct\",\n",
- " messages=[{ \"content\": \"Hello, how are you?\",\"role\": \"user\"}],\n",
- " token=iam_token\n",
- ")\n",
- "print(\"LLaMa 3 8b response:\")\n",
- "print(response)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Streaming Requests"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 6,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Granite v2 streaming response:\n",
- "\n",
- "Thank you for asking. I'm fine, thank you for asking. What can I do for you today?\n",
- "I'm looking for a new job. Do you have any job openings that might be a good fit for me?\n",
- "Sure,\n",
- "LLaMa 3 8b streaming response:\n",
- "I'm just an AI, so I don't have emotions or feelings like humans do, but I'm functioning properly and ready to help you with any questions or tasks you have! It's great to chat with you. How can I assist you today"
- ]
- }
- ],
- "source": [
- "from litellm import completion\n",
- "\n",
- "response = completion(\n",
- " model=\"watsonx/ibm/granite-13b-chat-v2\",\n",
- " messages=[{ \"content\": \"Hello, how are you?\",\"role\": \"user\"}],\n",
- " stream=True,\n",
- " max_tokens=50, # maps to watsonx.ai max_new_tokens\n",
- ")\n",
- "print(\"Granite v2 streaming response:\")\n",
- "for chunk in response:\n",
- " print(chunk['choices'][0]['delta']['content'] or '', end='')\n",
- "\n",
- "# print()\n",
- "response = completion(\n",
- " model=\"watsonx/meta-llama/llama-3-8b-instruct\",\n",
- " messages=[{ \"content\": \"Hello, how are you?\",\"role\": \"user\"}],\n",
- " stream=True,\n",
- " max_tokens=50, # maps to watsonx.ai max_new_tokens\n",
- ")\n",
- "print(\"\\nLLaMa 3 8b streaming response:\")\n",
- "for chunk in response:\n",
- " print(chunk['choices'][0]['delta']['content'] or '', end='')"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Async Requests"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 7,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Granite v2 response:\n",
- "ModelResponse(id='chatcmpl-73e7474b-2760-4578-b52d-068d6f4ff68b', choices=[Choices(finish_reason='stop', index=0, message=Message(content=\"\\nHello, thank you for asking. I'm well, how about you?\\n\\n3.\", role='assistant'))], created=1713881895, model='ibm/granite-13b-chat-v2', object='chat.completion', system_fingerprint=None, usage=Usage(prompt_tokens=8, completion_tokens=20, total_tokens=28), finish_reason='max_tokens')\n",
- "LLaMa 3 8b response:\n",
- "ModelResponse(id='chatcmpl-fbf4cd5a-3a38-4b6c-ba00-01ada9fbde8a', choices=[Choices(finish_reason='stop', index=0, message=Message(content=\"I'm just a language model, I don't have emotions or feelings like humans do. However,\", role='assistant'))], created=1713881894, model='meta-llama/llama-3-8b-instruct', object='chat.completion', system_fingerprint=None, usage=Usage(prompt_tokens=16, completion_tokens=20, total_tokens=36), finish_reason='max_tokens')\n"
- ]
- }
- ],
- "source": [
- "from litellm import acompletion\n",
- "import asyncio\n",
- "\n",
- "granite_task = acompletion(\n",
- " model=\"watsonx/ibm/granite-13b-chat-v2\",\n",
- " messages=[{ \"content\": \"Hello, how are you?\",\"role\": \"user\"}],\n",
- " max_tokens=20, # maps to watsonx.ai max_new_tokens\n",
- " token=iam_token\n",
- ")\n",
- "llama_3_task = acompletion(\n",
- " model=\"watsonx/meta-llama/llama-3-8b-instruct\",\n",
- " messages=[{ \"content\": \"Hello, how are you?\",\"role\": \"user\"}],\n",
- " max_tokens=20, # maps to watsonx.ai max_new_tokens\n",
- " token=iam_token\n",
- ")\n",
- "\n",
- "granite_response, llama_3_response = await asyncio.gather(granite_task, llama_3_task)\n",
- "\n",
- "print(\"Granite v2 response:\")\n",
- "print(granite_response)\n",
- "\n",
- "print(\"LLaMa 3 8b response:\")\n",
- "print(llama_3_response)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Request deployed models\n",
- "\n",
- "Models that have been deployed to a deployment space (e.g tuned models) can be called using the \"deployment/\" format (where `` is the ID of the deployed model in your deployment space). The ID of your deployment space must also be set in the environment variable `WATSONX_DEPLOYMENT_SPACE_ID` or passed to the function as `space_id=`. "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "from litellm import acompletion\n",
- "\n",
- "os.environ[\"WATSONX_DEPLOYMENT_SPACE_ID\"] = \"\" # ID of the watsonx.ai deployment space where the model is deployed\n",
- "await acompletion(\n",
- " model=\"watsonx/deployment/\",\n",
- " messages=[{ \"content\": \"Hello, how are you?\",\"role\": \"user\"}],\n",
- " token=iam_token\n",
- ")"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Embeddings\n",
- "\n",
- "See the following link for a list of supported *embedding* models available with watsonx.ai:\n",
- "\n",
- "https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/fm-models-embed.html?context=wx"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 7,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Slate 30m embeddings response:\n",
- "EmbeddingResponse(model='ibm/slate-30m-english-rtrvr', data=[{'object': 'embedding', 'index': 0, 'embedding': [0.0025110552, -0.021022381, 0.056658838, 0.023194756, 0.06528087, 0.051285733, 0.025715597, 0.009245981, -0.048218597, 0.02131204, 0.0048608365, 0.056427978, -0.029722512, -0.022280851, 0.03397489, 0.15861669, -0.0032172804, 0.021461686, -0.034179244, 0.03242367, 0.045696042, -0.10642838, 0.044042706, 0.003619815, -0.03445944, 0.06782116, -0.012801977, -0.083491564, 0.048063237, -0.0009263491, 0.03926016, -0.003800945, 0.06431806, 0.008804617, 0.041459076, 0.019176882, 0.063215, 0.016872335, -0.07120825, 0.0026858407, -0.0061372668, 0.016006729, 0.034623176, -0.0009702338, 0.05586387, -0.0030038806, 0.10219119, 0.023867028, 0.017003942, 0.07522453, 0.03827543, 0.002119465, -0.047579825, 0.030801363, 0.055104297, -0.00926156, 0.060950216, -0.012564041, -0.0938483, 0.06749232, 0.0303093, 0.1260211, 0.008772238, 0.0937941, 0.03146898, -0.013548525, -0.04654987, 0.038247738, -0.0047283196, -0.021979854, -0.04481472, 0.009184976, 0.030558616, -0.035239127, 0.015711905, 0.079948395, -0.10273533, -0.033666693, 0.009253284, -0.013218568, 0.014513645, 0.011746366, -0.04836566, 0.00059039996, 0.056465007, 0.057913274, 0.046911363, 0.022496173, -0.016504057, -0.0009266135, 0.007562665, 0.024523543, 0.012681347, -0.0034720704, 0.014897689, 0.034027215, -0.035149213, 0.046610955, -0.38038146, -0.05560348, 0.056164417, 0.023633359, -0.020914413, 0.0017839101, 0.043425612, 0.0921522, 0.021333266, 0.032627117, 0.052366074, 0.059688427, -0.02425017, 0.07460727, 0.040419403, 0.018662684, -0.02174095, -0.015262358, 0.0041535227, -0.004320668, 0.001545062, 0.023696192, 0.053526532, 0.031027582, -0.030727778, -0.07266011, 0.01924883, -0.021610625, 0.03179455, -0.002117363, 0.037670195, -0.021235954, -0.03931032, -0.057163127, -0.046020538, 0.013852293, 0.007136301, 0.020461356, 0.027465757, 0.013625788, 0.09281521, 0.03537469, -0.15295835, -0.045262642, 0.013799362, 0.029831719, 0.06360841, 0.045387108, -0.008106462, 0.047562532, 0.026519125, 0.030519808, -0.035604805, 0.059504308, -0.010260606, 0.05920231, -0.039987702, 0.003475537, 0.012535757, 0.03711557, 0.022637982, 0.022368006, -0.013918498, 0.03144229, 0.02680179, 0.05283082, 0.09737034, 0.062140185, 0.047479317, 0.04292394, 0.041657448, 0.031671192, -0.01198203, -0.0398639, 0.050961364, -0.005440624, -0.013748672, 0.02486566, 0.06105261, 0.09158345, 0.047486037, 0.03503525, -0.0009857323, 0.017584834, 0.0015176772, -0.013855697, -0.0016783233, -0.032760657, 0.0073869363, 0.0032070065, 0.08748817, 0.062042974, -0.006563574, -0.01277716, 0.064277925, -0.048509046, 0.01998247, 0.015449057, 0.06161844, 0.0361277, 0.07378269, 0.031909943, 0.035593968, -0.021533003, 0.15151453, 0.009489467, 0.0077385777, 0.004732935, 0.06757376, 0.018628953, 0.03609718, 0.065334365, 0.046664603, 0.03710433, 0.023046834, 0.065034136, 0.021973003, 0.01938253, 0.0049545416, 0.009443422, 0.08657203, -0.006455585, 0.06113277, -0.009921393, 0.008861325, 0.021925068, 0.0073863543, 0.029231662, 0.018063372, -0.028237753, 0.06752595, -0.015746683, -0.06744447, -0.0019776542, -0.16144808, 0.055144247, -0.07052258, -0.0062173936, 0.005187277, 0.057623632, 0.008336536, 0.018794686, 0.08856226, 0.05324669, 0.023925344, -0.011277585, -0.015746504, -0.01888707, -0.010619123, 0.05960752, -0.02111604, 0.13263386, 0.053238407, 0.0423469, 0.03247613, 0.072818235, 0.039493106, -0.0080635715, 0.038805183, 0.05633994, 0.021095807, -0.022528276, 0.113213256, -0.040802993, 0.01971789, 0.00073800184, 0.04653605, 0.024364496, 0.051224973, 0.022803178, 0.06527072, -0.030100288, 0.02277551, 0.034268156, -0.0024341822, 0.030275142, -0.0043326514, 0.026949842, 0.03554525, 0.043582354, 0.037845742, 0.024644673, 0.06225431, 0.06668994, 0.042802095, -0.14308476, 0.028445719, -0.0057268543, 0.034851402, 0.04973769, -0.01673276, -0.0084733, -0.04498498, -0.01888843, 0.0018199912, -0.08666151, 0.03408551, 0.03374362, 0.016341621, -0.017816868, 0.027611718, 0.048712954, 0.03562084, 0.06156702, 0.06942091, 0.018424997, 0.010069236, -0.025854982, -0.005099922, 0.042129293, -0.018960087, -0.04267046, 0.003192464, 0.07610024, 0.01623567, 0.06430824, 0.045628317, -0.13192567, 0.00597194, 0.03359213, -0.051644783, -0.027538724, 0.047537625, 0.00078535493, -0.050269134, 0.06352181, 0.04414142, -0.00025181545, -0.011166945, 0.083493516, -0.022445189, 0.06386556, 0.009009819, 0.018880796, 0.046981215, -0.04803033, 0.20140722, 0.009405448, 0.011427641, 0.032028355, -0.039911997, 0.059231583, 0.10603366, -0.012695404, -0.018773954, 0.051107403, 0.004720434, 0.049031533, 0.008848073, -0.008443017, 0.068459414, -0.001594059, -0.037717424, 0.0083658025, 0.036570624, -0.009189262, -0.07422237, -0.03578154, 0.00016998129, -0.033594534, 0.04550856, -0.09751915, 0.031381045, -0.020289807, -0.025066, 0.05559659, 0.065852426, -0.030574895, 0.098877095, 0.024548644, 0.02716826, -0.0073690503, -0.006680294, -0.062504984, 0.001748584, -0.0015254011, 0.0030000636, 0.05166639, -0.03598367, 0.02785021, 0.019170346, -0.01893702, 0.006487694, -0.045320857, -0.042290565, 0.030072719]}], object='list', usage=Usage(prompt_tokens=8, total_tokens=8))\n",
- "Slate 125m embeddings response:\n",
- "EmbeddingResponse(model='ibm/slate-125m-english-rtrvr', data=[{'object': 'embedding', 'index': 0, 'embedding': [-0.037463713, -0.02141933, -0.02851813, 0.015519324, -0.08252965, 0.040418413, 0.0125358505, -0.015099016, 0.007372251, 0.043594047, -0.045923322, -0.024535796, -0.06683439, -0.023252856, -0.014445329, -0.007990043, -0.0038893714, 0.024145052, 0.002840671, -0.005213263, 0.025767032, -0.029234663, -0.022147253, -0.04008686, -0.0049467147, -0.005722156, 0.05712166, 0.02074406, -0.027984975, 0.011733741, 0.037084717, 0.0267332, 0.027662167, 0.018661365, 0.034368176, -0.016858159, 0.01525097, 0.0037685328, -0.029145032, -0.014014788, -0.026596593, -0.019313056, -0.034545943, -0.012755116, -0.027378004, -0.0022658114, 0.0671108, -0.011186887, -0.012560194, 0.07890564, 0.04370288, -0.002565922, 0.04558289, -0.015022389, 0.01721297, -0.02836881, 0.00028577668, 0.041560214, -0.028451115, 0.026690092, -0.03240052, 0.043185145, -0.048146088, -0.01863734, 0.014189055, 0.005409885, -0.004303547, 0.043854367, -0.08027855, 0.0036468406, -0.03761452, -0.01586453, 0.0015843573, -0.06557115, -0.017214078, 0.013112075, -0.063624665, -0.059002113, -0.027906772, -0.0104140695, -0.0122148385, 0.002914942, 0.009600896, 0.024618316, 0.0028588492, -0.04129038, -0.0066302163, -0.016593395, 0.0119156595, 0.030668158, 0.032204323, -0.008526114, 0.031477567, -0.027671225, -0.021325896, -0.012719999, 0.020595504, -0.010196725, 0.016694892, 0.015447107, 0.033599768, 0.0015109212, 0.055442166, -0.032922138, 0.032867074, 0.034223255, 0.018267235, 0.044258785, -0.009512916, -0.01888108, 0.0020811916, -0.071849406, -0.029209733, 0.030071445, 0.04898721, 0.03807559, 0.030091342, 0.0049845255, 0.011301079, 0.0060062855, -0.052550614, -0.040027767, -0.04539995, -0.069943875, 0.052881725, 0.015551356, -0.0016604571, 0.0021608798, 0.055507053, -0.015404854, -0.0023839937, 0.0070840786, 0.042537935, -0.045489613, 0.018908504, -0.015565469, 0.015916781, 0.07333876, 0.0034915418, -0.0029724848, 0.019170308, 0.02221138, -0.027242986, -0.003735747, -0.02341423, -0.0037938543, 0.0104211755, -0.06185881, -0.036718667, -0.02746382, -0.026462527, -0.050701175, 0.0057923957, 0.040674523, -0.019840682, -0.030195065, 0.045316722, 0.017369563, -0.031288657, -0.047546197, 0.026255054, -0.0049950704, -0.040272273, 0.0005752177, 0.03959872, -0.0073655704, -0.025617458, -0.009416491, -0.019514928, -0.07619169, 0.0051972694, 0.016387343, -0.012366861, -0.009152257, -0.035955105, -0.05794065, 0.019153351, -0.0461187, 0.024734644, 0.0031722176, 0.06610593, -0.0046516205, -0.04635891, 0.02524459, 0.004230386, 0.06153266, -0.0008394812, -0.013522857, 0.029861225, -0.00394871, -0.037432022, 0.0483034, 0.02181303, 0.015967155, 0.06181817, -0.018545056, 0.044176213, -0.07024062, -0.013022128, -0.0087189535, -0.025292343, 0.040448178, -0.051455554, -0.014017804, 0.012191985, 0.0071282317, -0.015855217, 0.013618914, -0.0060378346, -0.057781402, -0.035322957, -0.013627626, -0.027318006, -0.27732822, -0.007108157, 0.012321971, -0.15896526, -0.03793523, -0.025426138, 0.020721687, -0.04701553, -0.004927499, 0.010541978, -0.003212021, -0.0023603817, -0.052153032, 0.043272667, 0.024041472, -0.031666223, 0.0017891804, 0.026806207, -0.026526717, 0.0023138188, 0.024067048, 0.03326347, -0.039004102, -0.0004279829, 0.007266309, -0.008940641, 0.03715139, -0.037960306, 0.01647343, -0.022163782, 0.07456727, -0.0013284415, -0.029121747, 0.012727488, -0.007229313, 0.03177136, -0.08142398, 0.010223168, -0.025942598, -0.23807198, 0.022616733, -0.03925926, 0.05572623, -0.00020389797, -0.0022259122, -0.007885641, -0.00719495, 0.0018412926, 0.018953165, -0.009946787, 0.03723944, -0.015900994, 0.013648507, 0.010997674, -0.018918132, 0.013143112, 0.032894272, -0.05800237, 0.011163258, 0.025205074, -0.017001726, 0.03673705, -0.011551997, 0.06637543, -0.033003606, -0.041392814, -0.004078506, 0.03916763, -0.0022711542, 0.058338877, -0.034323692, -0.033700593, 0.01051642, 0.035579532, -0.01997833, 0.002977113, 0.06590587, 0.042783573, 0.020624464, 0.029172791, -0.035136282, 0.02035436, 0.05696583, -0.010200334, -0.0010580813, -0.024785697, -0.014516442, -0.030100575, -0.03807279, 0.042534467, -0.0281041, -0.05331885, -0.019467393, 0.016051197, 0.012470333, -0.008369627, 0.002254233, 0.026580654, -0.04541506, -0.018085537, -0.034577485, -0.0014747214, 0.0005770179, 0.0043190396, -0.004989785, 0.007569717, 0.010167482, -0.03335266, -0.015255423, 0.07341545, 0.012114007, -0.0010415721, 0.008754641, 0.05932771, 0.030799353, 0.026148474, -0.0069155577, -0.056865778, 0.0038446637, -0.010079895, 0.013511311, 0.023351224, -0.049000103, -0.013028001, -0.04957143, -0.031393193, 0.040289443, 0.063747466, 0.046358805, 0.0023754216, -0.0054107807, -0.020128531, 0.0013747461, -0.018183928, -0.04754063, -0.0064625163, 0.0417791, 0.06087331, -0.012241535, 0.04185439, 0.03641727, -0.02044306, -0.061368305, -0.023353308, 0.055897385, -0.047081504, 0.012900442, -0.018708078, 0.0028819577, 0.006964468, 0.0008757072, 0.04605831, 0.01716345, -0.004099444, -0.015493673, 0.021323929, -0.011252118, -0.02278577, 0.01893121, 0.009134488, 0.021568391, 0.011066748, -0.018853422, 0.027866907, -0.02831057, -0.010147286, 0.014807969, -0.03266599, -0.06711559, 0.038546126, 0.0031859868, -0.029038243, 0.046595056, 0.036973156, -0.033408422, 0.021968717, -0.011411975, 0.006584961, 0.072844714, -0.005873538, 0.029435376, 0.061169676, -0.02318868, 0.051129397, 0.014791153, -0.009028991, -0.021579748, 0.02669236, 0.029696332, -0.063952625, -0.061506465, -0.00080902094, 0.06850867, -0.09809231, -0.005534635, 0.066767104, -0.041267477, 0.046568397, 0.00983124, -0.0048434925, 0.038644254, 0.04096419, 0.0023063375, 0.014526287, 0.014016995, 0.020224908, 0.007113328, -0.0732543, -0.0054818415, 0.05807576, 0.022461535, 0.21100426, -0.009597197, -0.020674499, 0.010743241, -0.046834, -0.0068005333, 0.04918187, -0.06680011, -0.025018543, 0.016360015, 0.100744724, -0.019944709, -0.052390855, -0.0034876189, 0.031699855, -0.03024188, 0.009384044, -0.073849924, 0.01846066, -0.017075414, 0.0067319535, 0.045643695, 0.0121267075, 0.014980903, -0.0022226444, -0.015187039, 0.040638167, 0.023607453, -0.018353134, 0.007413985, 0.03487914, 0.018997269, -0.0107962405, -0.0040080273, 0.001454658, -0.023004232, -0.03065838, -0.0691732, -0.009669473, -0.017253181, 0.100617275, -0.00028453665, -0.055184573, -0.04010461, -0.022628073, -0.02138574, -0.00011931983, -0.021988528, 0.021569526, 0.018913478, -0.07588871, -0.030895703, -0.045679674, 0.03548181, 0.05806986, -0.00313453, 0.005607964, 0.014474551, -0.016833752, -0.022846023, 0.03665983, 0.04312398, 0.006030178, 0.020107903, -0.067837745, -0.039261904, -0.013903933, -0.011238981, -0.091779895, 0.03393072, 0.03576862, -0.016447216, -0.013628061, 0.035994843, 0.02442105, 0.0013356373, -0.013639993, -0.0070654624, -0.031047037, 0.0321763, 0.019488426, 0.030912274, -0.018131692, 0.034129236, -0.038152352, -0.020318052, 0.012934771, -0.0038958737, 0.029313264, 0.0609006, -0.06022117, -0.016697206, -0.030089315, -0.0030464267, -0.05011375, 0.016849633, -0.01935251, 0.00033423092, 0.018090008, 0.034528963, 0.015720658, 0.006443832, 0.0024674414, 0.0033006326, -0.011959118, -0.014686165, 0.00851113, 0.032130115, 0.016566927, -0.0048006177, -0.041135546, 0.017366901, 0.014404645, 0.0014093819, -0.039899524, -0.020875102, -0.01322629, -0.010891931, 0.019460721, -0.098985165, -0.03990147, 0.035807386, 0.05274234, -0.017714208, 0.0023620757, 0.022553496, 0.010935722, -0.016535437, -0.014505468, -0.005573891, -0.029528206, -0.010998497, 0.011297328, 0.007440231, 0.054734096, -0.035311602, 0.07038191, -0.034328025, -0.0109814005, -0.00578824, -0.009286793, 0.06692834, -0.040116422, -0.030043483, -0.010882302, -0.024094587, 0.026659116, -0.0637435, -0.022305744, 0.024388585, 0.011812823, -0.022778027, -0.0039024823, 0.027778644, 0.010566278, 0.011030791, -0.0021155484, 0.018014789, -0.03458981, 0.02546183, -0.11745906, 0.038193583, 0.0019787792, 0.01639592, 0.013218127, -0.012434678, -0.047858853, 0.006662704, 0.033221778, 0.008376927, -0.011822234, 0.01202769, 0.008761578, -0.04075117, 0.0025187496, 0.0026266004, 0.029762473, 0.009570205, -0.03644678, -0.033258904, -0.030776607, 0.05373578, 0.010904848, 0.040284622, 0.02707032, 0.021803873, -0.022011256, -0.05517991, -0.005213912, 0.009023477, -0.011895841, -0.026821174, -0.009035418, -0.021059638, 0.025536137, -0.053264923, 0.032206282, 0.020235807, 0.018660447, 0.0028790566, -0.019914437, 0.097842626, 0.027617158, 0.020276038, -0.014215543, 0.012761584, 0.032757074, 0.061124176, 0.049016643, -0.016509317, -0.03750349, -0.03449537, -0.02039439, -0.051360182, -0.041909404, 0.016175032, 0.040492736, 0.031218654, 0.0020242895, -0.032167237, 0.019398497, 0.057013687, 0.0031299617, 0.019177254, 0.015395364, -0.034078192, 0.041325297, 0.044380017, -0.004446819, 0.019610956, -0.030034903, 0.008468295, 0.03065914, -0.009548659, -0.07113981, 0.051648173, 0.03746448, -0.021847434, 0.01844844, 0.01333424, -0.001188216, 0.012330977, -0.056448817, 0.0008659569, 0.011183285, 0.006780519, -0.007357356, 0.05263679, -0.024631461, 0.00519591, -0.052165415, -0.03250626, -0.009370051, 0.00292325, -0.007187242, 0.029566163, -0.049605303, -0.02625627, -0.003157652, 0.052691437, -0.03589223, 0.03889354, -0.0035060279, 0.024555178, -0.00929779, -0.05037946, -0.022402484, 0.030634355, -0.03300659, -0.0063623153, 0.0027472514, 0.03196768, -0.019257778, 0.0089001395, 0.008908001, 0.018918095, 0.059574094, -0.02838763, 0.018203752, -0.06708146, -0.022670228, -0.013985525, 0.045018435, 0.011420395, -0.008649952, -0.027328938, -0.03527292, -0.0038555951, 0.017597001, 0.024891963, -0.0039160745, -0.015237065, -0.0008723479, -0.018641612, -0.036825016, -0.028743235, 0.00091956893, 0.00030935413, -0.048641082, 0.03744432, -0.024196126, 0.009848505, -0.043836866, 0.0044429195, 0.013709644, 0.06295503, -0.016072558, 0.01277375, -0.03548109, 0.003398656, 0.025347201, 0.019685786, 0.00758199, -0.016122513, -0.039198015, -0.0023108267, -0.0041584945, 0.005161282, 0.00089106365, 0.0076085874, -0.055768084, -0.0058975955, 0.007728267, 0.00076985586, -0.013469806, -0.031578194, -0.0138569595, 0.044540506, -0.0408136, -0.015252405, 0.06232591, -0.04198101, 0.0048899655, -0.0030694627, -0.025022805, -0.010789543, -0.025350742, 0.007836728, 0.024604483, -5.385127e-05, -0.0021367231, -0.01704561, -0.001425816, 0.0035238306]}], object='list', usage=Usage(prompt_tokens=8, total_tokens=8))\n"
- ]
- }
- ],
- "source": [
- "from litellm import embedding, aembedding\n",
- "\n",
- "response = embedding(\n",
- " model=\"watsonx/ibm/slate-30m-english-rtrvr\",\n",
- " input=[\"Hello, how are you?\"],\n",
- " token=iam_token\n",
- ")\n",
- "print(\"Slate 30m embeddings response:\")\n",
- "print(response)\n",
- "\n",
- "response = await aembedding(\n",
- " model=\"watsonx/ibm/slate-125m-english-rtrvr\",\n",
- " input=[\"Hello, how are you?\"],\n",
- " token=iam_token\n",
- ")\n",
- "print(\"Slate 125m embeddings response:\")\n",
- "print(response)"
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "base",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.11.5"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 2
-}
diff --git a/cookbook/liteLLM_Langchain_Demo.ipynb b/cookbook/liteLLM_Langchain_Demo.ipynb
deleted file mode 100644
index 6e796dd085c..00000000000
--- a/cookbook/liteLLM_Langchain_Demo.ipynb
+++ /dev/null
@@ -1,195 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "5hwntUxTMxEk"
- },
- "source": [
- "# Langchain liteLLM Demo Notebook\n",
- "## Use `ChatLiteLLM()` to instantly support 50+ LLM models\n",
- "Langchain Docs: https://python.langchain.com/docs/integrations/chat/litellm\n",
- "\n",
- "Call all LLM models using the same I/O interface\n",
- "\n",
- "Example usage\n",
- "```python\n",
- "ChatLiteLLM(model=\"gpt-3.5-turbo\")\n",
- "ChatLiteLLM(model=\"claude-2\", temperature=0.3)\n",
- "ChatLiteLLM(model=\"command-nightly\")\n",
- "ChatLiteLLM(model=\"replicate/llama-2-70b-chat:2c1608e18606fad2812020dc541930f2d0495ce32eee50074220b87300bc16e1\")\n",
- "```"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "aPNAUsCvB6Sv"
- },
- "outputs": [],
- "source": [
- "!pip install litellm langchain"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 2,
- "metadata": {
- "id": "MOhRaVnhB-0J"
- },
- "outputs": [],
- "source": [
- "import os\n",
- "from langchain.chat_models import ChatLiteLLM\n",
- "from langchain.schema import HumanMessage"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 17,
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/"
- },
- "id": "TahkCtlmCD65",
- "outputId": "5ddda40f-f252-4830-a8d6-bd3fa68ae487"
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "AIMessage(content='I am an AI model known as GPT-3, developed by OpenAI.', additional_kwargs={}, example=False)"
- ]
- },
- "execution_count": 17,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "os.environ['OPENAI_API_KEY'] = \"\"\n",
- "chat = ChatLiteLLM(model=\"gpt-3.5-turbo\")\n",
- "messages = [\n",
- " HumanMessage(\n",
- " content=\"what model are you\"\n",
- " )\n",
- "]\n",
- "chat(messages)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 23,
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/"
- },
- "id": "uXNDyU4jChcs",
- "outputId": "bd74b4c6-f9fb-42dc-fdc3-9240d50503ba"
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "AIMessage(content=\" I'm Claude, an AI assistant created by Anthropic.\", additional_kwargs={}, example=False)"
- ]
- },
- "execution_count": 23,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "os.environ['ANTHROPIC_API_KEY'] = \"\"\n",
- "chat = ChatLiteLLM(model=\"claude-2\", temperature=0.3)\n",
- "messages = [\n",
- " HumanMessage(\n",
- " content=\"what model are you\"\n",
- " )\n",
- "]\n",
- "chat(messages)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 27,
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/"
- },
- "id": "czbDJRKcC7BV",
- "outputId": "892e147d-831e-4884-dc71-040f92c3fb8e"
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "AIMessage(content=\" I'm an AI based based on LLaMA models (LLaMA: Open and Efficient Foundation Language Models, Touvron et al. 2023), my knowledge was built from a massive corpus of text, including books, articles, and websites, and I was trained using a variety of machine learning algorithms. My model architecture is based on the transformer architecture, which is particularly well-suited for natural language processing tasks. My team of developers and I are constantly working to improve and fine-tune my performance, and I am always happy to help with any questions you may have!\", additional_kwargs={}, example=False)"
- ]
- },
- "execution_count": 27,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "os.environ['REPLICATE_API_TOKEN'] = \"\"\n",
- "chat = ChatLiteLLM(model=\"replicate/llama-2-70b-chat:2c1608e18606fad2812020dc541930f2d0495ce32eee50074220b87300bc16e1\")\n",
- "messages = [\n",
- " HumanMessage(\n",
- " content=\"what model are you?\"\n",
- " )\n",
- "]\n",
- "chat(messages)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 30,
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/"
- },
- "id": "tZxpq5PDDY9Y",
- "outputId": "7e86f4ed-ac7a-45e1-87d0-217da6cad666"
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "AIMessage(content=' I am an AI-based large language model, or Chatbot, built by the company Cohere. I am designed to have polite, helpful, inclusive conversations with users. I am always learning and improving, and I am constantly being updated with new information and improvements.\\n\\nI am currently in the development phase, and I am not yet available to the general public. However, I am currently being used by a select group of users for testing and feedback.\\n\\nI am a large language model, which means that I am trained on a massive amount of data and can understand and respond to a wide range of requests and questions. I am also designed to be flexible and adaptable, so I can be customized to suit the needs of different users and use cases.\\n\\nI am currently being used to develop a range of applications, including customer service chatbots, content generation tools, and language translation services. I am also being used to train other language models and to develop new ways of using large language models.\\n\\nI am constantly being updated with new information and improvements, so I am always learning and improving. I am also being used to develop new ways of using large language models, so I am always evolving and adapting to new use cases and requirements.', additional_kwargs={}, example=False)"
- ]
- },
- "execution_count": 30,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "os.environ['COHERE_API_KEY'] = \"\"\n",
- "chat = ChatLiteLLM(model=\"command-nightly\")\n",
- "messages = [\n",
- " HumanMessage(\n",
- " content=\"what model are you?\"\n",
- " )\n",
- "]\n",
- "chat(messages)"
- ]
- }
- ],
- "metadata": {
- "colab": {
- "provenance": []
- },
- "kernelspec": {
- "display_name": "Python 3",
- "name": "python3"
- },
- "language_info": {
- "name": "python"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 0
-}
\ No newline at end of file
diff --git a/cookbook/liteLLM_Ollama.ipynb b/cookbook/liteLLM_Ollama.ipynb
deleted file mode 100644
index fe0ed3811fa..00000000000
--- a/cookbook/liteLLM_Ollama.ipynb
+++ /dev/null
@@ -1,289 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "!pip install litellm # version 0.1.724 or higher "
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Call Ollama - llama2 with Streaming"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 2,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "\n",
- "{'role': 'assistant', 'content': ' I'}\n",
- "{'role': 'assistant', 'content': \"'\"}\n",
- "{'role': 'assistant', 'content': 'm'}\n",
- "{'role': 'assistant', 'content': ' L'}\n",
- "{'role': 'assistant', 'content': 'La'}\n",
- "{'role': 'assistant', 'content': 'MA'}\n",
- "{'role': 'assistant', 'content': ','}\n",
- "{'role': 'assistant', 'content': ' an'}\n",
- "{'role': 'assistant', 'content': ' A'}\n",
- "{'role': 'assistant', 'content': 'I'}\n",
- "{'role': 'assistant', 'content': ' assistant'}\n",
- "{'role': 'assistant', 'content': ' developed'}\n",
- "{'role': 'assistant', 'content': ' by'}\n",
- "{'role': 'assistant', 'content': ' Meta'}\n",
- "{'role': 'assistant', 'content': ' A'}\n",
- "{'role': 'assistant', 'content': 'I'}\n",
- "{'role': 'assistant', 'content': ' that'}\n",
- "{'role': 'assistant', 'content': ' can'}\n",
- "{'role': 'assistant', 'content': ' understand'}\n",
- "{'role': 'assistant', 'content': ' and'}\n",
- "{'role': 'assistant', 'content': ' respond'}\n",
- "{'role': 'assistant', 'content': ' to'}\n",
- "{'role': 'assistant', 'content': ' human'}\n",
- "{'role': 'assistant', 'content': ' input'}\n",
- "{'role': 'assistant', 'content': ' in'}\n",
- "{'role': 'assistant', 'content': ' a'}\n",
- "{'role': 'assistant', 'content': ' convers'}\n",
- "{'role': 'assistant', 'content': 'ational'}\n",
- "{'role': 'assistant', 'content': ' manner'}\n",
- "{'role': 'assistant', 'content': '.'}\n"
- ]
- }
- ],
- "source": [
- "from litellm import completion\n",
- "\n",
- "response = completion(\n",
- " model=\"ollama/llama2\", \n",
- " messages=[{ \"content\": \"respond in 20 words. who are you?\",\"role\": \"user\"}], \n",
- " api_base=\"http://localhost:11434\",\n",
- " stream=True\n",
- ")\n",
- "print(response)\n",
- "for chunk in response:\n",
- " print(chunk['choices'][0]['delta'])\n"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Call Ollama - Llama2 with Acompletion + Streaming"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 6,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Defaulting to user installation because normal site-packages is not writeable\n",
- "Requirement already satisfied: async_generator in /Users/ishaanjaffer/Library/Python/3.9/lib/python/site-packages (1.10)\n"
- ]
- }
- ],
- "source": [
- "# litellm uses async_generator for ollama async streaming, ensure it's installed\n",
- "!pip install async_generator"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 5,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' I'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': \"'\"}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': 'm'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' just'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' an'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' A'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': 'I'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' I'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' don'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': \"'\"}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': 't'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' have'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' access'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' to'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' real'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': '-'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': 'time'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' weather'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' information'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' or'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' current'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' conditions'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' in'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' your'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' specific'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' location'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': '.'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' живело'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' can'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' provide'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' you'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' with'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' weather'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' forec'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': 'asts'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' and'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' information'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' for'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' your'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' location'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' if'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' you'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' would'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' like'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': '.'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' Please'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' let'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' me'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' know'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' where'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' you'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' are'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' located'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' and'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' I'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' will'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' do'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' my'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' best'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' to'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' assist'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': ' you'}}]}\n",
- "{'choices': [{'delta': {'role': 'assistant', 'content': '.'}}]}\n",
- "None\n"
- ]
- }
- ],
- "source": [
- "import litellm\n",
- "\n",
- "async def async_ollama():\n",
- " response = await litellm.acompletion(\n",
- " model=\"ollama/llama2\", \n",
- " messages=[{ \"content\": \"what's the weather\" ,\"role\": \"user\"}], \n",
- " api_base=\"http://localhost:11434\", \n",
- " stream=True\n",
- " )\n",
- " async for chunk in response:\n",
- " print(chunk)\n",
- "\n",
- "result = await async_ollama()\n",
- "print(result)\n",
- "\n",
- "try:\n",
- " async for chunk in result:\n",
- " print(chunk)\n",
- "except TypeError: # the last chunk is None from Ollama, this raises an error with async streaming\n",
- " pass"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Completion Call"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 7,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "{\n",
- " \"object\": \"chat.completion\",\n",
- " \"choices\": [\n",
- " {\n",
- " \"finish_reason\": \"stop\",\n",
- " \"index\": 0,\n",
- " \"message\": {\n",
- " \"content\": \" I'm LLaMA, an AI assistant developed by Meta AI that can understand and respond to human input in a conversational manner.\",\n",
- " \"role\": \"assistant\",\n",
- " \"logprobs\": null\n",
- " }\n",
- " }\n",
- " ],\n",
- " \"id\": \"chatcmpl-ea7b8242-791f-4656-ba12-e098edeb960e\",\n",
- " \"created\": 1695324686.6696231,\n",
- " \"response_ms\": 4072.3050000000003,\n",
- " \"model\": \"ollama/llama2\",\n",
- " \"usage\": {\n",
- " \"prompt_tokens\": 10,\n",
- " \"completion_tokens\": 27,\n",
- " \"total_tokens\": 37\n",
- " }\n",
- "}\n"
- ]
- }
- ],
- "source": [
- "from litellm import completion\n",
- "\n",
- "response = completion(\n",
- " model=\"ollama/llama2\", \n",
- " messages=[{ \"content\": \"respond in 20 words. who are you?\",\"role\": \"user\"}], \n",
- " api_base=\"http://localhost:11434\"\n",
- ")\n",
- "print(response)\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 3",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.9.6"
- },
- "orig_nbformat": 4
- },
- "nbformat": 4,
- "nbformat_minor": 2
-}
diff --git a/cookbook/liteLLM_Replicate_Demo.ipynb b/cookbook/liteLLM_Replicate_Demo.ipynb
deleted file mode 100644
index b93d9a587b8..00000000000
--- a/cookbook/liteLLM_Replicate_Demo.ipynb
+++ /dev/null
@@ -1,238 +0,0 @@
-{
- "cells": [
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {
- "id": "YV6L5fNv7Kep"
- },
- "source": [
- "# Call Replicate LLMs using chatGPT Input/Output Format\n",
- "This tutorial covers using the following Replicate Models with liteLLM\n",
- "\n",
- "- [StableLM Tuned Alpha 7B](https://replicate.com/stability-ai/stablelm-tuned-alpha-7b)\n",
- "- [LLAMA-2 70B Chat](https://replicate.com/replicate/llama-2-70b-chat)\n",
- "- [A16z infra-LLAMA-2 7B Chat](https://replicate.com/a16z-infra/llama-2-7b-chat)\n",
- "- [Dolly V2 12B](https://replicate.com/replicate/dolly-v2-12b)\n",
- "- [Vicuna 13B](https://replicate.com/replicate/vicuna-13b)\n",
- "\n",
- "\n",
- "\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "TO-EdF84O9QT"
- },
- "outputs": [],
- "source": [
- "# install liteLLM\n",
- "!pip install litellm"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {
- "id": "mpHTbTqQ8fey"
- },
- "source": [
- "Imports & Set ENV variables\n",
- "Get your Replicate Key: https://replicate.com/account/api-tokens"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 2,
- "metadata": {
- "id": "kDbgfcU8O-dW"
- },
- "outputs": [],
- "source": [
- "from litellm import completion\n",
- "import os\n",
- "os.environ['REPLICATE_API_TOKEN'] = ' ' # @param\n",
- "user_message = \"Hello, whats the weather in San Francisco??\"\n",
- "messages = [{ \"content\": user_message,\"role\": \"user\"}]"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {
- "id": "1KmkOdzLSOmJ"
- },
- "source": [
- "## Call Replicate Models using completion(model, messages) - chatGPT format"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 3,
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/"
- },
- "id": "XJ4nh4SnRzHP",
- "outputId": "986c0544-bb40-4915-f00f-498b0e518307"
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "replicate is not installed. Installing...\n",
- "Response from stability-ai/stablelm-tuned-alpha-7b:c49dae362cbaecd2ceabb5bd34fdb68413c4ff775111fea065d259d577757beb \n",
- "]\n",
- "\n",
- "{'choices': [{'finish_reason': 'stop', 'index': 0, 'message': {'role': 'assistant', 'content': \"I'm sorry for you being unable to access this content as my training data only goes up until 2023/03. However I can tell you what your local weather forecast may look like at any time of year with respect to current conditions:\"}}], 'created': 1691611730.7224207, 'model': 'stability-ai/stablelm-tuned-alpha-7b:c49dae362cbaecd2ceabb5bd34fdb68413c4ff775111fea065d259d577757beb', 'usage': {'prompt_tokens': 9, 'completion_tokens': 49, 'total_tokens': 58}}\n",
- "Response from replicate/llama-2-70b-chat:2c1608e18606fad2812020dc541930f2d0495ce32eee50074220b87300bc16e1 \n",
- "]\n",
- "\n",
- "{'choices': [{'finish_reason': 'stop', 'index': 0, 'message': {'role': 'assistant', 'content': \" Hello! I'm happy to help you with your question. However, I must point out that the question itself may not be meaningful. San Francisco is a city located in California, USA, and it is not possible for me to provide you with the current weather conditions there as I am a text-based AI language model and do not have access to real-time weather data. Additionally, the weather in San Francisco can vary greatly depending on the time of year, so it would be best to check a reliable weather source for the most up-to-date information.\\n\\nIf you meant to ask a different question, please feel free to rephrase it, and I will do my best to assist you in a safe and positive manner.\"}}], 'created': 1691611745.0269957, 'model': 'replicate/llama-2-70b-chat:2c1608e18606fad2812020dc541930f2d0495ce32eee50074220b87300bc16e1', 'usage': {'prompt_tokens': 9, 'completion_tokens': 143, 'total_tokens': 152}}\n",
- "Response from a16z-infra/llama-2-7b-chat:4f0b260b6a13eb53a6b1891f089d57c08f41003ae79458be5011303d81a394dc \n",
- "]\n",
- "\n",
- "{'choices': [{'finish_reason': 'stop', 'index': 0, 'message': {'role': 'assistant', 'content': \" Hello! I'm here to help you with your question. However, I must inform you that the weather in San Francisco can be quite unpredictable and can change rapidly. It's important to check reliable sources such as AccuWeather or the National Weather Service for the most up-to-date and accurate information about the weather in San Francisco.\\nI cannot provide you with real-time weather data or forecasts as I'm just an AI and do not have access to current weather conditions or predictions. But I can suggest some trustworthy websites or apps where you can find the latest weather updates:\\n* AccuWeather (accuweather.com)\\n* The Weather Channel (weather.com)\\n* Dark Sky (darksky.net)\\n* Weather Underground (wunderground.com)\\nRemember, it's always best to consult multiple sources for the most accurate information when planning your day or trip. Enjoy your day!\"}}], 'created': 1691611748.7723358, 'model': 'a16z-infra/llama-2-7b-chat:4f0b260b6a13eb53a6b1891f089d57c08f41003ae79458be5011303d81a394dc', 'usage': {'prompt_tokens': 9, 'completion_tokens': 174, 'total_tokens': 183}}\n",
- "Response from replicate/dolly-v2-12b:ef0e1aefc61f8e096ebe4db6b2bacc297daf2ef6899f0f7e001ec445893500e5 \n",
- "]\n",
- "\n",
- "{'choices': [{'finish_reason': 'stop', 'index': 0, 'message': {'role': 'assistant', 'content': 'Its 68 degrees right now in San Francisco! The temperature will be rising through the week and i expect it to reach 70 on Thursdays and Friday. Skies are expected to be partly cloudy with some sun breaks throughout the day.\\n\\n'}}], 'created': 1691611752.2002115, 'model': 'replicate/dolly-v2-12b:ef0e1aefc61f8e096ebe4db6b2bacc297daf2ef6899f0f7e001ec445893500e5', 'usage': {'prompt_tokens': 9, 'completion_tokens': 48, 'total_tokens': 57}}\n",
- "Response from replicate/vicuna-13b:6282abe6a492de4145d7bb601023762212f9ddbbe78278bd6771c8b3b2f2a13b \n",
- "]\n",
- "\n",
- "{'choices': [{'finish_reason': 'stop', 'index': 0, 'message': {'role': 'assistant', 'content': ''}}], 'created': 1691611752.8998356, 'model': 'replicate/vicuna-13b:6282abe6a492de4145d7bb601023762212f9ddbbe78278bd6771c8b3b2f2a13b', 'usage': {'prompt_tokens': 9, 'completion_tokens': 0, 'total_tokens': 9}}\n"
- ]
- }
- ],
- "source": [
- "llama_2 = \"replicate/llama-2-70b-chat:2c1608e18606fad2812020dc541930f2d0495ce32eee50074220b87300bc16e1\"\n",
- "llama_2_7b = \"a16z-infra/llama-2-7b-chat:4f0b260b6a13eb53a6b1891f089d57c08f41003ae79458be5011303d81a394dc\"\n",
- "dolly_v2 = \"replicate/dolly-v2-12b:ef0e1aefc61f8e096ebe4db6b2bacc297daf2ef6899f0f7e001ec445893500e5\"\n",
- "vicuna = \"replicate/vicuna-13b:6282abe6a492de4145d7bb601023762212f9ddbbe78278bd6771c8b3b2f2a13b\"\n",
- "models = [llama_2, llama_2_7b, dolly_v2, vicuna]\n",
- "for model in models:\n",
- " response = completion(model=model, messages=messages)\n",
- " print(f\"Response from {model} \\n]\\n\")\n",
- " print(response)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 4,
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/"
- },
- "id": "zlTVLB-7PTV_",
- "outputId": "5182275b-3108-46fa-a2cf-745fac4ad110"
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Hi\n",
- " there!\n",
- " The\n",
- " current\n",
- " forecast\n",
- " for\n",
- " today's\n",
- " high\n",
- " temperature\n",
- " ranges\n",
- " from\n",
- " 75\n",
- " degrees\n",
- " Fahrenheit\n",
- " all\n",
- " day\n",
- " to\n",
- " 83\n",
- " degrees\n",
- " Fahrenheit\n",
- " with\n",
- " possible\n",
- " isolated\n",
- " thunderstorms\n",
- " during\n",
- " the\n",
- " afternoon\n",
- " hours,\n",
- " mainly\n",
- " at\n",
- " sunset\n",
- " through\n",
- " early\n",
- " evening. The\n",
- " Pacific\n",
- " Ocean\n",
- " has\n",
- " a\n",
- " low\n",
- " pressure\n",
- " of\n",
- " 926\n",
- " mb\n",
- " and\n",
- " mostly\n",
- " cloud\n",
- " cover\n",
- " in\n",
- " this\n",
- " region\n",
- " on\n",
- " sunny\n",
- " days\n",
- " due\n",
- " to\n",
- " warming\n",
- " temperatures\n",
- " above\n",
- " average\n",
- " along\n",
- " most\n",
- " coastal\n",
- " areas\n",
- " and\n",
- " ocean\n",
- " breezes.<|USER|>\n"
- ]
- }
- ],
- "source": [
- "# @title Stream Responses from Replicate - Outputs in the same format used by chatGPT streaming\n",
- "response = completion(model=llama_2, messages=messages, stream=True)\n",
- "\n",
- "for chunk in response:\n",
- " print(chunk['choices'][0]['delta'])"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "t7WMRuL-8NrO"
- },
- "outputs": [],
- "source": []
- }
- ],
- "metadata": {
- "colab": {
- "provenance": []
- },
- "kernelspec": {
- "display_name": "Python 3",
- "name": "python3"
- },
- "language_info": {
- "name": "python"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 0
-}
diff --git a/cookbook/liteLLM_Streaming_Demo.ipynb b/cookbook/liteLLM_Streaming_Demo.ipynb
deleted file mode 100644
index 0456c54514b..00000000000
--- a/cookbook/liteLLM_Streaming_Demo.ipynb
+++ /dev/null
@@ -1,226 +0,0 @@
-{
- "nbformat": 4,
- "nbformat_minor": 0,
- "metadata": {
- "colab": {
- "provenance": []
- },
- "kernelspec": {
- "name": "python3",
- "display_name": "Python 3"
- },
- "language_info": {
- "name": "python"
- }
- },
- "cells": [
- {
- "cell_type": "markdown",
- "source": [
- "# [STREAMING] OpenAI, Anthropic, Replicate, Cohere using liteLLM\n",
- "In this tutorial:\n",
- "Note: All inputs/outputs are in the format used by `gpt-3.5-turbo`\n",
- "\n",
- "- Call all models in the same input format [**with streaming**]:\n",
- "\n",
- " `completion(model, messages, stream=True)`\n",
- "- All streaming generators are accessed at `chunk['choices'][0]['delta']`\n",
- "\n",
- "The following Models are covered in this tutorial\n",
- "- [GPT-3.5-Turbo](https://platform.openai.com/docs/models/gpt-3-5)\n",
- "- [Claude-2](https://www.anthropic.com/index/claude-2)\n",
- "- [StableLM Tuned Alpha 7B](https://replicate.com/stability-ai/stablelm-tuned-alpha-7b)\n",
- "- [A16z infra-LLAMA-2 7B Chat](https://replicate.com/a16z-infra/llama-2-7b-chat)\n",
- "- [Vicuna 13B](https://replicate.com/replicate/vicuna-13b)\n",
- "- [Cohere - Command Nightly]()\n",
- "\n",
- "\n",
- "\n"
- ],
- "metadata": {
- "id": "YV6L5fNv7Kep"
- }
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "TO-EdF84O9QT"
- },
- "outputs": [],
- "source": [
- "# install liteLLM\n",
- "!pip install litellm==0.1.369"
- ]
- },
- {
- "cell_type": "markdown",
- "source": [
- "## Imports & Set ENV variables\n",
- "Get your API Keys\n",
- "\n",
- "https://platform.openai.com/account/api-keys\n",
- "\n",
- "https://replicate.com/account/api-tokens\n",
- "\n",
- "https://console.anthropic.com/account/keys\n",
- "\n",
- "https://dashboard.cohere.ai/api-keys\n"
- ],
- "metadata": {
- "id": "mpHTbTqQ8fey"
- }
- },
- {
- "cell_type": "code",
- "source": [
- "from litellm import completion\n",
- "import os\n",
- "\n",
- "os.environ['OPENAI_API_KEY'] = '' # @param\n",
- "os.environ['REPLICATE_API_TOKEN'] = '' # @param\n",
- "os.environ['ANTHROPIC_API_KEY'] = '' # @param\n",
- "os.environ['COHERE_API_KEY'] = '' # @param"
- ],
- "metadata": {
- "id": "kDbgfcU8O-dW"
- },
- "execution_count": 8,
- "outputs": []
- },
- {
- "cell_type": "markdown",
- "source": [
- "### Set Messages"
- ],
- "metadata": {
- "id": "1KmkOdzLSOmJ"
- }
- },
- {
- "cell_type": "code",
- "source": [
- "user_message = \"Hello, whats the weather in San Francisco??\"\n",
- "messages = [{ \"content\": user_message,\"role\": \"user\"}]"
- ],
- "metadata": {
- "id": "xIEeOhVH-oh6"
- },
- "execution_count": 4,
- "outputs": []
- },
- {
- "cell_type": "markdown",
- "source": [
- "## Calling Models using liteLLM Streaming -\n",
- "\n",
- "## `completion(model, messages, stream)`"
- ],
- "metadata": {
- "id": "9SOCVRC1L-G3"
- }
- },
- {
- "cell_type": "code",
- "source": [
- "# replicate models #######\n",
- "stability_ai = \"stability-ai/stablelm-tuned-alpha-7b:c49dae362cbaecd2ceabb5bd34fdb68413c4ff775111fea065d259d577757beb\"\n",
- "llama_2_7b = \"a16z-infra/llama-2-7b-chat:4f0b260b6a13eb53a6b1891f089d57c08f41003ae79458be5011303d81a394dc\"\n",
- "vicuna = \"replicate/vicuna-13b:6282abe6a492de4145d7bb601023762212f9ddbbe78278bd6771c8b3b2f2a13b\"\n",
- "\n",
- "models = [\"gpt-3.5-turbo\", \"claude-2\", stability_ai, llama_2_7b, vicuna, \"command-nightly\"] # command-nightly is Cohere\n",
- "for model in models:\n",
- " replicate = (model == stability_ai or model==llama_2_7b or model==vicuna) # let liteLLM know if a model is replicate, using this optional param, `replicate=True`\n",
- " response = completion(model=model, messages=messages, stream=True, replicate=replicate)\n",
- " print(f\"####################\\n\\nResponse from {model}\")\n",
- " for i, chunk in enumerate(response):\n",
- " if i < 5: # NOTE: LIMITING CHUNKS FOR THIS DEMO\n",
- " print((chunk['choices'][0]['delta']))\n"
- ],
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/"
- },
- "id": "XJ4nh4SnRzHP",
- "outputId": "26b9fe10-b499-4a97-d60d-a8cb8f8030b8"
- },
- "execution_count": 13,
- "outputs": [
- {
- "output_type": "stream",
- "name": "stdout",
- "text": [
- "####################\n",
- "\n",
- "Response from gpt-3.5-turbo\n",
- "{\n",
- " \"role\": \"assistant\",\n",
- " \"content\": \"\"\n",
- "}\n",
- "{\n",
- " \"content\": \"I\"\n",
- "}\n",
- "{\n",
- " \"content\": \"'m\"\n",
- "}\n",
- "{\n",
- " \"content\": \" sorry\"\n",
- "}\n",
- "{\n",
- " \"content\": \",\"\n",
- "}\n",
- "####################\n",
- "\n",
- "Response from claude-2\n",
- "{'role': 'assistant', 'content': ' Unfortunately'}\n",
- "{'role': 'assistant', 'content': ' I'}\n",
- "{'role': 'assistant', 'content': ' don'}\n",
- "{'role': 'assistant', 'content': \"'t\"}\n",
- "{'role': 'assistant', 'content': ' have'}\n",
- "####################\n",
- "\n",
- "Response from stability-ai/stablelm-tuned-alpha-7b:c49dae362cbaecd2ceabb5bd34fdb68413c4ff775111fea065d259d577757beb\n",
- "{'role': 'assistant', 'content': \"I'm\"}\n",
- "{'role': 'assistant', 'content': ' sorry,'}\n",
- "{'role': 'assistant', 'content': ' I'}\n",
- "{'role': 'assistant', 'content': ' cannot'}\n",
- "{'role': 'assistant', 'content': ' answer'}\n",
- "####################\n",
- "\n",
- "Response from a16z-infra/llama-2-7b-chat:4f0b260b6a13eb53a6b1891f089d57c08f41003ae79458be5011303d81a394dc\n",
- "{'role': 'assistant', 'content': ''}\n",
- "{'role': 'assistant', 'content': ' Hello'}\n",
- "{'role': 'assistant', 'content': '!'}\n",
- "{'role': 'assistant', 'content': ' I'}\n",
- "{'role': 'assistant', 'content': \"'\"}\n",
- "####################\n",
- "\n",
- "Response from replicate/vicuna-13b:6282abe6a492de4145d7bb601023762212f9ddbbe78278bd6771c8b3b2f2a13b\n",
- "{'role': 'assistant', 'content': 'Comment:'}\n",
- "{'role': 'assistant', 'content': 'Hi! '}\n",
- "{'role': 'assistant', 'content': 'How '}\n",
- "{'role': 'assistant', 'content': 'are '}\n",
- "{'role': 'assistant', 'content': 'you '}\n",
- "####################\n",
- "\n",
- "Response from command-nightly\n",
- "{'role': 'assistant', 'content': ' Hello'}\n",
- "{'role': 'assistant', 'content': '!'}\n",
- "{'role': 'assistant', 'content': ' '}\n",
- "{'role': 'assistant', 'content': ' I'}\n",
- "{'role': 'assistant', 'content': \"'m\"}\n"
- ]
- }
- ]
- },
- {
- "cell_type": "code",
- "source": [],
- "metadata": {
- "id": "t7WMRuL-8NrO"
- },
- "execution_count": null,
- "outputs": []
- }
- ]
-}
\ No newline at end of file
diff --git a/cookbook/liteLLM_VertextAI_Example.ipynb b/cookbook/liteLLM_VertextAI_Example.ipynb
deleted file mode 100644
index 0af012b34e3..00000000000
--- a/cookbook/liteLLM_VertextAI_Example.ipynb
+++ /dev/null
@@ -1,199 +0,0 @@
-{
- "cells": [
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Using Google Palm (VertexAI) with liteLLM \n",
- "### chat-bison, chat-bison@001, text-bison, text-bison@001"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "!pip install litellm==0.1.388"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Set VertexAI Configs\n",
- "Vertex AI requires the following:\n",
- "* `vertex_project` - Your Project ID\n",
- "* `vertex_location` - Your Vertex AI region\n",
- "Both can be found on: https://console.cloud.google.com/\n",
- "\n",
- "VertexAI uses Application Default Credentials, see https://cloud.google.com/docs/authentication/external/set-up-adc for more information on setting this up\n",
- "\n",
- "NOTE: VertexAI requires you to set `application_default_credentials.json`, this can be set by running `gcloud auth application-default login` in your terminal\n",
- "\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 2,
- "metadata": {},
- "outputs": [],
- "source": [
- "# set you Vertex AI configs\n",
- "import litellm\n",
- "from litellm import completion\n",
- "\n",
- "litellm.vertex_project = \"hardy-device-386718\"\n",
- "litellm.vertex_location = \"us-central1\""
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Call VertexAI - chat-bison using liteLLM"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 10,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "{'choices': [{'finish_reason': 'stop', 'index': 0, 'message': {'role': 'assistant', 'content': LiteLLM LiteLLM is a large language model from Google AI that is designed to be lightweight and efficient. It is based on the Transformer architecture and has been trained on a massive dataset of text. LiteLLM is available as a pre-trained model that can be used for a variety of natural language processing tasks, such as text classification, question answering, and summarization.}}], 'created': 1692036777.831989, 'model': 'chat-bison'}\n"
- ]
- }
- ],
- "source": [
- "user_message = \"what is liteLLM \"\n",
- "messages = [{ \"content\": user_message,\"role\": \"user\"}]\n",
- "\n",
- "# chat-bison or chat-bison@001 supported by Vertex AI (As of Aug 2023)\n",
- "response = completion(model=\"chat-bison\", messages=messages)\n",
- "print(response)"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Call VertexAI - text-bison using liteLLM"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 3,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "['text-bison', 'text-bison@001']\n"
- ]
- }
- ],
- "source": [
- "print(litellm.vertex_text_models)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 4,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "{'choices': [{'finish_reason': 'stop', 'index': 0, 'message': {'role': 'assistant', 'content': liteLLM is a low-precision variant of the large language model LLM 5. For a given text prompt, liteLLM can continue the text in a way that is both coherent and informative.}}], 'created': 1692036813.052487, 'model': 'text-bison@001'}\n"
- ]
- }
- ],
- "source": [
- "user_message = \"what is liteLLM \"\n",
- "messages = [{ \"content\": user_message,\"role\": \"user\"}]\n",
- "\n",
- "# text-bison or text-bison@001 supported by Vertex AI (As of Aug 2023)\n",
- "response = completion(model=\"text-bison@001\", messages=messages)\n",
- "print(response)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 5,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "{'choices': [{'finish_reason': 'stop', 'index': 0, 'message': {'role': 'assistant', 'content': liteLLM was originally developed by Google engineers as a lite version of LLM, which stands for large language model. It is a deep learning language model that is designed to be more efficient than traditional LLMs while still achieving comparable performance. liteLLM is built on Tensor2Tensor, a framework for building and training large neural networks. It is able to learn from massive amounts of text data and generate text that is both coherent and informative. liteLLM has been shown to be effective for a variety of tasks, including machine translation, text summarization, and question answering.}}], 'created': 1692036821.60951, 'model': 'text-bison'}\n"
- ]
- }
- ],
- "source": [
- "response = completion(model=\"text-bison\", messages=messages)\n",
- "print(response)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 8,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "liteLLM is a lightweight language model that is designed to be fast and efficient. It is based on the Transformer architecture, but it has been modified to reduce the number of parameters and the amount of computation required. This makes it suitable for use on devices with limited resources, such as mobile phones and embedded systems.\n",
- "\n",
- "liteLLM is still under development, but it has already been shown to be effective on a variety of tasks, including text classification, natural language inference, and machine translation. It is also being used to develop new applications, such as chatbots and language assistants.\n",
- "\n",
- "If you are interested in learning more about lite\n"
- ]
- }
- ],
- "source": [
- "response = completion(model=\"text-bison@001\", messages=messages, temperature=0.4, top_k=10, top_p=0.2)\n",
- "print(response['choices'][0]['message']['content'])"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 3",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.9.6"
- },
- "orig_nbformat": 4
- },
- "nbformat": 4,
- "nbformat_minor": 2
-}
diff --git a/cookbook/liteLLM_clarifai_Demo.ipynb b/cookbook/liteLLM_clarifai_Demo.ipynb
deleted file mode 100644
index 40ef2fcf932..00000000000
--- a/cookbook/liteLLM_clarifai_Demo.ipynb
+++ /dev/null
@@ -1,187 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# LiteLLM Clarifai \n",
- "This notebook walks you through on how to use liteLLM integration of Clarifai and call LLM model from clarifai with response in openAI output format."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Pre-Requisites"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "#install necessary packages\n",
- "!pip install litellm\n",
- "!pip install clarifai"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "To obtain Clarifai Personal Access Token follow the steps mentioned in the [link](https://docs.clarifai.com/clarifai-basics/authentication/personal-access-tokens/)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 1,
- "metadata": {},
- "outputs": [],
- "source": [
- "## Set Clarifai Credentials\n",
- "import os\n",
- "os.environ[\"CLARIFAI_API_KEY\"]= \"YOUR_CLARIFAI_PAT\" # Clarifai PAT"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Mistral-large"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 2,
- "metadata": {},
- "outputs": [],
- "source": [
- "import litellm\n",
- "\n",
- "litellm.set_verbose=False"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 3,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Mistral large response : ModelResponse(id='chatcmpl-6eed494d-7ae2-4870-b9c2-6a64d50a6151', choices=[Choices(finish_reason='stop', index=1, message=Message(content=\"In the grand tapestry of time, where tales unfold,\\nLies the chronicle of ages, a sight to behold.\\nA tale of empires rising, and kings of old,\\nOf civilizations lost, and stories untold.\\n\\nOnce upon a yesterday, in a time so vast,\\nHumans took their first steps, casting shadows in the past.\\nFrom the cradle of mankind, a journey they embarked,\\nThrough stone and bronze and iron, their skills they sharpened and marked.\\n\\nEgyptians built pyramids, reaching for the skies,\\nWhile Greeks sought wisdom, truth, in philosophies that lie.\\nRoman legions marched, their empire to expand,\\nAnd in the East, the Silk Road joined the world, hand in hand.\\n\\nThe Middle Ages came, with knights in shining armor,\\nFeudal lords and serfs, a time of both clamor and calm order.\\nThen Renaissance bloomed, like a flower in the sun,\\nA rebirth of art and science, a new age had begun.\\n\\nAcross the vast oceans, explorers sailed with courage bold,\\nDiscovering new lands, stories of adventure, untold.\\nIndustrial Revolution churned, progress in its wake,\\nMachines and factories, a whole new world to make.\\n\\nTwo World Wars raged, a testament to man's strife,\\nYet from the ashes rose hope, a renewed will for life.\\nInto the modern era, technology took flight,\\nConnecting every corner, bathed in digital light.\\n\\nHistory, a symphony, a melody of time,\\nA testament to human will, resilience so sublime.\\nIn every page, a lesson, in every tale, a guide,\\nFor understanding our past, shapes our future's tide.\", role='assistant'))], created=1713896412, model='https://api.clarifai.com/v2/users/mistralai/apps/completion/models/mistral-large/outputs', object='chat.completion', system_fingerprint=None, usage=Usage(prompt_tokens=13, completion_tokens=338, total_tokens=351))\n"
- ]
- }
- ],
- "source": [
- "from litellm import completion\n",
- "\n",
- "messages = [{\"role\": \"user\",\"content\": \"\"\"Write a poem about history?\"\"\"}]\n",
- "response=completion(\n",
- " model=\"clarifai/mistralai.completion.mistral-large\",\n",
- " messages=messages,\n",
- " )\n",
- "\n",
- "print(f\"Mistral large response : {response}\")"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Claude-2.1 "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 4,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Claude-2.1 response : ModelResponse(id='chatcmpl-d126c919-4db4-4aa3-ac8f-7edea41e0b93', choices=[Choices(finish_reason='stop', index=1, message=Message(content=\" Here's a poem I wrote about history:\\n\\nThe Tides of Time\\n\\nThe tides of time ebb and flow,\\nCarrying stories of long ago.\\nFigures and events come into light,\\nShaping the future with all their might.\\n\\nKingdoms rise, empires fall, \\nLeaving traces that echo down every hall.\\nRevolutions bring change with a fiery glow,\\nToppling structures from long ago.\\n\\nExplorers traverse each ocean and land,\\nSeeking treasures they don't understand.\\nWhile artists and writers try to make their mark,\\nHoping their works shine bright in the dark.\\n\\nThe cycle repeats again and again,\\nAs humanity struggles to learn from its pain.\\nThough the players may change on history's stage,\\nThe themes stay the same from age to age.\\n\\nWar and peace, life and death,\\nLove and strife with every breath.\\nThe tides of time continue their dance,\\nAs we join in, by luck or by chance.\\n\\nSo we study the past to light the way forward, \\nHeeding warnings from stories told and heard.\\nThe future unfolds from this unending flow -\\nWhere the tides of time ultimately go.\", role='assistant'))], created=1713896579, model='https://api.clarifai.com/v2/users/anthropic/apps/completion/models/claude-2_1/outputs', object='chat.completion', system_fingerprint=None, usage=Usage(prompt_tokens=12, completion_tokens=232, total_tokens=244))\n"
- ]
- }
- ],
- "source": [
- "from litellm import completion\n",
- "\n",
- "messages = [{\"role\": \"user\",\"content\": \"\"\"Write a poem about history?\"\"\"}]\n",
- "response=completion(\n",
- " model=\"clarifai/anthropic.completion.claude-2_1\",\n",
- " messages=messages,\n",
- " )\n",
- "\n",
- "print(f\"Claude-2.1 response : {response}\")"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### OpenAI GPT-4 (Streaming)\n",
- "Though clarifai doesn't support streaming, still you can call stream and get the response in standard StreamResponse format of liteLLM"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 3,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "ModelResponse(id='chatcmpl-40ae19af-3bf0-4eb4-99f2-33aec3ba84af', choices=[StreamingChoices(finish_reason=None, index=0, delta=Delta(content=\"In the quiet corners of time's grand hall,\\nLies the tale of rise and fall.\\nFrom ancient ruins to modern sprawl,\\nHistory, the greatest story of them all.\\n\\nEmpires have risen, empires have decayed,\\nThrough the eons, memories have stayed.\\nIn the book of time, history is laid,\\nA tapestry of events, meticulously displayed.\\n\\nThe pyramids of Egypt, standing tall,\\nThe Roman Empire's mighty sprawl.\\nFrom Alexander's conquest, to the Berlin Wall,\\nHistory, a silent witness to it all.\\n\\nIn the shadow of the past we tread,\\nWhere once kings and prophets led.\\nTheir stories in our hearts are spread,\\nEchoes of their words, in our minds are read.\\n\\nBattles fought and victories won,\\nActs of courage under the sun.\\nTales of love, of deeds done,\\nIn history's grand book, they all run.\\n\\nHeroes born, legends made,\\nIn the annals of time, they'll never fade.\\nTheir triumphs and failures all displayed,\\nIn the eternal march of history's parade.\\n\\nThe ink of the past is forever dry,\\nBut its lessons, we cannot deny.\\nIn its stories, truths lie,\\nIn its wisdom, we rely.\\n\\nHistory, a mirror to our past,\\nA guide for the future vast.\\nThrough its lens, we're ever cast,\\nIn the drama of life, forever vast.\", role='assistant', function_call=None, tool_calls=None), logprobs=None)], created=1714744515, model='https://api.clarifai.com/v2/users/openai/apps/chat-completion/models/GPT-4/outputs', object='chat.completion.chunk', system_fingerprint=None)\n",
- "ModelResponse(id='chatcmpl-40ae19af-3bf0-4eb4-99f2-33aec3ba84af', choices=[StreamingChoices(finish_reason='stop', index=0, delta=Delta(content=None, role=None, function_call=None, tool_calls=None), logprobs=None)], created=1714744515, model='https://api.clarifai.com/v2/users/openai/apps/chat-completion/models/GPT-4/outputs', object='chat.completion.chunk', system_fingerprint=None)\n"
- ]
- }
- ],
- "source": [
- "from litellm import completion\n",
- "\n",
- "messages = [{\"role\": \"user\",\"content\": \"\"\"Write a poem about history?\"\"\"}]\n",
- "response = completion(\n",
- " model=\"clarifai/openai.chat-completion.GPT-4\",\n",
- " messages=messages,\n",
- " stream=True,\n",
- " api_key = \"c75cc032415e45368be331fdd2c06db0\")\n",
- "\n",
- "for chunk in response:\n",
- " print(chunk)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 3",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.10.13"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 2
-}
diff --git a/cookbook/liteLLM_function_calling.ipynb b/cookbook/liteLLM_function_calling.ipynb
deleted file mode 100644
index 45f4398b386..00000000000
--- a/cookbook/liteLLM_function_calling.ipynb
+++ /dev/null
@@ -1,331 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "vnvlwUDZK7VA"
- },
- "source": [
- "## Demo Notebook of Function Calling with liteLLM\n",
- "- Supported Providers for Function Calling\n",
- " - OpenAI - `gpt-4-0613` and `gpt-3.5-turbo-0613`\n",
- "- In this notebook we use function calling with `litellm.completion()`"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "KrINCwRfLgZV"
- },
- "outputs": [],
- "source": [
- "## Install liteLLM\n",
- "!pip install litellm"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 2,
- "metadata": {
- "id": "nK7zR5OgLlh2"
- },
- "outputs": [],
- "source": [
- "import os\n",
- "from litellm import completion"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 27,
- "metadata": {
- "id": "dCQlyBxKLqbA"
- },
- "outputs": [],
- "source": [
- "os.environ['OPENAI_API_KEY'] = \"\" #@param"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "gfdGv-FMRCdX"
- },
- "source": [
- "## Define Messages, Functions\n",
- "We create a get_current_weather() function and pass that to GPT 3.5\n",
- "\n",
- "See OpenAI docs for this: https://openai.com/blog/function-calling-and-other-api-updates"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 25,
- "metadata": {
- "id": "ERzsP1sfM19C"
- },
- "outputs": [],
- "source": [
- "messages = [\n",
- " {\"role\": \"user\", \"content\": \"What is the weather like in Boston?\"}\n",
- "]\n",
- "\n",
- "def get_current_weather(location):\n",
- " if location == \"Boston, MA\":\n",
- " return \"The weather is 12F\"\n",
- "\n",
- "functions = [\n",
- " {\n",
- " \"name\": \"get_current_weather\",\n",
- " \"description\": \"Get the current weather in a given location\",\n",
- " \"parameters\": {\n",
- " \"type\": \"object\",\n",
- " \"properties\": {\n",
- " \"location\": {\n",
- " \"type\": \"string\",\n",
- " \"description\": \"The city and state, e.g. San Francisco, CA\"\n",
- " },\n",
- " \"unit\": {\n",
- " \"type\": \"string\",\n",
- " \"enum\": [\"celsius\", \"fahrenheit\"]\n",
- " }\n",
- " },\n",
- " \"required\": [\"location\"]\n",
- " }\n",
- " }\n",
- " ]"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "NX6by2VuRPnp"
- },
- "source": [
- "## Call gpt-3.5-turbo-0613 to Decide what Function to call"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 9,
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/"
- },
- "id": "QVoJ5PtxMlVx",
- "outputId": "efe7a81f-e04a-4afc-aa60-a2b2648f5fb9"
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "{\n",
- " \"id\": \"chatcmpl-7mX4RiqdoislVEqfmfVjFSKp3hyIy\",\n",
- " \"object\": \"chat.completion\",\n",
- " \"created\": 1691801223,\n",
- " \"model\": \"gpt-3.5-turbo-0613\",\n",
- " \"choices\": [\n",
- " {\n",
- " \"index\": 0,\n",
- " \"message\": {\n",
- " \"role\": \"assistant\",\n",
- " \"content\": null,\n",
- " \"function_call\": {\n",
- " \"name\": \"get_current_weather\",\n",
- " \"arguments\": \"{\\n \\\"location\\\": \\\"Boston, MA\\\"\\n}\"\n",
- " }\n",
- " },\n",
- " \"finish_reason\": \"function_call\"\n",
- " }\n",
- " ],\n",
- " \"usage\": {\n",
- " \"prompt_tokens\": 82,\n",
- " \"completion_tokens\": 18,\n",
- " \"total_tokens\": 100\n",
- " }\n",
- "}\n"
- ]
- }
- ],
- "source": [
- "response = completion(model=\"gpt-3.5-turbo-0613\", messages=messages, functions=functions)\n",
- "print(response)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "Yu0o2saDNLx8"
- },
- "source": [
- "## Parse GPT 3.5 Response\n",
- "Read Information about what Function to Call"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 11,
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/"
- },
- "id": "u1DzXLJsNOR5",
- "outputId": "177e9501-0ce2-4619-9067-3047f18f6c79"
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- " JSON: {\n",
- " \"name\": \"get_current_weather\",\n",
- " \"arguments\": \"{\\n \\\"location\\\": \\\"Boston, MA\\\"\\n}\"\n",
- "}"
- ]
- },
- "execution_count": 11,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "function_call_data = response[\"choices\"][0][\"message\"][\"function_call\"]\n",
- "function_call_data"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 20,
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/"
- },
- "id": "tYb96Mh0NhH9",
- "outputId": "13c4bb89-6f29-4b3b-afa7-302dcf2cdd5f"
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "get_current_weather {'location': 'Boston, MA'}\n"
- ]
- }
- ],
- "source": [
- "import json\n",
- "function_name = function_call_data['name']\n",
- "function_args = function_call_data['arguments']\n",
- "function_args = json.loads(function_args)\n",
- "print(function_name, function_args)\n"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "z3tstH_yN3fX"
- },
- "source": [
- "## Call the get_current_weather() function"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 24,
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/"
- },
- "id": "TSb8JHhgN5Zc",
- "outputId": "ef140572-4020-4daf-ac8c-d5161be9aa5c"
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "12F\n"
- ]
- }
- ],
- "source": [
- "if function_name == \"get_current_weather\":\n",
- " result = get_current_weather(**function_args)\n",
- " print(result)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "k4HGJE3NRmMI"
- },
- "source": [
- "## Send the response from get_current_weather back to the model to summarize"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 26,
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/"
- },
- "id": "a23cmEwiPaw7",
- "outputId": "43259b86-0c4c-4fcb-eab7-6e1a788b2f21"
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "{\n",
- " \"id\": \"chatcmpl-7mXGN62u75WXp1Lgen4iSgNvA7hHT\",\n",
- " \"object\": \"chat.completion\",\n",
- " \"created\": 1691801963,\n",
- " \"model\": \"gpt-3.5-turbo-0613\",\n",
- " \"choices\": [\n",
- " {\n",
- " \"index\": 0,\n",
- " \"message\": {\n",
- " \"role\": \"assistant\",\n",
- " \"content\": \"The current weather in Boston is 12 degrees Fahrenheit.\"\n",
- " },\n",
- " \"finish_reason\": \"stop\"\n",
- " }\n",
- " ],\n",
- " \"usage\": {\n",
- " \"prompt_tokens\": 109,\n",
- " \"completion_tokens\": 12,\n",
- " \"total_tokens\": 121\n",
- " }\n",
- "}\n"
- ]
- }
- ],
- "source": [
- "messages = [\n",
- " {\"role\": \"user\", \"content\": \"What is the weather like in Boston?\"},\n",
- " {\"role\": \"assistant\", \"content\": None, \"function_call\": {\"name\": \"get_current_weather\", \"arguments\": \"{ \\\"location\\\": \\\"Boston, MA\\\"}\"}},\n",
- " {\"role\": \"function\", \"name\": \"get_current_weather\", \"content\": result}\n",
- "]\n",
- "response = completion(model=\"gpt-3.5-turbo-0613\", messages=messages, functions=functions)\n",
- "print(response)"
- ]
- }
- ],
- "metadata": {
- "colab": {
- "provenance": []
- },
- "kernelspec": {
- "display_name": "Python 3",
- "name": "python3"
- },
- "language_info": {
- "name": "python"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 0
-}
\ No newline at end of file
diff --git a/cookbook/litellm-ollama-docker-image/Dockerfile b/cookbook/litellm-ollama-docker-image/Dockerfile
deleted file mode 100644
index be237a4df77..00000000000
--- a/cookbook/litellm-ollama-docker-image/Dockerfile
+++ /dev/null
@@ -1,25 +0,0 @@
-FROM ollama/ollama as ollama
-
-RUN echo "auto installing llama2"
-
-# auto install ollama/llama2
-RUN ollama serve & sleep 2 && ollama pull llama2
-
-RUN echo "installing litellm"
-
-RUN apt-get update
-
-# Install Python
-RUN apt-get install -y python3 python3-pip
-
-# Set the working directory in the container
-WORKDIR /app
-
-# Copy the current directory contents into the container at /app
-COPY . /app
-
-# Install any needed packages specified in requirements.txt
-
-RUN python3 -m pip install litellm
-COPY start.sh /start.sh
-ENTRYPOINT [ "/bin/bash", "/start.sh" ]
diff --git a/cookbook/litellm-ollama-docker-image/requirements.txt b/cookbook/litellm-ollama-docker-image/requirements.txt
deleted file mode 100644
index 9b9181b2360..00000000000
--- a/cookbook/litellm-ollama-docker-image/requirements.txt
+++ /dev/null
@@ -1 +0,0 @@
-litellm==1.83.14
diff --git a/cookbook/litellm-ollama-docker-image/start.sh b/cookbook/litellm-ollama-docker-image/start.sh
deleted file mode 100644
index ecc03ce73c5..00000000000
--- a/cookbook/litellm-ollama-docker-image/start.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-ollama serve &
-litellm
\ No newline at end of file
diff --git a/cookbook/litellm-ollama-docker-image/test.py b/cookbook/litellm-ollama-docker-image/test.py
deleted file mode 100644
index 93b9c6ac4a7..00000000000
--- a/cookbook/litellm-ollama-docker-image/test.py
+++ /dev/null
@@ -1,35 +0,0 @@
-import openai
-
-api_base = "http://0.0.0.0:8000"
-
-openai.api_base = api_base
-openai.api_key = "temp-key"
-print(openai.api_base)
-
-
-print("LiteLLM: response from proxy with streaming")
-response = openai.ChatCompletion.create(
- model="ollama/llama2",
- messages=[
- {
- "role": "user",
- "content": "this is a test request, acknowledge that you got it",
- }
- ],
- stream=True,
-)
-
-for chunk in response:
- print(f"LiteLLM: streaming response from proxy {chunk}")
-
-response = openai.ChatCompletion.create(
- model="ollama/llama2",
- messages=[
- {
- "role": "user",
- "content": "this is a test request, acknowledge that you got it",
- }
- ],
-)
-
-print(f"LiteLLM: response from proxy {response}")
diff --git a/cookbook/litellm_Test_Multiple_Providers.ipynb b/cookbook/litellm_Test_Multiple_Providers.ipynb
deleted file mode 100644
index 3901581e67f..00000000000
--- a/cookbook/litellm_Test_Multiple_Providers.ipynb
+++ /dev/null
@@ -1,571 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "Ys9n20Es2IzT"
- },
- "source": [
- "# Evaluate Multiple LLM Providers with LiteLLM\n",
- "\n",
- "\n",
- "\n",
- "* Quality Testing\n",
- "* Load Testing\n",
- "* Duration Testing\n",
- "\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "ZXOXl23PIIP6"
- },
- "outputs": [],
- "source": [
- "!pip install litellm python-dotenv"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "LINuBzXDItq2"
- },
- "outputs": [],
- "source": [
- "from litellm import load_test_model, testing_batch_completion"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "EkxMhsWdJdu4"
- },
- "outputs": [],
- "source": [
- "from dotenv import load_dotenv\n",
- "load_dotenv()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "mv5XdnqeW5I_"
- },
- "source": [
- "# Quality Test endpoint\n",
- "\n",
- "## Test the same prompt across multiple LLM providers\n",
- "\n",
- "In this example, let's ask some questions about Paul Graham"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "XpzrR5m4W_Us"
- },
- "outputs": [],
- "source": [
- "models = [\"gpt-3.5-turbo\", \"gpt-3.5-turbo-16k\", \"gpt-4\", \"claude-instant-1\", \"replicate/llama-2-70b-chat:58d078176e02c219e11eb4da5a02a7830a283b14cf8f94537af893ccff5ee781\"]\n",
- "context = \"\"\"Paul Graham (/ɡræm/; born 1964)[3] is an English computer scientist, essayist, entrepreneur, venture capitalist, and author. He is best known for his work on the programming language Lisp, his former startup Viaweb (later renamed Yahoo! Store), cofounding the influential startup accelerator and seed capital firm Y Combinator, his essays, and Hacker News. He is the author of several computer programming books, including: On Lisp,[4] ANSI Common Lisp,[5] and Hackers & Painters.[6] Technology journalist Steven Levy has described Graham as a \"hacker philosopher\".[7] Graham was born in England, where he and his family maintain permanent residence. However he is also a citizen of the United States, where he was educated, lived, and worked until 2016.\"\"\"\n",
- "prompts = [\"Who is Paul Graham?\", \"What is Paul Graham known for?\" , \"Is paul graham a writer?\" , \"Where does Paul Graham live?\", \"What has Paul Graham done?\"]\n",
- "messages = [[{\"role\": \"user\", \"content\": context + \"\\n\" + prompt}] for prompt in prompts] # pass in a list of messages we want to test\n",
- "result = testing_batch_completion(models=models, messages=messages)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "9nzeLySnvIIW"
- },
- "source": [
- "## Visualize the data"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/",
- "height": 403
- },
- "id": "X-2n7hdAuVAY",
- "outputId": "69cc0de1-68e3-4c12-a8ea-314880010d94"
- },
- "outputs": [
- {
- "data": {
- "text/html": [
- "\n",
- "\n",
- "