1. Bot drops the first call to make another one. C...
# ask-ai
b
1. Bot drops the first call to make another one. Call can be interrupted at any moment of conversation. 2. When answer the second call I hear 2 voices, sometimes in different languages. Check file attached. with
Copy code
try:
    await ctx.api.sip.create_sip_participant(
        api.CreateSIPParticipantRequest(
            room_name=ctx.room.name,
            sip_trunk_id=self.outbound_trunk_id,
            sip_number=self.sip_number,
            sip_call_to=phone_number,
            participant_identity=phone_number,
            wait_until_answered=True,
        )
    )
    <http://logger.info|logger.info>("SIP participant created successfully")
except api.TwirpError as e:
    logger.exception(
        "Error creating SIP participant: %s, SIP status: %s %s",
        e,
        e.metadata.get("sip_status_code"),
        e.metadata.get("sip_status"),
    )
    raise
t
Thanks for reaching out, Aleh. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I’m responding about the three issues you reported: the bot dropping the first call when making a second, calls being interrupted mid-conversation, and hearing two voices on the answered second call. I don’t yet have enough detail to diagnose the root cause. A few targeted questions and data points will quickly narrow this down: 1. Call flow and concurrency • Are you intentionally creating a second SIP participant while the first call is still active, or should the first call be ended before the second is created? • Do both calls use the same
participant_identity
(you show
participant_identity=phone_number
) or different identities? 1. Logs and errors • Do you see any logs or exceptions when the first call drops or when creating the second call? In particular, any TwirpError metadata (
sip_status_code
,
sip_status
) or room/participant events. • Please share the LiveKit server logs around the time of the incidents if available (timestamps are helpful). 1. Audio symptoms • When you hear two voices, is one clearly the original caller and the other the bot/second caller, or do they sound like mixed streams from two call legs? • Does the dual-voice issue occur only on the second call or sometimes on the first? 1. Configuration and expected behavior • Which outbound trunk/provider are you using? • Is the bot intended to handle multiple concurrent SIP legs in one room, or should only one SIP participant exist per phone number? • Any telephony features in use (call transfer, forking, conferencing, early media, forking to multiple SIP endpoints)? What I can do next once you provide that info: • Look for patterns in the logs (e.g., second create request causing first participant to be removed, SIP trunk responses, or forking behavior). • Suggest code changes or safeguards (for example, ensure only one active SIP participant per identity, or await explicit hangup before creating a new participant). • Suggest debugging steps to capture SIP signaling and RTP behavior if needed. Which of the requested logs/details can you share first? Sources: Agents telephony integration | LiveKit docs | Make outbound calls | LiveKit docs
b
2 sequential calls to one phone number
t
Thanks — I’m responding about your two sequential calls to the same phone number and the three symptoms you reported: the first call dropping when the second is started, calls being interrupted mid-conversation, and hearing two voices on the answered second call. A few focused checks and changes will likely resolve this: 1) Participant identity / room reuse • Ensure you are not reusing the same
participant_identity
and room for the second call while the first is still active. When two participants use the same identity, the server will disconnect the first one. • Use a unique identity per call (e.g., append a timestamp/counter) or create a new room per call. 2) Wait for call end before creating the next • Don’t create the second SIP participant until the first call’s
sip.callStatus
becomes
hangup
(or until the room is deleted). Queue outbound calls in your app logic to prevent signaling conflicts. • You already use
wait_until_answered=True
— that helps for dialing, but it does not prevent starting a second call while the first is active. 3) Clean up the room and tracks • After the call ends, explicitly delete the room to remove lingering participants/tracks:
Copy code
await ctx.api.room.delete_room(room=ctx.room.name)
• Ensure any subscriptions to the first call’s audio tracks are unsubscribed/removed before starting the next call to avoid overlapping audio. 4) Check for overlapping audio sources • Hearing two voices usually means audio from two active call legs is being mixed (either because the first participant wasn’t fully disconnected or because two agent instances are attached). Confirm only one SIP participant exists in the room when the second call is active. 5) Logs and TwirpError metadata to inspect • When the first call drops or when creating the second, capture any exceptions and the TwirpError metadata fields
sip_status_code
and
sip_status
. • Collect LiveKit agent logs (lk agent logs) around the event timestamps to see participant join/leave events and SIP signaling. If you want, share: • The code you use to set
participant_identity
and
room_name
for each outbound call. • Relevant log snippets (TwirpError metadata, participant events, timestamps). • Whether you expect to handle multiple concurrent calls to the same number or only sequential calls. I can then point to exact code changes or a small pattern (queueing, identity generation, explicit room deletion) to fix the behavior. Would you like to share those logs or the identity/room logic next? Sources: Rooms, participants, and tracks | LiveKit docs | Agents telephony integration | LiveKit docs | Make outbound calls | LiveKit docs | Making outbound calls | LiveKit docs | SIP APIs | LiveKit docs
b
1) Participant identity / room reuse • Ensure you are not reusing the same
participant_identity
and room for the second call while the first is still active. When two participants use the same identity, the server will disconnect the first one. • Use a unique identity per call (e.g., append a timestamp/counter) or create a new room per call. so even for same phone number i should set unique participant_identity?
t
Yes — use a unique
participant_identity
for each call, even when dialing the same phone number. Why: • The server treats each SIP call as a participant. If you reuse the same identity while the prior participant is still present, the server will disconnect the first participant, causing the drop/interrupt behavior you saw. • Unique identities prevent collisions and avoid lingering audio/tracks from the previous call. How to implement (simple options): • Append a timestamp or counter:
"+15105550123-169"
,
"+15105550123-169-2"
. • Or create a new room per call and keep identities unique within that room. Also ensure you explicitly delete the room or confirm
sip.callStatus == "hangup"
before starting the next call to avoid overlapping audio.
b
it this method
Copy code
async def create_sip_participant(self, ctx: JobContext, phone_number: str) -> None:
    """
    Create SIP participant for outbound call.

    Args:
        ctx: Job context
        phone_number: Phone number to call

    Raises:
        api.TwirpError: If SIP participant creation fails
    """
    try:
        await ctx.api.sip.create_sip_participant(
            api.CreateSIPParticipantRequest(
                room_name=ctx.room.name,
                sip_trunk_id=self.outbound_trunk_id,
                sip_number=self.sip_number,
                sip_call_to=phone_number,
                participant_identity=phone_number,
                wait_until_answered=True,
            )
        )
        <http://logger.info|logger.info>("SIP participant created successfully")
    except api.TwirpError as e:
        logger.exception(
            "Error creating SIP participant: %s, SIP status: %s %s",
            e,
            e.metadata.get("sip_status_code"),
            e.metadata.get("sip_status"),
        )
        raise
