FlexML AMD Application
AMD stands for Answering Machine Detection. This guide explains how to create a FlexML application using the Amd
verb to protect your destination number from spam calls made by automated systems.
The Amd
verb is an experimental feature. It is still in development, and the results may not be 100% accurate.
1. Plan Application Structure
Before building the application, let's outline its behavior and response logic. The application will follow this workflow:
- Caller calls the phone number that is associated with our FlexML application.
- Application greets the caller and waits for some actions.
- Application analyzes the caller's speech trying to determine whether the caller is human or machine and, depending on the result, redirects the caller to the correct destination:
- Human → Redirect to destination
- Machine → Hang up
- Not Sure → Treat as human
FlexML has verbs for all these actions, so we can proceed with development.
2. Build Application
In this section, we'll build a FlexML application using Python to handle call routing.
- Register with CarrierX to access the CarrierX portal.
- Rent a phone number from CarrierX to associate it with the created FlexML endpoint later.
- Have Python installed on your local machine.
- Add Flask micro web framework to your Python installation. It will serve as a simple web server and will run our FlexML application.
Step 1: Create the First Route
We'll start with a default route (we'll call it hello
) that accepts the call and responds. The flexml_amd.py
file will contain the Python code for this route (and subsequent routes for simplicity).
While this example uses Python, you can implement the server and routing in any programming language.
-
Import
Flask
, and initialize the Flask application with theapp = Flask(__name__)
command.from flask import Flask
app = Flask(__name__) -
Define the
hello
route (accessible via POST at http://example.com/hello).- Use the
Say
verb to greet the caller. It will pronounce the words stored inside the<Say></Say>
tags. - Wrap the response in a
Response
tag. - Add a
Pause
to add some time before the application responds to the call, so that the voice could be correctly loaded even on slower connections. The length of the pause is set in seconds, and three seconds is alright for most cases.
- Use the
The resulting code for our hello
route will look as follows:
from flask import Flask
app = Flask(__name__)
@app.route('/hello', methods=['POST'])
def hello():
return '''<Response>
<Pause length="3"/>
<Say>Welcome to CarrierX FlexML application. Please announce yourself</Say>
</Response>'''
Step 2: Start the Flask Server
Test the application by running the Flask server locally:
-
Set the environment variable:
Windows MacOS/Linux set FLASK_APP=flexml_amd.py
export FLASK_APP=flexml_amd.py
-
Start the Flask server:
Windows MacOS/Linux python -m flask run
python3 -m flask run
Now your application is running, but it is not visible to the outside world. Expose the localhost route publicly over the Internet so that your endpoint could access and load the FlexML instructions. To do this, you can use the free ngrok tool. If you choose to use ngrok, follow the instructions on their website to download it.
Once your Flask server is running, your terminal will list the port it is running on. Expose this port by running the following command. Make sure to replace 5000
(which is the Flask default port) with the port that your Flask server is running on.
ngrok http 5000
After running ngrok, you'll receive a public URL (e.g., https://4146-188-2-25-253.ngrok-free.app/hello
).
Associate this URL with a FlexML endpoint or a DID associated with some FlexML endpoint (see the FlexML Endpoint Guide to learn how).
Now we have an introductory route that greets the caller.
This logic can be described with the following block diagram:
The next step is to add the ability to our application to check if caller is human or machine.
Step 3: Check the Caller
Next, we'll use the Amd verb to analyze the caller. We'll apply the following rules:
-
Greeting length: Callers should introduce themselves briefly (≤2 seconds) or at least will say 'Hello'. Longer greetings of speech without introduction are flagged as machine-like.
-
Initial silence: Callers should start speaking within 5 seconds. Delays are flagged as machine-like.
-
Post-greeting pause: A short silence (≥500ms) after the greeting is expected. Immediate continuation is flagged as machine-like.
To match these rules, the following attributes of the Amd
verb are used:
Attribute | Description |
---|---|
greeting | Max greeting length (ms). Exceeding this flags the caller as a machine. |
initialSilence | Max silence (ms) before speaking. Exceeding this flags the caller as a machine. |
afterGreetingSilence | Required silence (ms) after greeting. Too short a pause flags the caller as a machine. |
totalAnalysisTime | Total time (ms) for analysis (includes all phases). |
For a full list of Amd
attributes, refer to the FlexML Syntax documentation.
The final code for the Amd
verb should look like this:
<Amd initialSilence="5000" greeting="2000" afterGreetingSilence="500" totalAnalysisTime="8000" />
Update the hello
route:
from flask import Flask
app = Flask(__name__)
@app.route('/hello', methods=['POST'])
def hello():
return '''<Response>
<Pause length="3"/>
<Say>Welcome to CarrierX FlexML application. Please announce yourself</Say>
<Amd initialSilence="5000" greeting="2000" afterGreetingSilence="500" totalAnalysisTime="8000" />
</Response>'''
The updated logic:
Step 4: Parse Analysis Results
Once the analysis is made, the application must parse its results and either redirect the human caller to the correct destination or hang up on the machine caller.
Let's add the amd
route where we will parse the results and make a decision. For now, we will simply announce the route and do nothing else afterwards:
@app.route('/amd', methods=['POST'])
def amd():
For the call to be redirected to the new amd
route, we add the action="/amd"
and method="POST"
attributes to the Amd
verb. The response for the hello
route will look like this:
<Response>
<Pause length="3"/>
<Say>Welcome to CarrierX FlexML application. Please announce yourself</Say>
<Amd action="/amd" method="POST" initialSilence="5000" greeting="2000" afterGreetingSilence="500" totalAnalysisTime="8000" />
</Response>
At this stage, we have the following resulting code for our application:
from flask import Flask
app = Flask(__name__)
@app.route('/hello', methods=['POST'])
def hello():
return '''<Response>
<Pause length="3"/>
<Say>Welcome to CarrierX FlexML application. Please announce yourself</Say>
<Amd action="/amd" method="POST" initialSilence="5000" greeting="2000" afterGreetingSilence="500" totalAnalysisTime="8000" />
</Response>'''
@app.route('/amd', methods=['POST'])
def amd():
The hello
route, once the Amd
verb makes the decision based on the caller behavior, sends the following data to the amd
route:
{
"AccountSid": "",
"AMDCause": "LONGGREETING-2000-2000",
"AMDStatus": "MACHINE",
"ApiVersion": "2.0",
"CallSid": "4f0d84c72ff90967f58351cbe8364a77",
"CallerName": "+19093189030",
"Direction": "inbound",
"From": "19093189030",
"OriginalFrom": "+19093189030",
"OriginalTo": "19093189029",
"RequestUrl": "https://4146-188-2-25-253.ngrok-free.app/hello",
"To": "19093189029"
}
This part is of interest to us:
{
"AMDCause": "LONGGREETING-2000-2000",
"AMDStatus": "MACHINE"
}
Where:
AMDStatus: HUMAN, NOTSURE, MACHINE, HANGUP.
AMDCause: Reason for the decision (e.g., LONGGREETING).
This is the result of the Amd
verb analysis and the reason for the decision, and in our example it considered the caller MACHINE
for the reason of a too long greeting (it exceeded 2 seconds).
In case of the correct detection of a human caller, the response will contain something like this:
{
"AMDCause": "HUMAN-500-500",
"AMDStatus": "HUMAN",
}
As this data is sent in a form of JSON, our application must get it and correctly retrieve the necessary AMDStatus
value.
This is done with the request.get_json()
method that is available in the Flask request module, which parses data as JSON.
Add the following lines to the amd
route of the application:
data = request.get_json()
amd_status = data.get('AMDStatus','')
Here the data
variable equals to all the JSON data received from the hello
route, and amd_status
accepts the AMDStatus
value and is equal to MACHINE
or HUMAN
.
And now we can compare the received status with our expectations. The result should follow this logic:
- if
amd_status
isHUMAN
orNOTSURE
, the application will redirect the caller to the destination (using the Dial FlexML verb). - if
amd_status
isMACHINE
orHANGUP
, the application will hang up.
The final code of our application:
from flask import Flask, request
app = Flask(__name__)
@app.route('/hello', methods=['POST'])
def hello():
return '''<Response>
<Pause length="3"/>
<Say>Welcome to CarrierX FlexML application. Please announce yourself</Say>
<Amd action="/amd" method="POST" initialSilence="5000" greeting="2000" afterGreetingSilence="500" totalAnalysisTime="8000" />
</Response>'''
@app.route('/amd', methods=['POST'])
def amd():
data = request.get_json()
amd_status = data.get('AMDStatus','')
if amd_status == 'HUMAN' or amd_status == 'NOTSURE':
return '''<Response>
<Say>Thank you! You will be now connected to the destination phone number</Say>
<Dial>
<Number>19093189031</Number>
</Dial>
</Response>'''
else:
return '''<Response>
<Hangup/>
</Response>'''
Test the application by calling the number and observing the behavior for human/machine responses.
3. Further Reading
You've built a FlexML application with AMD capabilities! Explore these resources for more::