white-telephone-81843
12/19/2025, 1:20 PMtall-airline-16756
12/19/2025, 1:20 PMtall-airline-16756
12/19/2025, 1:21 PMlivekit.rtc.AudioFrame objects, and stream them into the room.
• Use the agent session method that sends audio (for example session.say() with an audio generator) where the generator yields AudioFrame instances.
• Common formats including WAV, MP3, and AAC are supported; WAV is efficient for small files.
If you'd like, I can:
• Provide a short code example for your preferred language (JS/Node or Python).
• Explain how to handle sample rate/channel conversion if your file doesn't match the room's expected format.
Which would you prefer?
Sources: Playing Audio | LiveKit docs | Agent speech and audio | LiveKit docswhite-telephone-81843
12/19/2025, 1:23 PMtall-airline-16756
12/19/2025, 1:24 PMsession.say() as AudioFrame with the filename as text,
• shows a @function_tool that prints the recognized word when triggered,
• includes a simple test loop that plays {word}.wav files and waits for the tool to run.
Do you want the example:
• A) Minimal runnable script (requires LiveKit Agents Python packages and your .env), or
• B) Full example with sample WAV handling (sample-rate/channel checks and conversion hints)?
Pick A or B and confirm your preferred STT/LLM (if you want them specified), otherwise I’ll assume defaults and return the minimal runnable script.
Sources: Playing Audio | LiveKit docs | OpenAI and LiveKit partner to turn Advanced Voice into an API | Capturing the First Few Seconds of a Call for Processing | Job lifecycle | LiveKit docswhite-telephone-81843
12/19/2025, 1:26 PMtall-airline-16756
12/19/2025, 1:26 PMsession.say(...) with an AudioFrame, and define a @function_tool that prints the recognized word. It assumes you have LiveKit Agents Python packages installed and a .env with your LiveKit credentials.
Save as agent_wav_test.py and run with python agent_wav_test.py console.
import asyncio
import wave
from pathlib import Path
from dotenv import load_dotenv
from livekit.agents import Agent, AgentServer, AgentSession, function_tool, JobContext
from livekit import rtc
load_dotenv() # expects LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET in .env
class WavTestAgent(Agent):
def __init__(self):
super().__init__(instructions="Test agent: trigger tool when a word is heard.")
@function_tool
async def on_word_heard(self, word: str):
print(f"✅ Tool triggered: Recognized word is '{word}'")
return {"received": word}
server = AgentServer()
async def play_wav_via_session(session: AgentSession, wav_path: Path):
with wave.open(str(wav_path), "rb") as wf:
sample_rate = wf.getframerate()
num_channels = wf.getnchannels()
frames = wf.readframes(wf.getnframes())
samples_per_channel = len(frames) // (num_channels * 2) # 16-bit PCM
audio_frame = rtc.AudioFrame(
data=frames,
sample_rate=sample_rate,
num_channels=num_channels,
samples_per_channel=samples_per_channel,
)
# Use the filename (without .wav) as the text hint so LLM/STT sees the expected word
await session.say(text=wav_path.stem, audio=lambda: iter([audio_frame]))
@server.room_participant_entry()
async def on_participant_entry(ctx: JobContext):
agent = WavTestAgent()
# Create session with default STT/LLM; adjust if you have specific providers/models
session = AgentSession(room=ctx.room)
await session.start(agent=agent)
await ctx.connect(auto_subscribe=True)
# Single-file test: expects a file named "<word>.wav" next to this script
test_wav = Path(__file__).parent / "hello.wav" # change name to your test word
if not test_wav.exists():
print(f"Missing test WAV: {test_wav}")
return
# Play the WAV and then (optionally) let the agent tool be triggered by the LLM/STT pipeline.
await play_wav_via_session(session, test_wav)
# If you want to directly invoke the tool (useful for deterministic testing), call:
# await agent.on_word_heard(word=test_wav.stem)
# Wait briefly to allow processing
await asyncio.sleep(3)
if __name__ == "__main__":
server.run()
Quick notes:
• Place your test file as hello.wav (or change the filename) next to the script.
• This script uses session.say(text=...) with the filename as the text hint to help the STT/LLM pipeline recognize the expected word.
• If you want deterministic tests that bypass STT variability, uncomment the direct call to agent.on_word_heard(...) shown in the script.
If you want, I can:
• Provide the same minimal example adjusted to test multiple WAV files in a loop, or
• Add sample rate/channel conversion code if your WAVs differ from room expectations. Which next?white-telephone-81843
12/19/2025, 2:28 PMtall-airline-16756
12/19/2025, 2:28 PMsession.say(...) using rtc.AudioFrame,
• uses the filename as the text hint so the realtime STT/LLM pipeline can trigger your function tool,
• shows both automatic (STT→LLM→tool) flow and an optional deterministic direct tool call for tests.
Add/merge the following parts into your file (keeping your existing entrypoint and AzureRealtime config). Adjust paths and agent name as needed.
1) Tool on the Agent (prints recognized word)
from livekit.agents import function_tool
class Assistant(Agent):
def __init__(self, ctx: agents.JobContext) -> None:
super().__init__(instructions=system_prompt2, mcp_servers=[mcp.MCPServerHTTP(os.getenv("MCP_URL"))])
self.ctx = ctx
@function_tool
async def on_word_heard(self, word: str):
print(f"✅ Tool triggered: Recognized word is '{word}'")
return {"received": word}
2) WAV playback helper (reads WAV, wraps into rtc.AudioFrame, calls session.say)
import wave
from pathlib import Path
from livekit import rtc
async def play_wav_file(session, wav_path: Path):
if not wav_path.exists():
print(f"Missing WAV: {wav_path}")
return
with wave.open(str(wav_path), "rb") as wf:
sample_rate = wf.getframerate()
num_channels = wf.getnchannels()
frames = wf.readframes(wf.getnframes())
samples_per_channel = len(frames) // (num_channels * 2) # 16-bit PCM assumption
audio_frame = rtc.AudioFrame(
data=frames,
sample_rate=sample_rate,
num_channels=num_channels,
samples_per_channel=samples_per_channel,
)
# Use filename (without .wav) as text hint so STT/LLM sees the expected word
await session.say(text=wav_path.stem, audio=lambda: iter([audio_frame]))
3) Example test loop inside your entrypoint after session.start(...) and connect
from pathlib import Path
import asyncio
# after await session.start(...), await ctx.connect(...)
# Example single-run test (replace "hello.wav" with your file)
test_wav = Path(__file__).parent / "hello.wav"
await play_wav_file(session, test_wav)
# Wait to allow STT/LLM processing and tool invocation
await asyncio.sleep(3)
# Optional deterministic path for test harness: directly invoke the tool
# await assistant.on_word_heard(word=test_wav.stem)
4) Dispatching note
• Your existing dispatch code that calls LiveKit API to create an agent dispatch is correct. Ensure agent_name and room match what your worker expects.
• Run the worker (agents.cli.run_app / agents.WorkerOptions) so it picks up dispatched jobs.
Quick checklist
• Place WAV files next to the script, named exactly as the test word (e.g., hello.wav).
• Ensure WAV files are 16-bit PCM (the helper assumes 16-bit). If not, add conversion or let me add conversion code.
• Ensure your AgentSession(..., input_audio_transcription=...) is set (you already use Whisper) and the STT provider is available in the worker environment.
• For deterministic test results, call the tool directly as shown.
If you want, I can:
• Provide a small patch that merges these snippets into your posted file, or
• Add sample-rate/channel conversion (ffmpeg-based) if your WAVs are not 16-bit PCM.
Which would you prefer?white-telephone-81843
12/19/2025, 2:30 PMtall-airline-16756
12/19/2025, 2:30 PMsession.say(...) (using rtc.AudioFrame) with the filename as the text hint,
• defines a @function_tool that prints the recognized word,
• includes an optional direct invocation for deterministic testing.
Save as voice_agent_agent.py, install required packages, set env vars, place your test WAV next to the script (e.g., hello.wav), then run with: python voice_agent_agent.py console
# voice_agent_agent.py
import os
import asyncio
import wave
from pathlib import Path
from dotenv import load_dotenv
import logging
from livekit import agents, api, rtc
from livekit.agents import (
Agent,
AgentServer,
AgentSession,
JobContext,
function_tool,
RoomInputOptions,
mcp,
)
from livekit.plugins import openai, noise_cancellation
from openai.types.beta.realtime.session import TurnDetection, InputAudioTranscription
load_dotenv()
logging.basicConfig(level=<http://logging.INFO|logging.INFO>, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger("GMG_agent")
system_prompt2 = """You are a helpful and efficient assistant designed to interact with callers in Norwegian. Your primary task is to say hi"""
class Assistant(Agent):
def __init__(self, ctx: agents.JobContext) -> None:
super().__init__(
instructions=system_prompt2,
mcp_servers=[mcp.MCPServerHTTP(os.getenv("MCP_URL"))] if os.getenv("MCP_URL") else None,
)
self.ctx = ctx
@function_tool
async def on_word_heard(self, word: str):
print(f"✅ Tool triggered: Recognized word is '{word}'")
return {"received": word}
async def play_wav_file(session: AgentSession, wav_path: Path):
if not wav_path.exists():
print(f"Missing WAV: {wav_path}")
return
# Read WAV file (assumes 16-bit PCM)
with wave.open(str(wav_path), "rb") as wf:
sample_rate = wf.getframerate()
num_channels = wf.getnchannels()
frames = wf.readframes(wf.getnframes())
samples_per_channel = len(frames) // (num_channels * 2) # 16-bit PCM assumption
audio_frame = rtc.AudioFrame(
data=frames,
sample_rate=sample_rate,
num_channels=num_channels,
samples_per_channel=samples_per_channel,
)
# Use filename (without .wav) as text hint so STT/LLM sees the expected word
await session.say(text=wav_path.stem, audio=lambda: iter([audio_frame]))
<http://logger.info|logger.info>(f"Played WAV {wav_path.name} (hint text='{wav_path.stem}')")
server = AgentServer()
@server.room_participant_entry()
async def entrypoint(ctx: JobContext):
# Build AgentSession with Azure Realtime + Whisper transcription as you had
session = AgentSession(
llm=openai.realtime.RealtimeModel.with_azure(
voice="alloy",
azure_deployment=os.getenv("AZURE_OPENAI_DEPLOYMENT"),
azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
api_key=os.getenv("AZURE_OPENAI_KEY"),
api_version=os.getenv("AZURE_OPENAI_API_VERSION"),
turn_detection=TurnDetection(
type="semantic_vad",
eagerness="auto",
create_response=True,
interrupt_response=True,
),
input_audio_transcription=InputAudioTranscription(model="whisper-1", language="no"),
),
mcp_servers=[mcp.MCPServerHTTP(os.getenv("MCP_URL"))] if os.getenv("MCP_URL") else None,
)
assistant = Assistant(ctx)
await session.start(room=ctx.room, agent=assistant, room_input_options=RoomInputOptions(noise_cancellation=noise_cancellation.BVC()))
await ctx.connect(auto_subscribe=True)
# Give the session a moment to initialize
await asyncio.sleep(0.5)
# Single test WAV — change filename to your word (e.g., hello.wav)
test_wav = Path(__file__).parent / "hello.wav"
if not test_wav.exists():
print(f"Missing test WAV: {test_wav} — create a WAV named <word>.wav next to this script.")
# Optionally exit or continue waiting for audio from other participants
return
# Play the WAV into the room so STT/LLM can process it
await play_wav_file(session, test_wav)
# Wait to allow STT/LLM pipeline and tool triggering to run
await asyncio.sleep(4)
# Optional deterministic test: directly invoke tool (uncomment if desired)
# await assistant.on_word_heard(word=test_wav.stem)
# Keep the agent alive for a short while to capture any further events, then stop session
await asyncio.sleep(2)
await session.stop()
async def create_dispatch_and_run():
# Optional: create agent dispatch to a specific room (adjust as needed)
try:
lkapi = api.LiveKitAPI()
agent_name = os.getenv("AGENT_NAME")
room_name = os.getenv("TEST_ROOM", "test-room")
if agent_name:
await lkapi.agent_dispatch.create_dispatch(
api.CreateAgentDispatchRequest(agent_name=agent_name, room=room_name)
)
<http://logger.info|logger.info>(f"Created agent dispatch for agent '{agent_name}' into room '{room_name}'")
await lkapi.aclose()
except Exception as e:
logger.warning(f"Could not create dispatch: {e}")
if __name__ == "__main__":
# Optionally create a dispatch before starting the worker
asyncio.run(create_dispatch_and_run())
# Run agent worker — this will start the server and listen for jobs
# Run with: python voice_agent_agent.py console
server.run()
Quick checklist before running:
• Environment variables in .env: LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET, AZURE_OPENAI_DEPLOYMENT, AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_KEY, AZURE_OPENAI_API_VERSION. Optionally AGENT_NAME and MCP_URL.
• Install required packages (example): pip install livekit livekit-agents python-dotenv openai
• Place a 16-bit PCM WAV next to the script named exactly as the test word, e.g., hello.wav.
• If your WAVs are not 16-bit PCM, tell me and I’ll add ffmpeg-based conversion.
Want this adjusted to:
• run multiple WAVs in a loop, or
• add automatic sample-rate/channel conversion?white-telephone-81843
12/19/2025, 2:32 PMtall-airline-16756
12/19/2025, 2:32 PMroom_participant_entry. You're hitting this because the decorator name in the Agents API is different from what your script used.
What to change
• Replace the incorrect decorator @server.room_participant_entry() with the correct entrypoint registration used by the Agents server: @server.entrypoint().
Quick fixed script Below is the minimal corrected version of the complete script you can run. It updates the decorator and keeps the WAV playback + tool behavior:
# agent_for_test.py
import os
import asyncio
import wave
from pathlib import Path
from dotenv import load_dotenv
import logging
from livekit import agents, api, rtc
from livekit.agents import (
Agent,
AgentServer,
AgentSession,
JobContext,
function_tool,
RoomInputOptions,
mcp,
)
from livekit.plugins import openai, noise_cancellation
from openai.types.beta.realtime.session import TurnDetection, InputAudioTranscription
load_dotenv()
logging.basicConfig(level=<http://logging.INFO|logging.INFO>, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger("GMG_agent")
system_prompt2 = "You are a helpful and efficient assistant designed to interact with callers in Norwegian. Your primary task is to say hi"
class Assistant(Agent):
def __init__(self, ctx: agents.JobContext) -> None:
super().__init__(
instructions=system_prompt2,
mcp_servers=[mcp.MCPServerHTTP(os.getenv("MCP_URL"))] if os.getenv("MCP_URL") else None,
)
self.ctx = ctx
@function_tool
async def on_word_heard(self, word: str):
print(f"✅ Tool triggered: Recognized word is '{word}'")
return {"received": word}
async def play_wav_file(session: AgentSession, wav_path: Path):
if not wav_path.exists():
print(f"Missing WAV: {wav_path}")
return
with wave.open(str(wav_path), "rb") as wf:
sample_rate = wf.getframerate()
num_channels = wf.getnchannels()
frames = wf.readframes(wf.getnframes())
samples_per_channel = len(frames) // (num_channels * 2) # 16-bit PCM assumption
audio_frame = rtc.AudioFrame(
data=frames,
sample_rate=sample_rate,
num_channels=num_channels,
samples_per_channel=samples_per_channel,
)
await session.say(text=wav_path.stem, audio=lambda: iter([audio_frame]))
<http://logger.info|logger.info>(f"Played WAV {wav_path.name} (hint text='{wav_path.stem}')")
server = AgentServer()
# <-- FIX: use @server.entrypoint() not @server.room_participant_entry()
@server.entrypoint()
async def entrypoint(ctx: JobContext):
session = AgentSession(
llm=openai.realtime.RealtimeModel.with_azure(
voice="alloy",
azure_deployment=os.getenv("AZURE_OPENAI_DEPLOYMENT"),
azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
api_key=os.getenv("AZURE_OPENAI_KEY"),
api_version=os.getenv("AZURE_OPENAI_API_VERSION"),
turn_detection=TurnDetection(
type="semantic_vad",
eagerness="auto",
create_response=True,
interrupt_response=True,
),
input_audio_transcription=InputAudioTranscription(model="whisper-1", language="no"),
),
mcp_servers=[mcp.MCPServerHTTP(os.getenv("MCP_URL"))] if os.getenv("MCP_URL") else None,
)
assistant = Assistant(ctx)
await session.start(room=ctx.room, agent=assistant, room_input_options=RoomInputOptions(noise_cancellation=noise_cancellation.BVC()))
await ctx.connect(auto_subscribe=True)
await asyncio.sleep(0.5)
test_wav = Path(__file__).parent / "hello.wav"
if not test_wav.exists():
print(f"Missing test WAV: {test_wav} — create a WAV named <word>.wav next to this script.")
return
await play_wav_file(session, test_wav)
await asyncio.sleep(4)
# Optional deterministic call:
# await assistant.on_word_heard(word=test_wav.stem)
await asyncio.sleep(2)
await session.stop()
if __name__ == "__main__":
# optional dispatch creation
async def create_dispatch():
try:
lkapi = api.LiveKitAPI()
agent_name = os.getenv("AGENT_NAME")
room_name = os.getenv("TEST_ROOM", "test-room")
if agent_name:
await lkapi.agent_dispatch.create_dispatch(
api.CreateAgentDispatchRequest(agent_name=agent_name, room=room_name)
)
<http://logger.info|logger.info>(f"Created agent dispatch for agent '{agent_name}' into room '{room_name}'")
await lkapi.aclose()
except Exception as e:
logger.warning(f"Could not create dispatch: {e}")
asyncio.run(create_dispatch())
server.run()
What I changed
• Replaced @server.room_participant_entry() with @server.entrypoint() — this matches the AgentServer API you have and removes the AttributeError.
• Kept WAV playback and tool snippets intact.
If you still see an AttributeError after this change, please paste:
• the exact traceback,
• the LiveKit Agents package version (from pip: pip show livekit-agents),
• and the top lines of the file where AgentServer() is created.
I can then tailor the fix to your installed SDK version.white-telephone-81843
12/19/2025, 2:36 PMtall-airline-16756
12/19/2025, 2:37 PMroom_participant_entry, entrypoint, and rtc_session, and the SDK in your environment doesn't expose those methods.
Quick summary and next step:
• The correct decorator name depends on the livekit-agents version you have installed.
• I can't safely guess which decorator your installed SDK expects. To pick the right one I need your livekit-agents version.
Please run this and paste the output:
pip show livekit-agents
Once you share the version string, I will:
• give the exact decorator name to use for that version, and
• provide a corrected, complete script matching that SDK (including WAV playback and the @function_tool that prints the recognized word). Sources: Agent server options | LiveKit docswhite-telephone-81843
12/19/2025, 2:38 PMtall-airline-16756
12/19/2025, 2:39 PM# agent_for_test.py
import asyncio
import logging
import os
import wave
from pathlib import Path
from dotenv import load_dotenv
from livekit import agents, rtc
from livekit.agents import JobContext, JobRequest, WorkerOptions, cli
from livekit.plugins import silero, deepgram, openai
load_dotenv()
logging.basicConfig(level=<http://logging.INFO|logging.INFO>)
logger = logging.getLogger("voice-ai-agent")
# --- Agent definition ---
class WordAudioAgent(agents.Agent):
def __init__(self) -> None:
super().__init__(
name="WordAudioAgent",
instructions="You are an assistant that listens for words and can play corresponding WAV files.",
)
self.session = None # will be set by entrypoint
@agents.function_tool()
async def play_word_audio(self, word: str):
"""Tool: play <word>.wav and print the recognized word."""
safe_word = "".join(c for c in word if c.isalnum() or c in "-_").strip().lower()
audio_path = Path(__file__).parent / f"{safe_word}.wav"
if not audio_path.exists():
msg = f"Audio file not found for word '{word}': {audio_path}"
logger.warning(msg)
print(msg)
return {"error": msg}
# Read WAV (assumes 16-bit PCM)
try:
with wave.open(str(audio_path), "rb") as wf:
num_channels = wf.getnchannels()
sample_rate = wf.getframerate()
nframes = wf.getnframes()
frames = wf.readframes(nframes)
except Exception as e:
msg = f"Failed reading WAV {audio_path}: {e}"
logger.error(msg)
print(msg)
return {"error": msg}
samples_per_channel = nframes
audio_frame = rtc.AudioFrame(
data=frames,
sample_rate=sample_rate,
num_channels=num_channels,
samples_per_channel=samples_per_channel,
)
async def audio_gen():
# yield the single frame (small files). For longer audio chunk into multiple frames.
yield audio_frame
# Use filename as hint text so STT/LLM sees the expected word
await self.session.say(text=safe_word, audio=audio_gen)
print(f"✅ Tool triggered: Recognized word is '{word}' (played {audio_path.name})")
return {"played": str(audio_path)}
async def on_enter(self):
# optional greeting on session start
await self.session.say("Hello, ready to test word playback.")
# --- entrypoint / worker handler ---
async def entrypoint(ctx: JobContext):
await ctx.accept()
# preload VAD
vad = silero.VAD.load()
# set up providers (adjust models / providers as needed)
stt = deepgram.STT()
llm = openai.LLM()
tts = openai.TTS()
session = agents.AgentSession(
context=ctx,
stt=stt,
llm=llm,
tts=tts,
vad=vad,
allow_interruptions=True,
)
agent = WordAudioAgent()
agent.session = session
await session.start(agent=agent, room=ctx.room)
await ctx.connect()
# Give session time to initialize
await asyncio.sleep(0.5)
# OPTIONAL: play a local test WAV named "hello.wav"
test_wav = Path(__file__).parent / "hello.wav"
if test_wav.exists():
# call tool which will play and print
await agent.play_word_audio("hello")
await asyncio.sleep(3) # wait for processing
else:
<http://logger.info|logger.info>(f"No test WAV found at {test_wav}. Waiting for incoming audio or tool calls.")
# keep session alive briefly (adjust as needed)
await asyncio.sleep(2)
await session.stop()
# worker function called by the WorkerOptions-based CLI
async def worker_fnc(req: JobRequest) -> None:
<http://logger.info|logger.info>(f"Received job request: {req.job.sid}")
await req.accept(entrypoint_fnc=entrypoint)
if __name__ == "__main__":
# Run the worker with the callback-based pattern used by v1.2.8
cli.run_app(WorkerOptions(worker_fnc=worker_fnc))
Run instructions
• Ensure env vars (.env) include your LiveKit credentials and any plugin keys (Deepgram/OpenAI) if required.
• Install deps (example):
pip install "livekit-agents[silero]" python-dotenv
• Place a 16-bit PCM WAV next to the script named hello.wav (or create multiple files named after words you will test).
• Run the worker:
python agent_for_test.py console (or run via your usual environment invocation)
Notes and troubleshooting
• WAV format: this script assumes 16-bit PCM. If your files differ, let me know and I’ll add ffmpeg conversion.
• If STT/LLM providers (Deepgram/OpenAI) are not configured, the agent still can run and you can directly call the tool for deterministic tests (the script calls the tool directly for hello.wav if present).
• If you see other errors, paste the traceback and I’ll adjust the script quickly.
Would you like me to:
• add ffmpeg-based conversion for arbitrary WAVs, or
• change the script to loop through multiple test words automatically? Sources: Playing Audio | LiveKit docs