Source code for botiverse.bots.BasicTaskBot.utils

"""
This module ontains utility code used by the basic Taskbot module
such as Natural Language Understanding (NLU), Dialogue State Tracker (DST) ... etc.
"""
import re
import random

[docs]class RuleBasedNLU: """ Instantiate a Rule-Based Natural Language Understanding module. It uses Regex to extract domain and slot-fillers from the user's utterance. :param domains_pattern: Dictionary where the key is a name of a domain and the value is a Regex that is used to capture that domain. :type domains_pattern: dict[str, str] :param slots_pattern: Dictionary where the key is a name of a domain and the value is another dictionary where the key is a name of a slot inside the domain and the value is a Regex that is used to capture that slot. :type slots_pattern: dict[str, dict[str, str]] """ def __init__(self, domains_pattern, slots_pattern): self.domains_pattern = domains_pattern self.slots_pattern = slots_pattern
[docs] def get_domain(self, prev_domain, utterance): """ Takes previously extracted domain and the user's utterance and extract the domain from it, if no domain can be extracted it returns prev_domain. :param prev_domain: The domain from previous user's utterance. :type prev_domain: str :param utterance: The user's utterance. :type utterance: str :return: The success of the domain extraction & The extracted domain. :rtype: tuple[bool, str] """ detected = [] for domain, pattern in self.domains_pattern.items(): if re.search(pattern, utterance): detected.append(domain) if len(detected) > 1: return False, "" if len(detected) == 0: return True, prev_domain return True, detected[0]
[docs] def get_slot_fillers(self, current_domain, utterance): """ Takes the current extracted domain and the user's utterance and extract the slot-fillers. :param current_domain: The domain extracted from the current user's utterance. :type utterance: str :param utterance: The user's utterance. :type utterance: str :return: The success of the slot-fillers extraction, the extracted slots & the extracted slot-values. :rtype: tuple[bool, list[str], list[str]] """ slots, values = [], [] for slot, pattern in self.slots_pattern[current_domain].items(): matches = set() for match in re.finditer(pattern, utterance): matches.add(" ".join(match.groups())) if len(matches) > 1: return False, [], [] if len(matches) == 1: slots.append(slot) values.append(list(matches)[0]) return True, slots, values
[docs]class MostRecentDST: """ Instantiate a Dialogue State Tracker module. It maintains the state of the dialogue which is mainly represented in the slots and the slots-values that are extracted from the user's utterances. It is a most recent tracker which means it only keeps track of the recent value in case if multiple values appear for the same slot. :param domains_slots: The slots of each domain in the system. :type domains_slots: dict[str, list[str]] """ def __init__(self, domains_slots): self.state = {} for domain, slots in domains_slots.items(): self.state[domain] = {} for slot in slots: self.state[domain][slot] = None
[docs] def update_state(self, domain, slots, values): """ Update the slots values of a certain domain. :param domain: The domain of the slots to be updated. :type domain: str :param slots: List the of the slots to be updated. :type slots: list[str] :param values: List of the values of the slots. :type values: list[str] """ for i, slot in enumerate(slots): self.state[domain][slot] = values[i]
[docs] def get_dialogue_state(self): """ Gets the dialogue state. :return: The dialogue state, where state["domain"]["slot"] indicates value of slot "slot" in domain "domain". :rtype: dict[str, dict[str, str]] """ return self.state
[docs] def is_all_slots_filled(self, domain): """ Checks if all slots in a certain domain are filled. :param domain: The domain to be checked. :type domain: str :return: The success of the check. :rtype: bool """ for slot, value in self.state[domain].items(): if value is None: return False return True
[docs] def reset(self, domain=None, slot=None): """ Reset the dialogue state. Note: if the domain or the slot is equal to None all domains or slots will be reset respectively. :param domain: The domain to be reset, defaults to None. :type domain: str :param slot: The slot to be reset, defaults to None. :type slot: str """ if domain is None and slot is None: for cur_domain, slot_filler in self.state.items(): for cur_slot in slot_filler: self.state[cur_domain][cur_slot] = None elif domain is not None and slot is None: for cur_slot in self.state[domain]: self.state[domain][cur_slot] = None elif domain is None and slot is not None: for cur_domain, slot_filler in self.state.items(): if slot in slot_filler: self.state[cur_domain][slot] = None else: self.state[domain][slot] = None
[docs]class RandomDP: """ Instantiate a Random Dialogue Policy module. Randomly determines the next action which is the next empty slot to ask user about. """
[docs] def get_action(self, current_domain, state): """ Takes the current domain and dialogue state and return the next action which is the next empty slot to ask user about. :param current_domain: The current extracted domain. :type current_domain: str :param state: The current state of the dialogue. :type state: dict[str, dict[str, str]] :return: The next empty slot to ask the user about. :rtype: str """ unfilled = [] for slot, value in state[current_domain].items(): if value is None: unfilled.append(slot) max_len = len(unfilled) if max_len == 0: return "ALL-FILLED" return unfilled[random.randint(0, max_len-1)]
[docs]class TemplateBasedNLG: """ Instantiate a Template Based Natural Language Generation module. It uses a predefined templates to generate a response to the user and ask about empty slots. :param templates: The predefined templates, where templates["domain"]["slot"] is a list of questions asking about slot "slot" in domain "domain". :type templates: dict[str, dict[str, list[str]]] """ def __init__(self, templates): self.templates = templates
[docs] def generate(self, domain, slot): """ Takes a slot and the domain of the slot and generate a question randomly from the templates about the slot. :param domain: The domain of the slost. :type domain: str :param slot: The slot. :type slot: str :return: The generated question. :rtype: str """ response = '' if slot == "ALL-FILLED": if "ALL-FILLED" in self.templates[domain]: max_len = len(self.templates[domain]["ALL-FILLED"]) response = self.templates[domain]["ALL-FILLED"][random.randint(0, max_len-1)] else: max_len = len(self.templates[domain][slot]) response = self.templates[domain][slot][random.randint(0, max_len-1)] return response