hundreds-secretary-38403
12/15/2025, 1:14 PMtall-airline-16756
12/15/2025, 1:14 PMtall-airline-16756
12/15/2025, 1:14 PMparticipant.attributes['sip.phoneNumber']).
2. Call your external API with that phone number to get context (language, voice, stt/tts model names).
3. Construct the AgentSession with stt and tts parameters set from that context.
4. Start the session so the configured STT/TTS are used for the call.
Minimal Node.js example
await ctx.connect(undefined, AutoSubscribe.AUDIO_ONLY);
const participant = await ctx.waitForParticipant();
if (participant.kind === ParticipantKind.SIP) {
const phone = participant.attributes['sip.phoneNumber'];
const resp = await fetch(`<https://your-api.com/context?number=${encodeURIComponent(phone)}`>);
const ctxData = await resp.json();
const sttModel = ctxData.stt_model || 'deepgram/nova-2-general';
const ttsModel = ctxData.tts_model || 'cartesia/sonic-3';
const ttsVoice = ctxData.voice || 'default-voice';
const language = ctxData.language || 'en';
const session = new voice.AgentSession({
stt: new inference.STT({ model: sttModel, language }),
tts: new inference.TTS({ model: ttsModel, voice: ttsVoice, language }),
// other session options...
});
await session.start(ctx.room);
}
Minimal Python example
await ctx.connect(auto_subscribe=rtc.AutoSubscribe.AUDIO_ONLY)
participant = await ctx.wait_for_participant()
if participant.kind == rtc.ParticipantKind.PARTICIPANT_KIND_SIP:
phone = participant.attributes.get('sip.phoneNumber')
resp = await fetch_caller_context(phone) # your HTTP call
stt_model = resp.get('stt_model', 'deepgram/nova-2-general')
tts_model = resp.get('tts_model', 'cartesia/sonic-3')
voice = resp.get('voice', 'default-voice')
lang = resp.get('language', 'en')
session = AgentSession(
stt=stt.STT(model=stt_model, language=lang),
tts=tts.TTS(model=tts_model, voice=voice, language=lang),
)
await session.start(ctx.room)
Notes and tips
• Use participant.attributes['sip.phoneNumber'] to get the caller number.
• Make the external API call before constructing/starting AgentSession so STT/TTS are set ahead of audio processing.
• Provide sensible defaults if the external API lacks fields.
• If you need per-turn changes (not only at start), you can stop/start or reconfigure the session at runtime—tell me if you want examples for hot-swapping models/voices mid-call.
Would you like a tailored example for your language/runtime or help handling errors/timeouts from the external API?
Sources: SIP participant | LiveKit docs | Models | LiveKit Docshundreds-secretary-38403
12/15/2025, 1:18 PMtall-airline-16756
12/15/2025, 1:18 PMon_participant_connected_handler (or the equivalent place where you first observe the SIP participant) is the right spot.
What to do there:
• Read participant.attributes['sip.phoneNumber'].
• Call your external API to fetch language/voice/model values.
• Construct the AgentSession (or configure `stt`/`tts`) using those values.
• Start the session.
If you use the agent entrypoint pattern instead, the same flow can live after ctx.wait_for_participant() before session.start(ctx.room) — both approaches are equivalent; pick the one that fits your app architecture.
Would you like a short snippet showing this inside your specific handler signature (Node or Python)?hundreds-secretary-38403
12/15/2025, 10:52 PMtall-airline-16756
12/15/2025, 10:52 PMparticipant.attributes['sip.phoneNumber'],
• calling an external API for language/voice/model values,
• constructing an AgentSession with dynamic stt and tts,
• starting the session and generating an initial reply.
Choose the one for your runtime.
Node.js (TypeScript-style)
import fetch from 'node-fetch';
import { defineAgent } from '@livekit/agents';
import { ParticipantKind, TrackKind } from '@livekit/rtc-node';
import { voice, inference } from '@livekit/agents/voice'; // adjust imports per SDK
export default defineAgent({
entry: async (ctx) => {
// Connect to the room; auto-subscribe audio only
await ctx.connect(undefined, { autoSubscribe: false });
await ctx.waitForParticipant(); // ensure a participant will join if needed
// Wait for the SIP participant (this returns the first participant)
const participant = await ctx.waitForParticipant();
if (participant.kind !== ParticipantKind.SIP) return;
const phone = participant.attributes['sip.phoneNumber'];
if (!phone) throw new Error('No SIP phone number found');
// Fetch caller context from your external API
const resp = await fetch(`<https://your-api.com/context?number=${encodeURIComponent(phone)}`>);
const data = await resp.json();
const sttModel = data.stt_model || 'deepgram/nova-2-general';
const ttsModel = data.tts_model || 'cartesia/sonic-3';
const voiceName = data.voice || 'default-voice';
const language = data.language || 'en';
// Construct AgentSession with dynamic STT/TTS
const session = new voice.AgentSession({
stt: new inference.STT({ model: sttModel, language }),
tts: new inference.TTS({ model: ttsModel, voice: voiceName, language }),
// add other options like VAD, turn detection if desired
});
// Start using the room from ctx
await session.start(ctx.room);
// Send an initial prompt (optional)
await session.generateReply({ instructions: `Hello — I will use ${language} for speech.` });
// Keep session alive and stream audio until participant disconnects
const audioTrack = participant.tracks.get(TrackKind.AUDIO)?.track;
if (audioTrack) {
const audioStream = new ctx.rtc.AudioStream(audioTrack);
for await (const _chunk of audioStream) { /* keep reading */ }
}
},
});
Python
import aiohttp
import asyncio
from livekit import agents, rtc
from livekit.agents import JobContext, AgentSession
from livekit.agents import inference as inference_mod # adjust per SDK layout
async def fetch_caller_context(phone_number: str) -> dict:
async with aiohttp.ClientSession() as session:
async with session.get(f'<https://your-api.com/context?number={phone_number}>') as resp:
return await resp.json()
@agents.room.on('participant_connected')
async def on_participant_connected(room: rtc.Room, participant: rtc.Participant):
# spawn a background task so handler returns quickly
asyncio.create_task(handle_participant(room, participant))
async def handle_participant(room: rtc.Room, participant: rtc.Participant):
if participant.kind != rtc.ParticipantKind.PARTICIPANT_KIND_SIP:
return
phone = participant.attributes.get('sip.phoneNumber')
if not phone:
return
try:
ctx_data = await fetch_caller_context(phone)
stt_model = ctx_data.get('stt_model', 'deepgram/nova-2-general')
tts_model = ctx_data.get('tts_model', 'cartesia/sonic-3')
voice_name = ctx_data.get('voice', 'default-voice')
language = ctx_data.get('language', 'en')
# Build AgentSession with dynamic parameters
session = AgentSession(
stt=inference_mod.STT(model=stt_model, language=language),
tts=inference_mod.TTS(model=tts_model, voice=voice_name, language=language),
# add vad/turn_detection if needed
)
# Start session bound to the room
await session.start(room)
await session.generate_reply(instructions=f"Hello — I'll use {language} for speech.")
# Subscribe to audio to keep session alive (simple loop)
audio_track = participant.tracks.get(rtc.TrackKind.KIND_AUDIO).track
async for _ in rtc.AudioStream(audio_track):
pass
except Exception as e:
# handle/log error
print('Error handling participant:', e)
async def entrypoint(ctx: JobContext):
# If using event handlers above, entrypoint can be minimal
pass
if __name__ == '__main__':
agents.cli.run_app(agents.WorkerOptions(entrypoint_fnc=entrypoint, worker_type=agents.WorkerType.ROOM))
Notes
• Put this logic where you first detect the SIP participant: on_participant_connected or right after ctx.wait_for_participant().
• Make the external HTTP call before creating/starting AgentSession so STT/TTS are configured before audio processing begins.
• Provide defaults for missing fields from your API.
• If you need mid-call reconfiguration (hot-swap models/voices), say so and I can show runtime reconfiguration patterns.
Would you like this adapted to your exact SDK import layout or to include VAD/turn-detection examples?
Sources: Company directory phone assistant | LiveKit docs | Job lifecycle | LiveKit docs | SIP participant | LiveKit docs | Creating SIP Inbound Trunks and Dispatch Rules with Python SDK