cuddly-vr-85751
03/10/2026, 5:14 PMtall-airline-16756
03/10/2026, 5:14 PMtall-airline-16756
03/10/2026, 5:14 PMcuddly-vr-85751
03/10/2026, 5:14 PMtall-airline-16756
03/10/2026, 5:15 PMivr_detection=True on your AgentSession so LiveKit's agent logic will try to identify IVR systems automatically.
• DTMF detection/handling: listen for DTMF events (menu-driven prompts often expect DTMF). Use dtmf_received events to detect and respond to menu prompts, and send_dtmf_events to navigate IVR menus when needed.
• Silence / timing heuristics: detect long silence, structured pause patterns, or consistent prompt lengths (voicemail greetings and IVRs often follow predictable timing).
• LLM / content-based detection: run an LLM classifier on the captured audio transcript or use function-calling tools to classify greetings/menus vs. natural conversation (note: this is probabilistic — typical accuracy is in the 70–80% range).
• Combine signals for best results: built‑in ivr_detection + DTMF presence + silence/timing heuristics + transcript-based classification gives the most reliable outcome.
Concrete starting points
1. Enable session IVR detection
◦ Example: set AgentSession(ivr_detection=True) when creating the session so IVR detection is active for that call.
1. Handle DTMF events
◦ Subscribe to dtmf_received events and inspect the digit(s). Presence of structured digit prompts strongly indicates an IVR.
◦ Use the send_dtmf_events tool to respond to IVR prompts when you want to navigate a menu.
1. Use silence and timing checks
◦ Track when the remote side last spoke. Long consistent silences after a greeting or long fixed-length prompts can indicate voicemail/automated systems.
◦ Implement a configurable silence threshold (e.g., N seconds) to mark likely voicemail.
1. Use transcript-based/LLM approaches
◦ After capturing the greeting or initial few seconds, analyze the transcript for phrases like “press 1,” “to continue,” “please leave a message,” which are strong IVR signals.
◦ Optionally call an LLM function to classify the audio/transcript; treat results as probabilistic and combine with DTMF/silence signals.
Questions to tailor a recommended implementation
• Which SDK/platform are you using (Web, Node agent, Python agent, iOS, Android)?
• Do you want automatic detection only, or also automatic actions (e.g., leave voicemail, route to different flow, send DTMF)?
• Do you already capture remote audio transcripts or only raw audio/events?
• Is minimizing false-positives (mistaking agents for IVR) or minimizing false-negatives more important for your use case?
If you tell me your platform and whether you want detection only or detection + automated handling, I’ll give a short code example and recommended thresholds for that environment.
Sources: Agent session | LiveKit Documentation | examples/dtmf | livekit/livekit_composite | DeepWiki | How to detect and handle silence during calls | send_dtmf_events | LiveKit Documentationcuddly-vr-85751
03/10/2026, 5:16 PMtall-airline-16756
03/10/2026, 5:16 PMivr_detection=True on the AgentSession so LiveKit will attempt IVR identification automatically.
• Listen for DTMF events: IVRs commonly emit or ask for DTMF input. If you receive structured DTMF (or detect a prompt asking for digits), treat that as a strong IVR signal.
• Use transcript/keyword detection: look for phrases like “press 1,” “please leave a message,” or “recording” in the initial transcript — these are strong indicators.
• Use silence/timing heuristics as a fallback: long, consistent greetings or a fixed-length prompt pattern often indicate voicemail/IVR.
• Combine signals and apply a decision rule: require 1–2 strong signals (e.g., built‑in ivr_detection OR DTMF OR explicit IVR keywords) before ending the call to reduce false positives.
Minimal decision flow example
1. Wait for initial greeting (first 3–8 seconds) or for ivr_detection result.
2. If ivr_detection == true, end call immediately.
3. Otherwise, if DTMF is observed or transcript contains IVR keywords, end call.
4. Otherwise keep the call open.
Questions so I can give a concrete code example
• Which SDK/platform are you using? (Web, Node/Python agent, iOS, Android)
• Do you already capture transcripts in real time, or only audio/events?
• How aggressive should detection be? (e.g., end on a single signal, or require two signals to avoid false positives)
If you tell me the platform and your preference for aggressiveness, I’ll provide a short code snippet showing: 1) enabling ivr_detection, 2) subscribing to dtmf_received and transcript events, and 3) the exact call-end logic (calling the room/session disconnect).cuddly-vr-85751
03/10/2026, 5:23 PMtall-airline-16756
03/10/2026, 5:23 PMivr_detection=True on your AgentSession. If this flag returns true, treat it as a strong signal.
• Transcript keyword detection: listen for phrases like “please leave a message,” “after the tone,” “press 1,” “record your message,” or “leave a message.” These are strong indicators of voicemail.
• Silence/greeting pattern: voicemails often have a longer fixed greeting followed by silence and then a beep. Detect a long continuous monologue followed by extended silence or a sudden tone.
• Beep detection: detecting the beep tone after a greeting is a very strong voicemail signal.
• Absence of conversational interactivity: no turn-taking or no responses to prompts when agent speaks can be an indicator.
Recommended decision rule (safe, low false-positive)
• Treat ivr_detection == true or beep detection as immediate voicemail → end call.
• Otherwise, require two of the other signals within the first N seconds (e.g., 8–12s): keyword in transcript + long greeting pattern, OR keyword + silence after greeting, OR DTMF presence + structured prompts → end call.
• If signals are ambiguous, keep call open or escalate to human workflow.
High-level flow to implement
1. Start session with voicemail/IVR detection enabled:
◦ Set AgentSession(ivr_detection=True).
1. Capture these event streams in parallel:
◦ ivr_detection result.
◦ Real-time transcript (if available) to match keywords.
◦ Audio events for beep/silence detection and DTMF events.
1. Run decision rule within an initial window (e.g., first 8–12 seconds).
2. If voicemail is detected per rule, call the room/session disconnect to end the call.
Questions so I can give exact code
• Which platform/SDK are you using? (Web, Node/Python agent, iOS, Android)
• Do you have real‑time transcripts available, or only audio/events?
If you tell me those two details, I’ll provide a concise code snippet tailored to your SDK showing: 1) enabling ivr_detection, 2) subscribing to dtmf_received, transcript events, and audio/beep/silence detection, and 3) the precise call-end logic (calling disconnect).cuddly-vr-85751
03/10/2026, 5:35 PMtall-airline-16756
03/10/2026, 5:35 PMcuddly-vr-85751
03/10/2026, 5:36 PMtall-airline-16756
03/10/2026, 5:37 PMcuddly-vr-85751
03/10/2026, 5:38 PMtall-airline-16756
03/10/2026, 5:39 PMimport re
PII_PATTERNS = {
'credit_card': re.compile(r'\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b'),
'ssn': re.compile(r'\b\d{3}[-]?\d{2}[-]?\d{4}\b'),
'phone': re.compile(r'\b\d{3}[-.]?\d{3}[-.]?\d{4}\b'),
'email': re.compile(r'\b[\w.+-]+@[\w.-]+\.\w{2,}\b'),
}
Example STT override (place inside your VoiceAgent class):
from livekit.agents import stt, ModelSettings
from typing import AsyncIterable, Optional
async def stt_node(self, audio: AsyncIterable["rtc.AudioFrame"], model_settings: ModelSettings) -> Optional[AsyncIterable[stt.SpeechEvent]]:
async for event in super().stt_node(audio, model_settings):
# Only inspect final transcript events
if isinstance(event, stt.SpeechEvent) and event.type == stt.SpeechEventType.FINAL_TRANSCRIPT:
for alt in event.alternatives:
for name, pattern in PII_PATTERNS.items():
if pattern.search(alt.text):
# redact transcript text immediately for agent/LLM
alt.text = pattern.sub(f'[{name.upper()}]', alt.text)
# notify your flow about detection so you can act (stop recording, play tone, end call...)
await self.on_pii_detected({
"type": name,
"timestamp": alt.start_time if hasattr(alt, 'start_time') else None,
"text_snippet": "[REDACTED]"
})
yield event
2) What to do when PII is detected — options Option A — Stop recording temporarily (recommended for guaranteed non‑recording of sensitive data)
• You already have CallRecordingService in entrypoint. Expose it to the agent or provide a callable the agent can use.
Example on PII detection handler:
async def on_pii_detected(self, pii_info):
# stop recording quickly to avoid sensitive audio capture
try:
await self.ctx.proc.userdata['recorder'].stop_and_wait()
except Exception:
pass
# optionally tell participant/agent
await self.session.say("I cannot accept sensitive data over voice. Please use the secure link.")
# resume recording after a short safe window if desired
await asyncio.sleep(1.0)
await self.ctx.proc.userdata['recorder'].start_recording(room_name=self.ctx.room.name, filename=...)
• Pros: raw sensitive audio never reaches stored recording.
• Cons: recording will have gaps.
Option B — Play a masking tone live (client or mixer)
• If you control the client or have a server mixer that can inject audio into the composite track, play a short beep over the live audio when PII is detected.
• Client-side approach: send a message to the client to play a local beep and mute mic momentarily; this ensures the beep appears in the local recording but requires client cooperation.
• Server-side mixing: only possible if you have server mixing control to insert a tone into the composite. If not available, use Option A.
Example command to tell client to play tone (pseudo):
await self.session.send_control_event({"type":"play_tone", "tone":"beep", "duration_ms":300})
Then client SDK plays tone and optionally mutes mic for that duration.
3) Redact before storage / export (mandatory)
• Always run a final redaction pass on conversation history and transcript before saving or sending to downstream systems.
Add helper and use in your on_session_end:
def redact_pii_in_text(text: str) -> str:
for name, pattern in PII_PATTERNS.items():
text = pattern.sub(f'[{name.upper()}]', text)
return text
# in your on_session_end
safe_transcript = []
for item in session.history.to_dict().get('items', []):
if 'content' in item:
item['content'] = redact_pii_in_text(item['content'])
safe_transcript.append(item)
end_call_data['transcript'] = json.dumps(safe_transcript, indent=2)
• Also redact anything you store in logs or analytics.
4) PCI & compliance guidance (musts)
• Do not store or log card numbers. Redirect payments to PCI‑compliant payment pages/IVR.
• Prefer stopping recording when payment/card data is at risk.
• Limit access to raw unredacted recordings and delete them once redaction is complete.
5) Where to wire this into your posted code
• Put PII patterns and stt_node override into VoiceAgent.
• Make recorder available to the agent — you already create recorder in entrypoint. Store it in ctx.proc.userdata so agent can reference:
ctx.proc.userdata['recorder'] = recorder
• Implement on_pii_detected in VoiceAgent to call recorder stop/start or send control events via session.
• Ensure on_session_end does transcript redaction before calling end_call.
6) Small checklist before deploying
• Test regex false positives/negatives using recorded transcripts.
• Decide your sensitivity: immediate stop vs require two signals.
• Audit logs to ensure no raw PII is accidentally written.
• For payments, implement non‑recorded payment handoff.
If you want, I can:
• Provide a complete patch with the functions inserted into your posted file, or
• Produce a minimal client play-tone snippet if you control the web/mobile client.
Which would you prefer (server changes patch, or server+client tone approach)? Also confirm whether your STT is real‑time (yes/no) so I tailor the exact code.
Sources: Redacting PII from agent logs and transcripts | LiveKit | Handling payments with PCI compliance | LiveKit | Prebuilt tools | LiveKit Documentation