Guide to Building an Interactive Signage Assistant with LiveKit and Python

Build an interactive signage assistant with Python, LiveKit, and Protoface: realtime voice, avatar sync, sessions, and latency tips.
Introduction
Building an interactive signage assistant is mostly an integration problem: you need a voice agent that can answer questions, a realtime transport that keeps audio and video in sync, and a face that can lip-sync with low enough latency to feel present. The hard part is not generating a response; it is keeping the conversational loop tight enough that the result still works in a lobby, kiosk, or in-store display where people expect immediate feedback.
This post walks through the practical pieces you need to ship that experience with Python and LiveKit. By the end, you should understand how to connect a voice agent to a talking avatar, what the realtime session flow looks like, and where the operational edges are: latency, session management, security, and rate limits.
For the avatar layer, Protoface exposes the pieces you need as a LiveKit plugin, a REST API, a Python SDK, and customer-managed iframe embeds. The examples below focus on the LiveKit path because that is the most natural fit for an interactive signage assistant.
What “interactive signage” actually means technically
An interactive signage assistant is usually a loop with four moving parts:
The display device joins a realtime room and publishes or subscribes to media.
A voice agent receives user speech, runs ASR and LLM inference, and emits text or audio.
An avatar service turns that response into a synchronized face stream, ideally aligned to the agent’s speech timing.
The client renders the video face and routes audio so the interaction feels conversational rather than batchy.
In practice, the avatar is not “just video.” It is a realtime stream tied to a voice turn. If the assistant starts speaking before the face stream is ready, you get obvious desynchronization. If the face lags too far behind the audio, the whole thing looks synthetic. So the integration needs to treat the avatar as part of the agent pipeline, not as a separate UI effect.
LiveKit is a good transport for this because it already gives you media rooms, participant state, and low-latency delivery. The Protoface LiveKit plugin sits on top of that and injects the avatar stream into the agent flow so the agent gains a synchronized talking face without you having to build the rendering pipeline yourself.
Set up the voice agent and attach the avatar
If you already have a LiveKit agent, the integration point is usually where the agent publishes its response. With the Protoface plugin, the avatar becomes another output of the agent lifecycle. The exact classes and constructor fields can change, so treat the following as structural example code and confirm names in the docs and repo.
You’ll typically install the plugin, configure your Protoface credentials, and add the avatar service to the agent setup:
The important part is not the exact object names; it is the coupling. The avatar should be instantiated with the same session context as the voice agent so that speech timing, turn boundaries, and room membership stay aligned. In other words, the agent should not decide what to say in one subsystem and then separately ask another subsystem to generate a face after the fact.
If you want to see the plugin in a live codebase, the relevant examples are in the plugin repository, and the broader implementation notes are in the documentation.
Session flow: create, join, speak, end
For a signage deployment, the session lifecycle matters as much as the media path. You usually need to create an avatar or pick an existing one, open a realtime session, join it from your agent runtime, and tear it down when the kiosk is idle or the browser tab is closed.
If you prefer explicit backend control, the REST API is the cleanest interface. Use it to provision avatars and manage sessions from your own service. Authentication is via API key in a bearer header; never expose that key in the browser.
That request shape is intentionally generic because the exact field names are documented elsewhere. The point is to centralize session creation in a trusted backend, then hand the agent or client the short-lived session details it needs to join. For signage, this usually means your backend starts a session when the device boots, when a visitor taps “Start,” or when motion detection wakes the kiosk.
A good implementation keeps session state explicit:
Idle: no room joined, no media consuming resources.
Warm: avatar session exists, but the display is waiting for interaction.
Active: user is speaking, agent is generating a response, avatar is streaming.
Cooldown: interaction ended; session can be reused or terminated.
This matters because realtime avatars are billed by quality tier, and idle sessions can become an operational leak if you do not clean them up. The dashboard is useful here for checking active sessions, usage, and API key rotation.
Latency, lip sync, and the realities of kiosk hardware
Interactive signage has different failure modes than desktop chat. The device may be underpowered, the network may be lossy, and users may stand farther away than a typical webcam setup. The avatar needs to tolerate that environment without making the assistant feel sluggish.
Three things tend to dominate perceived quality:
1. Turn latency. The time from end-of-user-speech to first audible assistant response should stay low. If your ASR or LLM pipeline is slow, the avatar will faithfully display the delay, which makes it feel broken.
2. Audio/video alignment. Lip sync only looks right if the audio and face are generated from the same turn and delivered over a transport that preserves timing. Avoid stitching together independent video and audio pipelines unless you are prepared to manage sync drift.
3. Device rendering performance. Even if the avatar stream is fine, a kiosk browser can choke on unnecessary DOM work, high-DPI rendering, or an overloaded page. Keep the signage page simple and isolate the avatar element.
On the backend side, implement a timeout policy. A kiosk should not hold a session open indefinitely if no one is interacting. If the device loses connectivity, the server should reclaim the session automatically or mark it expired so the next visitor gets a fresh start.
When a browser embed is enough
Not every signage use case needs a custom backend. If you only need a web page with an interactive avatar and do not want to expose an API key in the browser, a customer-managed iframe embed is often the fastest path. The parent page can be locked down with an origin allowlist, and the embed can carry per-embed voice, custom instructions, and rate limits by IP and duration.
That makes it a good fit for simple kiosk pages, event displays, or demo installations where you want the assistant embedded in a site but do not need deep control over the realtime agent pipeline. The trade-off is obvious: less backend work, less control. If you need tight integration with an existing LiveKit voice stack or custom business logic, use the plugin or REST API instead.
Practical implementation notes
A few things are easy to miss on a first build:
Keep credentials server-side. API keys should only live in trusted services. If the browser needs to connect, use an iframe embed or a backend-issued short-lived session mechanism.
Design for interruptions. In signage, people interrupt, walk away, or talk over the assistant. Make sure your voice agent handles barge-in and resets the avatar state cleanly.
Choose a sane quality tier. Higher quality is not always necessary for a kiosk in a bright lobby. Measure perceived quality against bandwidth and cost.
Instrument session churn. If sessions are getting created and destroyed too often, you will see unnecessary load and worse perceived latency.
If you are building in Python, the Python SDK is useful for administrative workflows: provisioning avatars, creating sessions from your own service, and polling usage from the backend. The repo is here: protoface-sdk-python.
Again, treat this as a shape rather than a contract. The useful pattern is the same: keep provisioning in Python, keep secrets server-side, and hand only the minimum session data to the runtime that needs it.
How to think about production readiness
For a signage assistant, “works on my machine” is not enough. You want to validate the full chain under realistic conditions:
Cold-start the kiosk or browser and verify it can join a room reliably.
Simulate a short utterance, a long utterance, and an interruption.
Test network variability, especially if the display is on guest Wi-Fi or LTE.
Watch for session leaks when the page is refreshed or the machine sleeps.
Confirm the visual quality tier matches your room lighting and screen size.
If you are already using LiveKit Agents, the quickest path is to add the avatar plugin and validate one end-to-end turn. If you are starting from a browser-only kiosk, the iframe embed may get you to a usable MVP faster. If you need control over provisioning and session orchestration, use the REST API or Python SDK and keep the frontend thin.
Conclusion
The core pattern is straightforward: put the voice agent and the avatar in the same realtime session, keep session management server-side, and optimize for low-latency turn taking rather than for raw media fidelity alone. That gives you an interactive signage assistant that feels responsive instead of bolted together.
For implementation details, supported fields, and current examples, start with the documentation. If you are integrating with LiveKit, the plugin and quickstarts in the GitHub organization are the fastest way to see the wiring in practice. Build the smallest possible end-to-end turn first, then harden session lifecycle, timeouts, and deployment hygiene once the basic loop is stable.
