169 lines
6.3 KiB
YAML
169 lines
6.3 KiB
YAML
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<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
|
|
|
|
- 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[@]}" .
|