ovos-yes-no-plugin
August 1, 2026 · View on GitHub
A heuristic yes/no answer classifier for OpenVoiceOS.
Given a question and a spoken or typed response, it returns True (yes), False (no), or None (unclear). It handles common variants (yeah, sure, nah), double negatives (not a lie → yes), and language-specific word lists for 20+ locales.
Role in OVOS
OVOS skills ask yes/no questions through OVOSSkill.ask_yesno() in
ovos-workshop:
# ovos_workshop/skills/ovos.py
def ask_yesno(self, prompt: str, data=None) -> Optional[str]:
resp = self.get_response(dialog=prompt, data=data)
engine = self._get_yesno_engine()
answer = engine.yes_or_no(question=prompt, response=resp, lang=self.lang)
if answer is True:
return "yes"
elif answer is False:
return "no"
else:
return resp # pass-through when answer is ambiguous
_get_yesno_engine() loads the plugin named ovos-yes-no-plugin by
default. This package provides that plugin. The active engine can be overridden
per-skill in settings.json or system-wide in mycroft.conf:
# mycroft.conf
{
"skills": {
"ask_yesno_plugin": "ovos-yes-no-plugin"
}
}
# skill settings.json (overrides the system default for this skill only)
{
"ask_yesno_plugin": "my-custom-yesno-plugin"
}
Install
pip install ovos-yes-no-plugin
Usage
Inside an OVOS skill
class MySkill(OVOSSkill):
def handle_intent(self, message):
answer = self.ask_yesno("confirm.action") # plays TTS, waits for reply
if answer == "yes":
self.speak_dialog("confirmed")
elif answer == "no":
self.speak_dialog("cancelled")
else:
self.speak_dialog("didnt.understand")
ask_yesno returns "yes", "no", or the raw utterance when the engine
returns None.
Direct use
from ovos_yes_no import HeuristicYesNoEngine
engine = HeuristicYesNoEngine()
engine.yes_or_no("Do you want to continue?", "yes", "en-us") # True
engine.yes_or_no("Do you want to continue?", "no way", "en-us") # False
engine.yes_or_no("Do you want to continue?", "beans", "en-us") # None
engine.yes_or_no("Do you want to continue?", "it's not a lie", "en-us") # True (double negative)
engine.yes_or_no("Do you want to continue?", "yes, but actually, no", "en-us") # False (last word wins)
Via ovos-plugin-manager
from ovos_plugin_manager.agents import load_yesno_plugin
cls = load_yesno_plugin("ovos-yes-no-plugin")
engine = cls()
result = engine.yes_or_no("Shall I set a reminder?", "please do", "en-us") # True
Configuration
No mandatory configuration. An optional config dict is accepted by the
constructor. The only supported key is lang, which sets the default language
when yes_or_no is called without an explicit lang argument:
engine = HeuristicYesNoEngine(config={"lang": "de-de"})
Algorithm
Source: HeuristicYesNoEngine.yes_or_no
The engine scans the normalised response against a per-language
locale/<lang>/yesno.json resource file containing four word lists:
| Key | Role |
|---|---|
yes | Unambiguous affirmatives: yes, yeah, affirmative, ... |
no | Unambiguous negatives: no, nah, disagree, ... |
neutral_yes | Soft affirmatives counted only when no no word is present: sure, please, ... |
neutral_no | Soft negatives counted only when no yes word is present: wrong, mistake, lie, ... |
Decision rules (in order):
- Last word wins. When both a
yesword and anoword appear, the one at the higher character index is taken as the user's final intent. - Double negatives. A
no-category word immediately followed by aneutral_noword (e.g.,not+lie→"not a lie") is interpreted as affirmative. - Neutral words. If neither a
yesnor anoword was found,neutral_yesandneutral_nowords are considered as weak signals. - Default. No recognised word → return
None.
Language matching uses langcodes.tag_distance to find the closest available
locale, so en-AU silently falls back to en-US.
Supported Languages
an, az, ca-ES, cs-CZ, da-DK, de-DE, en-US, es-ES, eu-ES,
fa-IR, fr-FR, hu-HU, it-IT, nl-NL, pl-PL, pt-BR, ru-RU,
sv-SE, tr-TR, uk-UA
Adding a Language
Create ovos_yes_no/locale/<lang-TAG>/yesno.json with the four word lists
described above:
neutral_yes: mild agreement words that are positive but not direct synonyms of "yes" (e.g., French"bien sur", Portuguese"claro").neutral_no: words that imply disapproval indirectly (e.g., French"mensonge", Portuguese"errado").- Double-negative structures vary widely between languages. Test them explicitly.
Limitations
- No sarcasm or idiom detection.
- Vocabulary is limited to the words in the resource files. Unlisted slang is ignored.
- Complex nested negations beyond one level may yield incorrect results.
- Missing or incomplete resource files cause the engine to return
Nonefor that language rather than raising an error.
Related Projects
- OpenVoiceOS/ovos-workshop — the skill base class that calls this plugin through
ask_yesno(). - OpenVoiceOS/ovos-plugin-manager — loads this plugin by name at runtime.
License
Apache 2.0