From 4cae5431aa61099fc60dd3b21a594558d60fd9e6 Mon Sep 17 00:00:00 2001 From: Yassin Kortam Date: Wed, 22 Jul 2026 15:00:00 -0700 Subject: [PATCH] fix(rust): ship the Rust extension where the gateway actually imports litellm from (#34202) * fix(rust): ship the Rust extension where the gateway actually imports litellm from The runtime sets PYTHONPATH=/app, so `import litellm` resolves to the source tree copied by `COPY . .` rather than the wheel uv installs into /app/.venv. maturin compiles the Rust extension as an artifact of that wheel build, so it only ever lands under site-packages; the shadowing source tree wins at import time and litellm.rust_bridge falls back to the Python implementation without raising anything. The staging Rust gateway has therefore never executed Rust: requests succeed, return no x-litellm-rust marker, and the e2e suite would go green while exercising the Python path. Copy the extension next to the source it is imported from, and fail the build when it is absent so a Rust image that cannot run Rust is never published. * fix(rust): stop shadowing the installed litellm wheel with the source copy The image carries two copies of the litellm package: the source tree COPY . . puts at /app/litellm, and the wheel uv installs into /app/.venv. The runtime sets PYTHONPATH=/app, so the source copy wins. maturin compiles the Rust extension as an artifact of the wheel build, so it lives only in the copy that loses, and loader.py returns None rather than raising when it cannot import it. The gateway therefore serves every /messages request through Python while looking perfectly healthy. Drop the redundant source copy so import litellm resolves to the wheel; /app stays importable for gateway. Assert at build time that the extension is loadable, since build is the only point where 'this image must be able to run Rust' is knowable. Replaces an earlier version that copied the .so between the two copies: that fixed the one artifact we noticed while leaving the duplication in place. --- gateway/Dockerfile | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/gateway/Dockerfile b/gateway/Dockerfile index da2f2c9c1e0..6167c1cd2af 100644 --- a/gateway/Dockerfile +++ b/gateway/Dockerfile @@ -59,6 +59,16 @@ RUN --mount=type=cache,target=/root/.cache/uv \ --extra semantic-router \ --python python3 +# `COPY . .` leaves a second copy of the litellm package at /app/litellm, and +# the runtime puts /app ahead of site-packages on sys.path, so that copy shadows +# the wheel uv just installed. The wheel is the complete package and the only +# copy carrying the compiled Rust extension, so the shadow silently downgrades +# `litellm.rust_bridge` to its Python implementation. Drop the redundant copy: +# `import litellm` then resolves to the wheel, while /app stays importable for +# `gateway`. The assertion fails the build if the extension was never built. +RUN rm -rf /app/litellm && \ + python3 -c "import litellm.rust_bridge as rb; assert rb.get_native_bridge() is not None" + RUN mkdir -p /home/nonroot && \ HOME=/home/nonroot prisma generate --schema=./schema.prisma && \ chown -R nonroot:nonroot /home/nonroot/.cache