Skip to content

Commit 9136884

Browse files
committed
feat: add spatialreal avatar plugin
1 parent d75d61b commit 9136884

13 files changed

Lines changed: 996 additions & 1 deletion

File tree

examples/avatar_agents/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ These providers work with pre-configured avatars using unique avatar identifiers
1515
- **[LemonSlice](./lemonslice/)** - [Platform](https://www.lemonslice.com/) | [Integration Guide](https://lemonslice.com/docs/self-managed/livekit-agent-integration)
1616
- **[LiveAvatar](./liveavatar/)** - [Platform](https://www.liveavatar.com/)
1717
- **[Simli](./simli/)** - [Platform](https://app.simli.com/)
18+
- **[SpatialReal](./spatialreal/)** - [Platform](https://app.spatialreal.ai/) | [Integration Guide](https://docs.spatialreal.ai/guide/rtc-mode)
1819
- **[Tavus](./tavus/)** - [Platform](https://www.tavus.io/)
1920
- **[TruGen](./trugen/)** - [Platform](https://app.trugen.ai/)
2021

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# LiveKit SpatialReal Avatar Agent
2+
3+
This example demonstrates how to create a lip-synced avatar agent using [SpatialReal](https://www.spatialreal.ai/).
4+
5+
Create your SpatialReal app and avatar [here](https://app.spatialreal.ai/).
6+
7+
## Usage
8+
9+
* Update the environment:
10+
11+
```bash
12+
# SpatialReal config
13+
export SPATIALREAL_API_KEY="..."
14+
export SPATIALREAL_APP_ID="..."
15+
export SPATIALREAL_AVATAR_ID="..."
16+
17+
# OpenAI config (or other models, tts, stt)
18+
export OPENAI_API_KEY="..."
19+
20+
# LiveKit config
21+
export LIVEKIT_API_KEY="..."
22+
export LIVEKIT_API_SECRET="..."
23+
export LIVEKIT_URL="..."
24+
```
25+
26+
* Start the agent worker:
27+
28+
```bash
29+
python examples/avatar_agents/spatialreal/agent_worker.py dev
30+
```
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import logging
2+
import os
3+
4+
from dotenv import load_dotenv
5+
6+
from livekit.agents import Agent, AgentServer, AgentSession, JobContext, cli
7+
from livekit.plugins import openai, spatialreal
8+
9+
logger = logging.getLogger("spatialreal-avatar-example")
10+
logger.setLevel(logging.INFO)
11+
12+
load_dotenv()
13+
14+
server = AgentServer()
15+
16+
17+
@server.rtc_session()
18+
async def entrypoint(ctx: JobContext):
19+
session = AgentSession(
20+
llm=openai.realtime.RealtimeModel(voice="alloy"),
21+
resume_false_interruption=False,
22+
)
23+
24+
if not os.getenv("SPATIALREAL_API_KEY"):
25+
raise ValueError("SPATIALREAL_API_KEY is not set")
26+
27+
if not os.getenv("SPATIALREAL_APP_ID"):
28+
raise ValueError("SPATIALREAL_APP_ID is not set")
29+
30+
if not os.getenv("SPATIALREAL_AVATAR_ID"):
31+
raise ValueError("SPATIALREAL_AVATAR_ID is not set")
32+
33+
spatialreal_avatar = spatialreal.AvatarSession()
34+
await spatialreal_avatar.start(session, room=ctx.room)
35+
36+
# start the agent, it will join the room and wait for the avatar to join
37+
await session.start(
38+
agent=Agent(instructions="Talk to me!"),
39+
room=ctx.room,
40+
)
41+
42+
session.generate_reply(instructions="say hello to the user")
43+
44+
45+
if __name__ == "__main__":
46+
cli.run_app(server)

livekit-agents/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ soniox = ["livekit-plugins-soniox>=1.4.4"]
108108
speechify = ["livekit-plugins-speechify>=1.4.4"]
109109
speechmatics = ["livekit-plugins-speechmatics>=1.4.4"]
110110
spitch = ["livekit-plugins-spitch>=1.4.4"]
111+
spatialreal = ["livekit-plugins-spatialreal>=1.4.4"]
111112
tavus = ["livekit-plugins-tavus>=1.4.4"]
112113
trugen = ["livekit-plugins-trugen>=1.3.13"]
113114
turn-detector = ["livekit-plugins-turn-detector>=1.4.4"]
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# LiveKit Agents Plugin for SpatialReal Avatar
2+
3+
This plugin provides integration with [SpatialReal](https://www.spatialreal.ai/)'s avatar service for lip-synced avatar rendering in LiveKit voice agents.
4+
5+
## Installation
6+
7+
```bash
8+
pip install livekit-plugins-spatialreal
9+
```
10+
11+
## Configuration
12+
13+
Set the following environment variables:
14+
15+
```bash
16+
SPATIALREAL_API_KEY=your-api-key
17+
SPATIALREAL_APP_ID=your-app-id
18+
SPATIALREAL_AVATAR_ID=your-avatar-id
19+
20+
LIVEKIT_URL=
21+
LIVEKIT_API_KEY=
22+
LIVEKIT_API_SECRET=
23+
```
24+
25+
## Usage
26+
27+
```python
28+
from livekit.agents import Agent, AgentSession, JobContext, cli, WorkerOptions
29+
from livekit.plugins import spatialreal
30+
31+
class VoiceAssistant(Agent):
32+
def __init__(self):
33+
super().__init__(
34+
instructions="You are a helpful voice assistant."
35+
)
36+
37+
async def entrypoint(ctx: JobContext):
38+
await ctx.connect()
39+
40+
# Configure your pipeline components (STT, LLM, TTS)
41+
session = AgentSession(
42+
stt=stt,
43+
llm=llm,
44+
tts=tts,
45+
)
46+
47+
# Initialize and start the avatar session
48+
avatar = spatialreal.AvatarSession()
49+
await avatar.start(session, room=ctx.room)
50+
51+
# Start the agent session
52+
await session.start(
53+
agent=VoiceAssistant(),
54+
room=ctx.room,
55+
)
56+
57+
if __name__ == "__main__":
58+
cli.run_app(WorkerOptions(entrypoint_fnc=entrypoint))
59+
```
60+
61+
For production agents, catch `SpatialRealException` so you can decide whether to fail the job or continue without avatar output:
62+
63+
```python
64+
try:
65+
await avatar.start(session, room=ctx.room)
66+
except spatialreal.SpatialRealException as err:
67+
logger.error("Avatar startup failed: %s", err)
68+
raise
69+
```
70+
71+
## API Reference
72+
73+
### `AvatarSession`
74+
75+
Main class for integrating SpatialReal avatars with LiveKit agents.
76+
77+
#### Constructor Parameters
78+
79+
| Parameter | Type | Description |
80+
|-----------|------|-------------|
81+
| `api_key` | `str` | SpatialReal API key (or set `SPATIALREAL_API_KEY`) |
82+
| `app_id` | `str` | SpatialReal application ID (or set `SPATIALREAL_APP_ID`) |
83+
| `avatar_id` | `str` | Avatar ID to use (or set `SPATIALREAL_AVATAR_ID`) |
84+
| `console_endpoint_url` | `str` | Custom console endpoint URL |
85+
| `ingress_endpoint_url` | `str` | Custom ingress endpoint URL |
86+
| `avatar_participant_identity` | `str` | LiveKit identity for avatar participant |
87+
| `idle_timeout_seconds` | `int` | LiveKit egress idle timeout in seconds (`0` uses server defaults) |
88+
| `sample_rate` | `int \| None` | Optional avatar audio sample rate override |
89+
90+
#### Methods
91+
92+
- `start(agent_session, room, *, livekit_url, livekit_api_key, livekit_api_secret)`: Start the avatar session and hook into the agent's audio output. Raises `SpatialRealException` with actionable context if startup fails.
93+
- `aclose()`: Clean up avatar session resources.
94+
95+
When starting, the plugin automatically sets `lk.publish_on_behalf` to the
96+
agent participant identity for avatar worker association in LiveKit frontends.
97+
98+
### `SpatialRealException`
99+
100+
Exception raised for SpatialReal-related errors.
101+
102+
## License
103+
104+
MIT
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"""SpatialReal avatar plugin for LiveKit Agents.
2+
3+
This plugin provides integration with SpatialReal's avatar service for
4+
lip-synced avatar rendering in LiveKit voice agents.
5+
6+
See https://docs.spatialreal.ai for more information.
7+
8+
Usage:
9+
from livekit.plugins.spatialreal import AvatarSession
10+
11+
avatar = AvatarSession()
12+
await avatar.start(agent_session, room=ctx.room)
13+
"""
14+
15+
from .avatar import AvatarSession, SpatialRealException
16+
from .version import __version__
17+
18+
__all__ = [
19+
"AvatarSession",
20+
"SpatialRealException",
21+
"__version__",
22+
]
23+
24+
# Try to register plugin if Plugin class is available (livekit-agents >= 1.3)
25+
try:
26+
from livekit.agents import Plugin
27+
28+
from .log import logger
29+
30+
class SpatialRealPlugin(Plugin):
31+
def __init__(self) -> None:
32+
super().__init__(__name__, __version__, __package__, logger)
33+
34+
Plugin.register_plugin(SpatialRealPlugin())
35+
except (ImportError, AttributeError):
36+
# Plugin registration not available in older versions
37+
pass

0 commit comments

Comments
 (0)