Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 5 additions & 50 deletions .github/workflows/deploy-examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
# Fetch Git-LFS assets (e.g. examples/drive-thru/bg_noise.mp3) so the
# real binaries — not pointer files — get uploaded as the build context
# and copied into the image via `COPY . .`. This checkout authenticates
# with GITHUB_TOKEN, so its LFS fetch is allowed (unlike pip's anonymous
# with GITHUB_TOKEN, so its LFS fetch is allowed (unlike uv's anonymous
# git clone). Without this the agent crashes at runtime decoding a
# 131-byte pointer as audio.
lfs: true
Expand Down Expand Up @@ -73,62 +73,17 @@ jobs:
print(f"wrote examples/{slug}/livekit.toml")
PY

- name: Pin livekit-* requirements to the deployed ref
working-directory: examples/${{ matrix.example }}
- name: Pin in-repo dependencies to the deployed ref
env:
# Build the agent against the code at the ref we're deploying,
# not the latest release on PyPI. Without this, a branch deploy
# would silently run against the published livekit-agents instead
# of the branch's own SDK changes. Mirror the checkout ref above,
# using ref_name so it's a plain branch/tag pip can resolve.
# using ref_name so it's a plain branch/tag git can resolve.
DEPLOY_REF: ${{ inputs.ref || github.ref_name }}
run: |
python3 <<'PY'
import os, re
from pathlib import Path

REF = os.environ["DEPLOY_REF"]
REPO = "git+https://github.com/livekit/agents.git"
REPO_ROOT = Path(os.environ["GITHUB_WORKSPACE"])

# The package name (and optional [extras]) at the start of a
# requirement, e.g. "livekit-agents[evals]>=1.5.7".
REQUIREMENT = re.compile(r"^(?P<name>[A-Za-z0-9._-]+)(?P<extras>\[.*\])?")

def monorepo_path(name):
"""Path of `name` within this repo, or None if it lives elsewhere.

We only pin packages that actually exist in the checkout. The
directory basename is the distribution name for every package
here (livekit-agents at the root, everything else under
livekit-plugins/). Anything not found (livekit rtc,
livekit-blingfire, livekit-local-inference, …) keeps its
PyPI pin.
"""
for rel in (name, f"livekit-plugins/{name}"):
if (REPO_ROOT / rel).is_dir():
return rel
return None

def pin_to_ref(line):
"""Repoint an in-repo requirement at the deployed git ref.

External requirements, comments and blanks pass through
untouched (the regex doesn't match a leading '#' or '').
"""
match = REQUIREMENT.match(line.strip())
path = monorepo_path(match["name"]) if match else None
if path is None:
return line
return f"{match['name']}{match['extras'] or ''} @ {REPO}@{REF}#subdirectory={path}"

requirements = Path("requirements.txt")
pinned = [pin_to_ref(line) for line in requirements.read_text().splitlines()]
requirements.write_text("\n".join(pinned) + "\n")

print(f"--- requirements.txt pinned to {REF} ---")
print(requirements.read_text())
PY
python3 scripts/pin_example_to_ref.py \
"examples/${{ matrix.example }}" --ref "$DEPLOY_REF"

- name: Build secrets file
working-directory: examples/${{ matrix.example }}
Expand Down
14 changes: 14 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,20 @@ uv run examples/voice_agents/basic_agent.py console

Your agent is now running in the console.

### Taking an example with you

Each example directory stands on its own: copy it anywhere, and

```bash
uv sync
uv run agent.py console
```

installs from PyPI without the rest of the repo. `lk agent deploy .` works the
same way, off the `Dockerfile` in the directory. Inside this repo the same
directories are uv workspace members, so they run against your local SDK
changes instead.

