Header Logo

Adding Voice, Video, and Lip Sync to a Python AI Agent with WebSockets

Adding Voice, Video, and Lip Sync to a Python AI Agent with WebSockets

Add voice, video, and lip sync to a Python AI agent with WebSockets, SDK, LiveKit plugin, and iframe embed.

Introduction


If you have a working Python voice agent, the next step is usually not “make it smarter.” It is “make it feel present.” A text-to-speech loop is fine for demos, but once you add a synchronized face, the interaction changes: users can see turn-taking, perceive timing more naturally, and tolerate the small latency that always exists in realtime systems.


That sounds simple until you try to wire it together. You need to keep audio, video, and agent state aligned; you need to manage session lifecycle; and if you are doing this in a browser, you need a transport that behaves well under realtime constraints. By the end of this post, you should understand the moving parts well enough to add a lip-synced avatar to a Python agent, know where WebSockets fit versus WebRTC, and know which integration surface to use depending on whether you are building a voice bot, an embedded web experience, or a custom backend service.


What “voice + video + lip sync” actually means in a realtime agent


For a developer, the important distinction is that the avatar is not “rendered video” in the traditional sense. You are usually dealing with a realtime stream whose visual output is driven by the agent’s speech state. In practice:


  • your agent produces audio or text that becomes speech,

  • the avatar layer turns that speech into a synchronized facial animation or video face,

  • the client receives a continuous stream with timing information preserved closely enough that lip movement tracks phonemes and pauses.


The failure mode to avoid is treating voice and video as independent channels. If the audio gets ahead of the face, or the face begins “talking” before the audio starts, the result feels broken even if the underlying model quality is good. Good realtime avatar systems are mostly about transport, buffering, and synchronization discipline.


For web delivery, WebSockets are often the simplest control plane: they give you low-latency bidirectional messaging for session setup, state updates, and event handling. The media itself may be streamed through a different realtime path depending on the product. The key is that you need an always-on connection and an event model, not a one-shot request/response API.


Designing the agent loop: where speech, state, and timing meet


In a Python agent, the cleanest architecture is to separate three concerns:


  1. Conversation logic — what the agent says next.

  2. Media generation — turning text into audio and avatar motion.

  3. Session orchestration — connecting to the avatar service, tracking state, and handling disconnects.


That separation matters because the avatar layer should not know how your agent decides to answer, and your conversation logic should not care whether the output is a webcam-style face, an animated head, or a customer-facing character embedded in a product page.


Python SDK: programmatic session control


If you are managing avatars or sessions from backend code, use the Python SDK rather than calling the REST API everywhere. The SDK is the right place to create sessions, attach metadata, and keep the implementation readable.


The exact object names and fields depend on the current SDK version, so treat the following as a shape rather than copy-paste production code:


from protoface import ProtofaceClient
from protoface import ProtofaceClient
from protoface import ProtofaceClient


The practical pattern is: create or pick an avatar, start a session for a specific user interaction, then hand the session’s realtime endpoint to the part of your app that handles streaming. If your application already has conversation state, attach a stable identifier so you can correlate logs, usage, and debugging later.


If you prefer raw HTTP or need to debug outside the SDK, the REST API uses bearer authentication with API keys from your server-side environment:


curl -X POST <a href="https://api.protoface.com/sessions" data-framer-link="Link:{"url":"https://api.protoface.com/sessions","type":"url"}">https://api.protoface.com/sessions</a> <br>
curl -X POST <a href="https://api.protoface.com/sessions" data-framer-link="Link:{"url":"https://api.protoface.com/sessions","type":"url"}">https://api.protoface.com/sessions</a> <br>
curl -X POST <a href="https://api.protoface.com/sessions" data-framer-link="Link:{"url":"https://api.protoface.com/sessions","type":"url"}">https://api.protoface.com/sessions</a> <br>


That API shape is intentionally familiar: create a session on the server, pass only the minimum necessary realtime information to the client, and keep your key out of the browser.


Using the LiveKit Agents plugin when your agent already speaks LiveKit


