diff --git a/chatterbot/adapters/logic/__init__.py b/chatterbot/adapters/logic/__init__.py index 64c7015fc..9d3b8f874 100644 --- a/chatterbot/adapters/logic/__init__.py +++ b/chatterbot/adapters/logic/__init__.py @@ -1,9 +1,11 @@ from .logic_adapter import LogicAdapter +from .approximate_sentence_match import ApproximateSentenceMatchAdapter from .closest_match import ClosestMatchAdapter from .closest_meaning import ClosestMeaningAdapter -from .time_adapter import TimeLogicAdapter +from .low_confidence import LowConfidenceAdapter +from .mathematical_evaluation import MathematicalEvaluation from .multi_adapter import MultiLogicAdapter from .no_knowledge_adapter import NoKnowledgeAdapter -from .mathematical_evaluation import MathematicalEvaluation -from .approximate_sentence_match import ApproximateSentenceMatchAdapter from .sentiment_adapter import SentimentAdapter +from .specific_response import SpecificResponseAdapter +from .time_adapter import TimeLogicAdapter diff --git a/chatterbot/adapters/logic/low_confidence.py b/chatterbot/adapters/logic/low_confidence.py new file mode 100644 index 000000000..726d3b1e6 --- /dev/null +++ b/chatterbot/adapters/logic/low_confidence.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +from chatterbot.conversation import Statement +from .base_match import BaseMatchAdapter + + +class LowConfidenceAdapter(BaseMatchAdapter): + """ + Returns a default response with a high confidence + when a high confidence response is not known. + """ + + def __init__(self, **kwargs): + super(LowConfidenceAdapter, self).__init__(**kwargs) + + self.confidence_threshold = kwargs.get('threshold', 0.65) + self.default_response = kwargs.get( + 'default_response', + "I'm sorry, I do not understand." + ) + + def process(self, input_statement): + """ + Return a default response with a high confidence if + a high confidence response is not known. + """ + # Select the closest match to the input statement + confidence, closest_match = self.get(input_statement) + + # Confidence should be high only if it is less than the threshold + if confidence < self.confidence_threshold: + confidence = 1 + else: + confidence = 0 + + return confidence, Statement(self.default_response) diff --git a/chatterbot/adapters/logic/specific_response.py b/chatterbot/adapters/logic/specific_response.py new file mode 100644 index 000000000..1f2e64648 --- /dev/null +++ b/chatterbot/adapters/logic/specific_response.py @@ -0,0 +1,30 @@ +from .logic_adapter import LogicAdapter + + +class SpecificResponseAdapter(LogicAdapter): + """ + Return a specific response to a specific input. + """ + + def __init__(self, **kwargs): + super(SpecificResponseAdapter, self).__init__(**kwargs) + from chatterbot.conversation import Statement + + self.input_text = kwargs.get('input_text') + + output_text = kwargs.get('output_text') + self.response_statement = Statement(output_text) + + def can_process(self, statement): + if statement == self.input_text: + return True + + return False + + def process(self, statement): + confidence = 0 + + if statement == self.input_text: + confidence = 1 + + return confidence, self.response_statement diff --git a/docs/adapters/logic.rst b/docs/adapters/logic.rst index 967175963..70c8eadd3 100644 --- a/docs/adapters/logic.rst +++ b/docs/adapters/logic.rst @@ -26,6 +26,7 @@ take priority. ] ) + Closest Match Adapter ===================== @@ -38,6 +39,7 @@ 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 ======================= @@ -50,15 +52,16 @@ 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. + Approximate Sentence Match Adapter ----------------------------------- +================================== .. autofunction:: chatterbot.adapters.logic.ApproximateSentenceMatchAdapter The `ApproximateSentenceMatchAdapter` calculates a Jaccard index and give result to a given statement. How it works -++++++++++++ +------------ 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. @@ -77,6 +80,7 @@ The Jaccard index is composed of a numerator and denominator. 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%. + Time Logic Adapter ================== @@ -91,6 +95,7 @@ Example | User: What time is it? | Bot: The current time is 4:45PM. + Mathematical Evaluation Adapter =============================== @@ -107,6 +112,7 @@ Example | User: What is four plus four? | Bot: (4 + 4) = 8 + SentimentAdapter ================ @@ -115,6 +121,36 @@ SentimentAdapter This is a logic adapter that selects a response that has the closest matching sentiment value to the input. + +Low Confidence Response Adapter +=============================== + +This adapter returns a specified default response if a response can not be +determined with a high amount of confidence. + +.. autofunction:: chatterbot.adapters.logic.LowConfidenceAdapter + +Example usage +------------- + +.. literalinclude:: ../../examples/default_response_example.py + :language: python + + +Specific Response Adapter +========================= + +If the input that the chat bot recieves, matches the input text specified +for this adapter, the specified response will be returned. + +.. autofunction:: chatterbot.adapters.logic.SpecificResponseAdapter + +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/adapters/output.rst b/docs/adapters/output.rst index 153552c2c..95db251bf 100644 --- a/docs/adapters/output.rst +++ b/docs/adapters/output.rst @@ -78,14 +78,14 @@ Be sure to also see the documentation for the :ref:`HipChat input adapter `. +Be sure to also see the documentation for the `Microsoft input adapter `_. .. code-block:: python diff --git a/examples/default_response_example.py b/examples/default_response_example.py new file mode 100644 index 000000000..2075f0306 --- /dev/null +++ b/examples/default_response_example.py @@ -0,0 +1,32 @@ +from chatterbot import ChatBot + + +# Create a new instance of a ChatBot +bot = ChatBot( + 'Default Response Example Bot', + storage_adapter='chatterbot.adapters.storage.JsonFileStorageAdapter', + logic_adapters=[ + { + 'import_path': 'chatterbot.adapters.logic.ClosestMatchAdapter' + }, + { + 'import_path': 'chatterbot.adapters.logic.LowConfidenceAdapter', + 'threshold': 0.65, + 'default_response': 'I am sorry, but I do not understand.' + } + ], + trainer='chatterbot.trainers.ListTrainer' +) + +# Train the chat bot with a few responses +bot.train([ + 'How can I help you?', + 'I want to create a chat bot', + 'Have you read the documentation?', + 'No, I have not', + 'This should help get you started: http://chatterbot.rtfd.org/en/latest/quickstart.html' +]) + +# Get a response for some unexpected input +response = bot.get_response('How do I make an omelette?') +print(response) diff --git a/examples/specific_response_example.py b/examples/specific_response_example.py new file mode 100644 index 000000000..9b669a8a8 --- /dev/null +++ b/examples/specific_response_example.py @@ -0,0 +1,23 @@ +from chatterbot import ChatBot + + +# Create a new instance of a ChatBot +bot = ChatBot( + 'Exact Response Example Bot', + storage_adapter='chatterbot.adapters.storage.JsonFileStorageAdapter', + logic_adapters=[ + { + 'import_path': 'chatterbot.adapters.logic.ClosestMatchAdapter' + }, + { + 'import_path': 'chatterbot.adapters.logic.SpecificResponseAdapter', + 'input_text': 'Help me!', + 'output_text': 'Ok, here is a link: http://chatterbot.rtfd.org/en/latest/quickstart.html' + } + ], + trainer='chatterbot.trainers.ListTrainer' +) + +# Get a response given the specific input +response = bot.get_response('Help me!') +print(response) diff --git a/tests/logic_adapter_tests/test_low_confidence_adapter.py b/tests/logic_adapter_tests/test_low_confidence_adapter.py new file mode 100644 index 000000000..6626d500b --- /dev/null +++ b/tests/logic_adapter_tests/test_low_confidence_adapter.py @@ -0,0 +1,60 @@ +from unittest import TestCase +from mock import MagicMock +from chatterbot.adapters.logic import LowConfidenceAdapter +from chatterbot.conversation import Statement, Response +from .test_closest_match import MockContext + + +class LowConfidenceAdapterTestCase(TestCase): + """ + Test cases for the LowConfidenceAdapter + """ + + def setUp(self): + super(LowConfidenceAdapterTestCase, self).setUp() + self.adapter = LowConfidenceAdapter() + + # Add a mock storage adapter to the context + self.adapter.set_context(MockContext()) + + possible_choices = [ + Statement('Who do you love?', in_response_to=[ + Response('I hear you are going on a quest?') + ]), + Statement('What is the meaning of life?', in_response_to=[ + Response('Yuck, black licorice jelly beans.') + ]), + Statement('I am Iron Man.', in_response_to=[ + Response('What... is your quest?') + ]), + Statement('What... is your quest?', in_response_to=[ + Response('I am Iron Man.') + ]), + Statement('Yuck, black licorice jelly beans.', in_response_to=[ + Response('What is the meaning of life?') + ]), + Statement('I hear you are going on a quest?', in_response_to=[ + Response('Who do you love?') + ]), + ] + self.adapter.context.storage.filter = MagicMock(return_value=possible_choices) + + def test_high_confidence(self): + """ + Test the case that a high confidence response is known. + """ + statement = Statement('What is your quest?') + confidence, match = self.adapter.process(statement) + + self.assertEqual(confidence, 0) + self.assertEqual(match, self.adapter.default_response) + + def test_low_confidence(self): + """ + Test the case that a high confidence response is not known. + """ + statement = Statement('Is this a tomato?') + confidence, match = self.adapter.process(statement) + + self.assertEqual(confidence, 1) + self.assertEqual(match, self.adapter.default_response) diff --git a/tests/logic_adapter_tests/test_specific_response.py b/tests/logic_adapter_tests/test_specific_response.py new file mode 100644 index 000000000..e441154b2 --- /dev/null +++ b/tests/logic_adapter_tests/test_specific_response.py @@ -0,0 +1,36 @@ +from unittest import TestCase +from chatterbot.adapters.logic import SpecificResponseAdapter +from chatterbot.conversation import Statement + + +class SpecificResponseAdapterTestCase(TestCase): + """ + Test cases for the SpecificResponseAdapter + """ + + def setUp(self): + super(SpecificResponseAdapterTestCase, self).setUp() + self.adapter = SpecificResponseAdapter( + input_text='Open sesame!', + output_text='Your sesame seed hamburger roll is now open.' + ) + + def test_exact_match(self): + """ + Test the case that an exact match is given. + """ + statement = Statement('Open sesame!') + confidence, match = self.adapter.process(statement) + + self.assertEqual(confidence, 1) + self.assertEqual(match, self.adapter.response_statement) + + def test_not_exact_match(self): + """ + Test the case that an exact match is not given. + """ + statement = Statement('Open says me!') + confidence, match = self.adapter.process(statement) + + self.assertEqual(confidence, 0) + self.assertEqual(match, self.adapter.response_statement)