Skip to content

Commit

Permalink
add adapt match levels (#402)
Browse files Browse the repository at this point in the history
* add adapt match levels

* add limit default, fix handle_get_adapt

* adjust docstring

* infuse session

* commando back with lru cache

* fixes left and right

* fix type hint

* fix namedtuple

* adjust stop test

* Update requirements.txt

---------

Co-authored-by: JarbasAI <[email protected]>
  • Loading branch information
emphasize and JarbasAl authored Jan 24, 2024
1 parent 05c5876 commit 8bba0aa
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 25 deletions.
8 changes: 5 additions & 3 deletions ovos_core/intent_services/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,14 +223,16 @@ def get_pipeline(self, skips=None, session=None):
"stop_low": self.stop.match_stop_low,
"padatious_high": padatious_matcher.match_high,
"padacioso_high": self.padacioso_service.match_high,
"adapt": self.adapt_service.match_intent,
"adapt_high": self.adapt_service.match_high,
"common_qa": self.common_qa.match,
"fallback_high": self.fallback.high_prio,
"padatious_medium": padatious_matcher.match_medium,
"padacioso_medium": self.padacioso_service.match_medium,
"adapt_medium": self.adapt_service.match_medium,
"fallback_medium": self.fallback.medium_prio,
"padatious_low": padatious_matcher.match_low,
"padacioso_low": self.padacioso_service.match_low,
"adapt_low": self.adapt_service.match_low,
"fallback_low": self.fallback.low_prio
}
skips = skips or []
Expand Down Expand Up @@ -494,15 +496,15 @@ def handle_get_active_skills(self, message):
"""
self.converse.handle_get_active_skills(message)

def handle_get_adapt(self, message):
def handle_get_adapt(self, message: Message):
"""handler getting the adapt response for an utterance.
Args:
message (Message): message containing utterance
"""
utterance = message.data["utterance"]
lang = get_message_lang(message)
intent = self.adapt_service.match_intent([utterance], lang, message)
intent = self.adapt_service.match_intent((utterance,), lang, message.serialize())
intent_data = intent.intent_data if intent else None
self.bus.emit(message.reply("intent.service.adapt.reply",
{"intent": intent_data}))
Expand Down
75 changes: 66 additions & 9 deletions ovos_core/intent_services/adapt_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@
#
"""An intent parsing service using the Adapt parser."""
from threading import Lock
from functools import lru_cache
from typing import List, Tuple, Optional

from adapt.engine import IntentDeterminationEngine
from ovos_config.config import Configuration

from ovos_bus_client.message import Message
import ovos_core.intent_services
from ovos_bus_client.session import IntentContextManager as ContextManager, \
SessionManager
Expand Down Expand Up @@ -57,6 +59,11 @@ def __init__(self, config=None):
self.lock = Lock()
self.max_words = 50 # if an utterance contains more words than this, don't attempt to match

# TODO sanitize config option
self.conf_high = self.config.get("conf_high") or 0.95
self.conf_med = self.config.get("conf_med") or 0.8
self.conf_low = self.config.get("conf_low") or 0.5

@property
def context_keywords(self):
LOG.warning(
Expand Down Expand Up @@ -127,17 +134,65 @@ def update_context(self, intent):
sess = SessionManager.get()
ents = [tag['entities'][0] for tag in intent['__tags__'] if 'entities' in tag]
sess.context.update_context(ents)

def match_high(self, utterances: List[str],
lang: Optional[str] = None,
message: Optional[Message] = None):
"""Intent matcher for high confidence.
Args:
utterances (list of tuples): Utterances to parse, originals paired
with optional normalized version.
"""
match = self.match_intent(tuple(utterances), lang, message.serialize())
if match and match.intent_data.get("confidence", 0.0) > self.conf_high:
return match
return None

def match_medium(self, utterances: List[str],
lang: Optional[str] = None,
message: Optional[Message] = None):
"""Intent matcher for medium confidence.
def match_intent(self, utterances, lang=None, message=None):
Args:
utterances (list of tuples): Utterances to parse, originals paired
with optional normalized version.
"""
match = self.match_intent(tuple(utterances), lang, message.serialize())
if match and match.intent_data.get("confidence", 0.0) > self.conf_med:
return match
return None

def match_low(self, utterances: List[str],
lang: Optional[str] = None,
message: Optional[Message] = None):
"""Intent matcher for low confidence.
Args:
utterances (list of tuples): Utterances to parse, originals paired
with optional normalized version.
"""
match = self.match_intent(tuple(utterances), lang, message.serialize())
if match and match.intent_data.get("confidence", 0.0) > self.conf_low:
return match
return None

@lru_cache(maxsize=3)
def match_intent(self, utterances: Tuple[str],
lang: Optional[str] = None,
message: Optional[str] = None):
"""Run the Adapt engine to search for an matching intent.
Args:
utterances (iterable): utterances for consideration in intent
matching. As a practical matter, a single utterance will be
passed in most cases. But there are instances, such as
streaming STT that could pass multiple. Each utterance
is represented as a tuple containing the raw, normalized, and
possibly other variations of the utterance.
utterances (iterable): utterances for consideration in intent
matching. As a practical matter, a single utterance will
be passed in most cases. But there are instances, such as
streaming STT that could pass multiple. Each utterance is
represented as a tuple containing the raw, normalized, and
possibly other variations of the utterance.
limit (float): confidence threshold for intent matching
lang (str): language to use for intent matching
message (Message): message to use for context
Returns:
Intent structure, or None if no match was found.
Expand All @@ -160,11 +215,13 @@ def take_best(intent, utt):
nonlocal best_intent
best = best_intent.get('confidence', 0.0) if best_intent else 0.0
conf = intent.get('confidence', 0.0)
if conf > best:
if best < conf:
best_intent = intent
# TODO - Shouldn't Adapt do this?
best_intent['utterance'] = utt

