Skip to content

feat(voice): add answering machine detection helper#1215

Merged
toubatbrian merged 11 commits intomainfrom
codex/issue-1204-amd-draft
Apr 15, 2026
Merged

feat(voice): add answering machine detection helper#1215
toubatbrian merged 11 commits intomainfrom
codex/issue-1204-amd-draft

Conversation

@chenghao-mou
Copy link
Copy Markdown
Member

@chenghao-mou chenghao-mou commented Apr 9, 2026

Summary

Adds a JS/TS answering machine detection helper for voice sessions, following the Python AMD work in livekit/agents#4906 and the follow-up OTEL + authorization parity from #5376.

Changes

  • add voice.AMD to classify early call transcripts as human, machine-ivr, machine-vm, machine-unavailable, or uncertain
  • pause reply authorization while AMD is running so queued speech does not play before detection completes
  • resume authorization in a finally path so the session cannot remain stuck if detection throws
  • add a dedicated answering_machine_detection OTEL span with AMD-specific attributes and participant/session context
  • optionally force-interrupt queued speech when a machine outcome is detected
  • export the new helper from voice/index.ts
  • add a dedicated telephony_amd example that mirrors the Python PR flow for human, IVR, voicemail, and unavailable mailbox handling

Testing

  • pnpm test -- agents/src/voice/amd.test.ts agents/src/voice/agent_activity.test.ts
  • pnpm exec tsc -p agents/tsconfig.json --noEmit
  • pnpm exec eslint agents/src/voice/amd.ts agents/src/voice/amd.test.ts examples/src/telephony_amd.ts
  • pnpm exec prettier --check agents/src/voice/amd.ts agents/src/voice/amd.test.ts examples/src/telephony_amd.ts

Notes

This is the JS counterpart to the Python AMD helper, adapted to the current JS voice/session architecture, with category names aligned to the Python implementation.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 9, 2026

🦋 Changeset detected

Latest commit: e262c05

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 24 packages
Name Type
@livekit/agents Patch
@livekit/agents-plugin-anam Patch
@livekit/agents-plugin-assemblyai Patch
@livekit/agents-plugin-baseten Patch
@livekit/agents-plugin-bey Patch
@livekit/agents-plugin-cartesia Patch
@livekit/agents-plugin-deepgram Patch
@livekit/agents-plugin-elevenlabs Patch
@livekit/agents-plugin-google Patch
@livekit/agents-plugin-hedra Patch
@livekit/agents-plugin-inworld Patch
@livekit/agents-plugin-lemonslice Patch
@livekit/agents-plugin-livekit Patch
@livekit/agents-plugin-mistral Patch
@livekit/agents-plugin-neuphonic Patch
@livekit/agents-plugin-openai Patch
@livekit/agents-plugin-phonic Patch
@livekit/agents-plugin-resemble Patch
@livekit/agents-plugin-rime Patch
@livekit/agents-plugin-sarvam Patch
@livekit/agents-plugin-silero Patch
@livekit/agents-plugins-test Patch
@livekit/agents-plugin-trugen Patch
@livekit/agents-plugin-xai Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@chenghao-mou chenghao-mou force-pushed the codex/issue-1204-amd-draft branch from 960d8a4 to bab9d91 Compare April 9, 2026 02:46
@chenghao-mou chenghao-mou marked this pull request as ready for review April 9, 2026 03:34
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

Comment thread agents/src/voice/amd.ts
Comment on lines +237 to +246
const parsed = this.parseDetection(rawResponse);
return {
...parsed,
transcript,
rawResponse,
isMachine:
parsed.category === AMDCategory.MACHINE_IVR ||
parsed.category === AMDCategory.MACHINE_VM ||
parsed.category === AMDCategory.MACHINE_UNAVAILABLE,
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we not use the tool call here like what was done in python?

Comment thread agents/src/voice/amd.ts
Comment on lines +249 to +270
private parseDetection(rawResponse: string): Pick<AMDResult, 'category' | 'reason'> {
const normalized = rawResponse.trim();
const jsonStart = normalized.indexOf('{');
const jsonEnd = normalized.lastIndexOf('}');
const jsonChunk =
jsonStart >= 0 && jsonEnd >= jsonStart
? normalized.slice(jsonStart, jsonEnd + 1)
: normalized;

try {
const parsed = JSON.parse(jsonChunk) as { category?: string; reason?: string };
return {
category: this.normalizeCategory(parsed.category),
reason: parsed.reason?.trim() || 'No reason provided.',
};
} catch {
return {
category: AMDCategory.UNCERTAIN,
reason: normalized || 'Failed to parse AMD model response.',
};
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^

Refactor Answering Machine Detection to match Python's `_AMDClassifier`:

- Replace single 8s timeout with dual timers: no-speech (10s → MACHINE_UNAVAILABLE)
  and detection timeout (20s → UNCERTAIN), matching Python's NO_SPEECH_THRESHOLD
  and TIMEOUT constants
- Wire VAD via UserStateChanged events for speech start/end tracking
- Add short-greeting heuristic: speech ≤ 2.5s + 0.5s silence → HUMAN (skip LLM)
- Implement two-gate emit system: result only emits when both a verdict (LLM or
  heuristic) and silence gate (1.5s post-speech) are satisfied
- Add generation counter to discard stale LLM results when newer transcripts arrive
- Restructure from nested closures to class methods/fields for readability
- Extract isMachineCategory() helper and Set-based parseCategory() replacing
  identity switch statement
- Fix quadratic string concat in LLM stream (chunks[] + join)
- Fix aclose() to properly clean up timers and listeners

Made-with: Cursor
@toubatbrian
Copy link
Copy Markdown
Contributor

toubatbrian commented Apr 14, 2026

@chenghao-mou I made some changes to your PR, mostly style improvement + aligning the timeout handling to python. Regarding the tool calling v.s generating raw JSON. Based on my testing it works 100 out of 100, so probably it's fine to leave it as it is right now.

Let me know your thoughts as well!

devin-ai-integration[bot]

This comment was marked as resolved.

toubatbrian and others added 4 commits April 14, 2026 12:19
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
devin-ai-integration[bot]

This comment was marked as resolved.

@toubatbrian toubatbrian merged commit 3481aa5 into main Apr 15, 2026
9 checks passed
@toubatbrian toubatbrian deleted the codex/issue-1204-amd-draft branch April 15, 2026 02:01
@github-actions github-actions bot mentioned this pull request Apr 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants