Statements¶
In a nutshell
A "statement" is simply something the assistant says out loud. Instead of writing the exact words inside the program, OVOS keeps them in plain-text "dialog" files โ one phrase per line โ so the same skill can speak any language and pick from several wordings to sound less robotic. This page walks through writing those reply files, slotting in details like a name or a number, and waiting for speech to finish. For the broader file layout see Resource Files; for term definitions see the Glossary.
๐ Formal specification
The .dialog file is a formal resource format.
OVOS-INTENT-2 โ Locale Resource Formats
fixes the locale/ folder layout and the plain-text format of .dialog
(and its siblings .intent, .voc, .entity, .prompt), while
OVOS-INTENT-1 โ Sentence Template Grammar
defines the template grammar inside each line โ including the {variables}
(named slots) filled in by the caller before the text is spoken. For the
full set see the spec index.
Speaking a statement¶
One of OVOS's most important core capabilities is converting text to speech: speaking a statement.
Within a Skill's Intent handler, you can pass a string of text to OVOS and OVOS speaks it. For example: self.speak('this is my statement'). This is useful for experimenting, but hard-coded strings of text do not make Mycroft a multilingual product. OVOS has a design pattern for multilingualism instead.
Multilingualism¶
To support multilingualism, the text that OVOS speaks must come from a file. That file is called a dialog file. The dialog file contains statements (lines of text) that a listener in a particular language would consider equivalent. For instance, in USA English, the statements "I am okay" and "I am fine" are equivalent. Both of these statements might appear in a dialog file used for responding to the USA English question: "How are you?".
By convention, the dialog filename is formed by dot connected words and must end with ".dialog". The dialog filename should describe the contents as a whole. Sometimes the filename describes the question being answered, and other times it describes the answer itself. For the example above, the dialog filename might be how.are.you.dialog or i.am.fine.dialog.
Multilingualism is accomplished by translating the dialog files into other languages, and storing them in their own directory named for the country and language. The filenames remain the same. Using the same filenames in separate language-dependent directories keeps Skills language-agnostic, with no hard-coded text strings. Adjust the language setting for your device, and OVOS uses the corresponding set of dialog files. If the desired file does not exist in the directory for that language, OVOS uses the file from the USA English directory.
As an example of the concept, the contents of how.are.you.dialog in the directory for the French language in France (fr-fr) might include the statement: "Je vais bien".
The Tomato Skill Revisited¶
To demonstrate the multilingualism design pattern, we examine the usage of the speak_dialog() method in the Tomato Skill.
The Tomato Skill has two Intents: one demonstrates simple, straightforward statements, and the other demonstrates the use of variables within a statement.
Simple statement¶
The first Intent within the Tomato Skill, what.is.a.tomato.intent, handles inquiries about tomatoes, and the dialog file, tomato.description.dialog, provides the statements for OVOS to speak in reply to that inquiry.
Sample contents of the Intent and dialog files:
what.is.a.tomato.intent
tomato.description.dialog
The tomato is a fruit of the nightshade family
A tomato is an edible berry of the plant Solanum lycopersicum
A tomato is a fruit but nutrionists consider it a vegetable
Observe the statements in the tomato.description.dialog file. They are all acceptable answers to the question: "What is a tomato?" Providing more than one statement in a dialog file is one way to make OVOS sound less robotic and more natural. OVOS randomly selects one of the statements.
The Tomato Skill code snippet:
@intent_handler('what.is.a.tomato.intent')
def handle_what_is(self, message):
"""Speaks a statement from the dialog file."""
self.speak_dialog('tomato.description')
With the Tomato Skill installed, if the User says "Hey Mycroft, what is a tomato?", the Intent handler method handle_what_is() is called.
Inside handle_what_is(), we find: self.speak_dialog('tomato.description')
The parameter 'tomato.description' is the dialog filename without the ".dialog" extension. Calling this method opens the dialog file, selects one of the statements, and converts that text to speech. OVOS speaks a statement from the dialog file. In this example, OVOS might say "The tomato is a fruit of the nightshade family".
Remember, OVOS has a language setting that determines from which directory to find the dialog file.
File locations¶
The Skill Structure section describes where to place the Intent file and
dialog file. Put both in locale/en-us.
You will also see an older split layout in existing skills: the dialog file in dialog/en-us
and the intent file in vocab/en-us. dialog and vocab are deprecated. They still work,
so a skill you are reading may use them, but do not start a new skill that way. If both layouts
exist for the same resource, locale wins.
Statements with variables¶
The second template intent (Padatious), do.you.like.intent, demonstrates the use of variables in the Intent file and in one of the dialog files:
do.you.like.intent
like.tomato.type.dialog
like.tomato.generic.dialog
Compare these two dialog files. The like.tomato.generic.dialog file contains only simple statements. The statements in the like.tomato.type.dialog file include a variable named type. The variable is a placeholder in the statement specifying where text may be inserted. The speak_dialog() method accepts a dictionary as an optional parameter. If that dictionary contains an entry for a variable named in the statement, the value from the dictionary is inserted at the placeholder's location.
Dialog file variables are formed by surrounding the variable's name with curly braces. In OVOS parlance, curly braces are known as a mustache.
For multi-line dialog files, be sure to include the same variable on all lines.
The Tomato Skill code snippet:
@intent_handler('do.you.like.intent')
def handle_do_you_like(self, message):
tomato_type = message.data.get('type')
if tomato_type is not None:
self.speak_dialog('like.tomato.type',
{'type': tomato_type})
else:
self.speak_dialog('like.tomato.generic')
When the User says "Hey Mycroft, do you like RED tomatoes?", the second of the two Intent lines, "do you like {type} tomatoes", is recognized by Mycroft. The value 'RED' is returned in the message dictionary assigned to the 'type' entry when handle_do_you_like() is called.
The line tomato_type = message.data.get('type') extracts the value from the dictionary for the entry 'type'. In this case, the variable tomato_type receives the value 'RED'. speak_dialog() is called with the 'like.tomato.type' dialog file, and a dictionary with 'RED' assigned to 'type'. The statement "I do like {type} tomatoes" might be randomly selected. After inserting the value 'RED' for the placeholder variable {type}, OVOS would say: "I do like RED tomatoes".
Should the User say "Hey Mycroft, do you like tomatoes?", the first line in the Intent file, "do you like tomatoes", is recognized. There is no variable in this line. When handle_do_you_like() is called, the dictionary in the message is empty. This means tomato_type is None. speak_dialog('like.tomato.generic') is called, and Mycroft might reply with "Yes, I do like tomatoes".
Waiting for speech¶
By default, the speak_dialog() method is non-blocking. Any code following the call to speak_dialog() executes while OVOS is talking. This lets your Skill perform actions while it is speaking.
Rather than telling the User that we are fetching some data, then going out to fetch it, we can do both things at once for a better experience.
However, there are times when we need to wait until the statement has been spoken before doing something else. We have two options for this.
Wait Parameter¶
We can pass a wait=True parameter to our speak_dialog() method. This makes the method blocking and no other code will execute until the statement has been spoken.
@intent_handler('what.is.a.tomato.intent')
def handle_what_is(self, message):
"""Speaks a statement from the dialog file.
Waits (i.e. blocks) within speak_dialog() until
the speaking has completed. """
self.speak_dialog('tomato.description', wait=True)
self.log.info("I waited for you")
Using translatable resources¶
There may be a situation where the dialog file and the speak_dialog() method do not give the Skill enough flexibility. For instance, there may be a need to manipulate the statement from the dialog file before having it spoken by OVOS, or to load a list/lookup table of localized strings.
Use the skill's SkillResources object, available as self.resources. It is initialized per language for the current Session and exposes loaders for each resource type. Each loader uses the same locale/<lang>/ directory system, so the right localized file is chosen automatically.
# render a random line from a .dialog file (with mustache substitution),
# WITHOUT speaking it
text = self.resources.render_dialog("how.are.you", {"name": "Alice"})
# load a list of strings from a .list file
items = self.resources.load_list_file("options")
# load a {name: value} lookup from a .value file (CSV-style entries)
mapping = self.resources.load_named_value_file("synonyms")
# load lines from a .template file
templates = self.resources.load_template_file("greeting")
The legacy
self.translate(),self.translate_list(),self.translate_namedvalue()andself.translate_template()helper methods have been removed fromOVOSSkill. Use theself.resources.*loaders shown above instead.
Source code: OpenVoiceOS/ovos-workshop.
Read next: Skill Settings Related: Session Aware Skills ยท Asking the User for Responses in OVOS Skills ยท Dialog Transformers ยท Resource Files