Docs / Asterisk AMD configuration

Configuring AMDY AMD on an Existing Asterisk Dialplan

For dialers that already run Asterisk and want to replace or augment the built-in AMD() application with AMDY's AI detection. If you have not installed the AMDY client yet, do that first with the Asterisk install guide. This page assumes the EAGI script is already installed and covers the configuration and migration work around it: dialplan wiring, AMI event hooks, codec settings, and the gotchas that show up when cutting over from the stock detector.

Why replace Asterisk's built-in AMD()

Asterisk's AMD() application works on timing heuristics: silence thresholds, word/gap counts, and total analysis time, configured through parameters like initialSilence, greeting, afterGreetingSilence, totalAnalysisTime, minimumWordLength, betweenWordsSilence, and maximumNumberOfWords. Those thresholds were tuned against older PSTN audio and need re-tuning per carrier, per codec, and often per campaign. AMDY replaces the heuristic with a model that classifies the acoustic signature of the answer audio directly, so the dialplan no longer needs a parameter-tuning pass every time carrier audio characteristics shift.

You do not need to rip out AMD() to test AMDY. Both can run on the same channel and you can compare ${AMDSTATUS} against AMDY's result before cutting traffic over. See running side by side below.

1. Dialplan: cutover from AMD() to the AMDY EAGI

A typical existing context calling the stock detector looks like this:

[outbound-amd-old]
exten => s,1,Answer()
exten => s,n,AMD(2000,1500,300,3000,10,5,120,50)
exten => s,n,GotoIf($["${AMDSTATUS}" = "HUMAN"]?human:machine)
exten => s,n(machine),Hangup()
exten => s,n(human),Queue(sales-queue)

Replace the AMD() line with the EAGI call. The channel variables AMDY sets (AMDSTATUS, AMDCAUSE, AMDSTATS) are named to match Asterisk's own convention on purpose, so the rest of your routing logic below the detection step does not need to change:

[outbound-amd-new]
exten => s,1,Answer()
exten => s,n,EAGI(amdy-amd.py)
exten => s,n,GotoIf($["${AMDSTATUS}" = "HUMAN"]?human:machine)
exten => s,n(machine),NoOp(Machine detected: ${AMDCAUSE})
exten => s,n,Hangup()
exten => s,n(human),Queue(sales-queue)

Because the variable names match, most migrations are a one-line swap in the context. Audit any downstream logic that also reads AMDCAUSE values specific to the built-in detector (TOOLONG, INITIALSILENCE, MAXWORDLENGTH) — AMDY's cause set is HUMAN, MACHINE, and CONNECTION_ERROR, and any branch keyed on the old values needs to be updated or removed.

2. Running both detectors side by side

To validate before cutover, run AMD() first, capture its result into a separate variable, then run the AMDY EAGI and compare. This lets you log disagreements without changing live routing:

exten => s,1,Answer()
exten => s,n,AMD(2000,1500,300,3000,10,5,120,50)
exten => s,n,Set(OLD_AMDSTATUS=${AMDSTATUS})
exten => s,n,EAGI(amdy-amd.py)
exten => s,n,Set(CDR(amdy_vs_old)=${OLD_AMDSTATUS}/${AMDSTATUS})
; route on the new result while OLD_AMDSTATUS is logged for comparison
exten => s,n,GotoIf($["${AMDSTATUS}" = "HUMAN"]?human:machine)
exten => s,n(machine),Hangup()
exten => s,n(human),Queue(sales-queue)

Pull amdy_vs_old out of your CDR backend to see how often the two detectors disagree before you remove the old AMD() line entirely.

3. AMI hooks for external routing

If routing decisions live outside the dialplan (a dialer controller, a CTI layer), read the result off the channel via AMI instead of branching in extensions.conf. Once AMDSTATUS is set by the EAGI script, an Originate/Getvar or a UserEvent emitted from the dialplan both work:

; Emit a UserEvent right after detection so an AMI client can act on it
exten => s,n,EAGI(amdy-amd.py)
exten => s,n,UserEvent(AmdyResult,Channel: ${CHANNEL},Status: ${AMDSTATUS},Cause: ${AMDCAUSE})
exten => s,n,GotoIf($["${AMDSTATUS}" = "HUMAN"]?human:machine)

An AMI client subscribes to the UserEvent class and reacts:

Action: Events
EventMask: user

Event: UserEvent
UserEvent: AmdyResult
Channel: SIP/trunk-00000012
Status: MACHINE
Cause: MACHINE

Alternatively, poll the variable directly with an AMI GetVar action (Variable: AMDSTATUS) against the channel name after the EAGI step completes, if your controller prefers pull over push.

4. Codec and audio settings

The EAGI script reads raw audio off file descriptor 3 in the format the channel is bridged in internally, and resamples to 8kHz 16-bit mono PCM before streaming to AMDY. A few codec-related settings affect detection quality more than they affect the stock AMD():

  • Transcoding load. If inbound trunk legs use G.729 or G.722 and the internal bridge forces a transcode to slin before EAGI reads it, confirm transcode_via_sln=yes in codecs.conf so the audio reaching the script is uncompressed, not re-encoded lossy-to-lossy.
  • jitterbuffer. Enable jbenable=yes on SIP trunks feeding the detection context. Choppy audio from jitter under load degrades any AMD system, AMDY included, because the leading edge of the greeting is where most of the signal is.
  • Answer supervision. If the carrier sends early media or a false ANSWER before the far end actually picks up, the script starts listening too early and streams dead air or ringback. Confirm progressinband=never and check for accurate answer supervision on the trunk before troubleshooting detection accuracy.
  • Sample rate mismatches. If a channel driver reports 16kHz (slin16) audio, the script's resampler handles the downsample, but verify with core show channel that the read format matches what you expect; silent resample failures show up as CONNECTION_ERROR causes.

5. Migration gotchas

GotchaFix
Old dialplan branches on AMD()-specific cause codes (TOOLONG, MAXWORDLENGTH, etc.)Rewrite branches to AMDY's three causes: HUMAN, MACHINE, CONNECTION_ERROR
Both AMD() and EAGI run on the same channel and double-consume answer audioFine during a side-by-side validation window, but remove the AMD() line before full cutover; it adds latency for no routing benefit
Include()'d sub-contexts still reference the old context nameGrep extensions.conf for the old context name before deleting it; dangling Goto/Include references fail silently at dialplan reload
Predictive dialer abandons calls before EAGI returns a resultCheck pacing/answer-timeout settings in the dialer; the detection step needs the call held past answer, same requirement as the stock AMD()
CDR/reporting dashboards keyed off AMD()'s variable namesNo change needed for AMDSTATUS/AMDCAUSE reads since the names match; audit any dashboard reading AMD()-only variables like AMDSTATUS values not in AMDY's set
dialplan reload picks up new context but running calls still use cached extensionsUse dialplan reload, not core restart, and confirm with dialplan show outbound-amd-new that the new context is active before routing production traffic to it

Support

Need help with a migration? Email [email protected] with your current dialplan context, Asterisk version (asterisk -V), and the AMD() parameters you were using before.

Configuring AMDY AMD on an Existing Asterisk Dialplan | AMDY.IO