Skip to content

docs: Update projects (#2253) #102

docs: Update projects (#2253)

docs: Update projects (#2253) #102

Workflow file for this run

# SPDX-FileCopyrightText: 2026 The RISE Project
# SPDX-License-Identifier: MIT
---
# litellm is no longer pure-Python: since the 1.9x line its build-backend is
# maturin and it ships a PyO3/Rust extension (litellm.rust_bridge._native, crate
# at litellm-rust/crates/python-bridge). Upstream's PyPI release publishes exactly
# ONE Linux wheel per arch - litellm-<ver>-cp310-abi3 - loadable on every CPython
# 3.10-3.14 (requires-python is ">=3.10, <3.15"), and there is NO free-threaded
# (cp314t) wheel. The abi3 pyo3 feature that yields that single wheel is added at
# release time and is NOT in the git tag (see the "abi3 floor" step below), so we
# set it ourselves - pinned to abi3-py312 (RISE's minimum published Python) - and
# build one cp312-abi3 wheel, then let cibuildwheel reuse+test it on 3.12/3.13/3.14.
name: Build litellm wheels (riscv64)
on:
workflow_dispatch:
inputs:
version:
description: 'Version glob to (re)build; empty builds every version of docs/packages/litellm.yaml not released yet'
required: false
default: ''
pull_request:
branches: [main]
paths:
- '.github/workflows/build-litellm.yml'
- 'docs/packages/litellm.yaml'
push:
branches: [main]
paths:
- '.github/workflows/build-litellm.yml'
- 'docs/packages/litellm.yaml'
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
setup:
uses: $/.github/workflows/_setup.yml
with:
package: litellm
version: ${{ inputs.version }}
python_sdist:
needs: [setup]
if: needs.setup.outputs.versions != '[]'
strategy:
fail-fast: false
matrix:
version: ${{ fromJSON(needs.setup.outputs.versions) }}
runs-on: ubuntu-latest
env:
LITELLM_VERSION: ${{ matrix.version }}
steps:
- name: Checkout litellm v${{ env.LITELLM_VERSION }}
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: BerriAI/litellm
ref: v${{ env.LITELLM_VERSION }}
persist-credentials: false
- name: Install Python
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
python-version: '3.12'
activate-environment: true
enable-cache: false
- name: Set a fixed abi3 floor for the pyo3 extension
run: |
set -euo pipefail
# litellm ships a single abi3 wheel per arch on PyPI, and the floor that
# produces it keeps moving: <=1.97 carried it on the pyo3 dependency line,
# 1.100 dropped it entirely, 1.101 reintroduced it as a default crate
# feature with a py310 floor. We pin our own floor of abi3-py312 (RISE's
# minimum published Python) whichever shape the tag has.
#
# The floor must be explicit, not the bare `abi3` feature: bare `abi3` tags
# Abi3Version::CurrentPython, i.e. a different cpNN-abi3 per build
# interpreter, which is exactly the spray we must not publish. With
# abi3-py312 maturin always tags cp312-abi3, so every interpreter in the
# matrix yields the one identical wheel (deduped by find_compatible_wheel).
# Bake it in BEFORE building the sdist so maturin captures it.
python3 - <<'PY'
import pathlib, re, sys
p = pathlib.Path("litellm-rust/crates/python-bridge/Cargo.toml")
s = p.read_text()
out = re.sub(r'(abi3 = \["pyo3/abi3-py)\d+("\])', r'\g<1>312\g<2>', s)
if out == s:
out = re.sub(r'(pyo3 = \{ workspace = true, features = \["extension-module")("?\] \})',
r'\1, "abi3-py312"\2', s)
if out == s:
out = s.replace("[features]\n", '[features]\nabi3 = ["pyo3/abi3-py312"]\n', 1)
out = re.sub(r'default = \[([^\]]*)\]', r'default = [\1, "abi3"]', out, count=1)
if "abi3-py312" not in out or out == s:
sys.exit("::error::could not set the abi3-py312 floor (manifest shape changed?)")
p.write_text(out)
PY
grep -nE 'abi3|^default' litellm-rust/crates/python-bridge/Cargo.toml
- name: Build sdist
id: build_sdist
run: |
set -euo pipefail
rm -rf dist
# maturin is the build backend; `python -m build --sdist` invokes it to
# assemble the source tree (Cargo.lock + prebuilt frontend already in the
# checkout, plus the abi3 floor set above, are packaged in).
# twine check validates the metadata.
uv pip install 'maturin==1.9.4' build twine
python -m build --sdist --outdir dist
twine check dist/*
- name: Upload sdist artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: litellm-${{ env.LITELLM_VERSION }}-sdist
path: dist/*.tar.gz
if-no-files-found: error
build_wheels:
needs: [setup, python_sdist]
if: needs.setup.outputs.versions != '[]'
strategy:
fail-fast: false
matrix:
version: ${{ fromJSON(needs.setup.outputs.versions) }}
name: Build litellm ${{ matrix.version }} cp312-abi3-manylinux_riscv64
runs-on: ubuntu-24.04-riscv
timeout-minutes: 90
env:
LITELLM_VERSION: ${{ matrix.version }}
steps:
- name: Fetch sdist artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: litellm-${{ env.LITELLM_VERSION }}-sdist
path: dist
- id: sdist_path
run: echo "path=$(echo dist/*.tar.gz)" >> "$GITHUB_OUTPUT"
- name: Build and test wheel
uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0
with:
# cibuildwheel accepts an sdist tarball directly: it extracts it and
# builds from the self-contained tree (Cargo.lock + prebuilt frontend +
# the abi3 floor we set are all bundled).
package-dir: ${{ steps.sdist_path.outputs.path }}
env:
CIBW_ARCHS: riscv64
# With the abi3-py312 floor set on the sdist, maturin tags every build
# `cp312-abi3` regardless of the interpreter. Selecting three interpreters
# therefore does NOT rebuild three times: cibuildwheel compiles the Rust
# once (first tag -> cp312-abi3 wheel), then find_compatible_wheel matches
# that wheel for cp313/cp314 ("Skipping build step...") while STILL running
# the test phase on each - so we get ONE cp312-abi3 wheel out but genuine
# load-and-test coverage on 3.12/3.13/3.14. cp314t is excluded (abi3 can't
# target free-threaded, and fastuuid/other riscv64 runtime deps have no
# cp314t wheels).
CIBW_BUILD: "cp312-* cp313-* cp314-*"
# rustup.rs has no riscv64 musl host toolchain (same reason
# build-fastuuid/tiktoken skip it).
CIBW_SKIP: "*-musllinux_*"
# litellm ships no [tool.cibuildwheel], so install the Rust toolchain its
# maturin backend needs in-container and put it on PATH. Upstream builds
# on stable (test-rust.yml); the crate is edition 2024 / rust-version 1.88
# with no nightly features, so the default stable rustup satisfies it.
CIBW_BEFORE_ALL_LINUX: >-
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
# CIBW_ENVIRONMENT applies to BOTH build and test. Point pip at our
# registry (needed so riscv64-only test deps - fastuuid, tiktoken, hf-xet,
# multidict, frozenlist, pyyaml - resolve; harmless at build time) and add
# cargo to PATH for the maturin build. We deliberately do NOT set
# only-binary here - see CIBW_TEST_ENVIRONMENT (gotcha 12).
CIBW_ENVIRONMENT: >-
PATH="$PATH:$HOME/.cargo/bin"
PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/
# Test phase only: force wheels-only so the full runtime closure (13 deps)
# must resolve as riscv64 wheels instead of silently source-building a
# heavy dep on the runner. Verified the closure resolves to riscv64 wheels
# across public PyPI + our registry for cp312/cp313/cp314.
CIBW_TEST_ENVIRONMENT: >-
PIP_ONLY_BINARY=:all:
# Smoke-test the COMPILED extension, not just `import litellm`. The bridge
# loader (litellm/rust_bridge/loader.py) catches ImportError and returns
# None, so a plain import would pass even with a broken/missing _native.
# Assert the native module is really loaded and call gil_stats() (a
# zero-arg native fn returning {"releases": int}, no network/side effects).
CIBW_TEST_COMMAND: >-
python -c "import litellm;
from litellm.rust_bridge import _native;
from litellm.rust_bridge.loader import native_bridge_available, get_native_bridge;
assert native_bridge_available() is True, 'native bridge unavailable';
assert get_native_bridge() is _native;
s = _native.gil_stats();
assert isinstance(s, dict) and 'releases' in s, s;
print('litellm _native OK on', __import__('sys').version.split()[0], '-> gil_stats()=', s)"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: litellm-${{ env.LITELLM_VERSION }}-cp312-abi3-manylinux_riscv64
path: ./wheelhouse/*.whl
if-no-files-found: error
publish:
name: Publish litellm ${{ matrix.version }}
needs: [setup, python_sdist, build_wheels]
if: needs.setup.outputs.versions != '[]'
strategy:
fail-fast: false
matrix:
version: ${{ fromJSON(needs.setup.outputs.versions) }}
permissions:
contents: write
pull-requests: write
uses: $/.github/workflows/_publish-wheel.yml
secrets:
app-private-key: ${{ secrets.RISEPROJECT_APP_PRIVATE_KEY }}
with:
artifact-pattern: litellm-${{ matrix.version }}-*-manylinux_riscv64