sample code with `liveagent-js` with outbound call...
# ask-ai
a
sample code with
liveagent-js
with outbound call using SIP trunk
✅ 1
t
Thanks for reaching out, Tommy. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I can help with a sample. Quick clarifying questions so I give the right example: • Which environment are you targeting: browser (WebRTC) or Node.js server? • Do you already have a SIP trunk provider and its SIP URI/credentials ready? • Do you want the call to originate from an existing LiveAgent session or create a new outbound session? • Do you need DTMF, audio recording, or SIP headers included in the call? Once you confirm those, I’ll send a concise sample using
liveagent-js
(with token creation flow notes if needed). , with interactive elements
a
• node.js
t
Thanks — I’ll provide a concise Node.js sample for making an outbound SIP call with
livekit-agent-js
and notes you’ll need. This responds to your request for a Node.js example using a SIP trunk. Prerequisites • A LiveKit project with a configured SIP trunk and its
sip_trunk_id
. • An agent dispatched to a room where the agent can access
ctx.room.localParticipant
. • Dependencies installed:
Copy code
pnpm add @livekit/agents livekit-server-sdk
Minimal Node.js agent example (TypeScript)
Copy code
import { defineAgent, JobContext, log } from '@livekit/agents';

export default defineAgent({
  entry: async (ctx: JobContext) => {
    const logger = log();
    await ctx.connect();
    logger.info('Connected to room');

    const sipTrunkId = 'your-sip-trunk-id';      // replace
    const targetNumber = '+1234567890';         // replace E.164 number or SIP URI
    const participantIdentity = 'outbound-sip';

    const createRequest = {
      sip_trunk_id: sipTrunkId,
      sip_call_to: targetNumber,
      room_name: ctx.room.name,
      participant_identity: participantIdentity,
      play_dialtone: true,
      krisp_enabled: true,
      wait_until_answered: true,
    };

    try {
      await ctx.room.localParticipant.createSIPParticipant(createRequest);
      logger.info(`Outbound call initiated to ${targetNumber}`);
    } catch (err: any) {
      logger.error('Failed to create SIP participant', { error: err.message });
    }
  },
});
Notes and options •
sip_call_to
accepts a phone number (E.164) or SIP URI depending on your trunk. •
wait_until_answered: true
makes the call creation wait until the remote party answers. •
krisp_enabled
enables LiveKit’s noise cancellation for better audio. • To include DTMF, custom SIP headers, or call recording, you’ll need to add those fields to the create request per your SIP trunk and LiveKit configuration. If you want, tell me which of the following you need next and I’ll provide the exact snippet: • Creating the SIP trunk programmatically (server SDK). • Adding custom SIP headers or credentials. • DTMF or call recording configuration. • A JavaScript (non-TypeScript) version. Sources: examples/voice_agents | livekit/livekit_composite | DeepWiki | Agents telephony integration | LiveKit Documentation | Preserving Caller ID During SIP Transfers in LiveKit Agents | The Human-in-the-Loop (HITL) Pattern for Voice Agents | LiveKit, with interactive elements
a
do I need to use voice.AgentSession to start the session with the above code?
t
Good question — thanks for asking. Brief answer: • No, you do not need to use
voice.AgentSession
just to initiate the outbound SIP call with the sample code. Creating a SIP participant via
ctx.room.localParticipant.createSIPParticipant()
is sufficient to place the call and join the room. • Use
voice.AgentSession
only when you need the voice AI/media orchestration features — for example, managed STT/TTS, LLM-driven conversational loops, or real‑time voice processing within the room.
AgentSession
handles the media/AI pipelines; simple signaling/connectivity does not require it. If you plan to do any TTS, STT, or conversational AI with the caller, tell me which features you need and I’ll show how to start and use
voice.AgentSession
in your agent code. Sources: Agents Framework | livekit/livekit_composite | DeepWiki | sip | livekit/livekit_composite | DeepWiki | Agent session | LiveKit Documentation | OpenAI and LiveKit partner to turn Advanced Voice into an API | LiveKit, with interactive elements
a
yes - let me try my question again: I want to use agent to make outbound call with a phone participant using SIP trunk.. so I need to define the agent first (e.g. LLM, STT, TTS) and also make outbound call so they can make conversation in a room
t
Thanks — I understand: you want an agent that (1) starts a conversational voice session (LLM + STT + TTS) and (2) makes an outbound SIP call to a phone participant so the agent and caller can converse in the same room. Below is a concise Node.js/TypeScript pattern showing how to combine
voice.AgentSession
for media/AI orchestration and
createSIPParticipant
to place the outbound call. Prerequisites • SIP trunk configured in your LiveKit project and you have
sip_trunk_id
. • Dependencies:
Copy code
pnpm add @livekit/agents livekit-server-sdk
High-level flow 1. Define the agent with LLM/STT/TTS configs. 2. In the agent entry, connect and create a
voice.AgentSession
to manage STT/TTS/LLM pipelines. 3. After session is ready, call
ctx.room.localParticipant.createSIPParticipant()
to initiate the outbound call into the same room. 4. The AgentSession will handle audio input/output for conversation with the phone participant. Minimal example
Copy code
import { defineAgent, JobContext, log } from '@livekit/agents';
import * as voice from '@livekit/agents/voice'; // adjust import path per package

