-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Description
Bug Description
When we end the session, we often get the following error:
Traceback (most recent call last):
File "C:\Users\msameer.abbas\Documents\LangTest\LiveKit\.venv\Lib\site-packages\livekit\agents\utils\log.py", line 16, in async_fn_logs
return await fn(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\msameer.abbas\Documents\LangTest\LiveKit\liveavatar\avatar.py", line 226, in _recv_task
raise APIConnectionError(message="LiveAvatar connection closed unexpectedly.")
livekit.agents._exceptions.APIConnectionError: LiveAvatar connection closed unexpectedly. (body=None, retryable=True)Expected Behavior
Clean up should be clean without any raised exceptions
Reproduction Steps
I copied the github version of the liveavatar folder and tested locally on dev (uv run agent.py dev)
I started the avatar in the on_enter method of the agent session.
if self.avatar_enabled:
self.avatar = liveavatar.AvatarSession()
await self.avatar.start(self.session, room=self.ctx.room)Operating System
Windows 11
Models Used
Irrelevant
Package Versions
using the liveavatar plugin with this custom keep_alive implementation:
@utils.log_exceptions(logger=logger)
async def _keep_alive_task() -> None:
"""Periodically send keep-alive events to prevent session timeout."""
keep_alive_interval = 60.0 # Send every 60 seconds (sessions timeout after 5 minutes)
while not closing:
await asyncio.sleep(keep_alive_interval)
if closing:
break
self.send_event(
{
"type": "session.keep_alive",
"event_id": str(uuid.uuid4()),
}
)
io_tasks = [
asyncio.create_task(_forward_audio(), name="_forward_audio_task"),
asyncio.create_task(_send_task(), name="_send_task"),
asyncio.create_task(_recv_task(), name="_recv_task"),
asyncio.create_task(_keep_alive_task(), name="_keep_alive_task"),
]Session/Room/Call IDs
No response
Proposed Solution
Basically the error happens here:
done, _ = await asyncio.wait(io_tasks, return_when=asyncio.FIRST_COMPLETED)
for task in done:
logger.info(f"Task completed: {task.get_name()}")
task.result()As soon as the agent session stops, the msg_channel has nothing to send. I think this times out the websocket connection. Now since _send_task() is waiting blocked cuz no new messages are being sent to the channel, it doesn't reach it's exception block and doesn't set completed to True. We receive a close message in _recv_task(), however closing isn't True so we throw an error. A nice an easy solution for this would be to add an event handler that watches for the session's close event. Something like:
@self._agent_session.on("close")
def on_agent_session_close(ev):
self._msg_ch.close()This way, we'd close the channel so the _send_task() would be able to set closing=True before the websocket times out. Then whenever it times out (probably 10-15 seconds later), it won't throw the error. This is really me thinking aloud though so take this solution with a grain of salt (I haven't tested it thoroughly on my side).
Additional Context
No response
Screenshots and Recordings
No response