4.2. Advanced Interaction
Built-in Macros
Besides #ONT
for checking an ontology, STDM provides several macros useful for dialogue design.
#LEM
The following example shows a use case of the macro #LEM
that uses the NLTK Lemmatizer to match lemmas of "raining tacos":
transitions = {
'state': 'start',
'`What can I do for you?`': {
'[play, [!{#LEM(rain), rainy}, #LEM(taco)]]': {
'state': 'play_raining_tacos',
'`It\'s raining tacos. From out of the sky ...`': 'start'
},
'error': {
'`Sorry, I didn\'t understand you.`': 'end'
},
}
}
#5
: maches the input with the lemma of "rain" or "rainy", then the lemma of "taco".
S: What can I do for you?
U: Play raining tacos
S: It's raining tacos. From out of the sky ... What can I do for you?
U: Play raining taco
S: It's raining tacos. From out of the sky ... What can I do for you?
U: Play rain taco
S: It's raining tacos. From out of the sky ... What can I do for you?
U: Play rainy taco
S: It's raining tacos. From out of the sky ... What can I do for you?
U: Bye
S: Sorry, I didn't understand you.
#UNX
The #UNX
macro can be used as an alternative to the 'error'
transition, which prepends a short acknowledgment phrase (e.g., "yeah", "sure") to the error statement:
transitions = {
'state': 'start',
'`What can I do for you?`': {
'[play, [!{#LEM(rain), rainy}, #LEM(taco)]]': {
'state': 'play_raining_tacos',
'`It\'s raining tacos. From out of the sky ...`': 'start'
},
'#UNX': {
'`Thanks for sharing.`': 'end'
},
}
}
#8
: prepends an acknowledgment phrase to the error statement, "Thanks for sharing" in#8
.
S: What can I do for you?
U: My name is Jinho
S: Right. Thanks for sharing. What can I do for you?
U: I am a professor
S: Yeah. Thanks for sharing. What can I do for you?
U: Hello world
S: Thanks for sharing. What can I do for you?
U: Play raining tacos
S: It's raining tacos. From out of the sky ...
#3,5
: add acknowledgment phrases given the user inputs.#7
: does not add an acknowledgment phrase when the user input is short.
When the user input contains fewer than three tokens, #UNK
does not add any acknowledgment.
Seldomly, #UNK
gets selected even when conditions in other transitions match the input (due to an STDM bug). If you notice this during testing, assign a very slow score to the #UNK
state to ensure it gets the lowest priority among other branches.
#SET & #IF
Let us update the above transitions so that it sometimes refuses to sing the same song:
transitions = {
'state': 'start',
'`What can I do for you?`': {
'[play, [!{#LEM(rain), rainy}, #LEM(taco)]]': {
'state': 'play_raining_tacos',
'#IF($RAINING_TACOS=True) `Don\'t make me sing this again!`': 'start',
'`It\'s raining tacos. From out of the sky ...` #SET($RAINING_TACOS=True)': 'start',
},
'#UNX': {
'`Thanks for sharing.`': 'start'
},
}
}
#6
: can be picked if the variable$RAINING_TACOS
equals to the string'True'
.#7
: sets$RAINING_TACOS
to'True'
after prompting the system output.
S: What can I do for you?
U: Play raining tacos
S: It's raining tacos. From out of the sky ... What can I do for you?
U: Play raining tacos
S: Don't make me sing this again! What can I do for you?
...
Notice that the #SET
macro only assigns a string value to the variable. It is possible to write a macro to set any variable to an actual boolean value (e.g., True
, False
):
class MacroSetBool(Macro):
def run(self, ngrams: Ngrams, vars: Dict[str, Any], args: List[str]):
if len(args) != 2:
return False
variable = args[0]
if variable[0] == '$':
variable = variable[1:]
boolean = args[1].lower()
if boolean not in {'true', 'false'}:
return False
vars[variable] = bool(boolean)
return True
#3-4
: checks if there are two arguments.#6-8
: retrieves the variable name.#10-12
: checks if the argument is a proper boolean value.#14
: stores the boolean value to the variable.
Given MacroSetBool
, the above transitions can be updated as follow:
transitions = {
'state': 'start',
'`What can I do for you?`': {
'[play, [!{#LEM(rain), rainy}, #LEM(taco)]]': {
'state': 'play_raining_tacos',
'#IF($RAINING_TACOS) `Don\'t make me sing this again!`': 'start',
'`It\'s raining tacos. From out of the sky ...` #SETBOOL(RAINING_TACOS, True)': 'start',
},
'#UNX': {
'`Thanks for sharing.`': 'start'
},
}
}
macros = {
'SETBOOL': MacroSetBool()
}
#6
: is selected if$RAINING_TACOS
isTrue
.#0
: sets the variable$RAINING_TACOS
to the boolean valueTrue
.
Currently, STDM randomly chooses the statements in #6
and #7
once $RAINING_TACOS
becomes True
. We can write another macro that prompts #7
only for the first time:
class MacroPlayRainingTacos(Macro):
def run(self, ngrams: Ngrams, vars: Dict[str, Any], args: List[str]):
return not vars.get('RAINING_TACOS', False)
Given MacroPlayRainingTacos
, the transitions can be updated as follow:
transitions = {
'state': 'start',
'`What can I do for you?`': {
'[play, [!{#LEM(rain), rainy}, #LEM(taco)]]': {
'state': 'play_raining_tacos',
'#IF($RAINING_TACOS) `Don\'t make me sing this again!`': 'start',
'#IF(#PLAY_RAINING_TACOS) `It\'s raining tacos. From out of the sky ...` #SETBOOL(RAINING_TACOS, True)': 'start',
},
'#UNX': {
'`Thanks for sharing.`': 'start'
},
}
}
macros = {
'SETBOOL': MacroSetBool(),
'PLAY_RAINING_TACOS': MacroPlayRainingTacos()
}
df = DialogueFlow('start', end_state='end')
df.load_transitions(transitions)
df.add_macros(macros)
df.run()
#7
: is selected if the macro#PLAY_RAINING_TACOS
returnsTrue
.#17
: adds#PLAY_RAINING_TACOS
to the macro dictionary.
S: What can I do for you?
U: Play raining tacos
S: It's raining tacos. From out of the sky ... What can I do for you?
U: Play raining tacos
S: Don't make me sing this again! What can I do for you?
U: Play raining tacos
S: Don't make me sing this again! What can I do for you?
...
Music
It is possible to make our system actually sings instead of prompting the lyric. Let us first install the VLC package:
pip install python-vlc
Then, import the package and update the MacroPlayRainingTacos
macro to play the MP3 file, resources/raining_tacos.mp3:
import vlc
class MacroPlayRainingTacos(Macro):
def run(self, ngrams: Ngrams, vars: Dict[str, Any], args: List[str]):
if not vars.get('RAINING_TACOS', False):
vlc.MediaPlayer("resources/raining_tacos.mp3").play()
return True
return False
#1
: imports the VLC package.#6
: creates a VLC media player and plays the specified MP3 file.
When you run the above dialogue flow, it now plays the MP3 file and prompts the lyric.
Time
The transitions in Section 4.1 prompt the same time (3PM) upon every request. Let us create a new macro that checks the current (system) time:
import time
class MacroTime(Macro):
def run(self, ngrams: Ngrams, vars: Dict[str, Any], args: List[str]):
current_time = time.strftime("%H:%M")
return "It's currently {}.".format(current_time)
#1
: imports the time package.#5
: retrieves the current time in the specified format using thestrftime
method.#6
: returns the current time using thestr.format
method.
The macro MacroTime
can be called to generate the system output:
transitions = {
'state': 'start',
'`What can I do for you?`': {
'[play, [!{#LEM(rain), rainy}, #LEM(taco)]]': {
'state': 'play_raining_tacos',
'#IF($RAINING_TACOS) `Don\'t make me sing this again!`': 'start',
'#IF(#PLAY_RAINING_TACOS) `It\'s raining tacos. From out of the sky ...` #SETBOOL(RAINING_TACOS, True)': 'start',
},
'[{time, clock}]': {
'state': 'time',
'#TIME': 'end'
},
'#UNX': {
'`Thanks for sharing.`': 'start'
},
}
}
macros = {
'SETBOOL': MacroSetBool(),
'PLAY_RAINING_TACOS': MacroPlayRainingTacos(),
'TIME': MacroTime()
}
#11
: calls theTIME
macro to generate the system output displaying the current time.#22
: adds#TIME
to the macro dictionary.
S: What can I do for you?
U: What time is it now?
S: It's currently 05:27.
Weather
The transitions in Section 4.1 prompt the same weather (sunny) upon every request. Let us retrieve the latitude and the longitude of the system using Google Maps:

