l
hi
t
Thanks for reaching out, G. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Hi — welcome to LiveKit! How can I help you today? If you’re not sure where to start, a few examples of things I can help with: • Troubleshooting connection or audio/video issues • Creating or joining rooms and managing participants • SDK usage or implementation questions (web, iOS, Android) • Billing, limits, or account questions Tell me what you're trying to do or share any errors/logs and I’ll guide you.
l
We're migrating from a multi-service pipeline (STT→LLM→TTS) to OpenAI Realtime API using livekit-plugins-openai. We're hitting a TypeError on invalid constructor parameters. --- Question 1: How to pass system prompt/instructions to RealtimeModel? Our current code (fails): # realtime_agent.py:526-538 return realtime.RealtimeModel( model="gpt-4o-realtime-preview", voice=voice, modalities=["audio", "text"], turn_detection=turn_detection, input_audio_transcription={"model": "whisper-1"}, input_audio_noise_reduction=noise_config, temperature=temperature, max_response_output_tokens=max_tokens, # ❌ ERROR: unexpected keyword argument instructions=instructions, # ❓ Is this valid? ) Error: TypeError: RealtimeModel.__init__() got an unexpected keyword argument 'max_response_output_tokens' Questions: 1. What's the correct way to pass a system prompt/instructions to RealtimeModel? 2. Should we use model.update_options() after creation, or is there another pattern? 3. Is instructions a valid constructor parameter? --- Question 2: How to set max_response_output_tokens? We want to limit response length to ~512 tokens. Option A - Constructor (doesn't work): model = realtime.RealtimeModel( max_response_output_tokens=512, # ❌ Not accepted ) Option B - update_options() after creation: model = realtime.RealtimeModel(...) model.update_options(max_response_output_tokens=512) # ✓ Is this correct? Question: Which approach is correct? Should we call update_options() immediately after creation? --- Question 3: Correct AgentSession + RealtimeModel pattern? Our current pattern: # realtime_entrypoint.py:280-306 # Step 1: Create model realtime_model = create_realtime_model( voice=voice_config.voice, instructions=system_prompt, # Where should this go? vad_type="semantic_vad", eagerness="low", ) # Step 2: Create custom Agent class agent = RealtimeAgent( system_prompt=system_prompt, voice=voice_config.voice, transcript_buffer=transcript_buffer, ) # Step 3: Create session session = AgentSession(llm=realtime_model) # Step 4: Start await session.start(room=ctx.room, agent=agent) Questions: 1. Is this the correct pattern for Realtime API? 2. Where should the system prompt live - in RealtimeModel, Agent, or AgentSession? 3. Should we use AgentSession(llm=model) or a different constructor? --- Question 4: RealtimeAgent class overrides - are these correct? Our Agent subclass: class RealtimeAgent(Agent): """Custom agent with Realtime API.""" def __init__(self, system_prompt: str, voice: str, ...): super().__init__( instructions=system_prompt, # ✓ Is this right? ) self._system_prompt = system_prompt self._voice = voice async def on_enter(self) -> None: """Called when agent joins room.""" logger.info("Agent entered room") async def on_exit(self) -> None: """Called when agent leaves room.""" logger.info("Agent exited room") async def on_user_turn_completed(self, turn_ctx, new_message) -> None: """Called after user finishes speaking.""" # Log transcript, track costs, etc. pass Questions: 1. Is Agent.__init__(instructions=system_prompt) the correct way to set instructions? 2. Are on_enter, on_exit, on_user_turn_completed the right overrides? 3. Any other lifecycle methods we should implement? --- Question 5: Voice test room dispatch pattern Our backend creates room and dispatches agent: # backend/app/api/v1/test.py # Create room room = await lk_service.create_room( room_name="voice-test-abc123", metadata={"system_prompt": "...", "voice_config": {...}}, ) # Dispatch agent dispatch = await lkapi.agent_dispatch.create_dispatch( api.CreateAgentDispatchRequest( agent_name="poskai-realtime-agent", # Must match @breezy-insurance-66772.rtc_session room=room_name, metadata=json.dumps(metadata), ) ) Agent registration: # agent/main.py @breezy-insurance-66772.rtc_session(agent_name="poskai-realtime-agent") async def entrypoint(ctx: JobContext) -> None: await realtime_entrypoint(ctx) Questions: 1. Is @breezy-insurance-66772.rtc_session the right decorator for Realtime API? 2. Should we use @breezy-insurance-66772.entrypoint instead? 3. How does metadata flow from dispatch to the agent's ctx? --- Question 6: Minimal working example request Could you provide a minimal working example of: 1. Creating a RealtimeModel with system prompt and token limits 2. Creating an AgentSession 3. Starting the session with proper configuration What we expect to work: # Pseudocode of what we're trying to achieve model = realtime.RealtimeModel( model="gpt-4o-realtime-preview", voice="ash", instructions="You are a helpful assistant...", # System prompt max_tokens=512, # Response limit ) session = AgentSession(llm=model) await session.start(room=ctx.room, agent=my_agent) --- Package Versions livekit-agents==1.0.x (latest) livekit-plugins-openai==1.3.x (latest)
t
Thanks — I’ll walk through each question and give a concise, correct pattern plus a minimal working example. Quick summary of root issues: •
instructions
and
max_tokens
are not accepted as constructor kwargs for
RealtimeModel
. • Use
model.update_options(...)
to set runtime options (instructions, max tokens, turn detection, STT/TTS, etc.). • Use
AgentSession(llm=model)
and the provided agent helpers (e.g., VoiceAssistantAgent) unless you need a custom agent. Question 1 — How to pass system prompt/instructions to RealtimeModel? • You cannot pass
instructions
in the
RealtimeModel(...)
constructor. • Create the model, then call
model.update_options(instructions="...")
. That is the supported way to set a system prompt. Question 2 — How to set max_response_output_tokens? •
max_response_output_tokens
is not a valid parameter name. • Use
max_tokens
and set it via
model.update_options(max_tokens=512)
. • Yes — calling
update_options()
immediately after creation is the correct pattern for these settings. Question 3 — Correct AgentSession + RealtimeModel pattern? • Pattern: a. Create
RealtimeModel(...)
(minimal constructor args). b. Call
model.update_options(...)
for
instructions
,
max_tokens
,
input_audio_transcription
,
turn_detection
, etc. c. Create
session = AgentSession(llm=model)
. d. Start with
await session.start(room=ctx.room, agent=agent)
. • Where should the system prompt live? In the
RealtimeModel
via
update_options(instructions=...)
. You can keep a copy in your Agent for bookkeeping, but the model uses the instructions for generation. •
AgentSession(llm=model)
is correct. Question 4 — RealtimeAgent class overrides — are these correct? • Don’t pass
instructions
through
super().__init__
for LiveKit’s Realtime flow. Set instructions on the
RealtimeModel
instead. • Lifecycle hooks differ by SDK; common useful overrides: ◦ a method called when the agent is ready / session starts (e.g.,
on_agent_ready
or
on_room_connected
depending on base class) ◦ handler for end of user utterance / turn (e.g.,
on_user_utterance_end
or
on_user_turn_completed
if present) ◦ cleanup on exit • Your
on_enter
,
on_exit
,
on_user_turn_completed
may be fine if those match the base
Agent
API you use — verify names against your installed
livekit-agents
package. If unsure, prefer the built-in VoiceAssistantAgent unless you need custom behavior. Question 5 — Voice test room dispatch pattern • Use your dispatch to create a room and include metadata; that metadata is available to the agent via
ctx.room.metadata
in the entrypoint. • The decorator
@FlashCall Server.rtc_session
looks like a custom/internal decorator — the standard pattern is an entrypoint function the worker runs (e.g., CLI worker entrypoint). If your environment expects
@rtc_session
, keep using what your infra requires. The important part: metadata passed in create_dispatch ends up in
ctx.room.metadata
for the agent to read. Question 6 — Minimal working example (concise, corrected)
Copy code
# minimal_realtime_agent.py
from livekit.agents import AgentSession
from livekit.plugins.openai.realtime import RealtimeModel
from livekit.agents.helpers import VoiceAssistantAgent  # or your Agent subclass

