mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-10 03:28:53 +00:00
fix(docker): install bedrock-realtime extra in monolith proxy images (#39223)
The Dockerfile, docker/Dockerfile.non_root and docker/Dockerfile.database uv sync stages never passed --extra bedrock-realtime, so aws-sdk-bedrock-runtime was absent from the image venv and Bedrock Nova Sonic /v1/realtime sessions failed with 'Missing aws_sdk_bedrock_runtime'. gateway/Dockerfile already had the extra (PR #34426). Adds a static check over every uv sync in the proxy Dockerfiles and an image-level import probe that the image-scan workflow runs against the built root, non-root and gateway images. Co-authored-by: yassin <yassin@berri.ai> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
47b9d838aa
commit
93219a9257
6 changed files with 124 additions and 3 deletions
6
.github/workflows/image-scan.yml
vendored
6
.github/workflows/image-scan.yml
vendored
|
|
@ -80,7 +80,7 @@ jobs:
|
|||
LITELLM_IMAGE: litellm-image-scan:${{ github.sha }}
|
||||
run: |
|
||||
python -m pip install "pytest==9.0.3"
|
||||
python -m pytest tests/proxy_migration_tests/test_offline_image_migration.py -v
|
||||
python -m pytest tests/proxy_migration_tests/test_offline_image_migration.py tests/proxy_migration_tests/test_image_bedrock_realtime_extra.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
|
||||
|
|
@ -124,7 +124,7 @@ jobs:
|
|||
LITELLM_IMAGE: litellm-runtime-scan:${{ github.sha }}
|
||||
run: |
|
||||
python -m pip install "pytest==9.0.3"
|
||||
python -m pytest tests/proxy_migration_tests/test_offline_image_migration.py -v
|
||||
python -m pytest tests/proxy_migration_tests/test_offline_image_migration.py tests/proxy_migration_tests/test_image_bedrock_realtime_extra.py -v
|
||||
|
||||
migrations-image:
|
||||
name: migrations-image
|
||||
|
|
@ -185,7 +185,7 @@ jobs:
|
|||
LITELLM_COMPONENT_PORT: "4000"
|
||||
run: |
|
||||
python -m pip install "pytest==9.0.3"
|
||||
python -m pytest tests/proxy_migration_tests/test_component_image_serves_offline.py -v
|
||||
python -m pytest tests/proxy_migration_tests/test_component_image_serves_offline.py tests/proxy_migration_tests/test_image_bedrock_realtime_extra.py -v
|
||||
|
||||
ui-image:
|
||||
name: ui-image
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ RUN uv sync --frozen --no-install-project --no-install-workspace --no-default-gr
|
|||
--extra extra_proxy \
|
||||
--extra semantic-router \
|
||||
--extra saml \
|
||||
--extra bedrock-realtime \
|
||||
--python python3.13
|
||||
|
||||
# Copy full source tree
|
||||
|
|
@ -87,6 +88,7 @@ RUN uv sync --frozen --no-default-groups --no-editable \
|
|||
--extra extra_proxy \
|
||||
--extra semantic-router \
|
||||
--extra saml \
|
||||
--extra bedrock-realtime \
|
||||
--python python3.13
|
||||
|
||||
RUN HOME=/opt/prisma XDG_CACHE_HOME=/opt/prisma/.cache PRISMA_BINARY_CACHE_DIR=/opt/prisma/binaries \
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ RUN uv sync --frozen --no-install-project --no-install-workspace --no-default-gr
|
|||
--extra extra_proxy \
|
||||
--extra semantic-router \
|
||||
--extra saml \
|
||||
--extra bedrock-realtime \
|
||||
--python python3.13
|
||||
|
||||
# Copy full source tree
|
||||
|
|
@ -85,6 +86,7 @@ RUN uv sync --frozen --no-default-groups --no-editable \
|
|||
--extra extra_proxy \
|
||||
--extra semantic-router \
|
||||
--extra saml \
|
||||
--extra bedrock-realtime \
|
||||
--python python3.13
|
||||
|
||||
RUN HOME=/opt/prisma XDG_CACHE_HOME=/opt/prisma/.cache PRISMA_BINARY_CACHE_DIR=/opt/prisma/binaries \
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ RUN --mount=type=cache,target=/app/.cache/uv,id=litellm-uv-cache \
|
|||
--extra extra_proxy \
|
||||
--extra semantic-router \
|
||||
--extra saml \
|
||||
--extra bedrock-realtime \
|
||||
--python python3.13
|
||||
|
||||
# Copy full source tree
|
||||
|
|
@ -97,6 +98,7 @@ RUN --mount=type=cache,target=/app/.cache/uv,id=litellm-uv-cache \
|
|||
--extra extra_proxy \
|
||||
--extra semantic-router \
|
||||
--extra saml \
|
||||
--extra bedrock-realtime \
|
||||
--python python3.13 \
|
||||
--no-sources-package litellm-proxy-extras; \
|
||||
else \
|
||||
|
|
@ -106,6 +108,7 @@ RUN --mount=type=cache,target=/app/.cache/uv,id=litellm-uv-cache \
|
|||
--extra extra_proxy \
|
||||
--extra semantic-router \
|
||||
--extra saml \
|
||||
--extra bedrock-realtime \
|
||||
--python python3.13; \
|
||||
fi
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
"""Image-level check that the built proxy image can import the Bedrock realtime SDK.
|
||||
|
||||
Bedrock Nova Sonic (`/v1/realtime`) imports `aws_sdk_bedrock_runtime` lazily on the
|
||||
first session, so an image whose `uv sync` stages skip the `bedrock-realtime` extra
|
||||
boots, passes health checks, and then fails every Nova Sonic session with
|
||||
"Missing aws_sdk_bedrock_runtime". Importing inside the built image is what catches
|
||||
that class of regression (missing extra, lockfile drift, a stage that syncs a
|
||||
different set of extras), which a static Dockerfile check cannot.
|
||||
|
||||
Gated on LITELLM_IMAGE like the other image checks in this directory; exercised
|
||||
where an image has been built (the image-scan workflow). Requires a working docker CLI.
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
from typing import Final
|
||||
|
||||
import pytest
|
||||
|
||||
IMAGE: Final = os.getenv("LITELLM_IMAGE")
|
||||
NON_ROOT_UID: Final = "12345:0"
|
||||
IMPORT_PROBE: Final = "import aws_sdk_bedrock_runtime, smithy_aws_core; print('bedrock-realtime ok')"
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.skipif(IMAGE is None, reason="requires a built image (set LITELLM_IMAGE)"),
|
||||
pytest.mark.skipif(shutil.which("docker") is None, reason="requires the docker CLI"),
|
||||
]
|
||||
|
||||
|
||||
def test_image_imports_bedrock_realtime_sdk():
|
||||
assert IMAGE is not None
|
||||
|
||||
probe: Final = subprocess.run(
|
||||
[
|
||||
"docker",
|
||||
"run",
|
||||
"--rm",
|
||||
"--network",
|
||||
"none",
|
||||
"--user",
|
||||
NON_ROOT_UID,
|
||||
"--entrypoint",
|
||||
"python",
|
||||
IMAGE,
|
||||
"-c",
|
||||
IMPORT_PROBE,
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
|
||||
assert probe.returncode == 0 and "bedrock-realtime ok" in probe.stdout, (
|
||||
f"{IMAGE} cannot import aws_sdk_bedrock_runtime as uid {NON_ROOT_UID}, so Bedrock Nova Sonic "
|
||||
"/v1/realtime sessions fail with 'Missing aws_sdk_bedrock_runtime'. Is `--extra bedrock-realtime` "
|
||||
f"passed to every `uv sync` in its Dockerfile?\nstdout:\n{probe.stdout}\nstderr:\n{probe.stderr}"
|
||||
)
|
||||
56
tests/test_litellm/test_dockerfile_bedrock_realtime_extra.py
Normal file
56
tests/test_litellm/test_dockerfile_bedrock_realtime_extra.py
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
"""
|
||||
Static checks that every proxy Docker image installs the `bedrock-realtime` extra.
|
||||
|
||||
Bedrock Nova Sonic speech-to-speech (`/v1/realtime`) needs `aws-sdk-bedrock-runtime`,
|
||||
which only ships in the `bedrock-realtime` extra. An image whose `uv sync` stages
|
||||
omit the extra fails every Nova Sonic realtime session with
|
||||
"Missing aws_sdk_bedrock_runtime. Install with: pip install aws-sdk-bedrock-runtime".
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
from typing import Final
|
||||
|
||||
import pytest
|
||||
|
||||
REPO_ROOT: Final = os.path.join(os.path.dirname(__file__), "..", "..")
|
||||
|
||||
PROXY_DOCKERFILES: Final = (
|
||||
"Dockerfile",
|
||||
os.path.join("docker", "Dockerfile.non_root"),
|
||||
os.path.join("docker", "Dockerfile.database"),
|
||||
os.path.join("gateway", "Dockerfile"),
|
||||
)
|
||||
|
||||
CONTINUED_LINE_RE: Final = re.compile(r"(?:\\\n|[^\n])+")
|
||||
UV_SYNC_BOUNDARY_RE: Final = re.compile(r"(?=uv sync)")
|
||||
|
||||
|
||||
def _uv_sync_invocations(dockerfile_text: str) -> tuple[str, ...]:
|
||||
"""Return each `uv sync ...` command, split apart when one RUN holds several (if/else branches)."""
|
||||
return tuple(
|
||||
part
|
||||
for line in CONTINUED_LINE_RE.finditer(dockerfile_text)
|
||||
for part in UV_SYNC_BOUNDARY_RE.split(line.group(0))
|
||||
if part.startswith("uv sync")
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("relative_path", PROXY_DOCKERFILES)
|
||||
def test_every_uv_sync_installs_bedrock_realtime_extra(relative_path: str):
|
||||
dockerfile_path: Final = os.path.join(REPO_ROOT, relative_path)
|
||||
if not os.path.exists(dockerfile_path):
|
||||
pytest.skip(f"{relative_path} not present in this checkout")
|
||||
|
||||
with open(dockerfile_path, "r", encoding="utf-8") as f:
|
||||
contents: Final = f.read()
|
||||
|
||||
invocations: Final = _uv_sync_invocations(contents)
|
||||
assert invocations, f"{relative_path} has no `uv sync` invocation"
|
||||
|
||||
missing: Final = tuple(invocation for invocation in invocations if "--extra bedrock-realtime" not in invocation)
|
||||
assert not missing, (
|
||||
f"{relative_path}: {len(missing)} of {len(invocations)} `uv sync` invocations omit "
|
||||
"`--extra bedrock-realtime`, so aws-sdk-bedrock-runtime is absent and Bedrock Nova Sonic "
|
||||
"/v1/realtime sessions fail with 'Missing aws_sdk_bedrock_runtime'"
|
||||
)
|
||||
Loading…
Add table
Reference in a new issue