export default defineAgent({
  entry: async (ctx: JobContext) => {
    const logger = log();
    await ctx.connect();
    logger.info('Agent connected to room');

    // 1) Start AgentSession to manage STT/TTS/LLM
    const agentSession = new voice.AgentSession({
      room: ctx.room,
      localParticipant: ctx.room.localParticipant,
      llm: {
        provider: 'openai', // example provider config
        model: 'gpt-4o-mini', // example
        // apiKey, etc. configured via environment or agent config
      },
      stt: { provider: 'whisper', language: 'en-US' },
      tts: { provider: 'polly', voice: 'Matthew' },
      // any other session options (silence detection, partial transcripts, etc.)
    });

    await agentSession.start(); // prepares pipelines, handlers, etc.
    logger.info('AgentSession started (STT/TTS/LLM ready)');

    // 2) Initiate outbound SIP call that will join the same room
    const sipTrunkId = 'your-sip-trunk-id';
    const targetNumber = '+1234567890';
    try {
      const createRequest = {
        sip_trunk_id: sipTrunkId,
        sip_call_to: targetNumber,
        room_name: ctx.room.name,
        participant_identity: 'phone-participant',
        play_dialtone: true,
        krisp_enabled: true,
        wait_until_answered: true,
      };

      await ctx.room.localParticipant.createSIPParticipant(createRequest);
      logger.info(`Outbound SIP call initiated to ${targetNumber}`);
    } catch (err: any) {
      logger.error('Failed to create SIP participant', { error: err.message });
      throw err;
    }

    // 3) Wire up conversation handlers (example)
    agentSession.on('transcript', (text) => {
      logger.info('Transcript:', text);
      // optional: send transcript to LLM or take action
    });

    agentSession.on('ended', () => {
      logger.info('AgentSession ended');
    });

    // Keep the agent running to handle conversation; cleanup when appropriate
    // await agentSession.stop() when finished
  },
});
Important notes • The AgentSession is required for STT/TTS/LLM orchestration. Creating the SIP participant without AgentSession will place the call but the agent won’t have the managed media/AI pipeline. • Start
AgentSession
before or immediately around the time you create the SIP participant so audio streams are correctly attached when the phone participant joins. • Use
wait_until_answered: true
if you want the create call to block until the callee answers, ensuring the session is ready when the call connects. • Add handlers for transcripts, partial results, and TTS output routing as needed for your LLM conversation flow. • If you need code for specific LLM/STT/TTS providers (detailed config, API keys) or DTMF/recording/custom SIP headers, tell me which provider(s) and I’ll add the exact config snippets. Would you like a plain JavaScript version or a sample showing LLM prompt flow reacting to transcripts? , with interactive elements
a
I've got
Copy code
failed to recognize speech
before SIP trunk participant joins. What's the problem?
t
Thanks — that error usually means your STT started before any remote audio was available. You’re seeing this because the AgentSession is trying to recognize speech while the SIP participant hasn’t joined/started streaming yet. What to do • Start or enable STT only after the phone participant is actually in the room. ◦ Use
wait_until_answered: true
on
createSIPParticipant
so the call blocks until answered. ◦ Or wait for the participant join event (e.g.,
ctx.wait_for_participant()
or listen for
room.on('participantConnected', ...)
) before calling
agentSession.start()
or enabling recognition. • Ensure the SIP trunk successfully establishes RTP audio when answered (trunk config/auth must be correct). • If you must start earlier, handle transient empty-audio errors by retrying STT when audio data appears. Example pattern (concise) 1. Create AgentSession but don’t start STT/TTS processing yet. 2. Initiate outbound call with
wait_until_answered: true
(or wait for participant join). 3. Once the SIP participant is connected, call
agentSession.start()
(or enable STT) so audio is present. If you’d like, paste the relevant snippets (agent start, createSIPParticipant call, and logs) and I’ll point out exactly where to wait or change timing. Sources: examples/voice_agents | livekit/livekit_composite | DeepWiki | Sequential Pipeline Architecture for Voice Agents | LiveKit | Agent session | LiveKit Documentation, with interactive elements
a
What's the significance of
ctx.connect()
then? why can it be called before SIP trunk connection and agent start?
t
Good question — here’s the short, practical explanation. • What
ctx.connect()
does: it connects the agent to the LiveKit room and signaling layer and initializes the agent's local participant (so you have a room context, can publish/subscribed tracks, and call room APIs). • Why it can be called earlier:
ctx.connect()
only establishes the connection and signaling state — it does not imply any remote audio is present. Starting the SIP trunk call and actual RTP audio flow are separate steps. Calling
ctx.connect()
early lets your agent prepare room state, handlers, and local tracks before the phone participant joins. • Why you still must wait to start STT/TTS: speech processing depends on actual audio streams. Even though you’re connected, no remote audio exists until the SIP participant answers and RTP begins. Start or enable your
voice.AgentSession
(or STT) after the SIP participant has joined or use
wait_until_answered: true
/
ctx.wait_for_participant()
to avoid “failed to recognize speech” errors. • Extra tips: ◦ Use
ctx.connect({ auto_subscribe: ... })
or limit subscriptions until the participant is present to save bandwidth. ◦ You can create and publish local TTS tracks early, but only enable STT/processing once audio arrives. If you want, I can show the exact sequence in code that calls
ctx.connect()
, then creates the SIP participant with
wait_until_answered
, and only starts
AgentSession
after the participant is confirmed. Which format do you prefer (TypeScript or plain JS)? , with interactive elements
a
what if we start the session first by
voice.AgentSession.start()
, then create SIP participant (with
wait_for_participant
true, and finally
ctx.connect()
t
Thanks — good question. Short answer: that order will cause problems. Here’s why and the recommended sequence. Why the proposed order is problematic •
voice.AgentSession.start()
needs a connected LiveKit room/local participant and access to tracks. If you call it before
ctx.connect()
, the session won’t have the room context or media plumbing it needs. • Creating a SIP participant also requires the room to be connected so LiveKit can place the participant into that room. Calling
createSIPParticipant
before
ctx.connect()
is invalid. • If you somehow start STT before remote audio is present (even if you later create the SIP participant), you’ll get “failed to recognize speech” because no RTP/audio stream exists yet. Recommended sequence 1. Call
ctx.connect()
first — establishes the room, local participant, and signaling. 2. Option A (preferred for guaranteed audio): Create the SIP participant with
wait_until_answered: true
so the call blocks until answered. 3. After the SIP participant is joined (or after `ctx.wait_for_participant()`/participantConnected event), call
voice.AgentSession.start()
to enable STT/TTS/LLM processing. ◦ This ensures audio streams are present when STT starts. Alternate (non-blocking) pattern 1.
ctx.connect()
2. Create SIP participant without waiting. 3. Listen for participant join event (
participantConnected
) and only then call
agentSession.start()
. Why this matters •
ctx.connect()
provides the necessary room context. • Starting the AgentSession only after the phone participant joins prevents empty-audio STT errors. • Using
wait_until_answered
simplifies timing by ensuring participant presence before you start processing. If you want, I’ll show a concise TypeScript snippet for the preferred flow (connect → create SIP with wait → start AgentSession), or the event-driven alternative. Which one would you like? , with interactive elements
a
typescript snippet please
preferred flow
t
Below is a concise TypeScript snippet for the preferred flow: connect first, create the outbound SIP participant with
wait_until_answered: true
, then start the
voice.AgentSession
after the participant is in the room. Comments show why each step is ordered this way.
Copy code
import { defineAgent, JobContext, log } from '@livekit/agents';
import * as voice from '@livekit/agents/voice';

export default defineAgent({
  entry: async (ctx: JobContext) => {
    const logger = log();

    // 1) Connect to room & signaling so room/localParticipant exist
    await ctx.connect();
    logger.info('Connected to room');

    // 2) Place outbound SIP call and block until answered so audio will be ready
    const sipTrunkId = 'your-sip-trunk-id';
    const targetNumber = '+1234567890';
    try {
      await ctx.room.localParticipant.createSIPParticipant({
        sip_trunk_id: sipTrunkId,
        sip_call_to: targetNumber,
        room_name: ctx.room.name,
        participant_identity: 'phone-participant',
        play_dialtone: true,
        krisp_enabled: true,
        wait_until_answered: true, // ensures participant joins before continuing
      });
      logger.info('SIP participant answered and joined room');
    } catch (err: any) {
      logger.error('Failed to create SIP participant', { error: err.message });
      throw err;
    }

    // 3) Now start AgentSession so STT/TTS/LLM pipelines run with active audio present
    const agentSession = new voice.AgentSession({
      room: ctx.room,
      localParticipant: ctx.room.localParticipant,
      llm: { provider: 'openai', model: 'gpt-4o-mini' }, // example
      stt: { provider: 'whisper', language: 'en-US' },
      tts: { provider: 'polly', voice: 'Matthew' },
    });

    await agentSession.start();
    logger.info('AgentSession started (STT/TTS/LLM ready)');

    // Hook up transcript/events as needed
    agentSession.on('transcript', (t) => logger.info('Transcript:', t));
    agentSession.on('ended', () => logger.info('AgentSession ended'));

    // Keep running until you choose to stop
    // await agentSession.stop() when finished
  },
});
This sequence ensures the room and SIP participant (and thus RTP audio) exist before starting STT, preventing "failed to recognize speech" errors. If you want an event-driven alternative (create SIP without waiting and start the session on participantConnected), I can provide that snippet. , with interactive elements