Then, use a web API provided by the National Weather Service to retrieve the grid correlates to the coordinate: https://api.weather.gov/points/33.7904,-84.3266
{
...,
"properties": {
"@id": "https://api.weather.gov/points/33.7904,-84.3266",
...,
"forecast": "https://api.weather.gov/gridpoints/FFC/52,88/forecast",
...
}
}
Look for the
forecast
field underproperties
: https://api.weather.gov/gridpoints/FFC/52,88/forecast
Write a macro that retrieves the current weather for the grid using another web API:
import json
import requests
class MacroWeather(Macro):
def run(self, ngrams: Ngrams, vars: Dict[str, Any], args: List[Any]):
url = 'https://api.weather.gov/gridpoints/FFC/52,88/forecast'
r = requests.get(url)
d = json.loads(r.text)
periods = d['properties']['periods']
today = periods[0]
return today['detailedForecast']
#1
: imports the json package.#2
: imports the requests package.#6
: specifies the forecast URL.#7
: retrieves the content from the URL in JSON.#8
: saves the JSON content to a dictionary.#9
: retrieves forecasts for all supported periods.#10
: retrieves the forecast for today.#11
: returns today's forecast.
Finally, update the transitions with the weather macro:
transitions = {
'state': 'start',
'`What can I do for you?`': {
'[play, [!{#LEM(rain), rainy}, #LEM(taco)]]': {
'state': 'play_raining_tacos',
'#IF($RAINING_TACOS) `Don\'t make me sing this again!`': 'start',
'#IF(#PLAY_RAINING_TACOS) `It\'s raining tacos. From out of the sky ...` #SETBOOL(RAINING_TACOS, True)': 'start',
},
'[{time, clock}]': {
'state': 'time',
'#TIME': 'end'
},
'[{weather, forecast}]': {
'state': 'weather',
'#WEATHER': 'end'
},
'#UNX': {
'`Thanks for sharing.`': 'start'
},
}
}
macros = {
'SETBOOL': MacroSetBool(),
'PLAY_RAINING_TACOS': MacroPlayRainingTacos(),
'TIME': MacroTime(),
'WEATHER': MacroWeather()
}
#15
: calls theWEATHER
macro to generate the system output displaying today's weather.#22
: adds#WEATHER
to the macro dictionary.
S: What can I do for you?
U: How is the weather today?
S: A chance of rain showers. Cloudy, with a high near 68. South wind 0 to 5 mph. Chance of precipitation is 30%.
Last updated
Was this helpful?