diff --git a/chatterbot/chatterbot.py b/chatterbot/chatterbot.py index eb969983a..e3d41a781 100644 --- a/chatterbot/chatterbot.py +++ b/chatterbot/chatterbot.py @@ -20,7 +20,7 @@ def __init__(self, name, **kwargs): storage_adapter = kwargs.get('storage_adapter', 'chatterbot.storage.JsonFileStorageAdapter') logic_adapters = kwargs.get('logic_adapters', [ - 'chatterbot.logic.ClosestMatchAdapter' + 'chatterbot.logic.BestMatch' ]) input_adapter = kwargs.get('input_adapter', 'chatterbot.input.VariableInputTypeAdapter') diff --git a/chatterbot/comparisons.py b/chatterbot/comparisons.py new file mode 100644 index 000000000..bad5bcf9a --- /dev/null +++ b/chatterbot/comparisons.py @@ -0,0 +1,214 @@ +# -*- coding: utf-8 -*- + +""" +This module contains various text-comparison algorithms +designed to compare one statement to another. +""" + + +def levenshtein_distance(statement, other_statement): + """ + Compare two statements based on the Levenshtein distance + of each statement's text. + + For example, there is a 65% similarity between the statements + "where is the post office?" and "looking for the post office" + based on the Levenshtein distance algorithm. + + :return: The percent of similarity between the text of the statements. + :rtype: float + """ + import sys + + # Use python-Levenshtein if available + try: + from Levenshtein.StringMatcher import StringMatcher as SequenceMatcher + except ImportError: + from difflib import SequenceMatcher + + PYTHON = sys.version_info[0] + + # Return 0 if either statement has a falsy text value + if not statement.text or not statement.text: + return 0 + + # Get the lowercase version of both strings + if PYTHON < 3: + statement_text = unicode(statement.text.lower()) + other_statement_text = unicode(other_statement.text.lower()) + else: + statement_text = str(statement.text.lower()) + other_statement_text = str(other_statement.text.lower()) + + similarity = SequenceMatcher( + None, + statement_text, + other_statement_text + ) + + # Calculate a decimal percent of the similarity + percent = int(round(100 * similarity.ratio())) / 100.0 + + return percent + + +def synset_distance(statement, other_statement): + """ + Calculate the similarity of two statements. + This is based on the total maximum synset similarity between each word in each sentence. + + This algorithm uses the `wordnet`_ functionality of `NLTK`_ to determine the similarity + of two statements based on the path similarity between each token of each statement. + This is essentially an evaluation of the closeness of synonyms. + + :return: The percent of similarity between the closest synset distance. + :rtype: float + + .. _wordnet: http://www.nltk.org/howto/wordnet.html + .. _NLTK: http://www.nltk.org/ + """ + from nltk.corpus import wordnet + from nltk import word_tokenize + from chatterbot import utils + import itertools + + tokens1 = word_tokenize(statement.text.lower()) + tokens2 = word_tokenize(other_statement.text.lower()) + + # Remove all stop words from the list of word tokens + tokens1 = utils.remove_stopwords(tokens1, language='english') + tokens2 = utils.remove_stopwords(tokens2, language='english') + + # The maximum possible similarity is an exact match + # Because path_similarity returns a value between 0 and 1, + # max_possible_similarity is the number of words in the longer + # of the two input statements. + max_possible_similarity = max( + len(statement.text.split()), + len(other_statement.text.split()) + ) + + max_similarity = 0.0 + + # Get the highest matching value for each possible combination of words + for combination in itertools.product(*[tokens1, tokens2]): + + synset1 = wordnet.synsets(combination[0]) + synset2 = wordnet.synsets(combination[1]) + + if synset1 and synset2: + + # Get the highest similarity for each combination of synsets + for synset in itertools.product(*[synset1, synset2]): + similarity = synset[0].path_similarity(synset[1]) + + if similarity and (similarity > max_similarity): + max_similarity = similarity + + if max_possible_similarity == 0: + return 0 + + return max_similarity / max_possible_similarity + + +def sentiment_comparison(statement, other_statement): + """ + Calculate the similarity of two statements based on the closeness of + the sentiment value calculated for each statement. + + :return: The percent of similarity between the sentiment value. + :rtype: float + """ + from nltk.sentiment.vader import SentimentIntensityAnalyzer + + sentiment_analyzer = SentimentIntensityAnalyzer() + statement_polarity = sentiment_analyzer.polarity_scores(statement.text.lower()) + statement2_polarity = sentiment_analyzer.polarity_scores(other_statement.text.lower()) + + statement_greatest_polarity = 'neu' + statement_greatest_score = -1 + for polarity in sorted(statement_polarity): + if statement_polarity[polarity] > statement_greatest_score: + statement_greatest_polarity = polarity + statement_greatest_score = statement_polarity[polarity] + + statement2_greatest_polarity = 'neu' + statement2_greatest_score = -1 + for polarity in sorted(statement2_polarity): + if statement2_polarity[polarity] > statement2_greatest_score: + statement2_greatest_polarity = polarity + statement2_greatest_score = statement2_polarity[polarity] + + # Check if the polarity if of a different type + if statement_greatest_polarity != statement2_greatest_polarity: + return 0 + + values = [statement_greatest_score, statement2_greatest_score] + difference = max(values) - min(values) + + return 1.0 - difference + +def jaccard_similarity(statement, other_statement, threshold=0.5): + """ + Calculates the similarity of two statements based on the Jaccard index. + + The Jaccard index is composed of a numerator and denominator. + In the numerator, we count the number of items that are shared between the sets. + In the denominator, we count the total number of items across both sets. + Let's say we define sentences to be equivalent if 50% or more of their tokens are equivalent. + Here are two sample sentences: + + The young cat is hungry. + The cat is very hungry. + + When we parse these sentences to remove stopwords, we end up with the following two sets: + + {young, cat, hungry} + {cat, very, hungry} + + In our example above, our intersection is {cat, hungry}, which has count of two. + The union of the sets is {young, cat, very, hungry}, which has a count of four. + Therefore, our `Jaccard similarity index`_ is two divided by four, or 50%. + Given our threshold above, we would consider this to be a match. + + .. _`Jaccard similarity index`: https://en.wikipedia.org/wiki/Jaccard_index + """ + from nltk.corpus import wordnet + import nltk + import string + + a = statement.text.lower() + b = other_statement.text.lower() + + # Get default English stopwords and extend with punctuation + stopwords = nltk.corpus.stopwords.words('english') + stopwords.extend(string.punctuation) + stopwords.append('') + lemmatizer = nltk.stem.wordnet.WordNetLemmatizer() + + def get_wordnet_pos(pos_tag): + if pos_tag[1].startswith('J'): + return (pos_tag[0], wordnet.ADJ) + elif pos_tag[1].startswith('V'): + return (pos_tag[0], wordnet.VERB) + elif pos_tag[1].startswith('N'): + return (pos_tag[0], wordnet.NOUN) + elif pos_tag[1].startswith('R'): + return (pos_tag[0], wordnet.ADV) + else: + return (pos_tag[0], wordnet.NOUN) + + ratio = 0 + pos_a = map(get_wordnet_pos, nltk.pos_tag(nltk.tokenize.word_tokenize(a))) + pos_b = map(get_wordnet_pos, nltk.pos_tag(nltk.tokenize.word_tokenize(b))) + lemmae_a = [lemmatizer.lemmatize(token.strip(string.punctuation), pos) for token, pos in pos_a \ + if pos == wordnet.NOUN and token.strip(string.punctuation) not in stopwords] + lemmae_b = [lemmatizer.lemmatize(token.strip(string.punctuation), pos) for token, pos in pos_b \ + if pos == wordnet.NOUN and token.strip(string.punctuation) not in stopwords] + + # Calculate Jaccard similarity + try: + ratio = len(set(lemmae_a).intersection(lemmae_b)) / float(len(set(lemmae_a).union(lemmae_b))) + except Exception as e: + print('Error', e) + return ratio >= threshold diff --git a/chatterbot/conversation/comparisons.py b/chatterbot/conversation/comparisons.py index 0e8666879..b706d5167 100644 --- a/chatterbot/conversation/comparisons.py +++ b/chatterbot/conversation/comparisons.py @@ -1,200 +1,8 @@ -# -*- coding: utf-8 -*- - """ -This module contains various text-comparison algorithms -designed to compare one statement to another. +This file provides proxy methods for backwards compatability. """ - -def levenshtein_distance(statement, other_statement): - """ - Compare two statements based on the Levenshtein distance - (fuzzy string comparison) of each statement's text. - - :return: The percent of similarity between the text of the statements. - :rtype: float - """ - import sys - - # Use python-Levenshtein if available - try: - from Levenshtein.StringMatcher import StringMatcher as SequenceMatcher - except ImportError: - from difflib import SequenceMatcher - - PYTHON = sys.version_info[0] - - # Return 0 if either statement has a falsy text value - if not statement.text or not statement.text: - return 0 - - # Get the lowercase version of both strings - if PYTHON < 3: - statement_text = unicode(statement.text.lower()) - other_statement_text = unicode(other_statement.text.lower()) - else: - statement_text = str(statement.text.lower()) - other_statement_text = str(other_statement.text.lower()) - - similarity = SequenceMatcher( - None, - statement_text, - other_statement_text - ) - - # Calculate a decimal percent of the similarity - percent = int(round(100 * similarity.ratio())) / 100.0 - - return percent - - -def synset_distance(statement, other_statement): - """ - Calculate the similarity of two statements. - This is based on the total maximum synset similarity - between each word in each sentence. - - :return: The percent of similarity between the closest synset distance. - :rtype: float - """ - from nltk.corpus import wordnet - from nltk import word_tokenize - from chatterbot import utils - import itertools - - tokens1 = word_tokenize(statement.text.lower()) - tokens2 = word_tokenize(other_statement.text.lower()) - - # Remove all stop words from the list of word tokens - tokens1 = utils.remove_stopwords(tokens1, language='english') - tokens2 = utils.remove_stopwords(tokens2, language='english') - - # The maximum possible similarity is an exact match - # Because path_similarity returns a value between 0 and 1, - # max_possible_similarity is the number of words in the longer - # of the two input statements. - max_possible_similarity = max( - len(statement.text.split()), - len(other_statement.text.split()) - ) - - max_similarity = 0.0 - - # Get the highest matching value for each possible combination of words - for combination in itertools.product(*[tokens1, tokens2]): - - synset1 = wordnet.synsets(combination[0]) - synset2 = wordnet.synsets(combination[1]) - - if synset1 and synset2: - - # Get the highest similarity for each combination of synsets - for synset in itertools.product(*[synset1, synset2]): - similarity = synset[0].path_similarity(synset[1]) - - if similarity and (similarity > max_similarity): - max_similarity = similarity - - if max_possible_similarity == 0: - return 0 - - return max_similarity / max_possible_similarity - - -def sentiment_comparison(statement, other_statement): - """ - Calculate the similarity of two statements based on the closeness of - the sentiment value calculated for each statement. - - :return: The percent of similarity between the sentiment value. - :rtype: float - """ - from nltk.sentiment.vader import SentimentIntensityAnalyzer - - sentiment_analyzer = SentimentIntensityAnalyzer() - statement_polarity = sentiment_analyzer.polarity_scores(statement.text.lower()) - statement2_polarity = sentiment_analyzer.polarity_scores(other_statement.text.lower()) - - statement_greatest_polarity = 'neu' - statement_greatest_score = -1 - for polarity in sorted(statement_polarity): - if statement_polarity[polarity] > statement_greatest_score: - statement_greatest_polarity = polarity - statement_greatest_score = statement_polarity[polarity] - - statement2_greatest_polarity = 'neu' - statement2_greatest_score = -1 - for polarity in sorted(statement2_polarity): - if statement2_polarity[polarity] > statement2_greatest_score: - statement2_greatest_polarity = polarity - statement2_greatest_score = statement2_polarity[polarity] - - # Check if the polarity if of a different type - if statement_greatest_polarity != statement2_greatest_polarity: - return 0 - - values = [statement_greatest_score, statement2_greatest_score] - difference = max(values) - min(values) - - return 1.0 - difference - -def jaccard_similarity(statement, other_statement, threshold=0.5): - """ - The Jaccard index is composed of a numerator and denominator. - In the numerator, we count the number of items that are shared between the sets. - In the denominator, we count the total number of items across both sets. - Let's say we define sentences to be equivalent if 50% or more of their tokens are equivalent. - Here are two sample sentences: - - The young cat is hungry. - The cat is very hungry. - - When we parse these sentences to remove stopwords, we end up with the following two sets: - - {young, cat, hungry} - {cat, very, hungry} - - In our example above, our intersection is {cat, hungry}, which has count of two. - The union of the sets is {young, cat, very, hungry}, which has a count of four. - Therefore, our Jaccard similarity index is two divided by four, or 50%. - Given our threshold above, we would consider this to be a match. - """ - from nltk.corpus import wordnet - import nltk - import string - - a = statement.text - b = other_statement.text - - # Get default English stopwords and extend with punctuation - stopwords = nltk.corpus.stopwords.words('english') - stopwords.extend(string.punctuation) - stopwords.append('') - lemmatizer = nltk.stem.wordnet.WordNetLemmatizer() - - def get_wordnet_pos(pos_tag): - if pos_tag[1].startswith('J'): - return (pos_tag[0], wordnet.ADJ) - elif pos_tag[1].startswith('V'): - return (pos_tag[0], wordnet.VERB) - elif pos_tag[1].startswith('N'): - return (pos_tag[0], wordnet.NOUN) - elif pos_tag[1].startswith('R'): - return (pos_tag[0], wordnet.ADV) - else: - return (pos_tag[0], wordnet.NOUN) - - ratio = 0 - pos_a = map(get_wordnet_pos, nltk.pos_tag(nltk.tokenize.word_tokenize(a))) - pos_b = map(get_wordnet_pos, nltk.pos_tag(nltk.tokenize.word_tokenize(b))) - lemmae_a = [lemmatizer.lemmatize(token.lower().strip(string.punctuation), pos) for token, pos in pos_a \ - if pos == wordnet.NOUN and token.lower().strip(string.punctuation) not in stopwords] - lemmae_b = [lemmatizer.lemmatize(token.lower().strip(string.punctuation), pos) for token, pos in pos_b \ - if pos == wordnet.NOUN and token.lower().strip(string.punctuation) not in stopwords] - - # Calculate Jaccard similarity - try: - ratio = len(set(lemmae_a).intersection(lemmae_b)) / float(len(set(lemmae_a).union(lemmae_b))) - except Exception as e: - print('Error', e) - return ratio >= threshold +from chatterbot.comparisons import levenshtein_distance +from chatterbot.comparisons import synset_distance +from chatterbot.comparisons import sentiment_comparison +from chatterbot.comparisons import jaccard_similarity diff --git a/chatterbot/conversation/response_selection.py b/chatterbot/conversation/response_selection.py index 096593f97..aa551dadc 100644 --- a/chatterbot/conversation/response_selection.py +++ b/chatterbot/conversation/response_selection.py @@ -1,69 +1,7 @@ -import logging - """ -Response selection methods determines which response should be used in -the event that multiple responses are generated within a logic adapter. +This file provides proxy methods for backwards compatability. """ -def get_most_frequent_response(input_statement, response_list): - """ - :param input_statement: A statement, that closely matches an input to the chat bot. - :type input_statement: Statement - - :param response_list: A list of statement options to choose a response from. - :type response_list: list - - :return: The response statement with the greatest number of occurrences. - :rtype: Statement - """ - matching_response = None - occurrence_count = -1 - - logger = logging.getLogger(__name__) - logger.info(u'Selecting response with greatest number of occurrences.') - - for statement in response_list: - count = statement.get_response_count(input_statement) - - # Keep the more common statement - if count >= occurrence_count: - matching_response = statement - occurrence_count = count - - # Choose the most commonly occuring matching response - return matching_response - -def get_first_response(input_statement, response_list): - """ - :param input_statement: A statement, that closely matches an input to the chat bot. - :type input_statement: Statement - - :param response_list: A list of statement options to choose a response from. - :type response_list: list - - :return: Return the first statement in the response list. - :rtype: Statement - """ - logger = logging.getLogger(__name__) - logger.info(u'Selecting first response from list of {} options.'.format( - len(response_list) - )) - return response_list[0] - -def get_random_response(input_statement, response_list): - """ - :param input_statement: A statement, that closely matches an input to the chat bot. - :type input_statement: Statement - - :param response_list: A list of statement options to choose a response from. - :type response_list: list - - :return: Choose a random response from the selection. - :rtype: Statement - """ - from random import choice - logger = logging.getLogger(__name__) - logger.info(u'Selecting a response from list of {} options.'.format( - len(response_list) - )) - return choice(response_list) +from chatterbot.response_selection import get_most_frequent_response +from chatterbot.response_selection import get_first_response +from chatterbot.response_selection import get_random_response diff --git a/chatterbot/logic/__init__.py b/chatterbot/logic/__init__.py index 51b8b0e75..5d8f1003a 100644 --- a/chatterbot/logic/__init__.py +++ b/chatterbot/logic/__init__.py @@ -1,6 +1,6 @@ from .logic_adapter import LogicAdapter from .approximate_sentence_match import ApproximateSentenceMatchAdapter -from .base_match import BaseMatchAdapter +from .best_match import BestMatch from .closest_match import ClosestMatchAdapter from .closest_meaning import ClosestMeaningAdapter from .low_confidence import LowConfidenceAdapter @@ -10,3 +10,5 @@ from .sentiment_adapter import SentimentAdapter from .specific_response import SpecificResponseAdapter from .time_adapter import TimeLogicAdapter + +BaseMatchAdapter = BestMatch diff --git a/chatterbot/logic/approximate_sentence_match.py b/chatterbot/logic/approximate_sentence_match.py index 0fea743fc..94fccef61 100644 --- a/chatterbot/logic/approximate_sentence_match.py +++ b/chatterbot/logic/approximate_sentence_match.py @@ -1,13 +1,20 @@ -from .base_match import BaseMatchAdapter +import warnings +from .best_match import BestMatch -class ApproximateSentenceMatchAdapter(BaseMatchAdapter): +class ApproximateSentenceMatchAdapter(BestMatch): def __init__(self, **kwargs): super(ApproximateSentenceMatchAdapter, self).__init__(**kwargs) - from chatterbot.conversation.comparisons import jaccard_similarity + from chatterbot.comparisons import jaccard_similarity self.compare_statements = kwargs.get( 'statement_comparison_function', jaccard_similarity ) + + warnings.warn( + 'The ApproximateSentenceMatchAdapter is deprecated. ' + + 'Use "chatterbot.logic.BestMatch" response_selection_method="chatterbot.comparisons.jaccard_similarity" instead.', + DeprecationWarning + ) diff --git a/chatterbot/logic/base_match.py b/chatterbot/logic/best_match.py similarity index 95% rename from chatterbot/logic/base_match.py rename to chatterbot/logic/best_match.py index 4b07e6c15..cdddffdf5 100644 --- a/chatterbot/logic/base_match.py +++ b/chatterbot/logic/best_match.py @@ -2,10 +2,10 @@ from .logic_adapter import LogicAdapter -class BaseMatchAdapter(LogicAdapter): +class BestMatch(LogicAdapter): """ - This is a parent class used by the ClosestMatch and - ClosestMeaning adapters. + A logic adater that returns a response based on known responses to the + closest matches to the input statement. """ @property diff --git a/chatterbot/logic/closest_match.py b/chatterbot/logic/closest_match.py index ba75246f1..1d39eddeb 100644 --- a/chatterbot/logic/closest_match.py +++ b/chatterbot/logic/closest_match.py @@ -1,7 +1,8 @@ -from .base_match import BaseMatchAdapter +import warnings +from .best_match import BestMatch -class ClosestMatchAdapter(BaseMatchAdapter): +class ClosestMatchAdapter(BestMatch): """ The ClosestMatchAdapter logic adapter selects a known response to an input by searching for a known statement that most closely @@ -11,9 +12,15 @@ class ClosestMatchAdapter(BaseMatchAdapter): def __init__(self, **kwargs): super(ClosestMatchAdapter, self).__init__(**kwargs) - from chatterbot.conversation.comparisons import levenshtein_distance + from chatterbot.comparisons import levenshtein_distance self.compare_statements = kwargs.get( 'statement_comparison_function', levenshtein_distance ) + + warnings.warn( + 'The ClosestMatchAdapter is deprecated. ' + + "Use 'chatterbot.logic.BestMatch' instead.", + DeprecationWarning + ) diff --git a/chatterbot/logic/closest_meaning.py b/chatterbot/logic/closest_meaning.py index 38a1cbeaf..8beaaf62c 100644 --- a/chatterbot/logic/closest_meaning.py +++ b/chatterbot/logic/closest_meaning.py @@ -1,7 +1,8 @@ -from .base_match import BaseMatchAdapter +import warnings +from .best_match import BestMatch -class ClosestMeaningAdapter(BaseMatchAdapter): +class ClosestMeaningAdapter(BestMatch): """ This adapter selects a response by comparing the tokenized form of the input statement's text, with the tokenized form of possible matching @@ -13,9 +14,15 @@ class ClosestMeaningAdapter(BaseMatchAdapter): def __init__(self, **kwargs): super(ClosestMeaningAdapter, self).__init__(**kwargs) - from chatterbot.conversation.comparisons import synset_distance + from chatterbot.comparisons import synset_distance self.compare_statements = kwargs.get( 'statement_comparison_function', synset_distance ) + + warnings.warn( + 'The ClosestMeaningAdapter is deprecated. ' + + 'Use "chatterbot.logic.BestMatch" with response_selection_method="chatterbot.comparisons.synset_distance" instead.', + DeprecationWarning + ) diff --git a/chatterbot/logic/logic_adapter.py b/chatterbot/logic/logic_adapter.py index 362692b47..210b2c91d 100644 --- a/chatterbot/logic/logic_adapter.py +++ b/chatterbot/logic/logic_adapter.py @@ -11,8 +11,8 @@ class LogicAdapter(Adapter): def __init__(self, **kwargs): super(LogicAdapter, self).__init__(**kwargs) - from chatterbot.conversation.comparisons import levenshtein_distance - from chatterbot.conversation.response_selection import get_first_response + from chatterbot.comparisons import levenshtein_distance + from chatterbot.response_selection import get_first_response if 'tie_breaking_method' in kwargs: raise DeprecationWarning( diff --git a/chatterbot/logic/low_confidence.py b/chatterbot/logic/low_confidence.py index d11d24449..44abf347c 100644 --- a/chatterbot/logic/low_confidence.py +++ b/chatterbot/logic/low_confidence.py @@ -1,9 +1,9 @@ from __future__ import unicode_literals from chatterbot.conversation import Statement -from .base_match import BaseMatchAdapter +from .best_match import BestMatch -class LowConfidenceAdapter(BaseMatchAdapter): +class LowConfidenceAdapter(BestMatch): """ Returns a default response with a high confidence when a high confidence response is not known. diff --git a/chatterbot/logic/sentiment_adapter.py b/chatterbot/logic/sentiment_adapter.py index 189ac239b..6d5b2deaa 100644 --- a/chatterbot/logic/sentiment_adapter.py +++ b/chatterbot/logic/sentiment_adapter.py @@ -1,7 +1,8 @@ -from .base_match import BaseMatchAdapter +import warnings +from .best_match import BestMatch -class SentimentAdapter(BaseMatchAdapter): +class SentimentAdapter(BestMatch): """ This adapter selects a response with the closest matching sentiment value to the input statement. @@ -9,9 +10,15 @@ class SentimentAdapter(BaseMatchAdapter): def __init__(self, **kwargs): super(SentimentAdapter, self).__init__(**kwargs) - from chatterbot.conversation.comparisons import sentiment_comparison + from chatterbot.comparisons import sentiment_comparison self.compare_statements = kwargs.get( 'statement_comparison_function', sentiment_comparison ) + + warnings.warn( + 'The SentimentAdapter is deprecated. ' + + 'Use "chatterbot.logic.BestMatch" response_selection_method="chatterbot.comparisons.sentiment_comparison" instead.', + DeprecationWarning + ) diff --git a/chatterbot/response_selection.py b/chatterbot/response_selection.py new file mode 100644 index 000000000..096593f97 --- /dev/null +++ b/chatterbot/response_selection.py @@ -0,0 +1,69 @@ +import logging + +""" +Response selection methods determines which response should be used in +the event that multiple responses are generated within a logic adapter. +""" + +def get_most_frequent_response(input_statement, response_list): + """ + :param input_statement: A statement, that closely matches an input to the chat bot. + :type input_statement: Statement + + :param response_list: A list of statement options to choose a response from. + :type response_list: list + + :return: The response statement with the greatest number of occurrences. + :rtype: Statement + """ + matching_response = None + occurrence_count = -1 + + logger = logging.getLogger(__name__) + logger.info(u'Selecting response with greatest number of occurrences.') + + for statement in response_list: + count = statement.get_response_count(input_statement) + + # Keep the more common statement + if count >= occurrence_count: + matching_response = statement + occurrence_count = count + + # Choose the most commonly occuring matching response + return matching_response + +def get_first_response(input_statement, response_list): + """ + :param input_statement: A statement, that closely matches an input to the chat bot. + :type input_statement: Statement + + :param response_list: A list of statement options to choose a response from. + :type response_list: list + + :return: Return the first statement in the response list. + :rtype: Statement + """ + logger = logging.getLogger(__name__) + logger.info(u'Selecting first response from list of {} options.'.format( + len(response_list) + )) + return response_list[0] + +def get_random_response(input_statement, response_list): + """ + :param input_statement: A statement, that closely matches an input to the chat bot. + :type input_statement: Statement + + :param response_list: A list of statement options to choose a response from. + :type response_list: list + + :return: Choose a random response from the selection. + :rtype: Statement + """ + from random import choice + logger = logging.getLogger(__name__) + logger.info(u'Selecting a response from list of {} options.'.format( + len(response_list) + )) + return choice(response_list) diff --git a/docs/chatterbot.rst b/docs/chatterbot.rst index 0cff49b72..45678d346 100644 --- a/docs/chatterbot.rst +++ b/docs/chatterbot.rst @@ -76,8 +76,8 @@ which specifies the import path to the adapter class. logic_adapters=[ { 'import_path': 'my.logic.AdapterClass1', - 'statement_comparison_function': 'chatterbot.conversation.comparisons.levenshtein_distance' - 'response_selection_method': 'chatterbot.conversation.response_selection.get_first_response' + 'statement_comparison_function': 'chatterbot.comparisons.levenshtein_distance' + 'response_selection_method': 'chatterbot.response_selection.get_first_response' }, { 'import_path': 'my.logic.AdapterClass2', @@ -161,7 +161,7 @@ Adapter defaults ---------------- By default, ChatterBot uses the `JsonFileStorageAdapter` adapter for storage, -the `ClosestMatchAdapter` for logic, the `VariableInputTypeAdapter` for input +the `BestMatch` for logic, the `VariableInputTypeAdapter` for input and the OutputFormatAdapter for output. Each adapter can be set by passing in the dot-notated import path to the constructor as shown. @@ -174,7 +174,7 @@ Each adapter can be set by passing in the dot-notated import path to the constru input_adapter="chatterbot.input.VariableInputTypeAdapter", output_adapter="chatterbot.output.OutputFormatAdapter", logic_adapters=[ - "chatterbot.logic.ClosestMatchAdapter" + "chatterbot.logic.BestMatch" ], ) diff --git a/docs/conversations.rst b/docs/conversations.rst index 01f66406e..61f884980 100644 --- a/docs/conversations.rst +++ b/docs/conversations.rst @@ -41,6 +41,8 @@ The :code:`Response` object's :code:`occurrence` attribute indicates the number that the statement has been given as a response. This makes it possible for the chat bot to determine if a particular response is more commonly used than another. +.. _statement-comparison: + Statement comparison ==================== @@ -50,9 +52,25 @@ selects a response is based on it's ability to compare two statements to each other. There is a number of ways to do this, and ChatterBot comes with a handfull of method built in for you to use. -.. automodule:: chatterbot.conversation.comparisons +.. automodule:: chatterbot.comparisons :members: +Use your own comparison function +++++++++++++++++++++++++++++++++ + +You can create your own comparison function and use it as long as the function takes two statements +as parameters and returns a numeric value between 0 and 1. A 0 should represent the lowest possible +similarity and a 1 should represent the highest possibel similarity. + +.. code-block:: python + + def comparison_function(statement, other_statement): + + # Your comparison logic + + # Return your calculated value here + return 0.0 + Setting the comparison method ----------------------------- @@ -64,7 +82,7 @@ is shown bellow. .. code-block:: python from chatterbot import ChatBot - from chatterbot.conversation.comparisons import levenshtein_distance + from chatterbot.comparisons import levenshtein_distance chatbot = ChatBot( # ... diff --git a/docs/django/settings.rst b/docs/django/settings.rst index 5a9f742ab..d364a5d50 100644 --- a/docs/django/settings.rst +++ b/docs/django/settings.rst @@ -11,7 +11,7 @@ You can edit the ChatterBot configuration through your Django settings.py file. 'logic_adapters': [ 'chatterbot.logic.MathematicalEvaluation', 'chatterbot.logic.TimeLogicAdapter', - 'chatterbot.logic.ClosestMatchAdapter' + 'chatterbot.logic.BestMatch' ], 'trainer': 'chatterbot.trainers.ChatterBotCorpusTrainer', 'training_data': [ diff --git a/docs/logic/index.rst b/docs/logic/index.rst index a963cd04a..f313f408b 100644 --- a/docs/logic/index.rst +++ b/docs/logic/index.rst @@ -11,7 +11,8 @@ Logic adapters determine the logic for how ChatterBot selects a responses to a g response_selection create-a-logic-adapter -The logic adapter that your bot uses can be specified by setting the `logic_adapters` parameter to the import path of the logic adapter you want to use. +The logic adapter that your bot uses can be specified by setting the :code:`logic_adapters` parameter +to the import path of the logic adapter you want to use. It is possible to enter any number of logic adapters for your bot to use. If multiple adapters are used, then the bot will return the response with @@ -24,63 +25,55 @@ take priority. chatbot = ChatBot( "My ChatterBot", logic_adapters=[ - "chatterbot.logic.ClosestMatchAdapter" + "chatterbot.logic.BestMatch" ] ) -Closest Match Adapter -===================== - -.. autofunction:: chatterbot.logic.ClosestMatchAdapter - -The `ClosestMatchAdapter` selects a response based on the closest know match to a given statement. - -How it works ------------- - -The closest match algorithm determines the similarity between the input statement and a set of known statements. For example, there is a 65% similarity between the statements *"where is the post office?"* and *"looking for the post office"*. The closest match algorithm selects the highest matching known statements and returns a response based on that selection. - - -Closest Meaning Adapter -======================= +Best Match Adapter +================== -.. autofunction:: chatterbot.logic.ClosestMeaningAdapter +.. autofunction:: chatterbot.logic.BestMatch -The `ClosestMeaningAdapter` selects a response based on how closely two statements match each other based on the closeness of the synsets of each word in the word matrix formed by both sentences. +The :code:`BestMatch` logic adapter selects a response based on the best know match to a given statement. How it works ------------ -The closest meaning algorithm uses the `wordnet`_ functionality of `NLTK`_ to determine the similarity of two statements based on the path similarity between each token of each statement. This is essentially an evaluation of the closeness of synonyms. The statement that has the closest path similarity of synsets to the input statement is returned. +The best match adapter determines uses an function to compare the input statement to known statements. +Once it finds the closest match to the input statement, it uses another function to select one of the +known responses to that statement. +Setting parameters +------------------ -Approximate Sentence Match Adapter -================================== - -.. autofunction:: chatterbot.logic.ApproximateSentenceMatchAdapter +.. code-block:: python -The `ApproximateSentenceMatchAdapter` calculates a Jaccard index and give result to a given statement. + chatbot = ChatBot( + "My ChatterBot", + logic_adapters=[ + { + "import_path": "chatterbot.logic.BestMatch", + "response_selection_method": "chatterbot.comparisons.levenshtein_distance", + "statement_comparison_function": "chatterbot.response_selection.get_first_response" + } + ] + ) -How it works ------------- +.. note:: -The Jaccard index is composed of a numerator and denominator. - In the numerator, we count the number of items that are shared between the sets. - In the denominator, we count the total number of items across both sets. - Let's say we define sentences to be equivalent if 50% or more of their tokens are equivalent. Here are two sample sentences: + The values for :code:`response_selection_method` and :code:`statement_comparison_function` can be a string + of the path to the function, or a callable. - The young cat is hungry. - The cat is very hungry. +Comparison functions +-------------------- - When we parse these sentences to remove stopwords, we end up with the following two sets: +See the :ref:`statement-comparison` documentation for the list of functions included with ChatterBot. - {young, cat, hungry} - {cat, very, hungry} +Response selection methods +-------------------------- - In our example above, our intersection is {cat, hungry}, which has count of two. - The union of the sets is {young, cat, very, hungry}, which has a count of four. - Therefore, our `Jaccard similarity index`_ is two divided by four, or 50%. +See the :ref:`response-selection` documentation for the list of response selection methods included with ChatterBot. Time Logic Adapter @@ -88,14 +81,16 @@ Time Logic Adapter .. autofunction:: chatterbot.logic.TimeLogicAdapter -The `TimeLogicAdapter` identifies statements in which a question about the current time is asked. +The :code:`TimeLogicAdapter` identifies statements in which a question about the current time is asked. If a matching question is detected, then a response containing the current time is returned. Example ------- -| User: What time is it? -| Bot: The current time is 4:45PM. +.. code-block:: text + + User: What time is it? + Bot: The current time is 4:45PM. Mathematical Evaluation Adapter @@ -111,17 +106,10 @@ This adapter is able to handle any combination of word and numeric operators. Example ------- -| User: What is four plus four? -| Bot: (4 + 4) = 8 - - -SentimentAdapter -================ +.. code-block:: text -.. autofunction:: chatterbot.logic.SentimentAdapter - -This is a logic adapter that selects a response that has the closest matching -sentiment value to the input. + User: What is four plus four? + Bot: (4 + 4) = 8 Low Confidence Response Adapter @@ -152,7 +140,3 @@ Example usage .. literalinclude:: ../../examples/specific_response_example.py :language: python - -.. _wordnet: http://www.nltk.org/howto/wordnet.html -.. _NLTK: http://www.nltk.org/ -.. _`Jaccard similarity index`: https://en.wikipedia.org/wiki/Jaccard_index diff --git a/docs/logic/response_selection.rst b/docs/logic/response_selection.rst index b4568e26e..2a4f16f2b 100644 --- a/docs/logic/response_selection.rst +++ b/docs/logic/response_selection.rst @@ -15,12 +15,28 @@ To help with the selection of the response, several methods are build in to ChatterBot for selecting a response from the available options. +.. _response-selection: + Response selection methods ========================== -.. automodule:: chatterbot.conversation.response_selection +.. automodule:: chatterbot.response_selection :members: +Use your own response selection method +++++++++++++++++++++++++++++++++++++++ + +You can create your own response selection method and use it as long as the function takes +two parameters (a statements and a list of statements). The method must return a statement. + +.. code-block:: python + + def select_response(statement, statement_list): + + # Your selection logic + + return selected_statement + Setting the response selection method ===================================== @@ -32,7 +48,7 @@ is shown bellow. .. code-block:: python from chatterbot import ChatBot - from chatterbot.conversation.response_selection import get_most_frequent_response + from chatterbot.response_selection import get_most_frequent_response chatbot = ChatBot( # ... diff --git a/examples/default_response_example.py b/examples/default_response_example.py index 121244606..18bdd5bd6 100644 --- a/examples/default_response_example.py +++ b/examples/default_response_example.py @@ -8,7 +8,7 @@ storage_adapter='chatterbot.storage.JsonFileStorageAdapter', logic_adapters=[ { - 'import_path': 'chatterbot.logic.ClosestMatchAdapter' + 'import_path': 'chatterbot.logic.BestMatch' }, { 'import_path': 'chatterbot.logic.LowConfidenceAdapter', diff --git a/examples/learning_feedback_example.py b/examples/learning_feedback_example.py index fb727f61c..aa61172c2 100644 --- a/examples/learning_feedback_example.py +++ b/examples/learning_feedback_example.py @@ -16,7 +16,7 @@ 'Feedback Learning Bot', storage_adapter='chatterbot.storage.JsonFileStorageAdapter', logic_adapters=[ - 'chatterbot.logic.ClosestMatchAdapter' + 'chatterbot.logic.BestMatch' ], input_adapter='chatterbot.input.TerminalAdapter', output_adapter='chatterbot.output.TerminalAdapter' diff --git a/examples/specific_response_example.py b/examples/specific_response_example.py index 1865ed0ea..5e08d2b4f 100644 --- a/examples/specific_response_example.py +++ b/examples/specific_response_example.py @@ -8,7 +8,7 @@ storage_adapter='chatterbot.storage.JsonFileStorageAdapter', logic_adapters=[ { - 'import_path': 'chatterbot.logic.ClosestMatchAdapter' + 'import_path': 'chatterbot.logic.BestMatch' }, { 'import_path': 'chatterbot.logic.SpecificResponseAdapter', diff --git a/examples/terminal_example.py b/examples/terminal_example.py index cbb1060eb..2c3a4c6d2 100644 --- a/examples/terminal_example.py +++ b/examples/terminal_example.py @@ -12,7 +12,7 @@ logic_adapters=[ "chatterbot.logic.MathematicalEvaluation", "chatterbot.logic.TimeLogicAdapter", - "chatterbot.logic.ClosestMatchAdapter" + "chatterbot.logic.BestMatch" ], input_adapter="chatterbot.input.TerminalAdapter", output_adapter="chatterbot.output.TerminalAdapter", diff --git a/examples/terminal_mongo_example.py b/examples/terminal_mongo_example.py index 5b43c4b44..e14a9bcf5 100644 --- a/examples/terminal_mongo_example.py +++ b/examples/terminal_mongo_example.py @@ -10,7 +10,7 @@ bot = ChatBot('Terminal', storage_adapter='chatterbot.storage.MongoDatabaseAdapter', logic_adapters=[ - 'chatterbot.logic.ClosestMatchAdapter' + 'chatterbot.logic.BestMatch' ], filters=[ 'chatterbot.filters.RepetitiveResponseFilter' diff --git a/examples/tkinter_gui.py b/examples/tkinter_gui.py index 03b982b80..059798a1b 100644 --- a/examples/tkinter_gui.py +++ b/examples/tkinter_gui.py @@ -21,7 +21,7 @@ def __init__(self, *args, **kwargs): self.chatbot = ChatBot("No Output", storage_adapter="chatterbot.storage.JsonFileStorageAdapter", logic_adapters=[ - "chatterbot.logic.ClosestMatchAdapter" + "chatterbot.logic.BestMatch" ], input_adapter="chatterbot.input.VariableInputTypeAdapter", output_adapter="chatterbot.output.OutputFormatAdapter", diff --git a/examples/twitter_training_example.py b/examples/twitter_training_example.py index 3c82ba8a7..513f7480b 100644 --- a/examples/twitter_training_example.py +++ b/examples/twitter_training_example.py @@ -24,7 +24,7 @@ chatbot = ChatBot("TwitterBot", logic_adapters=[ - "chatterbot.logic.ClosestMatchAdapter" + "chatterbot.logic.BestMatch" ], input_adapter="chatterbot.input.TerminalAdapter", output_adapter="chatterbot.output.TerminalAdapter", diff --git a/requirements.txt b/requirements.txt index 7be2fb382..4640376eb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ jsondatabase>=0.1.1 -nltk<4.0.0 +nltk>=3.2.0,<4.0.0 +numpy>=1.11.0,<1.20.0 pymongo>=3.3.0,<4.0.0 python-twitter>=3.0 \ No newline at end of file diff --git a/tests/base_case.py b/tests/base_case.py index 14f56fd8e..fc741c18f 100644 --- a/tests/base_case.py +++ b/tests/base_case.py @@ -1,7 +1,20 @@ +import os +from mock import Mock from unittest import TestCase from unittest import SkipTest from chatterbot import ChatBot -import os + + +class MockChatBot(object): + def __init__(self): + from chatterbot.logic import LogicAdapter + from chatterbot.storage import StorageAdapter + + self.storage = StorageAdapter() + + self.storage.get_random = Mock( + side_effect=LogicAdapter.EmptyDatasetException() + ) class ChatBotTestCase(TestCase): diff --git a/tests/benchmarks.py b/tests/benchmarks.py index e08f9ca92..973b9244a 100644 --- a/tests/benchmarks.py +++ b/tests/benchmarks.py @@ -21,8 +21,8 @@ 'logic_adapters': [ { 'import_path': 'chatterbot.logic.BaseMatchAdapter', - 'statement_comparison_function': 'chatterbot.conversation.comparisons.levenshtein_distance', - 'response_selection_method': 'chatterbot.conversation.response_selection.get_first_response' + 'statement_comparison_function': 'chatterbot.comparisons.levenshtein_distance', + 'response_selection_method': 'chatterbot.response_selection.get_first_response' } ], 'storage_adapter': { @@ -35,8 +35,8 @@ 'logic_adapters': [ { 'import_path': 'chatterbot.logic.BaseMatchAdapter', - 'statement_comparison_function': 'chatterbot.conversation.comparisons.synset_distance', - 'response_selection_method': 'chatterbot.conversation.response_selection.get_first_response' + 'statement_comparison_function': 'chatterbot.comparisons.synset_distance', + 'response_selection_method': 'chatterbot.response_selection.get_first_response' } ], 'storage_adapter': { @@ -59,8 +59,8 @@ 'logic_adapters': [ { 'import_path': 'chatterbot.logic.BaseMatchAdapter', - 'statement_comparison_function': 'chatterbot.conversation.comparisons.levenshtein_distance', - 'response_selection_method': 'chatterbot.conversation.response_selection.get_first_response' + 'statement_comparison_function': 'chatterbot.comparisons.levenshtein_distance', + 'response_selection_method': 'chatterbot.response_selection.get_first_response' } ], 'storage_adapter': 'chatterbot.storage.MongoDatabaseAdapter' @@ -70,8 +70,8 @@ 'logic_adapters': [ { 'import_path': 'chatterbot.logic.BaseMatchAdapter', - 'statement_comparison_function': 'chatterbot.conversation.comparisons.synset_distance', - 'response_selection_method': 'chatterbot.conversation.response_selection.get_first_response' + 'statement_comparison_function': 'chatterbot.comparisons.synset_distance', + 'response_selection_method': 'chatterbot.response_selection.get_first_response' } ], 'storage_adapter': 'chatterbot.storage.MongoDatabaseAdapter' diff --git a/tests/logic_adapter_tests/best_match_integration_tests/__init__.py b/tests/logic_adapter_tests/best_match_integration_tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/logic_adapter_tests/test_closest_match.py b/tests/logic_adapter_tests/best_match_integration_tests/test_levenshtein_distance.py similarity index 82% rename from tests/logic_adapter_tests/test_closest_match.py rename to tests/logic_adapter_tests/best_match_integration_tests/test_levenshtein_distance.py index 7862aeff0..be15e0635 100644 --- a/tests/logic_adapter_tests/test_closest_match.py +++ b/tests/logic_adapter_tests/best_match_integration_tests/test_levenshtein_distance.py @@ -1,35 +1,26 @@ from unittest import TestCase -from mock import MagicMock, Mock -from chatterbot.logic import ClosestMatchAdapter -from chatterbot.storage import StorageAdapter +from mock import MagicMock +from chatterbot.logic import BestMatch from chatterbot.conversation import Statement, Response +from tests.base_case import MockChatBot -class MockChatBot(object): - def __init__(self): - self.storage = StorageAdapter() - - self.storage.get_random = Mock( - side_effect=ClosestMatchAdapter.EmptyDatasetException() - ) - - -class ClosestMatchAdapterTests(TestCase): +class BestMatchLevenshteinDistanceTestCase(TestCase): + """ + Integration tests for the BestMatch logic adapter + using Levenshtein distance as a comparison function. + """ def setUp(self): - self.adapter = ClosestMatchAdapter() + from chatterbot.comparisons import levenshtein_distance + + self.adapter = BestMatch( + statement_comparison_function=levenshtein_distance + ) # Add a mock chatbot to the logic adapter self.adapter.set_chatbot(MockChatBot()) - def test_no_choices(self): - self.adapter.chatbot.storage.filter = MagicMock(return_value=[]) - - statement = Statement("What is your quest?") - - with self.assertRaises(ClosestMatchAdapter.EmptyDatasetException): - self.adapter.get(statement) - def test_get_closest_statement(self): """ Note, the content of the in_response_to field for each of the diff --git a/tests/logic_adapter_tests/test_sentiment.py b/tests/logic_adapter_tests/best_match_integration_tests/test_sentiment_comparison.py similarity index 72% rename from tests/logic_adapter_tests/test_sentiment.py rename to tests/logic_adapter_tests/best_match_integration_tests/test_sentiment_comparison.py index e5f10a153..12671d826 100644 --- a/tests/logic_adapter_tests/test_sentiment.py +++ b/tests/logic_adapter_tests/best_match_integration_tests/test_sentiment_comparison.py @@ -1,20 +1,26 @@ from tests.base_case import ChatBotTestCase -from chatterbot.logic import SentimentAdapter +from chatterbot.logic import BestMatch from chatterbot.conversation import Statement -class SentimentAdapterTests(ChatBotTestCase): +class BestMatchSentimentComparisonTestCase(ChatBotTestCase): + """ + Integration tests for the BestMatch logic adapter + using the similarity of sentiment polarity as a comparison function. + """ def setUp(self): - super(SentimentAdapterTests, self).setUp() + super(BestMatchSentimentComparisonTestCase, self).setUp() from chatterbot.trainers import ListTrainer + from chatterbot.comparisons import sentiment_comparison self.chatbot.set_trainer(ListTrainer) - self.adapter = SentimentAdapter() + self.adapter = BestMatch( + statement_comparison_function=sentiment_comparison + ) self.adapter.set_chatbot(self.chatbot) def test_exact_input(self): - self.chatbot.train([ 'What is your favorite flavor of ice cream?', 'I enjoy raspberry ice cream.', diff --git a/tests/logic_adapter_tests/test_closest_meaning.py b/tests/logic_adapter_tests/best_match_integration_tests/test_synset_distance.py similarity index 81% rename from tests/logic_adapter_tests/test_closest_meaning.py rename to tests/logic_adapter_tests/best_match_integration_tests/test_synset_distance.py index 2b21349f0..63a59af49 100644 --- a/tests/logic_adapter_tests/test_closest_meaning.py +++ b/tests/logic_adapter_tests/best_match_integration_tests/test_synset_distance.py @@ -1,34 +1,31 @@ from unittest import TestCase from mock import MagicMock -from chatterbot.logic import ClosestMeaningAdapter +from chatterbot.logic import BestMatch from chatterbot.conversation import Statement, Response -from tests.logic_adapter_tests.test_closest_match import MockChatBot +from tests.base_case import MockChatBot -class ClosestMeaningAdapterTests(TestCase): +class BestMatchSynsetDistanceTestCase(TestCase): + """ + Integration tests for the BestMatch logic adapter + using the synset_distance comparison function. + """ def setUp(self): from chatterbot.utils import nltk_download_corpus + from chatterbot.comparisons import synset_distance nltk_download_corpus('stopwords') nltk_download_corpus('wordnet') nltk_download_corpus('punkt') - self.adapter = ClosestMeaningAdapter() + self.adapter = BestMatch( + statement_comparison_function=synset_distance + ) # Add a mock storage adapter to the logic adapter self.adapter.set_chatbot(MockChatBot()) - def test_no_choices(self): - """ - An exception should be raised if there is no data in the database. - """ - self.adapter.chatbot.storage.filter = MagicMock(return_value=[]) - statement = Statement('Hello') - - with self.assertRaises(ClosestMeaningAdapter.EmptyDatasetException): - self.adapter.get(statement) - def test_get_closest_statement(self): """ Note, the content of the in_response_to field for each of the diff --git a/tests/logic_adapter_tests/test_best_match.py b/tests/logic_adapter_tests/test_best_match.py new file mode 100644 index 000000000..5c1c6a57a --- /dev/null +++ b/tests/logic_adapter_tests/test_best_match.py @@ -0,0 +1,29 @@ +from unittest import TestCase +from mock import MagicMock +from chatterbot.logic import BestMatch +from chatterbot.conversation import Statement, Response +from tests.base_case import MockChatBot + + +class BestMatchTestCase(TestCase): + """ + Unit tests for the BestMatch logic adapter. + """ + + def setUp(self): + from chatterbot.comparisons import levenshtein_distance + + self.adapter = BestMatch() + + # Add a mock chatbot to the logic adapter + self.adapter.set_chatbot(MockChatBot()) + + def test_no_choices(self): + """ + An exception should be raised if there is no data in the database. + """ + self.adapter.chatbot.storage.filter = MagicMock(return_value=[]) + statement = Statement('What is your quest?') + + with self.assertRaises(BestMatch.EmptyDatasetException): + self.adapter.get(statement) diff --git a/tests/logic_adapter_tests/test_logic_adapter.py b/tests/logic_adapter_tests/test_logic_adapter.py index aaa13b4f0..c8592b9e1 100644 --- a/tests/logic_adapter_tests/test_logic_adapter.py +++ b/tests/logic_adapter_tests/test_logic_adapter.py @@ -26,12 +26,12 @@ def test_process(self): def test_set_statement_comparison_function_string(self): adapter = LogicAdapter( - statement_comparison_function='chatterbot.conversation.comparisons.levenshtein_distance' + statement_comparison_function='chatterbot.comparisons.levenshtein_distance' ) self.assertTrue(callable(adapter.compare_statements)) def test_set_statement_comparison_function_callable(self): - from chatterbot.conversation.comparisons import levenshtein_distance + from chatterbot.comparisons import levenshtein_distance adapter = LogicAdapter( statement_comparison_function=levenshtein_distance ) @@ -39,12 +39,12 @@ def test_set_statement_comparison_function_callable(self): def test_set_response_selection_method_string(self): adapter = LogicAdapter( - response_selection_method='chatterbot.conversation.response_selection.get_first_response' + response_selection_method='chatterbot.response_selection.get_first_response' ) self.assertTrue(callable(adapter.select_response)) def test_set_response_selection_method_callable(self): - from chatterbot.conversation.response_selection import get_first_response + from chatterbot.response_selection import get_first_response adapter = LogicAdapter( response_selection_method=get_first_response ) diff --git a/tests/logic_adapter_tests/test_low_confidence_adapter.py b/tests/logic_adapter_tests/test_low_confidence_adapter.py index c0e012314..700f57dbf 100644 --- a/tests/logic_adapter_tests/test_low_confidence_adapter.py +++ b/tests/logic_adapter_tests/test_low_confidence_adapter.py @@ -2,7 +2,7 @@ from mock import MagicMock from chatterbot.logic import LowConfidenceAdapter from chatterbot.conversation import Statement, Response -from .test_closest_match import MockChatBot +from tests.base_case import MockChatBot class LowConfidenceAdapterTestCase(TestCase): diff --git a/tests/test_adapter_validation.py b/tests/test_adapter_validation.py index 49c166c9b..fdfac4405 100644 --- a/tests/test_adapter_validation.py +++ b/tests/test_adapter_validation.py @@ -58,7 +58,7 @@ def test_invalid_logic_adapter(self): def test_valid_logic_adapter(self): kwargs = self.get_kwargs() - kwargs['logic_adapters'] = ['chatterbot.logic.ClosestMatchAdapter'] + kwargs['logic_adapters'] = ['chatterbot.logic.BestMatch'] try: self.chatbot = ChatBot('Test Bot', **kwargs) except ChatBot.InvalidAdapterException: @@ -77,7 +77,7 @@ def test_valid_adapter_dictionary(self): def test_invalid_adapter_dictionary(self): kwargs = self.get_kwargs() kwargs['storage_adapter'] = { - 'import_path': 'chatterbot.logic.ClosestMatchAdapter' + 'import_path': 'chatterbot.logic.BestMatch' } with self.assertRaises(ChatBot.InvalidAdapterException): self.chatbot = ChatBot('Test Bot', **kwargs) @@ -89,13 +89,13 @@ def test_add_logic_adapter(self): count_before = len(self.chatbot.logic.adapters) self.chatbot.logic.add_adapter( - 'chatterbot.logic.ClosestMatchAdapter' + 'chatterbot.logic.BestMatch' ) self.assertEqual(len(self.chatbot.logic.adapters), count_before + 1) def test_insert_logic_adapter(self): self.chatbot.logic.add_adapter('chatterbot.logic.TimeLogicAdapter') - self.chatbot.logic.add_adapter('chatterbot.logic.ClosestMatchAdapter') + self.chatbot.logic.add_adapter('chatterbot.logic.BestMatch') self.chatbot.logic.insert_logic_adapter('chatterbot.logic.MathematicalEvaluation', 1) diff --git a/tests/test_initialization.py b/tests/test_initialization.py index 84f459eac..acfbf101c 100644 --- a/tests/test_initialization.py +++ b/tests/test_initialization.py @@ -16,9 +16,9 @@ def test_storage_initialized(self): self.assertTrue(isinstance(self.chatbot.storage, JsonFileStorageAdapter)) def test_logic_initialized(self): - from chatterbot.logic import ClosestMatchAdapter + from chatterbot.logic import BestMatch self.assertEqual(len(self.chatbot.logic.adapters), 1) - self.assertTrue(isinstance(self.chatbot.logic.adapters[0], ClosestMatchAdapter)) + self.assertTrue(isinstance(self.chatbot.logic.adapters[0], BestMatch)) def test_input_initialized(self): from chatterbot.input import VariableInputTypeAdapter @@ -47,7 +47,7 @@ def get_kwargs(self): }, 'logic_adapters': [ { - 'import_path': 'chatterbot.logic.ClosestMatchAdapter', + 'import_path': 'chatterbot.logic.BestMatch', }, { 'import_path': 'chatterbot.logic.MathematicalEvaluation', @@ -60,10 +60,10 @@ def test_storage_initialized(self): self.assertTrue(isinstance(self.chatbot.storage, JsonFileStorageAdapter)) def test_logic_initialized(self): - from chatterbot.logic import ClosestMatchAdapter + from chatterbot.logic import BestMatch from chatterbot.logic import MathematicalEvaluation self.assertEqual(len(self.chatbot.logic.adapters), 2) - self.assertTrue(isinstance(self.chatbot.logic.adapters[0], ClosestMatchAdapter)) + self.assertTrue(isinstance(self.chatbot.logic.adapters[0], BestMatch)) self.assertTrue(isinstance(self.chatbot.logic.adapters[1], MathematicalEvaluation)) def test_input_initialized(self):