Skip to main content
The examples on this page use syntax specific to the LemonSlice LiveKit plugin, but the same concepts apply to our other integrations.

Handle avatar connect/disconnect events

Read this section if you experience any of the following issues:
  • Empty room (user sees black screen)
  • User gets no video or audio, but audio/text input are still active
  • Avatar disappears
  • Avatar is delayed in speaking
Listen for ParticipantConnected and ParticipantDisconnected events on the frontend to switch between your UI’s ringing, active, and finished states. The ParticipantConnected event will fire when the LemonSlice avatar is ready and has joined the room. Use this to trigger the UI to show the active call. The ParticipantDisconnected event will fire if the avatar leaves, which happens if the pipeline stops for any reason (normal call completion, idle timeout, upstream errors, etc.). Use this to leave the active call UI and disconnect from the room.
LiveKit users: You should also listen to these events on the backend and call agent_session.generate_reply() when the LemonSlice avatar joins to prevent any idle time before the avatar speaks.
After the user initiates a call, stay in the “ringing state” until the avatar joins (signaled through ParticipantConnected event). Then, if the avatar ever leaves (signaled through the ParticipantDisconnected event), move to the inactive call UI to allow the user to rejoin the call. Refer to our LiveKit demo for an example of registering these listeners on the backend. We provide a representative frontend snippet below:
JAVASCRIPT
const AVATAR_IDENTITY = "lemonslice-avatar-agent";

const room = new Room(...); // Logic to join to the LiveKit room

room.on(RoomEvent.ParticipantConnected, (participant) => {
  if (participant.identity === AVATAR_IDENTITY) {
    console.log("LemonSlice avatar joined:", participant);
    // TODO - swap over to active call UI. We recommend staying in the "ringing" state until the avatar joined. 
  }
});

room.on(RoomEvent.ParticipantDisconnected, (participant) => {
  if (participant.identity === AVATAR_IDENTITY) {
    console.log("LemonSlice avatar left");
    room.disconnect()
    // TODO - swap over to inactive call UI. Users can reinitate a new call from here. 
  }
});

Handle room errors

Read this section if you experience any of the following issues:
  • Avatar fails to join a call
  • Crashed calls. Avatar leaves the call or audio cuts out.
Your frontend should listen for Disconnected events to handle any issues with the room. These can include network errors, WebRTC failures, or join failures.
JAVASCRIPT
room.on(RoomEvent.Disconnected, (reason) => {
  switch (reason) {
    case DisconnectReason.CLIENT_INITIATED:
      console.log("Clean disconnect");
      // TODO - you manually called room.disconnect()
      // swap over to inactive call UI
      break;
    default:
      console.log("Disconnected:", reason);
      // TODO: swap over to inactive call UI
  }
});

Catch pipeline errors

Read this section if you experience any of the following issues:
  • Avatar does not speak
  • Session dies unexpectedly
Your backend should catch STT, LLM, TTS errors by subscribing to the error event. You can log these errors and then handle them gracefully. Errors tagged with err.recoverable == False should end the pipeline gracefully as the pipeline is now dead.
PYTHON
@session.on("error")
def on_session_error(ev: ErrorEvent) -> None:
    err = ev.error
    if isinstance(err, TTSError):
        logger.error(
            f"AgentSession TTS error",
            exc_info=err.error,
        )
    elif isinstance(err, STTError):
        logger.error(
            f"AgentSession STT error",
            exc_info=err.error,
        )
    elif isinstance(err, LLMError):
        logger.error(
            f"AgentSession LLM error",
            exc_info=err.error,
        )
    else:
        logger.error(
            f"AgentSession error",
            exc_info=err.error
        )

Handle startup failures

Read this section if you experience any of the following issues:
  • The call never connects
Sometimes the LemonSlice avatar may fail to join the room. This happens very rarely. To handle this, you can send a message via the WebRTC room to indicate a failure. This allows the UI to gracefully exit the call if the LemonSlice participant never joins due to an issue. We provide representative snippets below from our LiveKit example (relevant utility functions: wait_for_avatar_ready and publish_room_message)
PYTHON
# Wait until the avatar has joined the room and is ready. 
avatar_ready = await wait_for_avatar_ready(session_id)
if not avatar_ready:
    logger.warning("Avatar failed to become active, exiting")
    await publish_avatar_room_message(
        ctx.room,
        msg_type="startup_failure",
        message="Avatar failed to become active",
    )
    return
import { Room, RoomEvent } from 'livekit-client';

const ROOM_MESSAGE_TOPIC = 'lemonslice/message'

const room = new Room();
const decoder = new TextDecoder();
room.on(
  RoomEvent.DataReceived,
  (payload, participant, kind, topic) => {
    if (topic !== ROOM_MESSAGE_TOPIC) return;

    try {
      const message = JSON.parse(decoder.decode(payload));

      if (message.type === 'startup_failure') {
        console.log('Room closing event received');
        room.disconnect();
        // TODO - swap to UI where the user can restart the call (inactive call)
      }
    } catch (err) {
      console.error('Failed to parse data packet:', err);
    }
  }
);

Check timeouts

Read this section if you experience any of the following issues:
  • Avatar suddenly exits a call
  • Calls suddenly end after the same number of minutes
There are several LemonSlice timeouts that may affect a session, and likely others baked into third-party tools or integrations. Ensure these are all set to the values you intended:
  • LemonSlice idle timeout (default is 60 seconds). Resets when the avatar is talking.
  • LemonSlice GPU timeout (default is 30 minutes. If you regularly have longer calls, please contact support@lemonslice.com)
  • Third-party timeouts (e.g. LiveKit or ElevenLabs)
You can set idle_timeout to -1 to disable the LemonSlice idle timeout, but you should ensure sessions are properly terminated to avoid stale calls and runaway billing.
PYTHON
avatar = lemonslice.AvatarSession(
    agent_image_url="....",
    agent_prompt="a person talking.",
    idle_timeout=600,  # update this LemonSlice parameter
)
session_id = await avatar.start(session, room=ctx.room)