If your voice agent is already built on LiveKit Agents, the most direct integration is the LiveKit plugin. The point here is not to rebuild your voice stack; it is to drop a synced avatar into an existing agent with minimal glue code. The plugin handles the avatar side so your agent can keep using its existing speech pipeline and turn-taking logic.


That is the right abstraction if your app already has:


  • LiveKit rooms and participant state,

  • an audio pipeline for speech in and out,

  • agent code that reacts to transcripts or ASR events.


In that case, the avatar layer should feel like another agent service, not like a separate product you have to orchestrate manually. The LiveKit plugin documentation and examples are on the package page and repo; start with the GitHub repository for the code and the PyPI package for installation details. If you are integrating via Pipecat instead, there is a separate Pipecat guide and package path that follows the same basic architecture.


For a package install, the rough shape is:


pip install livekit-plugins-protoface<br
pip install livekit-plugins-protoface<br
pip install livekit-plugins-protoface<br


From there, your LiveKit agent can emit speech normally while the plugin keeps the avatar synchronized to the same conversational turn. The exact hook points depend on your existing agent setup, but the main idea is simple: keep your agent’s event loop authoritative, and let the avatar follow it.


Web integration: why iframe embeds are often the lowest-friction option


Not every avatar needs a custom backend. If your goal is to place an interactive character on a marketing site, product page, or in-app help center, a customer-managed iframe embed is often the easiest route. The important security property is that the browser never sees your API key. The parent page simply hosts an iframe, and the avatar experience runs inside a constrained embed with allowlisted origins.


This matters more than it sounds. The usual anti-pattern is pushing secrets into frontend code and then trying to claw back control with obfuscation. For realtime AI, that is a bad trade. A managed iframe lets you keep authentication server-side, scope per-embed behavior, and enforce limits like duration or per-IP usage without exposing operational details to the client.


Use this route when:


  • you want a fast web integration with no backend work,

  • you do not want to expose API keys in the browser,

  • you want the avatar experience to be configurable per embed.


Use a backend + SDK or REST integration when you need tighter control over your own application lifecycle, custom routing, or deeper orchestration with internal systems.


Operational details that save you time later


Realtime avatar systems are operationally closer to voice infrastructure than to ordinary web APIs. A few things are worth planning for up front:


  • Session boundaries: create explicit sessions per conversation so you can track usage, latency, and failures cleanly.

  • Latency budget: lip sync can hide small delays, but it cannot hide jitter or large stalls. Keep your agent and media path lean.

  • Fallback behavior: decide what happens if the avatar stream drops. A graceful audio-only fallback is better than a broken face.

  • Rate limits and quotas: especially for public embeds, enforce duration and IP-based limits so unexpected usage does not become a bill or an incident.

  • Observability: log session IDs, user IDs, and timestamps so you can correlate speech generation with media timing.


Also pay attention to quality tier selection. Higher fidelity avatars cost more, and in realtime systems cost often rises with the amount of compute and rendering involved. Pick the lowest tier that meets the product requirement, then measure whether the user experience actually benefits from more detail.


How Protoface fits into this stack


Protoface sits in the avatar layer: it gives you the realtime avatar/session primitives, the Python SDK for server-side orchestration, the REST API at api.protoface.com, and the LiveKit plugin when you already have a LiveKit voice agent. If you are building a web experience, the iframe embed path is the shortest route to a production-safe browser integration because the API key stays off the client entirely.


The useful mental model is: keep your agent logic where it belongs, and let the avatar system handle the hard realtime part of making speech visible. That division of responsibilities is what makes these integrations maintainable.


Conclusion


Adding a synced face to a Python AI agent is mostly an integration problem, not a modeling problem. You need a realtime transport, a clear session model, and a way to keep speech and animation aligned. For LiveKit-based agents, a plugin is the least disruptive path. For backend control, use the SDK or REST API. For browser delivery, prefer an iframe embed so you do not leak keys or invent your own security model.


If you want implementation details, field names, and current examples, start with the docs at docs.protoface.com. If you are coming from a specific stack, the quickstarts linked from the project README are also a good way to see the moving parts together before wiring them into your application.


Add a face to your AI.

No credit card needed.

Add a face to your AI.

No credit card needed.

Add a face to your AI.

No credit card needed.