refined-psychiatrist-80699
12/19/2025, 7:33 PMtall-airline-16756
12/19/2025, 7:33 PMtall-airline-16756
12/19/2025, 7:33 PMrefined-psychiatrist-80699
12/19/2025, 7:35 PMtall-airline-16756
12/19/2025, 7:35 PMuser_input_transcribed event in the agent session to receive final transcripts, or read the lk.transcription text stream on the frontend to display live transcriptions.
Why this is best
• Real-time transcripts during the call.
• Transcripts are delivered as events/text streams (no need to record + post-process).
• Easy to log, save to DB, or forward to downstream services.
Alternatives (less ideal)
• Egress the audio (record MP4/HLS) and run an external STT on the file. This works for offline transcripts but is not real-time and adds complexity.
Quick questions so I can give exact setup steps
1. Do you want real-time live transcripts or post-call transcripts?
2. Which language(s) do you need transcribed?
3. Are you using Node.js or Python for your agent code?
4. Do you have a preferred STT provider (Deepgram, AssemblyAI, ElevenLabs, Cartesia, or another)?
Tell me those and I’ll give concise setup steps or example code.
Sources: Transcriber | LiveKit docs | Deepgram STT | LiveKit docs | AssemblyAI STT | LiveKit docs | ElevenLabs STT | LiveKit docs | Cartesia STT | LiveKit docsrefined-psychiatrist-80699
12/19/2025, 7:36 PMtall-airline-16756
12/19/2025, 7:36 PMrefined-psychiatrist-80699
12/19/2025, 7:37 PMtall-airline-16756
12/19/2025, 7:37 PMrefined-psychiatrist-80699
12/19/2025, 7:40 PMtall-airline-16756
12/19/2025, 7:40 PMrefined-psychiatrist-80699
12/19/2025, 7:41 PMimport asyncio
import logging
from dotenv import load_dotenv
from livekit import agents, api
from livekit.agents import (
Agent,
AgentSession,
JobContext,
RunContext,
function_tool,
get_job_context,
)
from livekit.plugins import xai
logger = logging.getLogger("xai-telephony-agent")
load_dotenv(".env.local")
async def hangup_call():
"""Delete the room to end the call for all participants."""
ctx = get_job_context()
if ctx is None:
return
await ctx.api.room.delete_room(
api.DeleteRoomRequest(room=ctx.room.name)
)
class Assistant(Agent):
def __init__(self) -> None:
super().__init__(
instructions="""You are playful & mischievous and on a phone call.
Your responses should be conversational and without any complex formatting or punctuation
including emojis, asterisks, or other symbols.
When the user says goodbye or wants to end the call, use the hang_up tool.
Keep the responses short (under like 60 words)."""
)
@function_tool
async def hang_up(self, ctx: RunContext):
"""Hang up the phone call. Use when the user says goodbye or wants to end the call."""
<http://logger.info|logger.info>("Hang up tool called - initiating call termination")
# Give a moment for any pending audio to finish
await asyncio.sleep(0.5)
# Delete room to end the SIP call
await hangup_call()
<http://logger.info|logger.info>("Room deleted - call ended")
async def entrypoint(ctx: JobContext):
await ctx.connect()
<http://logger.info|logger.info>(f"Call started - Room: {ctx.room.name}")
model = xai.realtime.RealtimeModel(
voice="Ara",
)
session = AgentSession(
llm=model,
)
await session.start(
agent=Assistant(),
room=ctx.room,
)
# Wait for session to be fully ready before greeting
await asyncio.sleep(2)
# Try to greet, but don't fail the call if it times out
try:
await session.generate_reply(
instructions="Answer the phone warmly, like 'Hello! Thanks for calling. How can I help you today?'"
)
except Exception as e:
logger.warning(f"Initial greeting failed: {e} - waiting for user to speak first")
if __name__ == "__main__":
agents.cli.run_app(
agents.WorkerOptions(
entrypoint_fnc=entrypoint,
agent_name="test_agent", # Must match dispatch rule!
)
)
what do i need to add to it to send a recording to egress?tall-airline-16756
12/19/2025, 7:42 PMfrom livekit import api
import uuid
import asyncio
# Helper to start a Room Composite egress
async def start_room_composite_egress(api_client, room_name, s3_bucket, s3_key_prefix):
job_id = str(uuid.uuid4())
request = api.StartEgressRequest(
room_composite=api.RoomCompositeEgressRequest(
room=room_name,
layout=api.RoomCompositeEgressRequest.Layout.TIMELINE, # or GRID, CUSTOM
format=api.RoomCompositeEgressRequest.Format.MP4, # choose MP4/MP3/WAV/HLS
# output to S3
output=api.EgressOutput(
s3=api.S3Output(
bucket=s3_bucket,
key_prefix=s3_key_prefix,
)
),
)
)
resp = await api_client.egress.start_egress(request)
return resp
# Helper to poll for egress info (or use webhook/event system if you have one)
async def wait_for_egress_completion(api_client, egress_id, poll_interval=2):
while True:
status = await api_client.egress.get_egress(api.GetEgressRequest(egress_id=egress_id))
if status.state in (api.EgressState.COMPLETED, api.EgressState.FAILED):
return status
await asyncio.sleep(poll_interval)
Where to call it in your agent
• After you connect and the call starts in entrypoint, start egress. Example insertion:
# after await ctx.connect() and room available
s3_bucket = "my-bucket"
s3_prefix = f"recordings/{ctx.room.name}/{int(time.time())}"
# start egress
egress_resp = await start_room_composite_egress(ctx.api, ctx.room.name, s3_bucket, s3_prefix)
egress_id = egress_resp.egress_id
# optionally keep track of egress_id to stop later or poll for completion
# ... later, after call ends, wait for completion and get output URL(s)
egress_status = await wait_for_egress_completion(ctx.api, egress_id)
if egress_status.state == api.EgressState.COMPLETED:
# egress_status.output_info contains URLs or S3 locations
print("Egress completed:", egress_status.output_info)
else:
print("Egress failed:", egress_status.error)
Notes and tips
• Choose RoomComposite for a single file; use Track/Participant egress if you want separate per-speaker files.
• Configure format to WAV/FLAC for best STT accuracy.
• If you use S3, ensure IAM credentials/bucket permissions are configured for LiveKit cloud egress or provide a presigned upload configuration.
• You can stop an ongoing egress with api.egress.stop_egress using the egress_id if you need to end recording early.
• Alternatively, rely on egress webhooks/events (if available in your setup) instead of polling.
Next step Do you want a complete ready-to-run snippet that plugs into your current file (including imports, where to place calls, and example S3 config), or do you prefer an example that records separate tracks for speaker diarization?refined-psychiatrist-80699
12/19/2025, 7:43 PMtall-airline-16756
12/19/2025, 7:43 PMrefined-psychiatrist-80699
12/19/2025, 8:00 PMtall-airline-16756
12/19/2025, 8:00 PM