Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add two frequently requested logic adapters #409

Merged
merged 2 commits into from
Nov 17, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions chatterbot/adapters/logic/__init__.py
Original file line number Diff line number Diff line change
@@ -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
35 changes: 35 additions & 0 deletions chatterbot/adapters/logic/low_confidence.py
Original file line number Diff line number Diff line change
@@ -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)
30 changes: 30 additions & 0 deletions chatterbot/adapters/logic/specific_response.py
Original file line number Diff line number Diff line change
@@ -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
40 changes: 38 additions & 2 deletions docs/adapters/logic.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ take priority.
]
)


Closest Match Adapter
=====================

Expand All @@ -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
=======================

Expand All @@ -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.
Expand All @@ -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
==================

Expand All @@ -91,6 +95,7 @@ Example
| User: What time is it?
| Bot: The current time is 4:45PM.


Mathematical Evaluation Adapter
===============================

Expand All @@ -107,6 +112,7 @@ Example
| User: What is four plus four?
| Bot: (4 + 4) = 8


SentimentAdapter
================

Expand All @@ -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
4 changes: 2 additions & 2 deletions docs/adapters/output.rst
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,14 @@ Be sure to also see the documentation for the :ref:`HipChat input adapter <hipch
)

Microsoft Adapter
===============
=================

.. autofunction:: chatterbot.adapters.output.Microsoft

This is an output adapter that allows a ChatterBot instance to send responses
to a `Microsoft`_ using *Direct Line protocol*.

Be sure to also see the documentation for the :ref:`Microsoft input adapter <microsoft-input-adapter>`.
Be sure to also see the documentation for the `Microsoft input adapter <microsoft-input-adapter>`_.

.. code-block:: python

Expand Down
32 changes: 32 additions & 0 deletions examples/default_response_example.py
Original file line number Diff line number Diff line change
@@ -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)
23 changes: 23 additions & 0 deletions examples/specific_response_example.py
Original file line number Diff line number Diff line change
@@ -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)
60 changes: 60 additions & 0 deletions tests/logic_adapter_tests/test_low_confidence_adapter.py
Original file line number Diff line number Diff line change
@@ -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)
36 changes: 36 additions & 0 deletions tests/logic_adapter_tests/test_specific_response.py
Original file line number Diff line number Diff line change
@@ -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)