Skip to content

Liveavatar cleanup bug #4212

@MSameerAbbas

Description

@MSameerAbbas

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions