-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Make every example a uv workspace member #6649
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e8d4035
8c5a76c
fc81262
8f0c8c6
ca105a1
a97e2d6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| #!/usr/bin/env python3 | ||
| """Pin an example's in-repo dependencies to a git ref, in place. | ||
|
|
||
| An example resolves its livekit-* dependencies through the workspace root's | ||
| ``[tool.uv.sources]``, which only applies inside this repo. A deploy build | ||
| context is the example directory alone, so appending a git source for every | ||
| in-repo dependency is what builds the image against this checkout's code | ||
| instead of the latest release on PyPI. | ||
|
|
||
| python .github/pin_example_to_ref.py examples/hotel_receptionist --ref main | ||
|
|
||
| Needs Python 3.11+ for tomllib. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import argparse | ||
| import re | ||
| import sys | ||
| from pathlib import Path | ||
|
|
||
| import tomllib | ||
|
|
||
| REPO_ROOT = Path(__file__).resolve().parent.parent | ||
| REPO = "https://github.com/livekit/agents.git" | ||
|
|
||
| # The distribution name at the start of a requirement, e.g. "livekit-agents[mcp]>=1.6". | ||
| # Extras stay on the dependency itself; a source is keyed by name alone. | ||
| REQUIREMENT = re.compile(r"^(?P<name>[A-Za-z0-9._-]+)") | ||
|
|
||
|
|
||
| def monorepo_path(name: str) -> str | None: | ||
| """Path of ``name`` within this repo, or None if it lives elsewhere. | ||
|
|
||
| Only packages that actually exist in the checkout are pinned. 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 | ||
| specifier. | ||
| """ | ||
| for rel in (name, f"livekit-plugins/{name}"): | ||
| if (REPO_ROOT / rel).is_dir(): | ||
| return rel | ||
| return None | ||
|
|
||
|
|
||
| def git_sources(dependencies: list[str], ref: str) -> str: | ||
| lines = [ | ||
| "", | ||
| "# Written by .github/pin_example_to_ref.py for a deploy: the build context is", | ||
| "# this directory alone, so the in-repo dependencies come from the git ref being", | ||
| f"# deployed ({ref}) rather than from PyPI.", | ||
| "[tool.uv.sources]", | ||
| ] | ||
| for dep in dependencies: | ||
| match = REQUIREMENT.match(dep) | ||
| if match is None: | ||
| continue | ||
| path = monorepo_path(match["name"]) | ||
| if path is None: | ||
| continue | ||
| lines.append( | ||
| f'{match["name"]} = {{ git = "{REPO}", rev = "{ref}", subdirectory = "{path}" }}' | ||
| ) | ||
| return "\n".join(lines) + "\n" | ||
|
|
||
|
|
||
| def main() -> int: | ||
| parser = argparse.ArgumentParser(description=__doc__) | ||
| parser.add_argument("example", type=Path, help="path to the example directory") | ||
| parser.add_argument( | ||
| "--ref", | ||
| required=True, | ||
| help="git ref in-repo dependencies are pinned to (branch, tag or sha)", | ||
| ) | ||
| args = parser.parse_args() | ||
|
|
||
| pyproject = args.example / "pyproject.toml" | ||
| contents = pyproject.read_text() | ||
| parsed = tomllib.loads(contents) | ||
| if "sources" in parsed.get("tool", {}).get("uv", {}): | ||
| parser.error(f"{pyproject} already declares [tool.uv.sources]") | ||
|
|
||
| sources = git_sources(parsed["project"]["dependencies"], args.ref) | ||
| pyproject.write_text(contents + sources) | ||
| print(f"--- {pyproject} pinned to {args.ref} ---") | ||
| print(sources, end="") | ||
| return 0 | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| sys.exit(main()) |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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 \ | ||||||||||||||||||||
|
|
@@ -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> | ||||||||||||||||||||
|
Comment on lines
+38
to
+41
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Deploy instructions in example container recipes point at a script path that does not exist The build instructions tell the reader to run a pinning helper from a Impact: Someone deploying an example by hand follows a broken command and can silently build the wrong code. Where the mismatch comes fromThe last commit of this PR moved the pinning script into
Suggested change
Was this helpful? React with 👍 or 👎 to provide feedback. |
||||||||||||||||||||
| COPY pyproject.toml ./ | ||||||||||||||||||||
| RUN uv sync --no-cache | ||||||||||||||||||||
| ENV PATH="/app/.venv/bin:${PATH}" | ||||||||||||||||||||
|
Comment on lines
+42
to
+44
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 ( Why the interpreter is not the image's python3.13The base image is The previous
Suggested change
Was this helpful? React with 👍 or 👎 to provide feedback.
Comment on lines
+42
to
+44
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 ( Missing .dockerignore in examples/avatarEvery other example with a Dockerfile ships a Prompt for agentsWas 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. | ||||||||||||||||||||
|
|
||||||||||||||||||||
| 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. |
This file was deleted.
| 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. |
This file was deleted.
| 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. |
Uh oh!
There was an error while loading. Please reload this page.