Module livekit.plugins.protoface.avatar
Classes
class AvatarSession (*,
avatar_id: str = 'av_stock_001',
api_url: NotGivenOr[str | None] = NOT_GIVEN,
api_key: NotGivenOr[str | None] = NOT_GIVEN,
max_duration_seconds: NotGivenOr[int | None] = NOT_GIVEN,
avatar_participant_identity: NotGivenOr[str | None] = NOT_GIVEN,
avatar_participant_name: NotGivenOr[str | None] = NOT_GIVEN,
conn_options: APIConnectOptions = APIConnectOptions(max_retry=3, retry_interval=2.0, timeout=10.0))-
Expand source code
class AvatarSession(BaseAvatarSession): """A Protoface avatar session for LiveKit Agents.""" def __init__( self, *, avatar_id: str = DEFAULT_STOCK_AVATAR_ID, api_url: NotGivenOr[str | None] = NOT_GIVEN, api_key: NotGivenOr[str | None] = NOT_GIVEN, max_duration_seconds: NotGivenOr[int | None] = NOT_GIVEN, avatar_participant_identity: NotGivenOr[str | None] = NOT_GIVEN, avatar_participant_name: NotGivenOr[str | None] = NOT_GIVEN, conn_options: APIConnectOptions = DEFAULT_API_CONNECT_OPTIONS, ) -> None: """Create a Protoface avatar session. Args: avatar_id: Protoface avatar ID to render. Defaults to the stable stock avatar ID `av_stock_001`. api_url: Protoface API base URL. Defaults to `PROTOFACE_API_URL` or the public Protoface API. api_key: Protoface API key. Defaults to `PROTOFACE_API_KEY`. max_duration_seconds: Optional maximum session duration. Protoface applies the lower of this value and the account plan limit. avatar_participant_identity: LiveKit participant identity for the avatar worker. avatar_participant_name: Display name for the avatar participant. conn_options: Timeout and retry options for Protoface API requests. Raises: ProtofaceException: If no API key is provided. """ super().__init__() self._avatar_id = avatar_id self._max_duration_seconds = max_duration_seconds self._avatar_participant_identity = _resolve_optional_string( avatar_participant_identity, _AVATAR_AGENT_IDENTITY, ) self._avatar_participant_name = _resolve_optional_string( avatar_participant_name, _AVATAR_AGENT_NAME, ) self._api = ProtofaceAPI( api_key=api_key, api_url=api_url, conn_options=conn_options, ) self._session_id: str | None = None @property def avatar_identity(self) -> str: return self._avatar_participant_identity @property def provider(self) -> str: return "protoface" @property def session_id(self) -> str | None: """Protoface session ID after `start()` succeeds, otherwise `None`.""" return self._session_id async def start( self, agent_session: AgentSession, room: rtc.Room, *, livekit_url: NotGivenOr[str | None] = NOT_GIVEN, livekit_api_key: NotGivenOr[str | None] = NOT_GIVEN, livekit_api_secret: NotGivenOr[str | None] = NOT_GIVEN, ) -> None: """Start the Protoface session and connect it to the LiveKit room. Args: agent_session: LiveKit agent session whose audio output should be routed through the avatar. room: Connected LiveKit room for the agent job. livekit_url: LiveKit server URL. Defaults to `LIVEKIT_URL`. livekit_api_key: LiveKit API key. Defaults to `LIVEKIT_API_KEY`. livekit_api_secret: LiveKit API secret. Defaults to `LIVEKIT_API_SECRET`. Raises: RuntimeError: If the same `AvatarSession` is started more than once. ProtofaceException: If required LiveKit credentials are missing or Protoface returns an invalid session response. """ if self._session_id is not None: raise RuntimeError("AvatarSession.start() called twice; create a new AvatarSession.") await super().start(agent_session, room) livekit_url_value = _resolve_env_or_value(livekit_url, "LIVEKIT_URL") livekit_api_key_value = _resolve_env_or_value(livekit_api_key, "LIVEKIT_API_KEY") livekit_api_secret_value = _resolve_env_or_value( livekit_api_secret, "LIVEKIT_API_SECRET", ) if not livekit_url_value or not livekit_api_key_value or not livekit_api_secret_value: raise ProtofaceException( "livekit_url, livekit_api_key, and livekit_api_secret must be set " "by arguments or environment variables" ) worker_token = self._mint_worker_token( room=room, livekit_api_key=livekit_api_key_value, livekit_api_secret=livekit_api_secret_value, ) session = await self._api.start_session( avatar_id=self._avatar_id, transport={ "type": "livekit", "url": livekit_url_value, "room_name": room.name, "worker_token": worker_token, "worker_identity": self._avatar_participant_identity, "audio_source": "data_stream", }, max_duration_seconds=self._max_duration_seconds, ) session_id = session.get("id") if not isinstance(session_id, str): raise ProtofaceException("Protoface API response missing session id") self._session_id = session_id logger.debug( "protoface session started", extra={"session_id": self._session_id, "avatar_id": self._avatar_id}, ) agent_session.output.replace_audio_tail( DataStreamAudioOutput( room=room, destination_identity=self._avatar_participant_identity, sample_rate=SAMPLE_RATE, wait_remote_track=rtc.TrackKind.KIND_VIDEO, ), ) async def aclose(self) -> None: """End the Protoface session and release LiveKit avatar resources.""" session_id = self._session_id self._session_id = None try: if session_id is not None: try: await self._api.end_session(session_id) except Exception: logger.warning( "failed to end protoface session", extra={"session_id": session_id}, exc_info=True, ) finally: await super().aclose() def _mint_worker_token( self, *, room: rtc.Room, livekit_api_key: str, livekit_api_secret: str, ) -> str: job_ctx = get_job_context(required=False) if job_ctx is not None: local_participant_identity = job_ctx.local_participant_identity elif room.isconnected(): local_participant_identity = room.local_participant.identity else: raise ProtofaceException("failed to get local participant identity") if not local_participant_identity: raise ProtofaceException("failed to get local participant identity") grants = api.VideoGrants( room_join=True, room=room.name, can_publish=True, can_subscribe=True, can_publish_data=True, ) return ( api.AccessToken(api_key=livekit_api_key, api_secret=livekit_api_secret) .with_kind("agent") .with_identity(self._avatar_participant_identity) .with_name(self._avatar_participant_name) .with_grants(grants) .with_attributes({ATTRIBUTE_PUBLISH_ON_BEHALF: local_participant_identity}) .to_jwt() )A Protoface avatar session for LiveKit Agents.
Create a Protoface avatar session.
Args
avatar_id- Protoface avatar ID to render. Defaults to the stable
stock avatar ID
av_stock_001. api_url- Protoface API base URL. Defaults to
PROTOFACE_API_URLor the public Protoface API. api_key- Protoface API key. Defaults to
PROTOFACE_API_KEY. max_duration_seconds- Optional maximum session duration. Protoface applies the lower of this value and the account plan limit.
avatar_participant_identity- LiveKit participant identity for the avatar worker.
avatar_participant_name- Display name for the avatar participant.
conn_options- Timeout and retry options for Protoface API requests.
Raises
ProtofaceException- If no API key is provided.
Ancestors
- livekit.agents.voice.avatar._types.AvatarSession
- abc.ABC
- EventEmitter
- typing.Generic
Instance variables
prop avatar_identity : str-
Expand source code
@property def avatar_identity(self) -> str: return self._avatar_participant_identityThe participant identifier of the avatar
prop provider : str-
Expand source code
@property def provider(self) -> str: return "protoface"The provider of the avatar
prop session_id : str | None-
Expand source code
@property def session_id(self) -> str | None: """Protoface session ID after `start()` succeeds, otherwise `None`.""" return self._session_idProtoface session ID after
start()succeeds, otherwiseNone.
Methods
async def aclose(self) ‑> None-
Expand source code
async def aclose(self) -> None: """End the Protoface session and release LiveKit avatar resources.""" session_id = self._session_id self._session_id = None try: if session_id is not None: try: await self._api.end_session(session_id) except Exception: logger.warning( "failed to end protoface session", extra={"session_id": session_id}, exc_info=True, ) finally: await super().aclose()End the Protoface session and release LiveKit avatar resources.
async def start(self,
agent_session: AgentSession,
room: rtc.Room,
*,
livekit_url: NotGivenOr[str | None] = NOT_GIVEN,
livekit_api_key: NotGivenOr[str | None] = NOT_GIVEN,
livekit_api_secret: NotGivenOr[str | None] = NOT_GIVEN) ‑> None-
Expand source code
async def start( self, agent_session: AgentSession, room: rtc.Room, *, livekit_url: NotGivenOr[str | None] = NOT_GIVEN, livekit_api_key: NotGivenOr[str | None] = NOT_GIVEN, livekit_api_secret: NotGivenOr[str | None] = NOT_GIVEN, ) -> None: """Start the Protoface session and connect it to the LiveKit room. Args: agent_session: LiveKit agent session whose audio output should be routed through the avatar. room: Connected LiveKit room for the agent job. livekit_url: LiveKit server URL. Defaults to `LIVEKIT_URL`. livekit_api_key: LiveKit API key. Defaults to `LIVEKIT_API_KEY`. livekit_api_secret: LiveKit API secret. Defaults to `LIVEKIT_API_SECRET`. Raises: RuntimeError: If the same `AvatarSession` is started more than once. ProtofaceException: If required LiveKit credentials are missing or Protoface returns an invalid session response. """ if self._session_id is not None: raise RuntimeError("AvatarSession.start() called twice; create a new AvatarSession.") await super().start(agent_session, room) livekit_url_value = _resolve_env_or_value(livekit_url, "LIVEKIT_URL") livekit_api_key_value = _resolve_env_or_value(livekit_api_key, "LIVEKIT_API_KEY") livekit_api_secret_value = _resolve_env_or_value( livekit_api_secret, "LIVEKIT_API_SECRET", ) if not livekit_url_value or not livekit_api_key_value or not livekit_api_secret_value: raise ProtofaceException( "livekit_url, livekit_api_key, and livekit_api_secret must be set " "by arguments or environment variables" ) worker_token = self._mint_worker_token( room=room, livekit_api_key=livekit_api_key_value, livekit_api_secret=livekit_api_secret_value, ) session = await self._api.start_session( avatar_id=self._avatar_id, transport={ "type": "livekit", "url": livekit_url_value, "room_name": room.name, "worker_token": worker_token, "worker_identity": self._avatar_participant_identity, "audio_source": "data_stream", }, max_duration_seconds=self._max_duration_seconds, ) session_id = session.get("id") if not isinstance(session_id, str): raise ProtofaceException("Protoface API response missing session id") self._session_id = session_id logger.debug( "protoface session started", extra={"session_id": self._session_id, "avatar_id": self._avatar_id}, ) agent_session.output.replace_audio_tail( DataStreamAudioOutput( room=room, destination_identity=self._avatar_participant_identity, sample_rate=SAMPLE_RATE, wait_remote_track=rtc.TrackKind.KIND_VIDEO, ), )Start the Protoface session and connect it to the LiveKit room.
Args
agent_session- LiveKit agent session whose audio output should be routed through the avatar.
room- Connected LiveKit room for the agent job.
livekit_url- LiveKit server URL. Defaults to
LIVEKIT_URL. livekit_api_key- LiveKit API key. Defaults to
LIVEKIT_API_KEY. livekit_api_secret- LiveKit API secret. Defaults to
LIVEKIT_API_SECRET.
Raises
RuntimeError- If the same
AvatarSessionis started more than once. ProtofaceException- If required LiveKit credentials are missing or Protoface returns an invalid session response.
Inherited members