name: Deploy Examples on: # Manual runs deploy from the branch picked in the Actions "Use workflow # from" selector — no input needed. workflow_dispatch: # Called by publish.yml after a release to deploy the published commit. workflow_call: inputs: ref: description: "Commit/branch/tag to deploy. Defaults to the triggering ref." type: string required: false permissions: contents: read jobs: deploy: name: Deploy ${{ matrix.example }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: example: [healthcare, survey, frontdesk, drive-thru, inference, avatar, hotel_receptionist] steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: ref: ${{ inputs.ref || github.ref }} # 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 # git clone). Without this the agent crashes at runtime decoding a # 131-byte pointer as audio. lfs: true - name: Install LiveKit CLI run: | curl -sSL https://get.livekit.io/cli | bash lk --version - name: Add LiveKit Cloud project env: LIVEKIT_URL: ${{ secrets.LIVEKIT_EXAMPLES_URL }} LIVEKIT_API_KEY: ${{ secrets.LIVEKIT_EXAMPLES_API_KEY }} LIVEKIT_API_SECRET: ${{ secrets.LIVEKIT_EXAMPLES_API_SECRET }} run: | lk project add examples \ --url "$LIVEKIT_URL" \ --api-key "$LIVEKIT_API_KEY" \ --api-secret "$LIVEKIT_API_SECRET" \ --default - name: Regenerate livekit.toml from playground.yaml run: | python3 -m pip install --quiet pyyaml python3 <<'PY' import yaml from pathlib import Path data = yaml.safe_load(Path("examples/playground.yaml").read_text()) subdomain = data["project"]["subdomain"] slug = "${{ matrix.example }}" entry = data["examples"][slug] toml = ( "[project]\n" f' subdomain = "{subdomain}"\n' "\n" "[agent]\n" f' id = "{entry["agent_id"]}"\n' ) Path(f"examples/{slug}/livekit.toml").write_text(toml) print(f"wrote examples/{slug}/livekit.toml") PY - name: Pin livekit-* requirements to the deployed ref working-directory: examples/${{ matrix.example }} 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. 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[A-Za-z0-9._-]+)(?P\[.*\])?") 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 - name: Build secrets file working-directory: examples/${{ matrix.example }} env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} LEMONSLICE_API_KEY: ${{ secrets.LEMONSLICE_API_KEY }} run: | : > .env.deploy # Register the agent under a deterministic name so the playground # can dispatch to it explicitly. Matches the slug in playground.yaml. echo "LIVEKIT_AGENT_NAME=${{ matrix.example }}" >> .env.deploy case "${{ matrix.example }}" in healthcare) keys="OPENAI_API_KEY" ;; avatar) keys="LEMONSLICE_API_KEY" ;; *) keys="" ;; esac for k in $keys; do val="${!k}" if [ -n "$val" ]; then echo "${k}=${val}" >> .env.deploy fi done - name: Deploy ${{ matrix.example }} working-directory: examples/${{ matrix.example }} run: | args=() if [ -s .env.deploy ]; then args+=(--secrets-file .env.deploy) fi lk agent deploy "${args[@]}" .