if message:
message = Message.deserialize(message)
sess = SessionManager.get(message)
for utt in utterances:
try:
Expand Down
2 changes: 1 addition & 1 deletion requirements/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ adapt-parser>=1.0.0, <2.0.0
ovos-utils>=0.0.38
ovos_bus_client<0.1.0, >=0.0.8
ovos-plugin-manager<0.1.0, >=0.0.25
ovos-config~=0.0,>=0.0.12
ovos-config~=0.0,>=0.0.13a6
ovos-lingua-franca>=0.4.7
ovos-backend-client~=0.1.0
ovos-workshop<0.1.0, >=0.0.15
Expand Down
14 changes: 8 additions & 6 deletions test/end2end/session/test_stop.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,14 @@ def wait_for_n_messages(n):
"stop_high",
"converse",
"padatious_high",
"adapt",
"common_qa",
"adapt_high",
"fallback_high",
"stop_medium",
"padatious_medium",
"adapt_medium",
"adapt_low",
"common_qa",
"fallback_medium",
"padatious_low",
"fallback_low"
])

Expand Down Expand Up @@ -238,13 +239,14 @@ def wait_for_n_messages(n):
"stop_high",
"converse",
"padatious_high",
"adapt",
"common_qa",
"adapt_high",
"fallback_high",
"stop_medium",
"padatious_medium",
"adapt_medium",
"adapt_low",
"common_qa",
"fallback_medium",
"padatious_low",
"fallback_low"
])

Expand Down
18 changes: 12 additions & 6 deletions test/unittests/skills/test_intent_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ def test_keyword_backwards_compatibility(self):
)

# Check that the intent is returned
msg = Message('intent.service.adapt.get', data={'utterance': 'test'})
msg = Message('intent.service.adapt.get',
data={'utterance': 'test'})
self.intent_service.handle_get_adapt(msg)

reply = get_last_message(self.intent_service.bus)
Expand All @@ -157,7 +158,8 @@ def test_keyword_backwards_compatibility(self):
def test_get_adapt_intent(self):
self.setup_simple_adapt_intent()
# Check that the intent is returned
msg = Message('intent.service.adapt.get', data={'utterance': 'test'})
msg = Message('intent.service.adapt.get',
data={'utterance': 'test'})
self.intent_service.handle_get_adapt(msg)

reply = get_last_message(self.intent_service.bus)
Expand All @@ -168,7 +170,8 @@ def test_get_adapt_intent_no_match(self):
"""Check that if the intent doesn't match at all None is returned."""
self.setup_simple_adapt_intent()
# Check that no intent is matched
msg = Message('intent.service.adapt.get', data={'utterance': 'five'})
msg = Message('intent.service.adapt.get',
data={'utterance': 'five'})
self.intent_service.handle_get_adapt(msg)
reply = get_last_message(self.intent_service.bus)
self.assertEqual(reply.data['intent'], None)
Expand All @@ -177,7 +180,8 @@ def test_get_intent(self):
"""Check that the registered adapt intent is triggered."""
self.setup_simple_adapt_intent()
# Check that the intent is returned
msg = Message('intent.service.adapt.get', data={'utterance': 'test'})
msg = Message('intent.service.adapt.get',
data={'utterance': 'test'})
self.intent_service.handle_get_intent(msg)

reply = get_last_message(self.intent_service.bus)
Expand All @@ -188,7 +192,8 @@ def test_get_intent_no_match(self):
"""Check that if the intent doesn't match at all None is returned."""
self.setup_simple_adapt_intent()
# Check that no intent is matched
msg = Message('intent.service.intent.get', data={'utterance': 'five'})
msg = Message('intent.service.intent.get',
data={'utterance': 'five'})
self.intent_service.handle_get_intent(msg)
reply = get_last_message(self.intent_service.bus)
self.assertEqual(reply.data['intent'], None)
Expand All @@ -197,7 +202,8 @@ def test_get_intent_manifest(self):
"""Check that if the intent doesn't match at all None is returned."""
self.setup_simple_adapt_intent()
# Check that no intent is matched
msg = Message('intent.service.intent.get', data={'utterance': 'five'})
msg = Message('intent.service.intent.get',
data={'utterance': 'five'})
self.intent_service.handle_get_intent(msg)
reply = get_last_message(self.intent_service.bus)
self.assertEqual(reply.data['intent'], None)
Expand Down

0 comments on commit 8bba0aa

Please sign in to comment.