For frontend support, use the [Agents playground](https://agents-playground.livekit.io) or the [starter apps](https://docs.livekit.io/agents/start/frontend/#starter-apps).

## 📖 Additional Resources
Expand Down
14 changes: 10 additions & 4 deletions examples/avatar/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ ARG PYTHON_VERSION=3.13
FROM python:${PYTHON_VERSION}-slim AS base

ENV PYTHONUNBUFFERED=1
ENV PIP_DISABLE_PIP_VERSION_CHECK=1

COPY --from=ghcr.io/astral-sh/uv:0.11.6 /uv /usr/local/bin/uv

ARG UID=10001
RUN adduser \
Expand All @@ -26,16 +27,21 @@ RUN apt-get update && apt-get install -y \
python3-dev \
&& rm -rf /var/lib/apt/lists/*

# Enable git-lfs so pip's git installs smudge LFS-tracked binaries
# Enable git-lfs so git dependencies smudge LFS-tracked binaries
# (e.g. silero's bundled VAD onnx) instead of leaving pointer files.
# --system so the unprivileged appuser below inherits the filters.
RUN git lfs install --system

WORKDIR /app
USER appuser

COPY requirements.txt ./
RUN pip install --user --no-cache-dir -r requirements.txt
# The example directory is the whole build context, so pyproject.toml has to
# resolve on its own here — no workspace, no lockfile. A deploy from this repo
# repoints the livekit-* dependencies at the ref being deployed first:
# python scripts/pin_example_to_ref.py examples/<name> --ref <git-ref>
COPY pyproject.toml ./
RUN uv sync --no-cache
ENV PATH="/app/.venv/bin:${PATH}"
Comment on lines +42 to +44

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Example container images may be built with a downloaded Python instead of the one the image is pinned to

The dependency install step for every example image is run without telling the tool which Python to use (uv sync --no-cache at examples/avatar/Dockerfile:43), so it can fetch and use its own newest Python instead of the version the image is pinned to.
Impact: Example deployments can silently run on a different Python than intended, and the image build can fail when a dependency has no prebuilt package for that newer Python.

Why the interpreter is not the image's python3.13

The base image is python:${PYTHON_VERSION}-slim with ARG PYTHON_VERSION=3.13 (examples/avatar/Dockerfile:6-7), but uv's default python-preference is managed with automatic downloads enabled, which is exactly why Astral's own Docker guide sets UV_PYTHON_DOWNLOADS=0 "because we want to use the system interpreter". The example pyproject.toml only declares requires-python = ">=3.10" (examples/avatar/pyproject.toml:4) and there is no .python-version, so uv is free to install a managed interpreter (e.g. 3.14, still allowed by livekit-agents' >=3.10,<3.15) and create /app/.venv from it. Consequences: (1) ARG PYTHON_VERSION no longer controls the runtime interpreter; (2) dependencies are resolved for that interpreter, so packages such as av (a livekit-agents dependency) or apsw may have no wheel and fall back to an sdist build, which for av needs ffmpeg dev libraries not installed in the image. All 8 example Dockerfiles are byte-identical, so this applies to avatar, drive-thru, frontdesk, healthcare, homepage, hotel_receptionist, inference and survey.

The previous pip install --user -r requirements.txt always used the image's interpreter.

Suggested change
COPY pyproject.toml ./
RUN uv sync --no-cache
ENV PATH="/app/.venv/bin:${PATH}"
COPY pyproject.toml ./
# Use the interpreter this image is pinned to instead of letting uv download
# and prefer a managed Python of its own choosing.
ENV UV_PYTHON_DOWNLOADS=0
RUN uv sync --no-cache --python "$(command -v python3)"
ENV PATH="/app/.venv/bin:${PATH}"
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +42 to +44

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Avatar example image can be broken by a virtual environment left in the copied directory

The avatar example builds its dependencies into a virtual environment inside the image (uv sync at examples/avatar/Dockerfile:43) and then copies the whole directory on top of it, but that directory has no ignore file, so a virtual environment sitting next to the sources overwrites the one built in the image and the agent fails to start.
Impact: Deploying the avatar example from a working copy where someone already installed its dependencies locally produces an image that cannot run.

Missing .dockerignore in examples/avatar

Every other example with a Dockerfile ships a .dockerignore that excludes **/.venv/ (e.g. examples/hotel_receptionist/.dockerignore), so the final COPY . . (examples/avatar/Dockerfile:50) cannot clobber /app/.venv. examples/avatar/ has no .dockerignore at all. With the old pip install --user layout a stray host .venv in the context was inert; now /app/.venv is the interpreter selected by ENV PATH="/app/.venv/bin:${PATH}", so a host .venv (wrong platform, absolute paths baked in) replaces it and CMD ["python", "agent.py", "start"] breaks. examples/README.md:99-111 now explicitly tells users to copy an example directory and run uv sync there before lk agent deploy ., which is exactly how such a .venv appears.

Prompt for agents
examples/avatar/ is the only example with a Dockerfile that has no .dockerignore. The new Dockerfile installs dependencies into /app/.venv and puts it first on PATH, then runs `COPY . .`, so any .venv present in the build context replaces the image's environment and the container's `python` becomes a host-built venv. Add a .dockerignore to examples/avatar mirroring the one used by the other examples (e.g. examples/hotel_receptionist/.dockerignore), which excludes **/.venv/, caches, .env files and tests.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


# Pre-download model weights plugins ship (silero VAD, turn-detector, …)
# so the container is ready to take traffic without a cold-download stall.
Expand Down
6 changes: 3 additions & 3 deletions examples/avatar/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Try it in the [LiveKit Playground](https://agents.livekit.io/?example=avatar).
> authenticates with your LiveKit credentials and the gateway creates the
> provider session with LiveKit's wholesale key. Requires the
> `avatar_lemonslice` feature flag on your project. Run with
> `python inference_agent.py dev` and set `LIVEKIT_URL`, `LIVEKIT_API_KEY`,
> `uv run inference_agent.py dev` and set `LIVEKIT_URL`, `LIVEKIT_API_KEY`,
> `LIVEKIT_API_SECRET`, and `LEMONSLICE_IMAGE_URL`.

## What's in here
Expand Down Expand Up @@ -43,8 +43,8 @@ You'll need:
Then:

```bash
pip install -r requirements.txt
python agent.py dev
uv sync --all-extras --dev # from the repository root
uv run agent.py dev
```

Connect from any LiveKit client. The agent reads the starting persona
Expand Down
23 changes: 23 additions & 0 deletions examples/avatar/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[project]
name = "livekit-example-avatar"
version = "0"
requires-python = ">=3.10"
dependencies = [
"livekit-agents>=1.6",
"livekit-plugins-lemonslice>=1.5.7",
"livekit-plugins-silero>=1.5.7",
"livekit-plugins-turn-detector>=1.5.7",
"python-dotenv>=1.0.0",
"aiohttp>=3.9.0",
"numpy>=1.26.0",
# python:3.13-slim ships no system tzdata; without this, zoneinfo.ZoneInfo
# raises ZoneInfoNotFoundError at runtime.
"tzdata>=2024.1",
]

[tool.uv]
# an example is a script collection, not an installable distribution
package = false
# No [tool.uv.sources] here: the workspace root repoints the livekit-* deps at
# the in-repo copies for members, and staying free of workspace-only references
# is what lets a copy of this directory resolve on its own, outside the repo.
10 changes: 0 additions & 10 deletions examples/avatar/requirements.txt

This file was deleted.

14 changes: 10 additions & 4 deletions examples/drive-thru/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ ARG PYTHON_VERSION=3.13
FROM python:${PYTHON_VERSION}-slim AS base

ENV PYTHONUNBUFFERED=1
ENV PIP_DISABLE_PIP_VERSION_CHECK=1

COPY --from=ghcr.io/astral-sh/uv:0.11.6 /uv /usr/local/bin/uv

ARG UID=10001
RUN adduser \
Expand All @@ -26,16 +27,21 @@ RUN apt-get update && apt-get install -y \
python3-dev \
&& rm -rf /var/lib/apt/lists/*

# Enable git-lfs so pip's git installs smudge LFS-tracked binaries
# Enable git-lfs so git dependencies smudge LFS-tracked binaries
# (e.g. silero's bundled VAD onnx) instead of leaving pointer files.
# --system so the unprivileged appuser below inherits the filters.
RUN git lfs install --system

WORKDIR /app
USER appuser

COPY requirements.txt ./
RUN pip install --user --no-cache-dir -r requirements.txt
# The example directory is the whole build context, so pyproject.toml has to
# resolve on its own here — no workspace, no lockfile. A deploy from this repo
# repoints the livekit-* dependencies at the ref being deployed first:
# python scripts/pin_example_to_ref.py examples/<name> --ref <git-ref>
COPY pyproject.toml ./
RUN uv sync --no-cache
ENV PATH="/app/.venv/bin:${PATH}"

# Pre-download model weights plugins ship (silero VAD, turn-detector, …)
# so the container is ready to take traffic without a cold-download stall.
Expand Down
18 changes: 18 additions & 0 deletions examples/drive-thru/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[project]
name = "livekit-example-drive-thru"
version = "0"
requires-python = ">=3.10"
dependencies = [
"livekit-agents>=1.6",
"livekit-plugins-silero>=1.5.7",
"livekit-plugins-turn-detector>=1.5.7",
"python-dotenv>=1.0.0",
"pydantic>=2.0.0",
]

[tool.uv]
# an example is a script collection, not an installable distribution
package = false
# No [tool.uv.sources] here: the workspace root repoints the livekit-* deps at
# the in-repo copies for members, and staying free of workspace-only references
# is what lets a copy of this directory resolve on its own, outside the repo.
5 changes: 0 additions & 5 deletions examples/drive-thru/requirements.txt

This file was deleted.

14 changes: 10 additions & 4 deletions examples/frontdesk/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ ARG PYTHON_VERSION=3.13
FROM python:${PYTHON_VERSION}-slim AS base

ENV PYTHONUNBUFFERED=1
ENV PIP_DISABLE_PIP_VERSION_CHECK=1

COPY --from=ghcr.io/astral-sh/uv:0.11.6 /uv /usr/local/bin/uv

ARG UID=10001
RUN adduser \
Expand All @@ -26,16 +27,21 @@ RUN apt-get update && apt-get install -y \
python3-dev \
&& rm -rf /var/lib/apt/lists/*

# Enable git-lfs so pip's git installs smudge LFS-tracked binaries
# Enable git-lfs so git dependencies smudge LFS-tracked binaries
# (e.g. silero's bundled VAD onnx) instead of leaving pointer files.
# --system so the unprivileged appuser below inherits the filters.
RUN git lfs install --system

WORKDIR /app
USER appuser

COPY requirements.txt ./
RUN pip install --user --no-cache-dir -r requirements.txt
# The example directory is the whole build context, so pyproject.toml has to
# resolve on its own here — no workspace, no lockfile. A deploy from this repo
# repoints the livekit-* dependencies at the ref being deployed first:
# python scripts/pin_example_to_ref.py examples/<name> --ref <git-ref>
COPY pyproject.toml ./
RUN uv sync --no-cache
ENV PATH="/app/.venv/bin:${PATH}"

# Pre-download model weights plugins ship (silero VAD, turn-detector, …)
# so the container is ready to take traffic without a cold-download stall.
Expand Down
21 changes: 21 additions & 0 deletions examples/frontdesk/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[project]
name = "livekit-example-frontdesk"
version = "0"
requires-python = ">=3.10"
dependencies = [
"livekit-agents>=1.6",
"livekit-plugins-silero>=1.5.7",
"livekit-plugins-turn-detector>=1.5.7",
"python-dotenv>=1.0.0",
"aiohttp>=3.9.0",
# python:3.13-slim ships no system tzdata; without this, zoneinfo.ZoneInfo
# raises ZoneInfoNotFoundError at runtime.
"tzdata>=2024.1",
]

[tool.uv]
# an example is a script collection, not an installable distribution
package = false
# No [tool.uv.sources] here: the workspace root repoints the livekit-* deps at
# the in-repo copies for members, and staying free of workspace-only references
# is what lets a copy of this directory resolve on its own, outside the repo.
8 changes: 0 additions & 8 deletions examples/frontdesk/requirements.txt

This file was deleted.

14 changes: 10 additions & 4 deletions examples/healthcare/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ ARG PYTHON_VERSION=3.13
FROM python:${PYTHON_VERSION}-slim AS base

ENV PYTHONUNBUFFERED=1
ENV PIP_DISABLE_PIP_VERSION_CHECK=1

COPY --from=ghcr.io/astral-sh/uv:0.11.6 /uv /usr/local/bin/uv

ARG UID=10001
RUN adduser \
Expand All @@ -26,16 +27,21 @@ RUN apt-get update && apt-get install -y \
python3-dev \
&& rm -rf /var/lib/apt/lists/*

# Enable git-lfs so pip's git installs smudge LFS-tracked binaries
# Enable git-lfs so git dependencies smudge LFS-tracked binaries
# (e.g. silero's bundled VAD onnx) instead of leaving pointer files.
# --system so the unprivileged appuser below inherits the filters.
RUN git lfs install --system

WORKDIR /app
USER appuser

COPY requirements.txt ./
RUN pip install --user --no-cache-dir -r requirements.txt
# The example directory is the whole build context, so pyproject.toml has to
# resolve on its own here — no workspace, no lockfile. A deploy from this repo
# repoints the livekit-* dependencies at the ref being deployed first:
# python scripts/pin_example_to_ref.py examples/<name> --ref <git-ref>
COPY pyproject.toml ./
RUN uv sync --no-cache
ENV PATH="/app/.venv/bin:${PATH}"

# Pre-download model weights plugins ship (silero VAD, turn-detector, …)
# so the container is ready to take traffic without a cold-download stall.
Expand Down
19 changes: 19 additions & 0 deletions examples/healthcare/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[project]
name = "livekit-example-healthcare"
version = "0"
requires-python = ">=3.10"
dependencies = [
"livekit-agents>=1.6",
"livekit-plugins-openai>=1.5.7",
"livekit-plugins-silero>=1.5.7",
"openai>=1.0.0",
"python-dotenv>=1.0.0",
"pydantic>=2.0.0",
]

[tool.uv]
# an example is a script collection, not an installable distribution
package = false
# No [tool.uv.sources] here: the workspace root repoints the livekit-* deps at
# the in-repo copies for members, and staying free of workspace-only references
# is what lets a copy of this directory resolve on its own, outside the repo.
6 changes: 0 additions & 6 deletions examples/healthcare/requirements.txt

This file was deleted.

Loading
Loading