OVOSSkill provides two high-level methods for collecting structured user input: ask_yesno for binary yes/no questions and ask_selection for multiple-choice prompts. Both are backed by pluggable agent engines that can be swapped per-skill or system-wide.
OVOSSkill.ask_yesno(prompt: str, data: Optional[dict] = None) -> Optional[str]Speaks prompt, waits for the user's response, and classifies it as "yes", "no", or the raw response string if neither matched.
Source: OVOSSkill.ask_yesno — ovos_workshop/skills/ovos.py
| Parameter | Type | Description |
|---|---|---|
prompt |
str |
Dialog ID (looked up in locale/) or a literal string to speak. |
data |
dict | None |
Template variables for Mustache rendering of the dialog string. |
| Value | Meaning |
|---|---|
"yes" |
User confirmed (e.g. "yeah", "sure", "of course") |
"no" |
User declined (e.g. "nope", "nah", "definitely not") |
str |
User spoke something that could not be classified — raw transcript returned |
None |
No response received (timeout or user said nothing) |
class MySkill(OVOSSkill):
def handle_delete_intent(self, message):
if self.ask_yesno("confirm_delete") == "yes":
self._do_delete()
self.speak_dialog("deleted")
else:
self.speak_dialog("cancelled")locale/en-us/confirm_delete.dialog:
Are you sure you want to delete this?
Do you really want to delete it?
answer = self.ask_yesno("confirm_action", data={"action": "restart the server"})locale/en-us/confirm_action.dialog:
Are you sure you want to {{action}}?
answer = self.ask_yesno("do_you_want_music")
if answer == "yes":
self.play_music()
elif answer == "no":
self.speak_dialog("okay_nevermind")
elif answer is None:
self.speak_dialog("no_response")
else:
# answer is the raw transcript — user said something unexpected
self.speak_dialog("did_not_understand")get_response(dialog=prompt, data=data)— speaks the prompt, records user reply._get_yesno_engine()— loads the configuredYesNoEngineplugin (if any).- If a plugin is loaded:
engine.yes_or_no(question=prompt, response=resp, lang=self.lang)→True,False, orNone. - If no plugin:
YesNoSolver().match_yes_or_no(resp, lang=self.lang)(built-in fallback, always available). True→"yes",False→"no",None/unmatched → raw response.
OVOSSkill.ask_selection(
options: List[str],
dialog: str = '',
data: Optional[dict] = None,
min_conf: float = 0.65,
numeric: bool = False,
num_retries: int = -1,
) -> Optional[str]Speaks the options list to the user, optionally follows with a dialog prompt, then resolves the user's spoken response to one of the options.
Source: OVOSSkill.ask_selection — ovos_workshop/skills/ovos.py
| Parameter | Type | Default | Description |
|---|---|---|---|
options |
List[str] |
— | The predefined options to offer. |
dialog |
str |
'' |
Dialog ID or literal string spoken after the options list. |
data |
dict | None |
None |
Template variables for the dialog string. |
min_conf |
float |
0.65 |
Minimum fuzzy-match confidence for the default plugin. Passed to the OptionMatcherEngine config if no plugin-level config overrides it. |
numeric |
bool |
False |
If True, speaks each option prefixed with its number ("one, pizza; two, pasta; …"). If False, speaks them as a joined list ("pizza, pasta, or salad?"). |
num_retries |
int |
-1 |
How many times to re-prompt on no response. -1 means use the system default. |
| Value | Meaning |
|---|---|
str |
One of the strings from options, exactly as provided. |
None |
No match, no response, or plugin failure. |
Special cases handled before user interaction:
- Empty
options→Noneimmediately. - Single-element
options→ returns that element immediately (no prompt).
class MySkill(OVOSSkill):
def handle_transport_intent(self, message):
modes = ["bus", "train", "bicycle"]
choice = self.ask_selection(modes, dialog="which_transport")
if choice:
self.speak_dialog("you_chose", {"mode": choice})locale/en-us/which_transport.dialog:
Which would you prefer?
How would you like to travel?
The skill speaks: "bus, train, or bicycle? Which would you prefer?"
The user can say:
"train"— fuzzy-matched directly"the second one"/"number two"/"two"— position matched"the last one"— last-word matched"option 3"— numeric matched (requiresovos-number-parser)
choice = self.ask_selection(options, numeric=True)Speaks each option as: "one, bus; two, train; three, bicycle". Useful when options are long or ambiguous. The user can then say "two" or "the second one".
choice = self.ask_selection(options, dialog="which_one", num_retries=1)
if choice is None:
self.speak_dialog("could_not_understand")
return- Validates
options(raisesValueErrorif not a list; returns immediately for 0 or 1 items). - Speaks options (as list or numbered menu based on
numeric). get_response(dialog=dialog, data=data, num_retries=num_retries)— speaks optional follow-up dialog, records reply._get_selection_engine()— loads the configuredOptionMatcherEngineplugin.engine.match_option(utterance=resp, options=options, lang=self.lang)— resolves response to a slot.- If the engine raises or returns
None,ask_selectionreturnsNone.
Both methods are backed by pluggable agent engines discovered and loaded via ovos-plugin-manager.
Plugin type: YesNoEngine (opm.agents.yesno)
Config key: ask_yesno_plugin
Built-in fallback: ovos-solver-yes-no-plugin (always available, no config needed)
YesNoEngine plugins implement:
def yes_or_no(self, question: str, response: str, lang: Optional[str] = None) -> Optional[bool]:
... # True = yes, False = no, None = unclearThe question argument gives the plugin context about what was asked, enabling smarter inference (e.g. an LLM-backed plugin could use it to resolve ambiguous answers).
Available plugins:
| Plugin | Description |
|---|---|
ovos-solver-yes-no-plugin |
Rule-based multilingual yes/no classifier (default fallback) |
Plugin type: OptionMatcherEngine (opm.agents.option_matcher)
Config key: ask_selection_plugin
Default plugin: ovos-option-matcher-fuzzy-plugin (installed as a dependency of ovos-workshop)
OptionMatcherEngine plugins implement:
def match_option(self, utterance: str, options: List[str], lang: Optional[str] = None) -> Optional[str]:
... # returns one of options, or NoneAvailable plugins:
| Plugin | Description |
|---|---|
ovos-option-matcher-fuzzy-plugin |
Fuzzy + ordinal/cardinal vocab + numeric fallback. Default. |
{
"skills": {
"ask_yesno_plugin": "ovos-solver-yes-no-plugin",
"ask_selection_plugin": "ovos-option-matcher-fuzzy-plugin"
}
}ask_yesno_plugin defaults to None (built-in YesNoSolver used). ask_selection_plugin defaults to ovos-option-matcher-fuzzy-plugin.
Place in the skill's settings.json to override for that skill only:
{
"ask_yesno_plugin": "my-llm-yesno-plugin",
"ask_selection_plugin": "my-embedding-option-matcher"
}Pass configuration to the plugin via the same settings block:
{
"ask_selection_plugin": "ovos-option-matcher-fuzzy-plugin",
"ask_selection_plugin_config": {
"min_conf": 0.80
}
}settings.json > mycroft.conf skills block > built-in default
Plugins are loaded lazily on first use and cached per plugin name for the lifetime of the skill instance.
# my_yesno/__init__.py
from typing import Optional
from ovos_plugin_manager.templates.agents import YesNoEngine
class MyYesNoPlugin(YesNoEngine):
def yes_or_no(self, question: str, response: str,
lang: Optional[str] = None) -> Optional[bool]:
r = response.lower()
if "yes" in r or "sure" in r:
return True
if "no" in r or "never" in r:
return False
return None # unclear# pyproject.toml
[project.entry-points."opm.agents.yesno"]
my-yesno-plugin = "my_yesno:MyYesNoPlugin"Activate for a skill:
{ "ask_yesno_plugin": "my-yesno-plugin" }# my_matcher/__init__.py
from typing import List, Optional
from ovos_plugin_manager.templates.agents import OptionMatcherEngine
class MyOptionMatcher(OptionMatcherEngine):
def match_option(self, utterance: str, options: List[str],
lang: Optional[str] = None) -> Optional[str]:
# example: embedding similarity via a local model
scores = self._embed_and_score(utterance, options)
best_idx = max(range(len(scores)), key=lambda i: scores[i])
if scores[best_idx] >= self.config.get("min_conf", 0.6):
return options[best_idx]
return None# pyproject.toml
[project.entry-points."opm.agents.option_matcher"]
my-option-matcher-plugin = "my_matcher:MyOptionMatcher"Activate system-wide:
{ "skills": { "ask_selection_plugin": "my-option-matcher-plugin" } }| Scenario | ask_yesno result | ask_selection result |
|---|---|---|
| User says nothing (timeout) | None |
None |
| User response unclassifiable | raw transcript string | None |
| Plugin fails to load | falls back to YesNoSolver |
None |
| Plugin raises at runtime | falls back to YesNoSolver |
None |
| No plugin configured | YesNoSolver used |
default fuzzy plugin used |
ask_selection is intentionally strict: any failure returns None rather than guessing. Always handle the None case in your skill.
ovos-solver-yes-no-plugin— built-in yes/no classifierovos-option-matcher-fuzzy-plugin— default selection plugin, with full docsOVOSSkill.get_response— lower-level method used internally by both- OPM agent templates —
YesNoEngine,OptionMatcherEnginebase classes