async def entrypoint(ctx):
    # 1) Create model with basic constructor args
    model = RealtimeModel(
        model="gpt-4o-realtime-preview",
        voice="ash",
        modalities=["audio", "text"],
        temperature=0.7,
    )

    # 2) Set runtime options (instructions, token limits, STT/TTS, turn detection)
    model.update_options(
        instructions="You are a helpful assistant that speaks concisely.",
        max_tokens=512,
        input_audio_transcription={"model": "whisper-1"},
        input_audio_noise_reduction={"type": "passive"},
    )

    # 3) Create session using the RealtimeModel as LLM
    session = AgentSession(llm=model)

    # 4) Use provided agent helper or your custom agent
    agent = VoiceAssistantAgent(session)  # or your RealtimeAgent(session, ...)

    # 5) Start session for the room (ctx.room is populated by dispatch)
    await session.start(room=ctx.room, agent=agent)
Additional tips • Keep a copy of system prompt in your agent if you want to log or update it dynamically, but set it on the model for behavior. • For custom lifecycle method names, check your installed
livekit-agents
version to confirm exact hook names — common names are similar to
on_agent_ready
,
on_room_connected
,
on_user_utterance_end
, but your package may expose different method names. • Metadata from dispatch is accessible via
ctx.room.metadata
. If you want, tell me: • The exact method names available on your
Agent
base class (I can look them up if you paste the class or version), and I’ll confirm which lifecycle hooks to override; and • Whether you want the minimal example adapted to your existing RealtimeAgent subclass (I can show the precise changes). Sources: OpenAI Realtime API plugin guide | LiveKit docs | Realtime model plugins | LiveKit Docs | examples/voice_agents | livekit/livekit_composite | DeepWiki | OpenAI and LiveKit partner to turn Advanced Voice into an API | Agents (Node.js) | livekit/livekit_composite | DeepWiki
l
Context We're using livekit-plugins-openai with OpenAI Realtime API for a voice agent. We had errors with RealtimeModel configuration and made fixes based on reading the source code. Please validate our approach. --- Question 1: RealtimeModel Constructor Parameters Our current code: from livekit.plugins.openai import realtime from openai.types.beta.realtime.session import TurnDetection from openai.types.realtime import AudioTranscription model = realtime.RealtimeModel( model="gpt-4o-realtime-preview", voice="ash", modalities=["audio", "text"], input_audio_transcription=AudioTranscription(model="gpt-4o-transcribe"), input_audio_noise_reduction=None, # or "near_field" / "far_field" turn_detection=TurnDetection( type="semantic_vad", eagerness="low", create_response=True, interrupt_response=True, ), ) Questions: 1. Are these the correct constructor parameters for RealtimeModel? 2. Is AudioTranscription(model="gpt-4o-transcribe") the correct type and model name? 3. Can we use TurnDetection from openai.types.beta.realtime.session or should we use a different type? 4. Is input_audio_noise_reduction correctly passed as a string ("near_field") or should it be a different type? --- Question 2: Instructions - Where Do They Go? Previous error: TypeError: RealtimeModel.update_options() got an unexpected keyword argument 'instructions' Our understanding: - instructions is NOT a RealtimeModel parameter - instructions should be passed to the Agent class instead Our current approach: from livekit.agents import Agent class RealtimeAgent(Agent): def __init__(self, system_prompt: str = "..."): super().__init__(instructions=system_prompt) # <-- Instructions here # Then: agent = RealtimeAgent(system_prompt="You are a helpful assistant...") session = AgentSession(llm=model) await session.start(agent=agent, room=ctx.room) Questions: 1. Is this the correct way to pass instructions when using Realtime API? 2. Does AgentSession automatically sync the Agent.instructions to the Realtime session? 3. Or should we call session.update_instructions(instructions) separately? --- Question 3: update_options() Parameters Our current code for max tokens: model.update_options( max_response_output_tokens=512, ) Questions: 1. Is max_response_output_tokens the correct parameter name? 2. What other parameters are valid for update_options()? We believe these are valid: - voice - turn_detection - tool_choice - input_audio_transcription - input_audio_noise_reduction - max_response_output_tokens - speed - tracing Is this list correct? --- Question 4: Temperature Parameter Our code previously had: model = realtime.RealtimeModel( temperature=0.8, ... ) Questions: 1. Is temperature supported in RealtimeModel v1? 2. We see in the source code it's marked as "deprecated, unused in v1" - is this correct? 3. If we need temperature control, how should we configure it? --- Question 5: Full Working Example Request Could you provide a minimal working example showing: 1. Creating a RealtimeModel with semantic VAD 2. Creating an Agent subclass with instructions 3. Creating AgentSession and starting it 4. The correct way to set max tokens and other runtime options --- Question 6: Version Compatibility Our installed versions: livekit-agents >= 1.0.0 livekit-plugins-openai >= 1.3.8 Questions: 1. Are these versions compatible with the GA Realtime API (not beta)? 2. Should we be using types from openai.types.realtime or openai.types.beta.realtime.session? 3. Any breaking changes we should be aware of? --- Our Complete Current Implementation # realtime_agent.py from livekit.agents import Agent, AgentSession from livekit.plugins.openai import realtime from openai.types.beta.realtime.session import TurnDetection from openai.types.realtime import AudioTranscription class RealtimeAgent(Agent): def __init__(self, system_prompt: str): super().__init__(instructions=system_prompt) async def on_enter(self) -> None: self.session.generate_reply() def create_realtime_model( voice: str = "ash", vad_type: str = "semantic_vad", eagerness: str = "low", max_tokens: int = 512, ) -> realtime.RealtimeModel: turn_detection = TurnDetection( type=vad_type, eagerness=eagerness, create_response=True, interrupt_response=True, ) model = realtime.RealtimeModel( model="gpt-4o-realtime-preview", voice=voice, modalities=["audio", "text"], input_audio_transcription=AudioTranscription(model="gpt-4o-transcribe"), turn_detection=turn_detection, ) if max_tokens != 512: model.update_options(max_response_output_tokens=max_tokens) return model # Usage in entrypoint: async def realtime_entrypoint(ctx: JobContext): await ctx.connect(auto_subscribe=True) participant = await ctx.wait_for_participant() model = create_realtime_model(voice="ash", vad_type="semantic_vad") agent = RealtimeAgent(system_prompt="You are a helpful assistant.") session = AgentSession(llm=model) await session.start(agent=agent, room=ctx.room) # Wait for disconnect... Is this implementation correct? Any issues or improvements needed?
t
Thanks — this looks mostly correct. I’ll confirm each point, call out the one mismatch, and give a minimal corrected example you can drop into your flow. Quick summary • Your RealtimeModel constructor usage (voice, modalities, AudioTranscription, TurnDetection, noise reduction) is correct. • Instructions belong on the Agent (via
instructions
) — AgentSession will load those into the session at start; you only need to call a session/agent update API if you change instructions mid-session. • Use
model.update_options()
for runtime options. Most option names you listed are valid; the one name that differs across examples is subtle (see below). •
temperature
is deprecated/unused in RealtimeModel v1. • Your overall implementation is valid with the versions you listed. Details (by question) Question 1 — RealtimeModel constructor • Yes:
model
,
voice
,
modalities
,
input_audio_transcription=AudioTranscription(...)
,
turn_detection=TurnDetection(...)
, and
input_audio_noise_reduction
set to None or strings like
"near_field"
are correct. •
AudioTranscription(model="gpt-4o-transcribe")
is the correct shape and model name. • Using
TurnDetection
from
openai.types.beta.realtime.session
is acceptable (examples use the
beta
import). •
input_audio_noise_reduction
can be
None
or a named value (string) per examples. Question 2 — Where instructions go • Your approach is correct: set
instructions
on your Agent (
super().__init__(instructions=system_prompt)
). • When you call
await session.start(agent=agent, room=ctx.room)
, the session loads the agent’s instructions into the realtime LLM context automatically. • Only call a special
session.update_instructions(...)
if you need to change the instructions dynamically after session start. Question 3 — update_options() parameters • The parameter name you used in one place differs across examples: ◦
max_response_output_tokens
is shown in some LiveKit examples and test code and is valid for limiting response length. ◦ You may also see
max_tokens
in other contexts; for the Realtime plugin the documented example uses
max_response_output_tokens
. • Valid update_options fields include (common ones):
voice
,
turn_detection
,
tool_choice
,
input_audio_transcription
,
input_audio_noise_reduction
,
max_response_output_tokens
,
speed
,
tracing
. Your list is correct; use
max_response_output_tokens
for RealtimeModel per examples. Question 4 — temperature •
temperature
is deprecated/unused in RealtimeModel v1. You cannot rely on it to control response randomness in the current Realtime flow. Question 5 — Minimal working example
Copy code
from livekit.agents import Agent, AgentSession, JobContext
from livekit.plugins.openai import realtime
from openai.types.beta.realtime.session import TurnDetection
from openai.types.realtime import AudioTranscription

