boundless-author-7811
04/13/2026, 5:24 AMtall-airline-16756
04/13/2026, 5:24 AMtall-airline-16756
04/13/2026, 5:24 AMboundless-author-7811
04/13/2026, 5:37 AMOpenAI Realtime's input_audio_noise_reduction (server-side)
You're already set up for this. The RealtimeModel accepts it:
llm = openai.realtime.RealtimeModel.with_azure(
...
input_audio_noise_reduction="near_field", # or "far_field"
)
- near_field — for headset/close-mic (less aggressive)
- far_field — for speakerphone/noisy environments (more aggressive)
This runs on OpenAI's side, so no CPU cost to your pods. For phone calls, "far_field" is the
right choice since you can't control the caller's environment
Silero VAD threshold tuning
Not noise cancellation per se, but increasing min_speech_duration and VAD sensitivity
thresholds reduces false triggers from background noise.
Bottom Line
For your self-hosted setup, input_audio_noise_reduction="far_field" on the RealtimeModel is
the best option available right now — it's free, runs on OpenAI's servers, and handles the
noisy telephony audio before the LLM processes it.
The audio flow is:
Phone (SIP) LiveKit server Your worker receives raw audio→ →
↓
Silero VAD (local, on raw audio)
↓
OpenAI Realtime API (with noise_reduction)
The problem: input_audio_noise_reduction runs on OpenAI's side, after audio is sent to the
API. Silero VAD runs locally on the raw, noisy audio before it reaches OpenAI. So:
- OpenAI gets clean audio LLM understands speech better→
- Silero still gets dirty audio still triggers on background noise/voices→
This means the Silero VAD false triggers (empty buffer commits, overlapping responses) you
saw in the logs won't be fixed by OpenAI's noise reduction. Silero will still fire on
background voices because it never sees the cleaned audio.
What would actually help Silero:
1. Increase Silero's thresholds — not perfect but practical:
- Higher min_speech_duration (skip short noise bursts)
- Higher min_silence_duration (wait longer before declaring end-of-speech)
- These are params on silero.VAD.load()
2. Go back to OpenAI server-side turn detection — OpenAI's semantic VAD runs on the noise-
reduced audio, so it handles noisy calls much better. You lose allow_interruptions=False on
the greeting, but the overall call quality improves significantly in noisy environments.
For a collection bot on phone calls where callers are often in noisy environments (street, office,
TV in background), Option 2 is probably the pragmatic choice. OpenAI's semantic VAD
understands speech content, not just audio energy — it won't trigger on a TV in the
background the way Silero does.
The trade-off is clear: Silero gives you allow_interruptions=False but struggles with noise.
OpenAI server-side VAD handles noise well but can't disable interruptions.tall-airline-16756
04/13/2026, 5:38 AMinput_audio_noise_reduction="near_field"|"far_field" runs on OpenAI's servers (no CPU cost to your pods) and should improve LLM performance on noisy telephony audio; for phone calls, far_field is appropriate.
• Silero VAD runs locally on the raw audio before it's sent to OpenAI, so OpenAI's server-side noise reduction does not change what Silero sees.
• Because Silero processes the uncleaned audio, it can still false-trigger on background sounds; OpenAI's noise reduction won't prevent those local false triggers.
• LiveKit's turn-detection priority prefers realtime LLM VAD (OpenAI semantic/server VAD) over a local VAD like Silero. If OpenAI turn detection is enabled it takes precedence.
• Practical options:
◦ Tune Silero (increase min_speech_duration, raise sensitivity thresholds, increase min_silence_duration) to reduce false triggers.
◦ Or rely on OpenAI server-side turn detection (semantic VAD) to handle noisy calls better; this sacrifices local VAD behaviors like guaranteed allow_interruptions=False for greetings.
If you want, tell me which you prefer (keep Silero with tuned thresholds for interruption control, or switch to OpenAI server-side VAD for better noise robustness) and I’ll provide the specific AgentSession configuration and parameter suggestions.
, with interactive elementsboundless-author-7811
04/13/2026, 5:57 AMtall-airline-16756
04/13/2026, 5:58 AMallow_interruption. Here are two practical approaches and a recommended hybrid solution.
Acknowledgement
• You want an interruption-free greeting, but OpenAI's VAD can't enforce allow_interruption=False. Silero can enforce no interruptions but is sensitive to noise.
Options
• Pure OpenAI VAD
◦ Pros: best noise robustness (use input_audio_noise_reduction="far_field").
◦ Cons: cannot enforce allow_interruption=False for the greeting.
• Pure Silero VAD (local)
◦ Pros: can enforce allow_interruption=False locally.
◦ Cons: false triggers from noisy telephony audio unless tuned heavily.
• Hybrid (recommended)
a. Use OpenAI Realtime as the primary turn detector for the session (keeps noise reduction benefits).
b. Also run Silero locally but only for short-term interruption blocking during the introduction:
• Before playing the greeting, enable a local “hard block” period: ignore any Silero triggers for X seconds (the greeting duration) OR treat the first Silero trigger during the greeting as suppressed.
• Alternatively, use Silero to detect interruptions only if its confidence and min_speech_duration exceed stricter thresholds (so casual background noise is ignored).
a. After the greeting completes, revert to normal behavior:
• If you want interruptions allowed afterwards, re-enable normal interruption handling (either allow OpenAI semantic turn detection to take over or use Silero to permit interruptions).
Practical parameter suggestions for the hybrid approach
• Set OpenAI Realtime with input_audio_noise_reduction="far_field".
• For Silero during greeting:
◦ Increase min_speech_duration (skip short bursts).
◦ Increase activation_threshold (make detection less sensitive).
◦ Use a short explicit suppression window equal to the greeting length (simple and reliable).
What I can provide next
• Exact AgentSession code/config snippet showing: OpenAI Realtime model + Silero VAD + a suppression window or stricter Silero thresholds for the greeting. Which would you prefer: a code snippet for suppression-by-time or a snippet for stricter VAD thresholds?, with interactive elementsboundless-author-7811
04/13/2026, 6:01 AMproc.userdata["vad"] = silero.VAD.load()
session = AgentSession[CallState](
llm=openai.realtime.RealtimeModel.with_azure(
voice="alloy",
modalities=["audio", "text"],
turn_detection=None,
azure_endpoint=deployment.base_url or "",
api_key=deployment.api_key,
azure_deployment=deployment.azure_deployment_name,
api_version=deployment.api_version or "2024-10-01-preview"
),
vad=ctx.proc.userdata["vad"],
userdata=state,
ivr_detection=True,
user_away_timeout=15.0,
aec_warmup_duration=3.0,
turn_handling={
"interruption": {
"enabled": True,
"min_duration": 0.8,
"min_words": 2,
},
},
max_tool_steps=5,
)tall-airline-16756
04/13/2026, 6:01 AMturn_detection=None or use server VAD if you prefer).
• Keep Silero loaded locally but suppress/ignore Silero-triggered interruptions only during the greeting playback window (hard-block period).
• After the greeting completes, re-enable Silero-based interruption detection (or let OpenAI semantic VAD handle turns normally).
Why: OpenAI’s noise reduction improves LLM input while Silero enforces interruption control. Suppressing Silero only for the greeting gives you a guaranteed no-interruption intro without reverting to purely local VAD for the whole call.
Two options — pick one and I’ll give code:
• A: Time-based suppression (disable processing of Silero triggers for N seconds equal to greeting length).
• B: Confidence/threshold gating (temporarily raise min_speech_duration / activation_threshold for Silero during the greeting).
Which do you prefer — A (time window) or B (stricter thresholds)? I’ll provide the AgentSession code snippet and where to apply it.
, with interactive elementsboundless-author-7811
04/13/2026, 6:04 AMasync def on_enter(self) -> None:
"""
Called after the SIP participant has connected AND the agent
session has started. Sets disposition to ANSWERED and triggers
the greeting.
"""
self._call_state.disposition = CallDisposition.ANSWERED
self.session.generate_reply(
instructions="Deliver your opening greeting exactly as specified in your system prompt.",
allow_interruptions=False
)tall-airline-16756
04/13/2026, 6:05 AMallow_interruptions=False and your session uses OpenAI Realtime with Silero VAD. To guarantee no interruptions during that greeting while keeping OpenAI noise reduction, implement a short “local suppression” around the greeting: temporarily ignore Silero VAD triggers (or raise its thresholds) for the greeting duration, then restore normal VAD handling.
Below are two minimal, safe options with code you can drop into your flow. Pick one.
1) Time-based suppression (simple, reliable)
• Before calling generate_reply, set a flag suppress_vad_until = now + greeting_duration.
• In your Silero callback that enqueues/commits VAD events, ignore triggers while now < suppress_vad_until.
• After the greeting (or when time passes), clear the flag.
Example (pseudo/async pattern to fit your code):
# set suppression window (seconds)
greeting_duration = 6.0 # set to expected greeting length
ctx.proc.userdata["suppress_vad_until"] = time.time() + greeting_duration
# play greeting (no interruptions)
self.session.generate_reply(
instructions="Deliver your opening greeting exactly as specified in your system prompt.",
allow_interruptions=False
)
In your Silero VAD handler:
def on_vad_detected(...):
if time.time() < ctx.proc.userdata.get("suppress_vad_until", 0):
return # ignore during greeting
# normal interruption logic follows
2) Threshold-gating during greeting (less clock-dependent)
• Temporarily raise Silero sensitivity parameters (e.g., min_speech_duration, activation_threshold) before the greeting, then restore them after the greeting.
• This suppresses short/noisy triggers but still allows long, deliberate interruptions if desired.
Example pattern:
# store originals
vad = ctx.proc.userdata["vad"]
orig_params = vad.get_params()
# tighten VAD for greeting
vad.set_params(min_speech_duration=1.5, activation_threshold=0.8)
self.session.generate_reply(..., allow_interruptions=False)
# restore original params
vad.set_params(**orig_params)
Recommendation
• Use option 1 (time-based suppression) for absolute, easy-to-reason no-interruption guarantees during the greeting.
• Use option 2 if you prefer adaptive behavior and want to still catch only strong interruptions.
If you want, I can produce a full integrated snippet that wires the suppression flag into your existing Silero callback and AgentSession flow — which option should I generate?
, with interactive elementsboundless-author-7811
04/13/2026, 6:07 AMtall-airline-16756
04/13/2026, 6:08 AMsuppress_vad_until = now + window immediately before generate_reply.
• Ensure your Silero callback ignores VAD triggers while now < suppress_vad_until.
• Add server-side logs/metrics for suppression start/end and ignored triggers.
• Add a hard fail-safe: clamp max suppression (e.g., 30s) to avoid accidental long blocks.
• Test with noisy call recordings and edge cases (early disconnects, retries).
If you want, I can produce a short integrated code snippet wiring suppression into your existing flow and Silero callback. Which language/runtime would you like it in (Python/async or your existing framework)?
, with interactive elementsboundless-author-7811
04/13/2026, 6:10 AMtall-airline-16756
04/13/2026, 6:10 AMAgentSession and Silero VAD instance, logs suppression events, clamps max suppression, and restores normal behavior after the greeting.
Notes:
• Adjust GREETING_DURATION_SEC + SAFETY_MARGIN_SEC to your greeting length.
• Ensure your Silero callback checks suppress_vad_until before committing VAD events.
Code:
import time
import logging
from typing import Optional
LOGGER = logging.getLogger("call_vad")
MAX_SUPPRESSION_SEC = 30.0 # safety clamp
# call-time globals stored on proc.userdata in your setup
# ctx.proc.userdata["vad"] = silero.VAD.load()
# ctx.proc.userdata["suppress_vad_until"] = 0.0
def start_vad_suppression(ctx, greeting_duration_sec: float, safety_margin_sec: float = 0.5) -> None:
"""Set suppression timestamp before playing greeting."""
window = min(greeting_duration_sec + safety_margin_sec, MAX_SUPPRESSION_SEC)
suppress_until = time.time() + window
ctx.proc.userdata["suppress_vad_until"] = suppress_until
LOGGER.info("VAD suppression started, until=%s (window=%.2fs)", suppress_until, window)
def clear_vad_suppression(ctx) -> None:
ctx.proc.userdata["suppress_vad_until"] = 0.0
LOGGER.info("VAD suppression cleared")
def is_vad_suppressed(ctx) -> bool:
return time.time() < ctx.proc.userdata.get("suppress_vad_until", 0.0)
# Example Silero VAD callback that the VAD plugin calls when speech is detected.
# Adapt parameters/signature to your actual callback.
def on_silero_vad_event(ctx, vad_event) -> None:
"""
Called when Silero detects speech. If suppression is active, ignore event.
Otherwise, proceed with normal interruption handling (commit buffer / notify agent).
"""
if is_vad_suppressed(ctx):
LOGGER.debug("Silero VAD suppressed — ignoring event at %s", time.time())
# Optionally increment metric for ignored triggers
return
# Normal handling: commit buffer / trigger interruption flow
handle_vad_commit(ctx, vad_event)
def handle_vad_commit(ctx, vad_event) -> None:
"""
Your existing logic when Silero VAD decides to commit speech.
Keep this consistent with your session's interruption handling.
"""
LOGGER.info("Silero VAD commit at %s: %s", time.time(), vad_event)
# e.g., ctx.session.on_vad_commit(...) or enqueue audio buffer for processing
# Replace with your actual commit/interrupt logic.
try:
ctx.session.handle_vad_commit(vad_event)
except Exception as e:
LOGGER.exception("Error handling VAD commit: %s", e)
# Integrate suppression into your greeting flow
class GreetingState:
async def on_enter(self) -> None:
self._call_state.disposition = CallDisposition.ANSWERED
# Estimate greeting duration and start suppression
GREETING_DURATION_SEC = 6.0 # adjust to actual expected duration
SAFETY_MARGIN_SEC = 0.5
start_vad_suppression(self.ctx, GREETING_DURATION_SEC, SAFETY_MARGIN_SEC)
# Generate reply with allow_interruptions=False
await self.session.generate_reply(
instructions="Deliver your opening greeting exactly as specified in your system prompt.",
allow_interruptions=False
)
# Optionally: ensure suppression remains at least until reply playback completes.
# If you have a playback-complete event/callback, clear suppression there.
# For safety, clear suppression here after greeting duration (non-blocking)
# or after you observe playback-complete:
def clear_after_window():
# Wait until suppression window naturally expires or clear immediately if expired
remaining = max(0.0, self.ctx.proc.userdata.get("suppress_vad_until", 0.0) - time.time())
time.sleep(remaining)
clear_vad_suppression(self.ctx)
# Run clear_after_window in background thread or task (don't block event loop)
import threading
threading.Thread(target=clear_after_window, daemon=True).start()
Quick operational tips:
• Log suppression starts/ends and count ignored VAD triggers for observability.
• Clamp suppression to avoid accidental long blocks (we used MAX_SUPPRESSION_SEC).
• If you have a playback end event, clear suppression there instead of sleeping.
• Test with noisy recordings and early caller speech to validate behavior.
If you want, I can adapt this to use your real Silero callback signature and your async playback-complete hook so suppression is cleared exactly when the greeting finishes. Which hook/event indicates the greeting finished in your system?
, with interactive elementsboundless-author-7811
04/13/2026, 6:12 AMtall-airline-16756
04/13/2026, 6:12 AMuserdata (e.g., suppress_vad_until) and a short safety window. Clear it when playback completes or let it expire.
• Keep your current AgentSession mostly as-is; no special new parameter is required—store suppression state in userdata.
Minimal AgentSession-related items to ensure
• vad — your Silero instance (you already set this).
• userdata — used to hold suppress_vad_until and any original VAD params.
• turn_handling — keep interruption config (you already have it) so normal interruption logic runs when suppression is not active.
Suggested userdata keys
• suppress_vad_until (float timestamp)
• optional: vad_orig_params (to restore after threshold-gating, if used)
• optional: greeting_expected_duration (for observability)
Tiny snippet (showing where to set suppression on greeting):
# before generate_reply
GREETING_DURATION_SEC = 6.0
SAFETY_MARGIN = 0.5
ctx.proc.userdata["suppress_vad_until"] = time.time() + min(GREETING_DURATION_SEC + SAFETY_MARGIN, 30.0)
await session.generate_reply(..., allow_interruptions=False)
Silero callback check:
def is_vad_suppressed(ctx):
return time.time() < ctx.proc.userdata.get("suppress_vad_until", 0.0)
def on_silero_vad_event(ctx, vad_event):
if is_vad_suppressed(ctx):
# ignore / log metric
return
handle_vad_commit(ctx, vad_event)
Observability and fallback
• Log suppression start/end and count ignored triggers.
• Clamp suppression (e.g., max 30s) to avoid accidental long blocks.
• If you later add a playback-complete hook, clear suppress_vad_until there for exactness.
If you want, I can provide the small async-friendly snippet to clear suppression when playback finishes (once you point to the playback event or API you use).
, with interactive elementsboundless-author-7811
04/13/2026, 6:15 AMtall-airline-16756
04/13/2026, 6:15 AMself.session.generate_reply(...) to play the greeting and you don't have a playback-complete hook, use time-based suppression stored in session.userdata and tie suppression window to an estimated greeting duration plus a safety margin. The Silero callback must check that flag before committing VAD events.
Minimal steps and code (async-friendly):
1) Before generating the reply, set a suppression timestamp on userdata (clamped). 2) Call generate_reply(...) with allow_interruptions=False. 3) Let suppression expire by timestamp. Optionally clear it earlier if you add a playback-complete hook later. 4) Ensure your Silero VAD handler ignores events while suppression is active.
Code snippet to drop into your flow:
import time
import asyncio
MAX_SUPPRESSION_SEC = 30.0
def start_suppression(ctx, greeting_sec: float, safety_margin: float = 0.5):
window = min(greeting_sec + safety_margin, MAX_SUPPRESSION_SEC)
ctx.proc.userdata["suppress_vad_until"] = time.time() + window
def is_suppressed(ctx) -> bool:
return time.time() < ctx.proc.userdata.get("suppress_vad_until", 0.0)
# Silero callback (adapt signature)
def on_silero_vad_event(ctx, vad_event):
if is_suppressed(ctx):
# ignore and log metric
return
handle_vad_commit(ctx, vad_event)
# In your greeting state
class GreetingState:
async def on_enter(self) -> None:
self._call_state.disposition = CallDisposition.ANSWERED
# adjust to expected greeting length
GREETING_DURATION_SEC = 6.0
start_suppression(self.ctx, GREETING_DURATION_SEC, safety_margin=0.5)
# play greeting (no interruptions)
await self.session.generate_reply(
instructions="Deliver your opening greeting exactly as specified in your system prompt.",
allow_interruptions=False
)
# Optional: ensure suppression cleared after window if no playback hook
# (non-blocking wait)
async def clear_after_window():
remaining = max(0.0, self.ctx.proc.userdata.get("suppress_vad_until", 0.0) - time.time())
if remaining:
await asyncio.sleep(remaining)
self.ctx.proc.userdata["suppress_vad_until"] = 0.0
asyncio.create_task(clear_after_window())
Operational tips:
• Set GREETING_DURATION_SEC to the expected playback length plus a small safety margin.
• Log suppression start/end and count ignored VAD events for visibility.
• Keep MAX_SUPPRESSION_SEC to avoid accidental long blocks.
• If you later get a playback-complete event, clear suppress_vad_until there instead of waiting for timeout.
If you want, I can adapt the snippet to your exact Silero callback signature or help compute a safe greeting duration based on your TTS voice. Which would you like?
, with interactive elementsboundless-author-7811
04/13/2026, 6:17 AMtall-airline-16756
04/13/2026, 6:17 AMinput_audio_noise_reduction on the RealtimeModel for noise robustness, and keep the Silero suppression for guaranteeing no interruptions during the greeting. Brief summary and a focused Python snippet showing both together.
Why both:
• input_audio_noise_reduction="far_field" runs on OpenAI servers and improves LLM transcription/semantic VAD performance for noisy phone calls (no CPU cost).
• Silero runs locally on raw audio and can enforce an interruption-free greeting via a suppression window. OpenAI noise reduction does not change what Silero sees, so you still need local suppression to guarantee no interruptions.
What to set in AgentSession
• Keep your llm as OpenAI Realtime with noise reduction:
◦ input_audio_noise_reduction="far_field" (phone calls)
• Keep vad set to your Silero instance.
• Use userdata["suppress_vad_until"] for time-based suppression around the greeting.
Minimal integrated snippet (async Python):
import time, asyncio
# AgentSession creation (keep as you have, add input_audio_noise_reduction)
session = AgentSession(
llm=openai.realtime.RealtimeModel.with_azure(
voice="alloy",
modalities=["audio","text"],
turn_detection=None,
input_audio_noise_reduction="far_field",
azure_endpoint=deployment.base_url or "",
api_key=deployment.api_key,
azure_deployment=deployment.azure_deployment_name,
api_version=deployment.api_version or "2024-10-01-preview"
),
vad=ctx.proc.userdata["vad"],
userdata=state,
turn_handling={ "interruption": { "enabled": True, "min_duration": 0.8, "min_words": 2 } },
...
)
# Suppression helpers
MAX_SUPPRESSION_SEC = 30.0
def start_suppression(ctx, greeting_sec: float, safety_margin: float = 0.5):
window = min(greeting_sec + safety_margin, MAX_SUPPRESSION_SEC)
ctx.proc.userdata["suppress_vad_until"] = time.time() + window
def is_suppressed(ctx) -> bool:
return time.time() < ctx.proc.userdata.get("suppress_vad_until", 0.0)
# Silero VAD event handler (adapt signature)
def on_silero_vad_event(ctx, vad_event):
if is_suppressed(ctx):
# ignore / log metric
return
# normal commit logic
ctx.session.handle_vad_commit(vad_event)
# Greeting flow using generate_reply
class GreetingState:
async def on_enter(self) -> None:
self._call_state.disposition = CallDisposition.ANSWERED
GREETING_DURATION_SEC = 6.0 # set to expected TTS length
start_suppression(self.ctx, GREETING_DURATION_SEC, safety_margin=0.5)
await self.session.generate_reply(
instructions="Deliver your opening greeting exactly as specified in your system prompt.",
allow_interruptions=False
)
# non-blocking clear after suppression window expires
async def clear_after_window():
remaining = max(0.0, self.ctx.proc.userdata.get("suppress_vad_until",0.0) - time.time())
if remaining:
await asyncio.sleep(remaining)
self.ctx.proc.userdata["suppress_vad_until"] = 0.0
asyncio.create_task(clear_after_window())
Operational notes
• Set GREETING_DURATION_SEC to expected playback length (TTS duration). Add margin.
• Log suppression starts/ends and ignored VAD events.
• Keep MAX_SUPPRESSION_SEC to avoid accidental long blocks.
• Prefer far_field for phone calls.
• If you later gain a playback-complete event, clear suppression there instead of waiting for timeout.
If you want, I can produce a version that estimates TTS duration from the chosen voice or wire suppression to a playback-complete signal when you add that hook. Which would you prefer?
, with interactive elementsboundless-author-7811
04/13/2026, 6:28 AMfrom livekit.agents import room_io
from livekit.plugins import ai_coustics # or noise_cancellation
# ...
await session.start(
# ...,
room_options=room_io.RoomOptions(
audio_input=room_io.AudioInputOptions(
noise_cancellation=ai_coustics.audio_enhancement(model=ai_coustics.EnhancerModel.QUAIL_VF_L),
# or noise_cancellation.BVC()
# or noise_cancellation.BVCTelephony()
),
),
)
# ...tall-airline-16756
04/13/2026, 6:28 AMai_coustics.audio_enhancement(model=ai_coustics.EnhancerModel.QUAIL_VF_L) line is not supported/documented for self‑hosted LiveKit. Use the confirmed noise-cancellation options instead.
What to do for self-hosted:
• Keep your pattern of passing room_options=room_io.RoomOptions(audio_input=...).
• For self‑hosted, use the documented plugins:
◦ noise_cancellation.BVC() — general background voice cancellation
◦ noise_cancellation.BVCTelephony() — telephony‑optimized
• Example:
from livekit.agents import room_io
from livekit.plugins import noise_cancellation
await session.start(
...,
room_options=room_io.RoomOptions(
audio_input=room_io.AudioInputOptions(
noise_cancellation=noise_cancellation.BVCTelephony(),
),
),
)
Notes:
• LiveKit Cloud offers other enhanced options (e.g., Krisp) that are not present by default in self‑hosted — those must be integrated separately if needed.
• If you want, tell me which noise profile you need (telephony vs general) and I’ll suggest the exact option and parameters. Sources: Agents telephony integration | LiveKit Documentation | Google AI and LiveKit | LiveKit Documentation | Voice AI quickstart | LiveKit Documentation | Agent session | LiveKit Documentation, with interactive elementsboundless-author-7811
04/13/2026, 6:33 AMtall-airline-16756
04/13/2026, 6:33 AMnoise_cancellation.BVCTelephony().
Minimal recommended config (drop into your existing flow):
from livekit.agents import room_io
from livekit.plugins import noise_cancellation
await session.start(
# ... other args ...
room_options=room_io.RoomOptions(
audio_input=room_io.AudioInputOptions(
noise_cancellation=noise_cancellation.BVCTelephony(),
),
),
)
Notes:
• This runs locally in your agent pipeline (self‑hosted) and is the documented telephony option.
• Keep input_audio_noise_reduction="far_field" on your OpenAI RealtimeModel too — that runs server‑side and improves LLM/audio quality.
• Continue using Silero VAD suppression around the greeting (time‑based) to guarantee no interruptions; noise cancellation + OpenAI noise reduction help LLM robustness but do not change what Silero sees.
If you want, I can produce the full integrated snippet showing: AgentSession with input_audio_noise_reduction="far_field", vad=Silero, and BVCTelephony() wired into room_options. Would you like that?
, with interactive elementsboundless-author-7811
04/13/2026, 7:00 AM{"message": "livekit_ffi::server::room:150:livekit_ffi::server::room - audio filter cannot be enabled: LiveKit Cloud is required", "level": "ERROR",
i used noise_cancellation.BVCTelephony(), but got this in the logstall-airline-16756
04/13/2026, 7:00 AMnoise_cancellation.BVCTelephony() from your RoomOptions to stop the error.
• Use the self‑hosted supported option noise_cancellation.BVC() instead, or rely on OpenAI server‑side noise reduction (input_audio_noise_reduction="far_field") for telephony robustness.
• Keep Silero VAD suppression for the greeting as discussed (time-based) — server-side noise reduction does not change what Silero sees.
Example replacement:
from livekit.plugins import noise_cancellation
# use BVC() for self-hosted
room_options=room_io.RoomOptions(
audio_input=room_io.AudioInputOptions(
noise_cancellation=noise_cancellation.BVC(),
),
)
Operational notes
• BVCTelephony / other cloud audio filters are only available with LiveKit Cloud.
• Test with noisy recordings after switching to BVC() + input_audio_noise_reduction="far_field".
• If you want, I can give an integrated snippet showing AgentSession + Silero suppression + BVC() + OpenAI noise reduction. Would you like that?, with interactive elementsboundless-author-7811
04/13/2026, 7:29 AM{"message": "ignoring byte stream with topic 'lk.agent.session', no callback attached", "level": "INFO", "name": "root", "room":tall-airline-16756
04/13/2026, 7:29 AMboundless-author-7811
04/13/2026, 7:31 AMtall-airline-16756
04/13/2026, 7:32 AMmin_speech_duration (e.g., 0.1–0.25s) and/or activation_threshold to reduce short false triggers.
◦ Keep prefix_padding_duration so commits include a bit of leading audio.
1. Use suppression when greeting
◦ Ensure your suppress_vad_until logic actually ignores Silero commits during greeting (we discussed this). If suppression logic returns early, it can lead to empty commits — verify the Silero callback returns without committing.
1. Verify audio flow / races
◦ Ensure audio input is enabled and streaming before you allow commits.
◦ If you call any commit immediately after enabling input, add a small warm-up (100–200ms) or wait for the first audio frames.
1. Instrument and fallback
◦ Log buffer durations at commit time and count empty-commit attempts.
◦ If an empty commit is attempted, drop it and retry after collecting more audio (don’t forward to OpenAI).
Example guard in Silero callback (conceptual):
buffer_ms = ctx.proc.userdata.get("current_audio_buffer_ms", 0)
if buffer_ms < 100:
LOGGER.debug("Skipping commit: buffer too small (%sms)", buffer_ms)
return
# proceed to commit
If you want, I can:
• Produce a concrete patch for your Silero commit path (async Python) that checks buffer length and retries safely, or
• Review the exact handler where you call session.commit/handle_vad_commit and give a tailored change. Which would you prefer? Sources: Silero VAD plugin | LiveKit Documentation | OpenAI Realtime API plugin guide | LiveKit Documentation | Turns overview | LiveKit Documentation | Voice AI quickstart | LiveKit Documentation | Speech-to-text (STT) models overview | LiveKit Documentation, with interactive elements