thousands-school-86950
09/18/2025, 8:39 AMfierce-jordan-44682
09/18/2025, 8:54 AMfierce-jordan-44682
09/18/2025, 9:22 AMfierce-jordan-44682
09/18/2025, 9:27 AMhappy-dawn-98083
09/18/2025, 9:34 AMbulky-yak-50122
09/18/2025, 9:42 AMbulky-yak-50122
09/18/2025, 9:55 AMfast-intern-10974
09/18/2025, 10:18 AMtranscription_node
like this:
async def transcription_node(self, text_stream: AsyncIterable[str], model_settings: ModelSettings):
async for text in super().transcription_node(text_stream, model_settings):
if self.filter_ssml:
text = self.ssml_tag_pattern.sub("", text)
yield text
This works for messages sent using session.say(), but responses generated by the LLM are still not filtered.
Does anyone know a way to clean or remove the SSML tags from live transcription for LLM outputs as well?
Thanks a lot!cold-yacht-71881
09/18/2025, 10:36 AMsalmon-belgium-62133
09/18/2025, 10:52 AMlemon-cricket-49002
09/18/2025, 11:15 AMsalmon-belgium-62133
09/18/2025, 11:15 AMimport logging
import asyncio
import os
import json
from dotenv import load_dotenv
from livekit.agents import (
NOT_GIVEN,
Agent,
AgentFalseInterruptionEvent,
AgentSession,
JobContext,
JobProcess,
AgentStateChangedEvent,
UserInputTranscribedEvent,
SpeechCreatedEvent,
UserStateChangedEvent,
AgentHandoffEvent,
MetricsCollectedEvent,
RoomInputOptions,
RunContext,
WorkerOptions,
function_tool,
cli,
metrics,
)
from livekit.plugins import silero
import livekit.plugins.groq as groq
from livekit.plugins.turn_detector.multilingual import MultilingualModel
from livekit.plugins import noise_cancellation
logger = logging.getLogger("agent")
load_dotenv(".env")
class Assistant(Agent):
def __init__(self) -> None:
super().__init__(
instructions="""You are a helpful voice AI assistant.
You eagerly assist users with their questions by providing information from your extensive knowledge.
Your responses are concise, to the point, and without any complex formatting or punctuation including emojis, asterisks, or other symbols.
You are curious, friendly, and have a sense of humor.""",
)
@function_tool
async def lookup_weather(self, context: RunContext, location: str):
logger.info(f"Looking up weather for {location}")
return "sunny with a temperature of 70 degrees."
def prewarm(proc: JobProcess):
proc.userdata["vad"] = silero.VAD.load()
async def entrypoint(ctx: JobContext):
ctx.log_context_fields = {"room": ctx.room.name}
print(f"Starting agent in room: {ctx.room.name}")
# Set up voice AI pipeline
session = AgentSession(
llm=groq.LLM(model="openai/gpt-oss-20b"),
stt=groq.STT(model="whisper-large-v3-turbo", language="en"),
tts=groq.TTS(model="playai-tts", voice="Aaliyah-PlayAI"),
turn_detection=MultilingualModel(),
vad=ctx.proc.userdata["vad"],
preemptive_generation=False,
)
@session.on("agent_false_interruption")
def _on_agent_false_interruption(ev: AgentFalseInterruptionEvent):
logger.info("False positive interruption, resuming")
session.generate_reply(instructions=ev.extra_instructions or NOT_GIVEN)
payload = json.dumps({
"type": "agent_false_interruption",
"data": ev.dict()
})
asyncio.create_task(ctx.room.local_participant.publish_data(payload.encode("utf-8"), reliable=True))
logger.info("Sent agent_false_interruption via data channel")
usage_collector = metrics.UsageCollector()
# @session.on("metrics_collected")
# def _on_metrics_collected(ev: MetricsCollectedEvent):
# metrics.log_metrics(ev.metrics)
# usage_collector.collect(ev.metrics)
# payload = json.dumps({
# "type": "metrics_collected",
# "data": ev.metrics.dict()
# })
# asyncio.create_task(ctx.room.local_participant.publish_data(payload.encode("utf-8"), reliable=True))
# logger.info("Sent metrics_collected via data channel")
@session.on("agent_state_changed")
def _on_agent_state_changed(ev: AgentStateChangedEvent):
logger.info(f"Agent state changed: {ev}")
payload = json.dumps({
"type": "agent_state_changed",
"data": ev.dict()
})
asyncio.create_task(ctx.room.local_participant.publish_data(payload.encode("utf-8"), reliable=True))
logger.info("Sent agent_state_changed via data channel")
@session.on("user_input_transcribed")
def _on_user_input_transcribed(ev: UserInputTranscribedEvent):
logger.info(f"User said: {ev}")
payload = json.dumps({
"type": "user_input_transcribed",
"data": ev.dict()
})
asyncio.create_task(ctx.room.local_participant.publish_data(payload.encode("utf-8"), reliable=True))
logger.info("Sent user_input_transcribed via data channel")
@session.on("speech_created")
def _on_speech_created(ev: SpeechCreatedEvent):
# logger.info(f"Speech created with id: {ev.speech_id}, duration: {ev.duration_ms}ms")
payload = json.dumps({
"type": "speech_created",
"data": ev.dict()
})
asyncio.create_task(ctx.room.local_participant.publish_data(payload.encode("utf-8"), reliable=True))
logger.info("Sent speech_created via data channel")
async def log_usage():
summary = usage_collector.get_summary()
logger.info(f"Usage: {summary}")
payload = json.dumps({
"type": "usage_summary",
"summary": summary.llm_prompt_tokens
})
# session.local_participant.publishData(payload.encode("utf-8"), reliable=True)
logger.info("Sent usage_summary via data channel")
ctx.add_shutdown_callback(log_usage)
await session.start(
agent=Assistant(),
room=ctx.room,
room_input_options=RoomInputOptions(
noise_cancellation=noise_cancellation.BVC(),
),
)
await ctx.connect()
if name == "__main__":
cli.run_app(WorkerOptions(entrypoint_fnc=entrypoint, prewarm_fnc=prewarm))
can i get 160000 samplerate audio from livekit??
bulky-machine-37433
09/18/2025, 12:25 PMsteep-balloon-41261
09/18/2025, 12:27 PMchilly-balloon-11273
09/18/2025, 12:44 PMalert-australia-33132
09/18/2025, 1:05 PMnumerous-whale-53652
09/18/2025, 1:10 PMhttps://${storageAccountName}.<http://blob.core.windows.net|blob.core.windows.net>
,
sharedKeyCredential
);
const containerClient = blobServiceClient.getContainerClient(containerName);
// List all blobs that start with the room name pattern
const prefix = `livekit/record/${roomName}`;
const existingFiles: string[] = [];
for await (const blob of containerClient.listBlobsFlat({ prefix })) {
existingFiles.push(blob.name);
}
// Extract version numbers from existing files
const versions: number[] = [];
const basePattern = `livekit/record/${roomName}`;
for (const fileName of existingFiles) {
if (fileName === ${basePattern}.mp4
) {
// This is the base file (no version number)
versions.push(0);
} else {
_// Try to extract version number from pattern: roomName_X.mp4_
_const match = fileName.match(new RegExp(^${basePattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}_([0-9]+)\\.mp4$
));_
if (match) {
versions.push(parseInt(match[1], 10));
}
}
}
// Determine the next version number
let nextVersion = 0;
if (versions.length > 0) {
const maxVersion = Math.max(...versions);
nextVersion = maxVersion + 1;
}
// console.log(Room: ${roomName}, Found versions: [${versions.join(', ')}], Next version: ${nextVersion}
);
// Generate filename based on version
if (nextVersion === 0) {
return `livekit/record/${roomName}.mp4`;
} else if (nextVersion === 1) {
_return `livekit/record/${roomName}_1.mp4`;_
} else {
_return `livekit/record/${roomName}_${nextVersion}.mp4`;_
}
} catch (error) {
console.error('Error checking existing files:', error);
// Fallback to base filename if there's an error
return `livekit/record/${roomName}.mp4`;
}
}
*/
export async function *GET*(_req_: NextRequest) {
try {
const roomName = req.nextUrl.searchParams.get('roomName');
if (roomName === null) {
return new NextResponse('Missing roomName parameter', { status: 403 });
}
const {
LIVEKIT_API_KEY,
LIVEKIT_API_SECRET,
LIVEKIT_URL,
} = process.env;
// S3 configuration
const S3_BUCKET_NAME = 'XXXXXXXXXXXXX';
const S3_REGION = 'XXXXXXXXXXXXX';
// Debug logging
console.log(Attempting to start recording for room: ${roomName}
);
console.log(LiveKit URL: ${LIVEKIT_URL}
);
// Use the same URL conversion as the original working code
const hostURL = new URL(LIVEKIT_URL!);
hostURL.protocol = 'https:';
console.log(Egress URL: ${hostURL.origin}
);
const egressClient = new EgressClient(hostURL.origin, LIVEKIT_API_KEY, LIVEKIT_API_SECRET);
// Check if room exists first
try {
const roomClient = new RoomServiceClient(LIVEKIT_URL!, LIVEKIT_API_KEY, LIVEKIT_API_SECRET);
const rooms = await roomClient.listRooms([roomName]);
if (rooms.length === 0) {
console.log(Room ${roomName} does not exist
);
return new NextResponse('Room does not exist. Please ensure participants have joined the room before starting recording.', { status: 404 });
}
console.log(Room ${roomName} found with ${rooms[0].numParticipants} participants
);
} catch (roomError) {
console.error('Error checking room existence:', roomError);
// Continue anyway, let egress handle it
}
const existingEgresses = await egressClient.listEgress({ roomName });
if (existingEgresses.length > 0 && existingEgresses.some((e) => e.status < 2)) {
return new NextResponse('Meeting is already being recorded', { status: 409 });
}
// Generate simple filename for S3 upload
const fileName = `livekit/record/${roomName}.mp4`;
console.log(Generated filename: ${fileName}
);
const fileOutput = new EncodedFileOutput({
filepath: fileName,
output: {
case: 's3',
value: new S3Upload({
_// accessKey: AWS_ACCESS_KEY_ID!,_
_// secret: AWS_SECRET_ACCESS_KEY!,_
region: S3_REGION,
bucket: S3_BUCKET_NAME,
}),
},
});
console.log('File output configuration:', JSON.stringify({
filepath: fileName,
outputType: 's3',
region: S3_REGION,
bucket: S3_BUCKET_NAME
}, null, 2));
// Use the exact method signature from your original working code
console.log('Starting room composite egress...');
await egressClient.startRoomCompositeEgress(
roomName,
{
file: fileOutput,
},
{
layout: 'speaker',
},
);
console.log('Recording started successfully');
// Construct the URL of the saved file in S3
const fileUrl = `https://${S3_BUCKET_NAME}.s3.${S3_REGION}.amazonaws.com/${fileName}`;
console.log(Recording started. File will be saved to: ${fileUrl}
);
// save the meeting record
try {
const response = await fetch(${process.env.PYTHON_DISPATCHER_URL}/recordings
, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
room_name: roomName,
recording_url: fileUrl,
}),
});
if (!response.ok) {
console.error(Failed to save recording link. Status: ${response.status}
);
}
} catch (saveError) {
console.error(Error saving recording link for room: ${roomName}
, saveError);
}
return new NextResponse(null, { status: 200 });
} catch (error) {
if (error instanceof Error) {
console.error('Error starting recording:', error.message);
return new NextResponse(error.message, { status: 500 });
}
console.error('Unknown error starting recording:', error);
return new NextResponse('Internal server error', { status: 500 });
}
}thousands-school-86950
09/18/2025, 1:17 PMsteep-balloon-41261
09/18/2025, 1:27 PMnumerous-whale-53652
09/18/2025, 1:28 PMproud-lawyer-39815
09/18/2025, 1:46 PMloud-postman-20421
09/18/2025, 2:32 PMswift-nail-77471
09/18/2025, 2:40 PMthankful-plastic-70087
09/18/2025, 3:07 PMacoustic-doctor-87652
09/18/2025, 3:24 PMmagnificent-furniture-73003
09/18/2025, 4:00 PMsteep-balloon-41261
09/18/2025, 4:16 PMabundant-musician-30420
09/18/2025, 5:06 PMnutritious-australia-78231
09/18/2025, 5:43 PMignoring incoming text stream due to no handler for topic lk.transcription
chilly-soccer-432
09/18/2025, 6:30 PM