class RealtimeAgent(Agent):
    def __init__(self, system_prompt: str):
        super().__init__(instructions=system_prompt)

    async def on_enter(self) -> None:
        # example hook — verify exact hook name in your installed package
        self.session.generate_reply()

def create_realtime_model(max_tokens: int = 512) -> realtime.RealtimeModel:
    turn_detection = TurnDetection(
        type="semantic_vad",
        eagerness="low",
        create_response=True,
        interrupt_response=True,
    )

    model = realtime.RealtimeModel(
        model="gpt-4o-realtime-preview",
        voice="ash",
        modalities=["audio", "text"],
        input_audio_transcription=AudioTranscription(model="gpt-4o-transcribe"),
        input_audio_noise_reduction=None,
        turn_detection=turn_detection,
    )

    # runtime options (instructions set on Agent; runtime limits here)
    model.update_options(max_response_output_tokens=max_tokens)
    return model

async def realtime_entrypoint(ctx: JobContext):
    await ctx.connect(auto_subscribe=True)
    model = create_realtime_model(max_tokens=512)
    agent = RealtimeAgent(system_prompt="You are a helpful assistant.")
    session = AgentSession(llm=model)
    await session.start(agent=agent, room=ctx.room)
Question 6 — versions & types • Your versions (livekit-agents >=1.0.0 and livekit-plugins-openai >=1.3.8) are compatible with the Realtime API patterns used in examples. • Use
openai.types.beta.realtime.session
for
TurnDetection
as in the examples.
openai.types.realtime
can contain other types; follow the example imports for the pieces you need. • Breaking-note:
temperature
is effectively not used in v1 Realtime; rely on the provided runtime options instead. Minor suggestions / gotchas • Verify lifecycle hook names (
on_enter
,
on_exit
, etc.) against the exact installed
Agent
base class — method names can vary; if in doubt use the built-in VoiceAssistantAgent or inspect your package. • Keep a copy of system prompt on both Agent (for bookkeeping/logging) and rely on the Agent→Session startup sync for injection into the LLM. • Use
model.update_options(...)
for any runtime reconfiguration (including dynamic changes), and only call session/agent update calls if you need to change behavior mid-session. If you’d like, I can: • Check your exact installed package’s Agent base class methods (paste the class or version) and confirm the exact hook names to implement. • Provide a version-adapted snippet replacing
on_enter
with the correct hook name for your install. Sources: OpenAI Realtime API plugin guide | LiveKit docs | Agents and handoffs | LiveKit docs | Agents (Python) | livekit/livekit_composite | DeepWiki | Using the Testing Framework with Realtime Models | examples/voice_agents | livekit/livekit_composite | DeepWiki
l
Questions for LiveKit Developer Question 1: Instructions Not Being Passed? Our observation: - We pass instructions to Agent: super().__init__(instructions="You are a helpful assistant...") - AgentSession starts: await session.start(agent=agent, room=ctx.room) - OpenAI Realtime responds in Spanish (not our instructions) - Our instructions are in English/Lithuanian Code: class RealtimeAgent(Agent): def __init__(self, system_prompt: str): super().__init__(instructions=system_prompt) # We set instructions here # Later: agent = RealtimeAgent(system_prompt="You are a helpful AI assistant. You speak English...") session = AgentSession(llm=realtime_model) await session.start(agent=agent, room=ctx.room) Questions: 1. Does AgentSession.start() automatically sync Agent.instructions to the Realtime session? 2. If yes, when exactly does this happen? We see the agent responding before instructions might be synced. 3. Should we explicitly call something like session.update_instructions(agent.instructions) after start? 4. Is there a way to log/verify that instructions were sent to OpenAI Realtime? --- Question 2: Agent Only Responds Once Then Stops Our observation: - Agent speaks once (Spanish greeting) - User speaks (we talked) - Agent does NOT respond again - Logs show only one agent_turn span Questions: 1. After the initial greeting (triggered by session.generate_reply() in on_enter), what triggers subsequent responses? 2. Does semantic_vad with create_response=True automatically trigger responses after user speech? 3. Should we see input_speech_started / input_speech_stopped events in logs when user speaks? 4. Is there something we need to do to "listen" for user speech after the initial greeting? --- Question 3: Correct Way to Trigger Initial Greeting + Ongoing Conversation Our current approach: class RealtimeAgent(Agent): async def on_enter(self) -> None: logger.info("RealtimeAgent activated") self.session.generate_reply() # Trigger greeting Questions: 1. Is self.session.generate_reply() the correct way to trigger the initial greeting? 2. After generate_reply(), does the session automatically continue the conversation when user speaks? 3. Or do we need to implement on_user_turn_completed to trigger responses? Should we be doing something like this instead? async def on_user_turn_completed(self, turn_ctx, new_message) -> None: # After user speaks, trigger agent response? self.session.generate_reply() --- Question 4: Debugging - How to See What Instructions Were Sent? Questions: 1. Is there a way to log the actual session configuration that was sent to OpenAI? 2. Can we enable debug logging for livekit-plugins-openai to see the WebSocket messages? 3. Is there an event we can listen to that confirms instructions were loaded? --- Question 5: Minimal Working Example with Instructions Could you provide a minimal working example showing: 1. Creating RealtimeModel 2. Creating Agent with instructions 3. Starting session so that instructions ARE applied 4. Agent greets user 5. Agent responds to user speech What we expect: User joins → Agent greets (using our instructions) → User speaks → Agent responds → repeat What we're seeing: User joins → Agent greets (in SPANISH, ignoring instructions) → User speaks → silence
t
Thanks — I’ll address each question directly, confirm expected behavior, and give a minimal troubleshooting checklist + example to verify instructions and ongoing conversation. Quick summary • Agent.instructions are synced into the Realtime session during session start. If the model ignores them (Spanish), the instructions either didn’t reach the Realtime session as expected or the model applied a different default prompt. • Use semantic_vad with create_response=True to have the Realtime API auto-generate replies after user speech ends; if that’s set correctly, you don’t need to call generate_reply() after every user turn. • Enable debug logging to inspect WebSocket frames/events and add local logs for instructions and session events to confirm what was sent/received. Question 1 — Instructions not being passed? 1. Yes — AgentSession.start() synchronizes Agent.instructions into the Realtime session at session creation. 2. This happens during session initialization (when start() opens the Realtime session). If the agent replies before instructions are applied, that indicates either (a) the reply used a model-default/system prompt or (b) instructions were not attached to the session payload. 3. You should not routinely need session.update_instructions() after start(). Use it only to change the system prompt dynamically. 4. How to verify: ◦ Log Agent.instructions locally just before start: logger.info("instructions: %s", agent.instructions). ◦ Enable plugin debug to view WS frames: set environment debug flag (e.g., LK_OPENAI_REALTIME_DEBUG=true or enable DEBUG logging for the plugin/SDK). ◦ Listen to generation events and inspect the first generation content to confirm instructions were applied. Question 2 — Agent speaks once then stops 1. Subsequent responses are triggered by turn detection (VAD) detecting end-of-user-speech. With RealtimeModel + create_response=True, the Realtime API auto-starts generation after user turn ends. 2. Yes — semantic_vad with create_response=True should automatically trigger replies after user speech ends. 3. Yes — you should see events like input_speech_started and input_speech_stopped (and generation_created) if audio is flowing and the SDK forwards those events. 4. If you don’t see those events: ◦ Verify audio is being forwarded to the Realtime session (client SDK publishing). ◦ Confirm turn_detection is configured and attached to the RealtimeModel used by the session. ◦ Add event handlers to log input_speech_started/stopped and generation_created to confirm the pipeline. Question 3 — Initial greeting + ongoing conversation 1. session.generate_reply() in on_enter is a valid way to trigger the initial greeting. 2. After generate_reply(), ongoing replies should occur automatically if turn_detection is enabled with create_response=True. You do not normally need to call generate_reply() after every user turn. 3. on_user_turn_completed can be used as a fallback to call generate_reply() if auto-response is not working or if you want to modify context before replying. Suggested pattern: • on_enter: await session.generate_reply() to greet once. • Ensure RealtimeModel has turn_detection with create_response=True so user turns automatically produce replies. • Optionally implement on_user_turn_completed only if you need manual control or to adjust context before generation. Question 4 — Debugging: confirm instructions were sent • Local logging: log the exact
agent.instructions
and the RealtimeModel options immediately before session.start(). • Enable plugin/SDK debug logging (environment variable or Python logging DEBUG) to capture WebSocket frames. Example: set LK_OPENAI_REALTIME_DEBUG=true or set logging.basicConfig(level=logging.DEBUG) for the process. • Add event listeners on session.llm for: input_speech_started, input_speech_stopped, generation_created, generation_updated (these show input and generation lifecycle). • Inspect the first generation content — if instructions had applied the greeting style/content, you’ll see expected output. If not, the WS frames will show the session payload that went to OpenAI. Minimal checklist to run now 1. Add logs: ◦ logger.info("Agent.instructions=%s", agent.instructions) ◦ logger.info("RealtimeModel options=%s", model._model_opts or model.inspect_options()) 1. Wire event listeners: ◦ session.llm.on("input_speech_started", lambda e: logger.info("speech started")) ◦ session.llm.on("input_speech_stopped", lambda e: logger.info("speech stopped")) ◦ session.llm.on("generation_created", lambda e: logger.info("generation started")) 1. Enable DEBUG logging for the process / plugin. 2. Confirm client audio is actually published to the room (client side test). 3. If agent still replies in Spanish, paste the debug logs (first session creation frame + first generation) and I’ll help interpret. Minimal working example (concise)
Copy code
import logging
from livekit.agents import Agent, AgentSession, JobContext, cli
from livekit.plugins.openai import realtime
from openai.types.beta.realtime.session import TurnDetection
from openai.types.realtime import AudioTranscription

