-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Added TruGen Avatar Plugin. #4430
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
30a61f8
40f7695
c2a9ac2
8c09c38
d8521b4
3771338
73e289d
4e657dc
32ec0cc
0dc18c4
1879a5c
0a4fc57
7b1700a
f74cc10
605213f
c46ddb8
4ca0c1b
6c98401
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| # LiveKit TruGen.AI Realtime Avatar | ||
|
|
||
| This example demonstrates how to create a realtime avatar session for your Livekit Voice Agents using [TruGen Developer Studio](https://app.trugen.ai/). | ||
|
|
||
| Select your avatar [list](https://docs.trugen.ai/docs/avatars/overview) | ||
|
|
||
| ## Usage | ||
|
|
||
| * Update the environment: | ||
|
|
||
| ```bash | ||
| # TruGen Config | ||
| export TRUGEN_API_KEY="..." | ||
|
|
||
| # Google config (or other models, tts, stt) | ||
| export GOOGLE_API_KEY="..." | ||
|
|
||
| # LiveKit config | ||
| export LIVEKIT_API_KEY="..." | ||
| export LIVEKIT_API_SECRET="..." | ||
| export LIVEKIT_URL="..." | ||
| ``` | ||
|
|
||
| * Start the agent worker: | ||
|
|
||
| ```bash | ||
| python examples/avatar_agents/trugen/agent_worker.py dev | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| import logging | ||
| import os | ||
|
|
||
| from dotenv import load_dotenv | ||
|
|
||
| from livekit.agents import Agent, AgentServer, AgentSession, JobContext, cli | ||
| from livekit.plugins import google, trugen | ||
|
|
||
| logger = logging.getLogger("trugen-avatar-example") | ||
| logger.setLevel(logging.INFO) | ||
|
|
||
| load_dotenv() | ||
|
|
||
| server = AgentServer() | ||
|
|
||
|
|
||
| @server.rtc_session() | ||
| async def entrypoint(ctx: JobContext): | ||
| session = AgentSession( | ||
| llm=google.realtime.RealtimeModel(), | ||
| resume_false_interruption=False, | ||
| ) | ||
|
|
||
| avatar_id = os.getenv("TRUGEN_AVATAR_ID") or "45e3f732" | ||
| trugen_avatar = trugen.AvatarSession(avatar_id=avatar_id) | ||
| await trugen_avatar.start(session, room=ctx.room) | ||
|
|
||
| await session.start( | ||
| agent=Agent(instructions="You are a friendly AI Agent."), | ||
| room=ctx.room, | ||
| ) | ||
| session.generate_reply(instructions="Greet the user with a joke.") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| cli.run_app(server) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| # TruGen AI plugin for LiveKit Agents | ||
|
|
||
| Adding support for [TruGen.AI](https://docs.trugen.ai) realtime avatars. | ||
|
|
||
| ## Installation | ||
|
|
||
| ```bash | ||
| pip install livekit-plugins-trugen | ||
| ``` | ||
|
|
||
| ## Pre-requisites | ||
|
|
||
| Generate an API key from our Developer Studio [link](https://app.trugen.ai) and set the `TRUGEN_API_KEY` environment variable with it: | ||
|
|
||
| ```bash | ||
| export TRUGEN_API_KEY=<trugen-api-key> | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| # Copyright 2023 LiveKit, Inc. | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| """TruGen.AI plugin for LiveKit Agents""" | ||
|
|
||
| from .avatar import AvatarSession | ||
| from .version import __version__ | ||
|
|
||
| __all__ = [ | ||
| "AvatarSession", | ||
| "__version__", | ||
| ] | ||
|
|
||
| from livekit.agents import Plugin | ||
|
|
||
| from .log import logger | ||
|
|
||
|
|
||
| class TrugenPlugin(Plugin): | ||
| def __init__(self) -> None: | ||
| super().__init__(__name__, __version__, __package__, logger) | ||
|
|
||
|
|
||
| Plugin.register_plugin(TrugenPlugin()) | ||
hari-trugen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| # Cleanup docs of unexported modules | ||
| _module = dir() | ||
| NOT_IN_ALL = [m for m in _module if m not in __all__] | ||
|
|
||
| __pdoc__ = {} | ||
|
|
||
| for n in NOT_IN_ALL: | ||
| __pdoc__[n] = False | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,137 @@ | ||||||
| from __future__ import annotations | ||||||
|
|
||||||
| import asyncio | ||||||
| import os | ||||||
|
|
||||||
| import aiohttp | ||||||
|
|
||||||
| from livekit import api, rtc | ||||||
| from livekit.agents import ( | ||||||
| DEFAULT_API_CONNECT_OPTIONS, | ||||||
| NOT_GIVEN, | ||||||
| AgentSession, | ||||||
| APIConnectionError, | ||||||
| APIConnectOptions, | ||||||
| APIStatusError, | ||||||
| NotGivenOr, | ||||||
| get_job_context, | ||||||
| utils, | ||||||
| ) | ||||||
| from livekit.agents.voice.avatar import DataStreamAudioOutput | ||||||
| from livekit.agents.voice.room_io import ATTRIBUTE_PUBLISH_ON_BEHALF | ||||||
|
|
||||||
| from .log import logger | ||||||
|
|
||||||
| _BASE_API_URL = "https://api.trugen.ai" | ||||||
| _AVATAR_AGENT_IDENTITY = "trugen-avatar" | ||||||
| _AVATAR_AGENT_NAME = "Trugen Avatar" | ||||||
|
|
||||||
|
|
||||||
| class AvatarSession: | ||||||
| """TruGen Realtime Avatar Session""" | ||||||
|
|
||||||
| def __init__( | ||||||
| self, | ||||||
| *, | ||||||
| avatar_id: NotGivenOr[str | None] = NOT_GIVEN, | ||||||
tinalenguyen marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| api_key: NotGivenOr[str] = NOT_GIVEN, | ||||||
| avatar_participant_identity: NotGivenOr[str] = NOT_GIVEN, | ||||||
| avatar_participant_name: NotGivenOr[str] = NOT_GIVEN, | ||||||
| conn_options: APIConnectOptions = DEFAULT_API_CONNECT_OPTIONS, | ||||||
| ) -> None: | ||||||
| self._avatar_id = avatar_id | ||||||
| self._api_url = _BASE_API_URL | ||||||
| self._api_key = api_key or os.getenv("TRUGEN_API_KEY") | ||||||
| if self._api_key is None: | ||||||
| raise Exception( | ||||||
| "The api_key not found; set this by passing api_key to the client or " | ||||||
| "by setting the TRUGEN_API_KEY environment variable" | ||||||
| ) | ||||||
|
|
||||||
| self._avatar_participant_identity = avatar_participant_identity or _AVATAR_AGENT_IDENTITY | ||||||
| self._avatar_participant_name = avatar_participant_name or _AVATAR_AGENT_NAME | ||||||
| self._http_session: aiohttp.ClientSession | None = None | ||||||
| self._conn_options = conn_options | ||||||
|
|
||||||
| def _ensure_http_session(self) -> aiohttp.ClientSession: | ||||||
| if self._http_session is None: | ||||||
| self._http_session = utils.http_context.http_session() | ||||||
|
|
||||||
| return self._http_session | ||||||
|
|
||||||
| async def start( | ||||||
| self, | ||||||
| agent_session: AgentSession, | ||||||
| room: rtc.Room, | ||||||
| *, | ||||||
| livekit_url: NotGivenOr[str] = NOT_GIVEN, | ||||||
| livekit_api_key: NotGivenOr[str] = NOT_GIVEN, | ||||||
| livekit_api_secret: NotGivenOr[str] = NOT_GIVEN, | ||||||
| ) -> None: | ||||||
| livekit_url = livekit_url or (os.getenv("LIVEKIT_URL") or NOT_GIVEN) | ||||||
| livekit_api_key = livekit_api_key or (os.getenv("LIVEKIT_API_KEY") or NOT_GIVEN) | ||||||
| livekit_api_secret = livekit_api_secret or (os.getenv("LIVEKIT_API_SECRET") or NOT_GIVEN) | ||||||
| if not livekit_url or not livekit_api_key or not livekit_api_secret: | ||||||
| raise Exception( | ||||||
tinalenguyen marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| "livekit_url, livekit_api_key, and livekit_api_secret not found," | ||||||
| "either pass then as arguments here or set enviroment variables." | ||||||
| ) | ||||||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
|
||||||
| job_ctx = get_job_context() | ||||||
| local_participant_identity = job_ctx.local_participant_identity | ||||||
| livekit_token = ( | ||||||
| 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(api.VideoGrants(room_join=True, room=room.name)) | ||||||
| # allow the avatar agent to publish audio and video on behalf of your local agent | ||||||
| .with_attributes({ATTRIBUTE_PUBLISH_ON_BEHALF: local_participant_identity}) | ||||||
| .to_jwt() | ||||||
| ) | ||||||
|
|
||||||
| logger.debug("Starting Realtime Avatar Session") | ||||||
| await self._start_session(livekit_url, livekit_token) | ||||||
|
|
||||||
| agent_session.output.audio = DataStreamAudioOutput( | ||||||
| room=room, | ||||||
| destination_identity=self._avatar_participant_identity, | ||||||
| wait_remote_track=rtc.TrackKind.KIND_VIDEO, | ||||||
| ) | ||||||
|
|
||||||
| async def _start_session(self, livekit_url: str, livekit_token: str) -> None: | ||||||
| assert self._api_key is not None | ||||||
| for i in range(self._conn_options.max_retry): | ||||||
| try: | ||||||
| async with self._ensure_http_session().post( | ||||||
| f"{self._api_url}/v1/sessions", | ||||||
| headers={ | ||||||
| "x-api-key": self._api_key, | ||||||
| }, | ||||||
| json={ | ||||||
| "avatar_id": self._avatar_id, | ||||||
| "livekit_url": livekit_url, | ||||||
| "livekit_token": livekit_token, | ||||||
| }, | ||||||
| timeout=aiohttp.ClientTimeout(sock_connect=self._conn_options.timeout), | ||||||
| ) as response: | ||||||
| if not response.ok: | ||||||
| text = await response.text() | ||||||
| raise APIStatusError( | ||||||
| "Server returned an error", status_code=response.status, body=text | ||||||
| ) | ||||||
| return | ||||||
|
|
||||||
| except Exception as e: | ||||||
| if isinstance(e, APIConnectionError): | ||||||
| logger.warning( | ||||||
| "API Error; Unable to trigger TruGen.AI API backend.", | ||||||
| extra={"error": str(e)}, | ||||||
| ) | ||||||
| else: | ||||||
| logger.exception("API Error; Unable to trigger TruGen.AI API backend.") | ||||||
|
|
||||||
| if i < self._conn_options.max_retry - 1: | ||||||
| await asyncio.sleep(self._conn_options.retry_interval) | ||||||
|
|
||||||
hari-trugen marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| raise APIConnectionError("Max retry exhaused; Unable to start TruGen.AI Avatar Session.") | ||||||
|
||||||
| raise APIConnectionError("Max retry exhaused; Unable to start TruGen.AI Avatar Session.") | |
| raise APIConnectionError("Max retries exhausted; Unable to start TruGen.AI Avatar Session.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @tinalenguyen,
This is implemented.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| import logging | ||
|
|
||
| logger = logging.getLogger("livekit.plugins.trugen") |
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,15 @@ | ||||||||
| # Copyright 2025 LiveKit, Inc. | ||||||||
| # | ||||||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||||||
| # you may not use this file except in compliance with the License. | ||||||||
| # You may obtain a copy of the License at | ||||||||
| # | ||||||||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||||||||
| # | ||||||||
| # Unless required by applicable law or agreed to in writing, software | ||||||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||||
| # See the License for the specific language governing permissions and | ||||||||
| # limitations under the License. | ||||||||
|
|
||||||||
| __version__ = "1.3.10" | ||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Plugin version The trugen plugin's own version is Root Cause and Comparison with Other PluginsEvery other avatar plugin in the repo has its Additionally, the trugen Impact:
Suggested change
Was this helpful? React with 👍 or 👎 to provide feedback. |
||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| [build-system] | ||
| requires = ["hatchling"] | ||
| build-backend = "hatchling.build" | ||
|
|
||
| [project] | ||
| name = "livekit-plugins-trugen" | ||
| dynamic = ["version"] | ||
| description = "Livekit Agent framework plugin for realtime TruGen AI avatars" | ||
| readme = "README.md" | ||
| license = "Apache-2.0" | ||
| requires-python = ">=3.9.0" | ||
| authors = [{ name = "LiveKit", email = "support@livekit.io" }] | ||
| keywords = ["voice", "ai", "realtime", "audio", "video", "livekit", "webrtc"] | ||
| classifiers = [ | ||
| "Intended Audience :: Developers", | ||
| "License :: OSI Approved :: Apache Software License", | ||
| "Topic :: Multimedia :: Sound/Audio", | ||
| "Topic :: Multimedia :: Video", | ||
| "Topic :: Scientific/Engineering :: Artificial Intelligence", | ||
| "Programming Language :: Python :: 3", | ||
| "Programming Language :: Python :: 3.9", | ||
| "Programming Language :: Python :: 3.10", | ||
| "Programming Language :: Python :: 3 :: Only", | ||
| ] | ||
| dependencies = ["livekit-agents>=1.3.10"] | ||
|
|
||
| [project.urls] | ||
| Documentation = "https://docs.livekit.io" | ||
| Website = "https://livekit.io/" | ||
| Source = "https://github.com/livekit/agents" | ||
|
|
||
| [tool.hatch.version] | ||
| path = "livekit/plugins/trugen/version.py" | ||
|
|
||
| [tool.hatch.build.targets.wheel] | ||
| packages = ["livekit"] | ||
|
|
||
| [tool.hatch.build.targets.sdist] | ||
| include = ["/livekit"] |
Uh oh!
There was an error while loading. Please reload this page.