how i can confirm
sip.callStatus == "hangup"
before starting the next call to avoid overlapping audio.
t
You're on the right track — I'll show a concise way to ensure a previous SIP call has ended (
sip.callStatus == "hangup"
) before creating the next SIP participant, and remind you to use unique participant identities. 1) High-level approach • Listen for participant attribute changes (preferred) or poll the participant state. • Wait until the SIP participant either leaves the room or its
sip.callStatus
becomes
"hangup"
. • Only then call
create_sip_participant
. 2) Example: wait using participant attribute events
Copy code
import asyncio

async def wait_for_hangup(room, identity, timeout=30.0):
    # quick lookup: if participant not present, assume ended
    participant = room.participants_by_identity.get(identity)
    if not participant:
        return

    # immediate check
    if participant.attributes.get("sip.callStatus") == "hangup":
        return

    ev = asyncio.Event()

    def on_attrs_changed(changed_attrs, p):
        if p.identity != identity:
            return
        if "sip.callStatus" in changed_attrs and changed_attrs["sip.callStatus"] == "hangup":
            ev.set()

    room.on("participant_attributes_changed", on_attrs_changed)

    try:
        await asyncio.wait_for(ev.wait(), timeout)
    except asyncio.TimeoutError:
        # timeout handling: proceed or raise depending on desired behavior
        pass
    finally:
        room.off("participant_attributes_changed", on_attrs_changed)
3) Integrate into your create method
Copy code
async def create_sip_participant(self, ctx: JobContext, phone_number: str) -> None:
    # ensure any existing call for this identity has finished
    await wait_for_hangup(ctx.room, phone_number, timeout=30.0)

    # use a unique identity per call to avoid replacing an active participant
    identity = f"{phone_number}_{uuid4().hex[:8]}"

    await ctx.api.sip.create_sip_participant(
        api.CreateSIPParticipantRequest(
            room_name=ctx.room.name,
            sip_trunk_id=self.outbound_trunk_id,
            sip_number=self.sip_number,
            sip_call_to=phone_number,
            participant_identity=identity,
            wait_until_answered=True,
        )
    )
4) Alternatives and notes • Polling: If events are unavailable/unreliable, poll
room.participants_by_identity
and
participant.attributes["sip.callStatus"]
every second until hangup or timeout. • Room cleanup: After call end consider deleting the room (
ctx.api.room.delete_room(room=ctx.room.name)
) to ensure no lingering tracks. • Identity: Yes — use a unique
participant_identity
per call (e.g., append timestamp/UUID). Reusing the same identity replaces/disconnects the existing participant and causes the behavior you observed. •
wait_until_answered=True
waits for answer but does not wait for call end — you must monitor
sip.callStatus
for
hangup
. If you want, share how your room/participant objects expose events in your runtime and I’ll adapt the snippet to that API. Sources: Rooms, participants, and tracks | LiveKit docs | Agents telephony integration | LiveKit docs | SIP participant | LiveKit docs