logging.basicConfig(level=<http://logging.INFO|logging.INFO>)
logger = logging.getLogger("realtime-agent")

class RealtimeAgent(Agent):
    def __init__(self, system_prompt: str):
        super().__init__(instructions=system_prompt)

    async def on_enter(self):
        <http://logger.info|logger.info>("on_enter: generating greeting")
        await self.session.generate_reply()

async def entrypoint(ctx: JobContext):
    # create model with semantic VAD
    td = TurnDetection(type="semantic_vad", eagerness="low", create_response=True, interrupt_response=True)
    model = realtime.RealtimeModel(
        model="gpt-4o-realtime-preview",
        voice="ash",
        modalities=["audio", "text"],
        input_audio_transcription=AudioTranscription(model="gpt-4o-transcribe"),
        turn_detection=td,
    )
    model.update_options(max_response_output_tokens=512)

    session = AgentSession(llm=model)

    # debug listeners
    session.llm.on("input_speech_started", lambda e: <http://logger.info|logger.info>("event: input_speech_started"))
    session.llm.on("input_speech_stopped", lambda e: <http://logger.info|logger.info>("event: input_speech_stopped"))
    session.llm.on("generation_created", lambda e: <http://logger.info|logger.info>("event: generation_created"))

    agent = RealtimeAgent(system_prompt="You are a helpful assistant. Always respond in English.")
    <http://logger.info|logger.info>("Agent.instructions=%s", agent.instructions)
    await session.start(agent=agent, room=ctx.room)
If this still yields a Spanish greeting: • Enable debug logging and share the session creation frame and first generation logs (I can help read them). • Confirm the client publishes audio (no audio → no input events). • If you want, I can point to exact log lines to look for in the WS trace that show initialMessages/instructions. Would you like help reading the debug WebSocket frames if you paste them here? Sources: examples/voice_agents | livekit/livekit_composite | DeepWiki | Ultravox plugin guide | LiveKit docs | Pipeline nodes and hooks | LiveKit docs | OpenAI Realtime API plugin guide | LiveKit docs | Building an Automated IVR Menu Caller | LiveKit docs
l
Questions for LiveKit Developer Here are comprehensive questions with code snippets: --- Question 1: Why is input_audio_tokens: 0? Our logs show the OpenAI Realtime API receives zero audio tokens from the user, yet the agent speaks successfully: "gen_ai.usage.input_audio_tokens": 0 ← PROBLEM "gen_ai.usage.output_audio_tokens": 54 Current setup: # realtime_entrypoint.py session = AgentSession(llm=realtime_model) await session.start(room=ctx.room, agent=agent) # realtime_agent.py model = realtime.RealtimeModel( model="gpt-4o-realtime-preview", voice="ash", modalities=["audio", "text"], input_audio_transcription=AudioTranscription(model="gpt-4o-transcribe"), turn_detection=TurnDetection( type="semantic_vad", eagerness="low", create_response=True, interrupt_response=True, ), ) Questions: 1. Is there a missing step to connect participant audio to the Realtime model? 2. Do we need RoomInputOptions to specify audio input? 3. Is auto_subscribe=True in ctx.connect() sufficient for Realtime? --- Question 2: Debug events not firing We added these event handlers per your recommendation, but they never fire: @session.on("input_speech_started") def _on_input_speech_started(event): logger.info("EVENT: input_speech_started") # Never logged @session.on("input_speech_stopped") def _on_input_speech_stopped(event): logger.info("EVENT: input_speech_stopped") # Never logged @session.on("generation_created") def _on_generation_created(event): logger.info("EVENT: generation_created") # Never logged Questions: 1. Are these the correct event names for AgentSession with Realtime? 2. Should we be listening on realtime_model instead of session? 3. Is there an alternative way to verify audio is flowing? --- Question 3: AgentSession with RealtimeModel - correct initialization? Is this the correct way to use AgentSession with RealtimeModel? # Create model model = realtime.RealtimeModel( model="gpt-4o-realtime-preview", voice="ash", modalities=["audio", "text"], input_audio_transcription=AudioTranscription(model="gpt-4o-transcribe"), turn_detection=turn_detection, ) # Create agent with instructions agent = RealtimeAgent(system_prompt="You are a helpful assistant...") # RealtimeAgent extends Agent: super().__init__(instructions=system_prompt) # Create session - is this correct for Realtime? session = AgentSession(llm=model) # Start session await session.start(room=ctx.room, agent=agent) Questions: 1. For Realtime, should we use AgentSession or a different session class? 2. Does AgentSession(llm=realtime_model) automatically handle audio input? 3. Is there a RealtimeSession class we should use instead? --- Question 4: Participant audio subscription User confirms their microphone works. We connect with: await ctx.connect(auto_subscribe=True) participant = await ctx.wait_for_participant() logger.info(f"Participant joined: {participant.identity}") Questions: 1. How can we verify the participant's audio track is being received by the agent? 2. Is there a way to check if audio is flowing from WebRTC to the Realtime model? 3. Should we explicitly subscribe to the participant's audio track? --- Question 5: Full working example Could you provide a minimal working example of a Realtime voice agent that: 1. Receives user audio (verified with logs) 2. Triggers VAD events (input_speech_started/stopped) 3. Generates multiple responses in a conversation We want to compare against our implementation to find what's missing. --- Summary for LiveKit Developer | Component | Status | Issue | |-----------------------|-------------------|---------------------------| | Agent speaks | ✅ Works | - | | Instructions sync | ✅ Works | English greeting | | User audio → Realtime | ❌ Broken | input_audio_tokens: 0 | | VAD events | ❌ No events | input_speech_* never fire | | Turn detection | ❌ Not triggering | No 2nd response | The core issue: Audio is somehow not flowing from the LiveKit room participant to the OpenAI Realtime API, despite auto_subscribe=True and proper model configuration.
t
Thanks — I reviewed your symptoms and code. Short answer: audio isn’t being forwarded from the LiveKit room into the OpenAI Realtime session, so the model never receives user audio (hence input_audio_tokens: 0), VAD/generation events never fire, and only the initial reply happens. Below are focused checks, fixes, and a minimal example to get audio flowing and VAD events firing. Immediate checks (do these first) 1. Ensure you wait for a participant (and their audio track) before starting the session. ◦ Do: await ctx.connect(auto_subscribe=True); participant = await ctx.wait_for_participant(); then start session. 1. Confirm agent token permissions include audio read (roomaudioread / room:read). 2. Confirm the client actually publishes an audio track (mic works and track is published). Why input_audio_tokens is 0 • That metric is 0 when no audio frames reach the Realtime WebSocket. The most likely causes: ◦ Session started before any participant audio was available. ◦ The agent never subscribed / RoomIO didn’t consume the track (e.g., permissions or timing). ◦ The pipeline did not route the subscribed track into the RealtimeModel input. Event handlers not firing • The Realtime events (input_speech_started, input_speech_stopped, generation_created) are emitted by the Realtime session component; if audio never reaches the RealtimeModel, those events won’t appear. • Attach listeners to the model/session object that exposes RT events (or use AgentSession’s internal model handle). Also add room track subscription listeners to verify audio exists. Concrete fixes and checks 1. Wait for participant and track before session.start(): ◦ await ctx.connect(auto_subscribe=True) ◦ participant = await ctx.wait_for_participant() ◦ (optional) wait for track publication or subscription events ◦ then await session.start(room=ctx.room, agent=agent) 1. Add debug logging and track event listeners before starting: ◦ ctx.room.on("track_published", ...) and ctx.room.on("track_subscribed", ...) ◦ Log agent.instructions and model options immediately before start. 1. Enable SDK/plugin debug logging to see WebSocket frames: ◦ Set environment logging to DEBUG (or LK_OPENAI_REALTIME_DEBUG if available) for the process and inspect the session creation frame and first generation. 1. Verify token permissions: ◦ Ensure the token used by the agent process has read/subscription rights for audio. Where to attach event listeners • Listen for room track events on ctx.room (track_published, track_subscribed) to confirm audio exists. • Listen for Realtime model events (input_speech_started/stopped, generation_created) on the RealtimeModel / underlying RealtimeSession if accessible. If you can’t access that object, rely on debug WS logs and the session’s logged metrics. Minimal working pattern (essentials only) • Wait for participant and audio track, then start session. Add these checks and listeners:
Copy code
# pseudo-code (concise)
await ctx.connect(auto_subscribe=True)
participant = await ctx.wait_for_participant()
# optional: wait for track publication/subscription events here

# create model
model = realtime.RealtimeModel(
  model="gpt-4o-realtime-preview",
  voice="ash",
  modalities=["audio","text"],
  input_audio_transcription=AudioTranscription(model="gpt-4o-transcribe"),
  turn_detection=TurnDetection(type="semantic_vad", create_response=True, interrupt_response=True),
)
model.update_options(max_response_output_tokens=512)

session = AgentSession(llm=model)

# room track listeners (verify audio)
@ctx.room.on("track_published")
def on_track_published(tp, participant):
    <http://logger.info|logger.info>(f"track_published: {tp.kind} {tp.track_name}")

@ctx.room.on("track_subscribed")
def on_track_subscribed(track, publication, participant):
    <http://logger.info|logger.info>("track_subscribed: kind=%s name=%s", publication.kind, publication.track_name)

# Realtime event listeners (if exposed)
session.llm.on("input_speech_started", lambda e: <http://logger.info|logger.info>("input_speech_started"))
session.llm.on("input_speech_stopped", lambda e: <http://logger.info|logger.info>("input_speech_stopped"))
session.llm.on("generation_created", lambda e: <http://logger.info|logger.info>("generation_created"))

agent = RealtimeAgent(system_prompt="You are a helpful assistant. Respond in English.")
<http://logger.info|logger.info>("Agent.instructions=%s", agent.instructions)

await session.start(room=ctx.room, agent=agent)
Debugging tips (what to look for) • Track events: you should see track_published and track_subscribed entries when the user joins/publishes. • WS/session creation frame: contains initialMessages/system prompt — confirm it includes your instructions. • input_speech_started/stopped events after user speaks; generation_created when model starts replying. • If track_subscribed never appears, audio never reached the agent process. Common fixes that solve input_audio_tokens=0 • Start session only after participant and track exist. • Ensure the agent process token has audio read permissions. • Ensure client publishes audio (e.g., set mic track enabled). • Use the track_subscribed listener to assert the agent actually receives the audio track. If you want I can: • Review logs (track_published/track_subscribed, session creation WS frame, and any generation logs) and point to the exact failure. • Provide a node or fuller Python example matched to your environment. Which would you like next — paste the track_subscribed logs and the session creation WS frame, or I can give a slightly expanded runnable example you can drop in and test? Sources: Voice AI quickstart | LiveKit docs | OpenAI Realtime API plugin guide | LiveKit docs | Agents (Node.js) | livekit/livekit_composite | DeepWiki | OpenAI and LiveKit | LiveKit docs | examples/voice_agents | livekit/livekit_composite | DeepWiki
l
1. RealtimeModel Constructor Parameters What parameters does livekit.agents.llm.openai.realtime.RealtimeModel accept in its init? We're currently trying to pass: - model (e.g., "gpt-4o-realtime-preview") - voice (e.g., "ash", "alloy", "shimmer") - instructions (system prompt string) - temperature - max_tokens / max_response_output_tokens - VAD settings (vad_threshold, vad_silence_ms, eagerness) - noise_reduction Which of these are valid? What are the exact parameter names? --- 2. How to Set System Prompt/Instructions How do we pass a system prompt to the RealtimeModel? Is it: - A constructor parameter? - Set via AgentSession? - Set via the Agent class? - Something else? --- 3. Minimal Working Example Can you provide a minimal working example of: # Creating RealtimeModel # Creating AgentSession with it # Starting the session --- 4. VAD Configuration How do we configure Voice Activity Detection with RealtimeModel? Is it built-in or do we need to pass a separate VAD? --- 5. Version What version of livekit-agents and livekit-plugins-openai should we use for RealtimeModel?
t
I don't have the answer you're looking for. You could also try asking your question: • in one of the other Slack channels or • to https://deepwiki.com/livekit/livekit_composite which is trained on all LiveKit source code If you find the answer, please post it here to help others!