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
9 changes: 7 additions & 2 deletions examples/avatar_agents/audio_wave/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,17 @@ This example demonstrates how to create an animated avatar that responds to audi

## Usage

The dispatcher is intended to run as a local helper. Its `/launch` endpoint
accepts LiveKit room tokens and starts local worker processes, so do not expose
it directly on a public network without adding your own authentication and
network controls.

1. Start the avatar dispatcher server:
```bash
python examples/avatar/dispatcher.py [--port 8089]
python examples/avatar_agents/audio_wave/dispatcher.py [--port 8089]
```

2. Start the agent worker:
```bash
python examples/avatar/agent_worker.py dev [--avatar-url http://localhost:8089/launch]
python examples/avatar_agents/audio_wave/agent_worker.py dev
```
16 changes: 12 additions & 4 deletions examples/avatar_agents/audio_wave/dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
logging.basicConfig(level=logging.INFO)

THIS_DIR = Path(__file__).parent.absolute()
DEFAULT_HOST = "127.0.0.1"


@dataclass
Expand Down Expand Up @@ -74,7 +75,7 @@ async def launch_worker(self, connection_info: AvatarConnectionInfo) -> None:
logger.info(f"Launched avatar worker for room: {room_name}")
except Exception as e:
logger.error(f"Failed to launch worker: {e}")
raise HTTPException(status_code=500, detail=str(e)) # noqa: B904
raise HTTPException(status_code=500, detail=str(e)) from e

async def _monitor(self) -> None:
while True:
Expand Down Expand Up @@ -110,10 +111,10 @@ async def handle_launch(self, connection_info: AvatarConnectionInfo) -> dict:
}
except Exception as e:
logger.error(f"Error handling launch request: {e}")
raise HTTPException(status_code=500, detail=f"Failed to launch worker: {str(e)}") # noqa: B904
raise HTTPException(status_code=500, detail=f"Failed to launch worker: {str(e)}") from e


def run_server(host: str = "0.0.0.0", port: int = 8089):
def run_server(host: str = DEFAULT_HOST, port: int = 8089):
dispatcher = AvatarDispatcher()
uvicorn.run(dispatcher.app, host=host, port=port, log_level="info")

Expand All @@ -122,7 +123,14 @@ def run_server(host: str = "0.0.0.0", port: int = 8089):
from argparse import ArgumentParser

parser = ArgumentParser()
parser.add_argument("--host", default="0.0.0.0", help="Host to run server on")
parser.add_argument(
"--host",
default=DEFAULT_HOST,
help=(
"Host to run server on. The default binds to localhost because "
"the dispatcher accepts LiveKit room tokens and starts worker processes."
),
)
parser.add_argument("--port", default=8089, help="Port to run server on")
args = parser.parse_args()
run_server(args.host, args.port)
43 changes: 43 additions & 0 deletions tests/test_audio_wave_dispatcher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import ast
from pathlib import Path

import pytest

pytestmark = pytest.mark.unit


DISPATCHER_PATH = (
Path(__file__).parents[1] / "examples" / "avatar_agents" / "audio_wave" / "dispatcher.py"
)


def _dispatcher_tree() -> ast.Module:
return ast.parse(DISPATCHER_PATH.read_text(encoding="utf-8"))


def test_dispatcher_default_host_is_localhost() -> None:
tree = _dispatcher_tree()

default_host = next(
node.value.value
for node in tree.body
if isinstance(node, ast.Assign)
for target in node.targets
if isinstance(target, ast.Name)
and target.id == "DEFAULT_HOST"
and isinstance(node.value, ast.Constant)
)

assert default_host == "127.0.0.1"


def test_run_server_uses_safe_default_host_constant() -> None:
tree = _dispatcher_tree()
run_server = next(
node
for node in tree.body
if isinstance(node, ast.FunctionDef) and node.name == "run_server"
)

assert isinstance(run_server.args.defaults[0], ast.Name)
assert run_server.args.defaults[0].id == "DEFAULT_HOST"