From 3352a2a326f44e8fadcef75aa1ca86ff2c3af020 Mon Sep 17 00:00:00 2001 From: Gunther Cox Date: Fri, 7 Jul 2017 08:17:54 -0400 Subject: [PATCH 01/28] Add Conversation model to SQL Alchemy --- chatterbot/chatterbot.py | 6 +-- chatterbot/storage/sql_storage.py | 64 ++++++++++++++++++++++++++- chatterbot/storage/storage_adapter.py | 17 +++++++ tests/test_chatbot.py | 8 ++-- 4 files changed, 86 insertions(+), 9 deletions(-) diff --git a/chatterbot/chatterbot.py b/chatterbot/chatterbot.py index 01e8ec6a1..3c4737997 100644 --- a/chatterbot/chatterbot.py +++ b/chatterbot/chatterbot.py @@ -99,7 +99,7 @@ def get_response(self, input_item, session_id=None): :rtype: Statement """ if not session_id: - session_id = str(self.default_session.uuid) + session_id = self.default_session_id input_statement = self.input.process_input_statement(input_item) @@ -110,9 +110,7 @@ def get_response(self, input_item, session_id=None): statement, response = self.generate_response(input_statement, session_id) # Learn that the user's input was a valid response to the chat bot's previous output - previous_statement = self.conversation_sessions.get( - session_id - ).conversation.get_last_response_statement() + previous_statement = self.storage.get_latest_response(session_id) self.learn_response(statement, previous_statement) self.conversation_sessions.update(session_id, (statement, response, )) diff --git a/chatterbot/storage/sql_storage.py b/chatterbot/storage/sql_storage.py index eb4832400..d129ab3c4 100644 --- a/chatterbot/storage/sql_storage.py +++ b/chatterbot/storage/sql_storage.py @@ -8,7 +8,8 @@ from chatterbot.ext.sqlalchemy_app.models import Base from sqlalchemy.orm import relationship from sqlalchemy.sql import func - from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, PickleType + from sqlalchemy import Table, Column, Integer, String, DateTime, ForeignKey, PickleType + class StatementTable(Base): """ @@ -61,6 +62,28 @@ def get_response(self): occ = {'occurrence': self.occurrence} return Response(text=self.text, **occ) + conversation_association_table = Table( + 'conversation_association', + Base.metadata, + Column('conversation_id', Integer, ForeignKey('conversation.id')), + Column('statement_id', Integer, ForeignKey('StatementTable.id')) + ) + + class Conversation(Base): + """ + A conversation. + """ + + __tablename__ = 'conversation' + + id = Column(Integer, primary_key=True) + + statements = relationship( + 'StatementTable', + secondary=lambda: conversation_association_table, + backref='conversations' + ) + except ImportError: pass @@ -279,6 +302,45 @@ def update(self, statement): self._session_finish(session) + def create_conversation(self): + """ + Create a new conversation. + """ + session = self.Session() + + conversation = Conversation() + session.add(conversation) + + conversation_id = conversation.id + + self._session_finish(session) + + return conversation_id + + def get_latest_response(self, conversation_id): + """ + Returns the latest response in a conversation if it exists. + Returns None if a matching conversation cannot be found. + """ + from sqlalchemy import text + + session = self.Session() + statement = None + + statement_query = session.query( + StatementTable.conversations + ).filter_by( + id=conversation_id + ).first() + # TODO: Get the last response statement, not just the first in the result set + + if statement_query: + statement = statement_query.get_statement() + + session.close() + + return statement + def get_random(self): """ Returns a random statement from the database diff --git a/chatterbot/storage/storage_adapter.py b/chatterbot/storage/storage_adapter.py index d8011d59a..24ba3c387 100644 --- a/chatterbot/storage/storage_adapter.py +++ b/chatterbot/storage/storage_adapter.py @@ -91,6 +91,23 @@ def update(self, statement): 'The `update` method is not implemented by this adapter.' ) + def get_latest_response(self, conversation_id): + """ + Returns the latest response in a conversation if it exists. + Returns None if a matching conversation cannot be found. + """ + raise self.AdapterMethodNotImplementedError( + 'The `get_conversation` method is not implemented by this adapter.' + ) + + def create_conversation(self): + """ + Creates a new conversation. + """ + raise self.AdapterMethodNotImplementedError( + 'The `create_conversation` method is not implemented by this adapter.' + ) + def get_random(self): """ Returns a random statement from the database. diff --git a/tests/test_chatbot.py b/tests/test_chatbot.py index 636480ea0..767b9a16f 100644 --- a/tests/test_chatbot.py +++ b/tests/test_chatbot.py @@ -42,11 +42,11 @@ def test_statement_added_to_recent_response_list(self): """ statement_text = 'Wow!' response = self.chatbot.get_response(statement_text) - session = self.chatbot.conversation_sessions.get( - self.chatbot.default_session.id_string + response_statement = self.chatbot.storage.get_latest_response( + self.chatbot.default_session_id ) - self.assertIn(statement_text, session.conversation[0]) + self.assertEqual(statement_text, response_statement.text) self.assertEqual(response, statement_text) def test_response_known(self): @@ -138,7 +138,7 @@ def test_generate_response(self): statement = Statement('Many insects adopt a tripedal gait for rapid yet stable walking.') input_statement, response = self.chatbot.generate_response( statement, - self.chatbot.default_session.id + self.chatbot.default_session_id ) self.assertEqual(input_statement, statement) From 64e05ac6ff618d5c5c70493560b3935c432fcd3d Mon Sep 17 00:00:00 2001 From: Gunther Cox Date: Sat, 8 Jul 2017 05:09:31 -0400 Subject: [PATCH 02/28] Save conversations to the database --- chatterbot/chatterbot.py | 3 +- chatterbot/ext/sqlalchemy_app/models.py | 2 +- chatterbot/storage/sql_storage.py | 51 +++++++++++++++++++++---- chatterbot/storage/storage_adapter.py | 8 ++++ 4 files changed, 54 insertions(+), 10 deletions(-) diff --git a/chatterbot/chatterbot.py b/chatterbot/chatterbot.py index 3c4737997..4a9c08ac4 100644 --- a/chatterbot/chatterbot.py +++ b/chatterbot/chatterbot.py @@ -113,7 +113,8 @@ def get_response(self, input_item, session_id=None): previous_statement = self.storage.get_latest_response(session_id) self.learn_response(statement, previous_statement) - self.conversation_sessions.update(session_id, (statement, response, )) + if not self.read_only: + self.storage.add_to_converation(session_id, statement, response) # Process the response output with the output adapter return self.output.process_response(response, session_id) diff --git a/chatterbot/ext/sqlalchemy_app/models.py b/chatterbot/ext/sqlalchemy_app/models.py index 1b8b74c8b..3c9402ffb 100644 --- a/chatterbot/ext/sqlalchemy_app/models.py +++ b/chatterbot/ext/sqlalchemy_app/models.py @@ -23,4 +23,4 @@ def __tablename__(cls): ) -Base = declarative_base(cls=ModelBase) +Base = declarative_base(cls=ModelBase) \ No newline at end of file diff --git a/chatterbot/storage/sql_storage.py b/chatterbot/storage/sql_storage.py index d129ab3c4..3fac0977f 100644 --- a/chatterbot/storage/sql_storage.py +++ b/chatterbot/storage/sql_storage.py @@ -76,7 +76,7 @@ class Conversation(Base): __tablename__ = 'conversation' - id = Column(Integer, primary_key=True) + id = Column(Integer, primary_key=True, autoincrement=True, default=1) statements = relationship( 'StatementTable', @@ -198,7 +198,7 @@ def remove(self, statement_text): session.delete(record) - self._session_finish(session, statement_text) + self._session_finish(session) def filter(self, **kwargs): """ @@ -309,14 +309,39 @@ def create_conversation(self): session = self.Session() conversation = Conversation() + session.add(conversation) + session.flush() + session.refresh(conversation) conversation_id = conversation.id - self._session_finish(session) + session.commit() + session.close() return conversation_id + def add_to_converation(self, conversation_id, statement, response): + """ + Add the statement and response to the conversation. + """ + session = self.Session() + + conversation = session.query(Conversation).get(conversation_id) + + statement_query = session.query(StatementTable).filter_by( + text=statement.text + ).first() + response_query = session.query(StatementTable).filter_by( + text=response.text + ).first() + + conversation.statements.append(statement_query) + conversation.statements.append(response_query) + + session.add(conversation) + self._session_finish(session) + def get_latest_response(self, conversation_id): """ Returns the latest response in a conversation if it exists. @@ -328,11 +353,10 @@ def get_latest_response(self, conversation_id): statement = None statement_query = session.query( - StatementTable.conversations - ).filter_by( - id=conversation_id - ).first() - # TODO: Get the last response statement, not just the first in the result set + StatementTable + ).filter( + StatementTable.conversations.any(id=conversation_id) + ).order_by(StatementTable.id.desc()).limit(2).first() if statement_query: statement = statement_query.get_statement() @@ -370,15 +394,26 @@ def create(self): """ Base.metadata.create_all(self.engine) +<<<<<<< HEAD def _session_finish(self, session, statement_text=None): from sqlalchemy.exc import InvalidRequestError +======= + def _session_finish(self, session): + from sqlalchemy.exc import DatabaseError +>>>>>>> Save conversations to the database try: if not self.read_only: session.commit() else: session.rollback() +<<<<<<< HEAD except InvalidRequestError: # Log the statement text and the exception self.logger.exception(statement_text) +======= + except DatabaseError: + # Log the exception + self.logger.exception('An error occurred.') +>>>>>>> Save conversations to the database finally: session.close() diff --git a/chatterbot/storage/storage_adapter.py b/chatterbot/storage/storage_adapter.py index 24ba3c387..da1a4af07 100644 --- a/chatterbot/storage/storage_adapter.py +++ b/chatterbot/storage/storage_adapter.py @@ -108,6 +108,14 @@ def create_conversation(self): 'The `create_conversation` method is not implemented by this adapter.' ) + def add_to_converation(self, conversation_id, statement, response): + """ + Add the statement and response to the conversation. + """ + raise self.AdapterMethodNotImplementedError( + 'The `add_to_converation` method is not implemented by this adapter.' + ) + def get_random(self): """ Returns a random statement from the database. From d9ae366f7a9782d7f5657896e172dadf6e82b9dd Mon Sep 17 00:00:00 2001 From: Gunther Cox Date: Sat, 8 Jul 2017 07:02:15 -0400 Subject: [PATCH 03/28] Add conversation methods to mongodb adapter --- chatterbot/storage/mongodb.py | 48 +++++++++++++++++++++++++++ chatterbot/storage/storage_adapter.py | 2 +- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/chatterbot/storage/mongodb.py b/chatterbot/storage/mongodb.py index e025c9c3e..613efb8c0 100644 --- a/chatterbot/storage/mongodb.py +++ b/chatterbot/storage/mongodb.py @@ -98,6 +98,9 @@ def __init__(self, **kwargs): # The mongo collection of statement documents self.statements = self.database['statements'] + # The mongo collection of conversation documents + self.conversations = self.database['conversations'] + # Set a requirement for the text attribute to be unique self.statements.create_index('text', unique=True) @@ -236,6 +239,51 @@ def update(self, statement): return statement + def create_conversation(self): + """ + Create a new conversation. + """ + conversation_id = self.conversations.insert_one({ + 'statements': [] + }).inserted_id + return conversation_id + + def get_latest_response(self, conversation_id): + """ + Returns the latest response in a conversation if it exists. + Returns None if a matching conversation cannot be found. + """ + conversation = self.conversations.find_one({ + '_id': conversation_id + }) + + if not conversation['statements']: + return None + + # TODO: Check if ordering is needed + + return conversation['statements'][-2] + + def add_to_converation(self, conversation_id, statement, response): + """ + Add the statement and response to the conversation. + """ + self.conversations.update_one( + { + 'id': conversation_id + }, + { + '$push': { + 'conversations': { + '$each': [ + statement.text, + response.text + ] + } + } + } + ) + def get_random(self): """ Returns a random statement from the database diff --git a/chatterbot/storage/storage_adapter.py b/chatterbot/storage/storage_adapter.py index da1a4af07..09967cb9a 100644 --- a/chatterbot/storage/storage_adapter.py +++ b/chatterbot/storage/storage_adapter.py @@ -97,7 +97,7 @@ def get_latest_response(self, conversation_id): Returns None if a matching conversation cannot be found. """ raise self.AdapterMethodNotImplementedError( - 'The `get_conversation` method is not implemented by this adapter.' + 'The `get_latest_response` method is not implemented by this adapter.' ) def create_conversation(self): From 52f1b24dff8bcfa8a331ba5a3d2b5a6f0208f536 Mon Sep 17 00:00:00 2001 From: Gunther Cox Date: Sat, 8 Jul 2017 08:43:02 -0400 Subject: [PATCH 04/28] Remove conversation_sessions variable --- chatterbot/chatterbot.py | 1 - chatterbot/filters.py | 18 +++++++++++------- chatterbot/storage/sql_storage.py | 28 ++++++++++++++-------------- tests/test_context.py | 21 ++++++++++++++------- 4 files changed, 39 insertions(+), 29 deletions(-) diff --git a/chatterbot/chatterbot.py b/chatterbot/chatterbot.py index 4a9c08ac4..764483bb5 100644 --- a/chatterbot/chatterbot.py +++ b/chatterbot/chatterbot.py @@ -12,7 +12,6 @@ class ChatBot(object): """ def __init__(self, name, **kwargs): - from .conversation.session import ConversationSessionManager from .logic import MultiLogicAdapter self.name = name diff --git a/chatterbot/filters.py b/chatterbot/filters.py index 1b45574d6..80a613496 100644 --- a/chatterbot/filters.py +++ b/chatterbot/filters.py @@ -26,15 +26,19 @@ class RepetitiveResponseFilter(Filter): def filter_selection(self, chatterbot, session_id): - session = chatterbot.conversation_sessions.get(session_id) - - if session.conversation.empty(): - return chatterbot.storage.base_query - text_of_recent_responses = [] - for statement, response in session.conversation: - text_of_recent_responses.append(response.text) + # TODO: Add a larger quantity of response history + text_of_recent_responses.append( + chatterbot.storage.get_latest_response(session_id) + ) + + # Return the query with no changes if there are no statements to exclude + if not text_of_recent_responses: + return super(RepetitiveResponseFilter, self).filter_selection( + chatterbot, + session_id + ) query = chatterbot.storage.base_query.statement_text_not_in( text_of_recent_responses diff --git a/chatterbot/storage/sql_storage.py b/chatterbot/storage/sql_storage.py index 3fac0977f..dbfda81eb 100644 --- a/chatterbot/storage/sql_storage.py +++ b/chatterbot/storage/sql_storage.py @@ -76,7 +76,7 @@ class Conversation(Base): __tablename__ = 'conversation' - id = Column(Integer, primary_key=True, autoincrement=True, default=1) + id = Column(Integer, primary_key=True) statements = relationship( 'StatementTable', @@ -336,6 +336,19 @@ def add_to_converation(self, conversation_id, statement, response): text=response.text ).first() + # Make sure the statements exist + if not statement_query: + self.update(statement) + statement_query = session.query(StatementTable).filter_by( + text=statement.text + ).first() + + if not response_query: + self.update(response) + response_query = session.query(StatementTable).filter_by( + text=response.text + ).first() + conversation.statements.append(statement_query) conversation.statements.append(response_query) @@ -347,8 +360,6 @@ def get_latest_response(self, conversation_id): Returns the latest response in a conversation if it exists. Returns None if a matching conversation cannot be found. """ - from sqlalchemy import text - session = self.Session() statement = None @@ -394,26 +405,15 @@ def create(self): """ Base.metadata.create_all(self.engine) -<<<<<<< HEAD def _session_finish(self, session, statement_text=None): from sqlalchemy.exc import InvalidRequestError -======= - def _session_finish(self, session): - from sqlalchemy.exc import DatabaseError ->>>>>>> Save conversations to the database try: if not self.read_only: session.commit() else: session.rollback() -<<<<<<< HEAD except InvalidRequestError: # Log the statement text and the exception self.logger.exception(statement_text) -======= - except DatabaseError: - # Log the exception - self.logger.exception('An error occurred.') ->>>>>>> Save conversations to the database finally: session.close() diff --git a/tests/test_context.py b/tests/test_context.py index 7fa6f411e..add6dd5d9 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -8,14 +8,21 @@ def test_modify_chatbot(self): When one adapter modifies its chatbot instance, the change should be the same in all other adapters. """ - session = self.chatbot.input.chatbot.conversation_sessions.new() - self.chatbot.input.chatbot.conversation_sessions.update( - session.id_string, - ('A', 'B', ) + self.chatbot.input.chatbot.read_only = 'TESTING' + + value = self.chatbot.output.chatbot.read_only + + self.assertEqual('TESTING', value) + + def test_get_latest_response(self): + from chatterbot.conversation import Statement + conversation_id = self.chatbot.storage.create_conversation() + self.chatbot.storage.add_to_converation( + conversation_id, Statement(text='A'), Statement(text='B') ) - session = self.chatbot.output.chatbot.conversation_sessions.get( - session.id_string + response_statement = self.chatbot.storage.get_latest_response( + conversation_id ) - self.assertIn(('A', 'B', ), session.conversation) + self.assertEqual('A', response_statement.text) From bf07247a9612aa78b2354da4075d418423a22683 Mon Sep 17 00:00:00 2001 From: Gunther Cox Date: Mon, 10 Jul 2017 23:23:25 -0400 Subject: [PATCH 05/28] Add method to django adapter for creating conversations --- chatterbot/storage/django_storage.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/chatterbot/storage/django_storage.py b/chatterbot/storage/django_storage.py index 1e8061089..387a53e8f 100644 --- a/chatterbot/storage/django_storage.py +++ b/chatterbot/storage/django_storage.py @@ -138,6 +138,15 @@ def remove(self, statement_text): responses.delete() statements.delete() + def create_conversation(self): + """ + Create a new conversation. + """ + from django.apps import apps + Conversation = apps.get_model(self.django_app_name, 'Conversation') + conversation = Conversation.objects.create() + return conversation.id + def drop(self): """ Remove all data from the database. From c9a0f457dc2b95e20df27865e33a8192b38534e5 Mon Sep 17 00:00:00 2001 From: Gunther Cox Date: Mon, 10 Jul 2017 23:24:28 -0400 Subject: [PATCH 06/28] Fix order of latest statement response query in sql adapter --- chatterbot/storage/sql_storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chatterbot/storage/sql_storage.py b/chatterbot/storage/sql_storage.py index dbfda81eb..0253a69c9 100644 --- a/chatterbot/storage/sql_storage.py +++ b/chatterbot/storage/sql_storage.py @@ -367,7 +367,7 @@ def get_latest_response(self, conversation_id): StatementTable ).filter( StatementTable.conversations.any(id=conversation_id) - ).order_by(StatementTable.id.desc()).limit(2).first() + ).order_by(StatementTable.id).limit(2).first() if statement_query: statement = statement_query.get_statement() From 3efc1006e908e9f826f607655deacb2c597cbff2 Mon Sep 17 00:00:00 2001 From: Gunther Cox Date: Tue, 11 Jul 2017 06:40:09 -0400 Subject: [PATCH 07/28] Properly order returned conversation statements from mongodb --- chatterbot/filters.py | 6 ++--- chatterbot/storage/mongodb.py | 43 ++++++++++++++++++++++------------- 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/chatterbot/filters.py b/chatterbot/filters.py index 80a613496..9f13cbc34 100644 --- a/chatterbot/filters.py +++ b/chatterbot/filters.py @@ -29,9 +29,9 @@ def filter_selection(self, chatterbot, session_id): text_of_recent_responses = [] # TODO: Add a larger quantity of response history - text_of_recent_responses.append( - chatterbot.storage.get_latest_response(session_id) - ) + latest_response = chatterbot.storage.get_latest_response(session_id) + if latest_response: + text_of_recent_responses.append(latest_response.text) # Return the query with no changes if there are no statements to exclude if not text_of_recent_responses: diff --git a/chatterbot/storage/mongodb.py b/chatterbot/storage/mongodb.py index 613efb8c0..2a130d7f6 100644 --- a/chatterbot/storage/mongodb.py +++ b/chatterbot/storage/mongodb.py @@ -243,9 +243,7 @@ def create_conversation(self): """ Create a new conversation. """ - conversation_id = self.conversations.insert_one({ - 'statements': [] - }).inserted_id + conversation_id = self.conversations.insert_one({}).inserted_id return conversation_id def get_latest_response(self, conversation_id): @@ -253,32 +251,45 @@ def get_latest_response(self, conversation_id): Returns the latest response in a conversation if it exists. Returns None if a matching conversation cannot be found. """ - conversation = self.conversations.find_one({ - '_id': conversation_id - }) + from pymongo import DESCENDING - if not conversation['statements']: - return None + statements = list(self.statements.find({ + 'conversations.id': conversation_id + }).sort('conversations.created_at', DESCENDING)) - # TODO: Check if ordering is needed + if not statements: + return None - return conversation['statements'][-2] + return self.mongo_to_object(statements[-2]) def add_to_converation(self, conversation_id, statement, response): """ Add the statement and response to the conversation. """ - self.conversations.update_one( + from datetime import datetime, timedelta + self.statements.update_one( + { + 'text': statement.text + }, + { + '$push': { + 'conversations': { + 'id': conversation_id, + 'created_at': datetime.utcnow() + } + } + } + ) + self.statements.update_one( { - 'id': conversation_id + 'text': response.text }, { '$push': { 'conversations': { - '$each': [ - statement.text, - response.text - ] + 'id': conversation_id, + # Force the response to be at least one millisecond after the input statement + 'created_at': datetime.utcnow() + timedelta(milliseconds=1) } } } From b65f2bb1ce43c11023814a1f4f79764bbd4de37e Mon Sep 17 00:00:00 2001 From: Gunther Cox Date: Tue, 11 Jul 2017 07:24:54 -0400 Subject: [PATCH 08/28] Update django view to use conversations --- chatterbot/ext/django_chatterbot/views.py | 43 ++++++++++++----------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/chatterbot/ext/django_chatterbot/views.py b/chatterbot/ext/django_chatterbot/views.py index faf62ddc0..9dfe39d89 100644 --- a/chatterbot/ext/django_chatterbot/views.py +++ b/chatterbot/ext/django_chatterbot/views.py @@ -28,14 +28,28 @@ def get_chat_session(self, request): Return the current session for the chat if one exists. Create a new session if one does not exist. """ + class Conversation(object): + def __init__(self): + self.id = None + self.statements = [] + + conversation = Conversation() + chat_session_id = request.session.get('chat_session_id', None) - chat_session = self.chatterbot.conversation_sessions.get(chat_session_id, None) + statements = self.chatterbot.storage.filter( + conversation__id=chat_session_id + ) - if not chat_session: - chat_session = self.chatterbot.conversation_sessions.new() - request.session['chat_session_id'] = chat_session.id_string + if not statements: + chat_session_id = self.chatterbot.storage.create_conversation() + request.session['chat_session_id'] = chat_session_id - return chat_session + conversation.id = chat_session_id + conversation.statements = [ + statement.serialize() for statement in statements + ] + + return conversation class ChatterBotView(ChatterBotViewMixin, View): @@ -43,17 +57,6 @@ class ChatterBotView(ChatterBotViewMixin, View): Provide an API endpoint to interact with ChatterBot. """ - def _serialize_conversation(self, session): - if session.conversation.empty(): - return [] - - conversation = [] - - for statement, response in session.conversation: - conversation.append([statement.serialize(), response.serialize()]) - - return conversation - def post(self, request, *args, **kwargs): """ Return a response to the statement in the posted data. @@ -62,9 +65,9 @@ def post(self, request, *args, **kwargs): self.validate(input_data) - chat_session = self.get_chat_session(request) + conversation = self.get_chat_session(request) - response = self.chatterbot.get_response(input_data, chat_session.id_string) + response = self.chatterbot.get_response(input_data, conversation.id) response_data = response.serialize() return JsonResponse(response_data, status=200) @@ -73,12 +76,12 @@ def get(self, request, *args, **kwargs): """ Return data corresponding to the current conversation. """ - chat_session = self.get_chat_session(request) + conversation = self.get_chat_session(request) data = { 'detail': 'You should make a POST request to this endpoint.', 'name': self.chatterbot.name, - 'conversation': self._serialize_conversation(chat_session) + 'conversation': conversation.statements } # Return a method not allowed response From c5e06f7e5074a299a49b4830cc105bad171f1978 Mon Sep 17 00:00:00 2001 From: Gunther Cox Date: Wed, 12 Jul 2017 06:39:42 -0400 Subject: [PATCH 09/28] Add remaining conversation methods to django storage adapter --- chatterbot/storage/django_storage.py | 29 ++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/chatterbot/storage/django_storage.py b/chatterbot/storage/django_storage.py index 387a53e8f..9f1f87a50 100644 --- a/chatterbot/storage/django_storage.py +++ b/chatterbot/storage/django_storage.py @@ -138,6 +138,21 @@ def remove(self, statement_text): responses.delete() statements.delete() + def get_latest_response(self, conversation_id): + """ + Returns the latest response in a conversation if it exists. + Returns None if a matching conversation cannot be found. + """ + from django.apps import apps + + Statement = apps.get_model(self.django_app_name, 'Statement') + + return Statement.objects.filter( + conversation__id=conversation_id + ).order_by( + 'created_at' + )[:2].first() + def create_conversation(self): """ Create a new conversation. @@ -147,6 +162,20 @@ def create_conversation(self): conversation = Conversation.objects.create() return conversation.id + def add_to_converation(self, conversation_id, statement, response): + """ + Add the statement and response to the conversation. + """ + from django.apps import apps + + Statement = apps.get_model(self.django_app_name, 'Statement') + + first_statement = Statement.objects.filter(text=statement.text).first() + first_response = Statement.objects.filter(text=response.text).first() + + first_statement.conversation.add(conversation_id) + first_response.conversation.add(conversation_id) + def drop(self): """ Remove all data from the database. From f3aba21b6cd91b199d9abf3330c637d33c0bc174 Mon Sep 17 00:00:00 2001 From: Gunther Cox Date: Fri, 14 Jul 2017 02:05:56 -0400 Subject: [PATCH 10/28] Update Django app --- chatterbot/chatterbot.py | 4 ++++ chatterbot/ext/django_chatterbot/models.py | 2 +- chatterbot/ext/django_chatterbot/views.py | 5 +++-- tests_django/test_api_view.py | 13 ------------- tests_django/test_views.py | 17 +++++++++++------ 5 files changed, 19 insertions(+), 22 deletions(-) diff --git a/chatterbot/chatterbot.py b/chatterbot/chatterbot.py index 764483bb5..cead0e738 100644 --- a/chatterbot/chatterbot.py +++ b/chatterbot/chatterbot.py @@ -75,6 +75,8 @@ def __init__(self, name, **kwargs): self.trainer = TrainerClass(self.storage, **kwargs) self.training_data = kwargs.get('training_data') + self.default_session_id = None + self.logger = kwargs.get('logger', logging.getLogger(__name__)) # Allow the bot to save input it receives so that it can learn @@ -98,6 +100,8 @@ def get_response(self, input_item, session_id=None): :rtype: Statement """ if not session_id: + if not self.default_session_id: + self.default_session_id = self.storage.create_conversation() session_id = self.default_session_id input_statement = self.input.process_input_statement(input_item) diff --git a/chatterbot/ext/django_chatterbot/models.py b/chatterbot/ext/django_chatterbot/models.py index 3297f2171..2f6794408 100644 --- a/chatterbot/ext/django_chatterbot/models.py +++ b/chatterbot/ext/django_chatterbot/models.py @@ -219,4 +219,4 @@ class Conversation(AbstractBaseConversation): """ A sequence of statements representing a conversation. """ - pass + pass \ No newline at end of file diff --git a/chatterbot/ext/django_chatterbot/views.py b/chatterbot/ext/django_chatterbot/views.py index 9dfe39d89..b3410d3d4 100644 --- a/chatterbot/ext/django_chatterbot/views.py +++ b/chatterbot/ext/django_chatterbot/views.py @@ -28,14 +28,15 @@ def get_chat_session(self, request): Return the current session for the chat if one exists. Create a new session if one does not exist. """ - class Conversation(object): + class Obj(object): def __init__(self): self.id = None self.statements = [] - conversation = Conversation() + conversation = Obj() chat_session_id = request.session.get('chat_session_id', None) + statements = self.chatterbot.storage.filter( conversation__id=chat_session_id ) diff --git a/tests_django/test_api_view.py b/tests_django/test_api_view.py index dbf0fcd78..c6fdaf22e 100644 --- a/tests_django/test_api_view.py +++ b/tests_django/test_api_view.py @@ -10,19 +10,6 @@ def setUp(self): super(ApiIntegrationTestCase, self).setUp() self.api_url = reverse('chatterbot') - # Clear the response queue before tests - ChatterBotView.chatterbot.conversation_sessions.get( - ChatterBotView.chatterbot.default_session.id_string - ).conversation.flush() - - def tearDown(self): - super(ApiIntegrationTestCase, self).tearDown() - - # Clear the response queue after tests - ChatterBotView.chatterbot.conversation_sessions.get( - ChatterBotView.chatterbot.default_session.id_string - ).conversation.flush() - def _get_json(self, response): from django.utils.encoding import force_text return json.loads(force_text(response.content)) diff --git a/tests_django/test_views.py b/tests_django/test_views.py index f7b3f7382..e002c7b84 100644 --- a/tests_django/test_views.py +++ b/tests_django/test_views.py @@ -30,21 +30,26 @@ def test_validate_invalid_text(self): }) def test_get_chat_session(self): - session = self.view.chatterbot.conversation_sessions.new() - mock_response = MockResponse(session.id_string) + from chatterbot.ext.django_chatterbot.factories import StatementFactory + + conversation_id = self.view.chatterbot.storage.create_conversation() + + statement = StatementFactory() + + mock_response = MockResponse(conversation_id) get_session = self.view.get_chat_session(mock_response) - self.assertEqual(session.id_string, get_session.id_string) + self.assertEqual(conversation_id, get_session.id) def test_get_chat_session_invalid(self): - mock_response = MockResponse('--invalid--') + mock_response = MockResponse(0) session = self.view.get_chat_session(mock_response) - self.assertNotEqual(session.id_string, 'test-session-id') + self.assertNotEqual(session.id, 'test-session-id') def test_get_chat_session_no_session(self): mock_response = MockResponse(None) mock_response.session = {} session = self.view.get_chat_session(mock_response) - self.assertNotEqual(session.id_string, 'test-session-id') + self.assertNotEqual(session.id, 'test-session-id') From 5ecec3e2d8cab5e79d4f768cb57f2959e234e21a Mon Sep 17 00:00:00 2001 From: Gunther Cox Date: Fri, 14 Jul 2017 03:09:43 -0400 Subject: [PATCH 11/28] A new conversation will be created if it has no statements --- tests_django/test_views.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests_django/test_views.py b/tests_django/test_views.py index e002c7b84..e16ee4bd1 100644 --- a/tests_django/test_views.py +++ b/tests_django/test_views.py @@ -30,11 +30,12 @@ def test_validate_invalid_text(self): }) def test_get_chat_session(self): - from chatterbot.ext.django_chatterbot.factories import StatementFactory + from chatterbot.ext.django_chatterbot.models import Statement conversation_id = self.view.chatterbot.storage.create_conversation() - statement = StatementFactory() + statement = Statement.objects.create(text='Test statement') + statement.conversation.add(conversation_id) mock_response = MockResponse(conversation_id) get_session = self.view.get_chat_session(mock_response) From 67775fc6b885567f964ba9057582a214ef2d9e48 Mon Sep 17 00:00:00 2001 From: Gunther Cox Date: Fri, 14 Jul 2017 17:09:25 -0400 Subject: [PATCH 12/28] Don't check read-only status in learn function --- chatterbot/chatterbot.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/chatterbot/chatterbot.py b/chatterbot/chatterbot.py index cead0e738..a0b1b0734 100644 --- a/chatterbot/chatterbot.py +++ b/chatterbot/chatterbot.py @@ -114,9 +114,9 @@ def get_response(self, input_item, session_id=None): # Learn that the user's input was a valid response to the chat bot's previous output previous_statement = self.storage.get_latest_response(session_id) - self.learn_response(statement, previous_statement) if not self.read_only: + self.learn_response(statement, previous_statement) self.storage.add_to_converation(session_id, statement, response) # Process the response output with the output adapter @@ -149,8 +149,7 @@ def learn_response(self, statement, previous_statement): )) # Save the statement after selecting a response - if not self.read_only: - self.storage.update(statement) + self.storage.update(statement) def set_trainer(self, training_class, **kwargs): """ From 5a6da8f1efeb9989f2d24ac6b478de89bb40848e Mon Sep 17 00:00:00 2001 From: Gunther Cox Date: Fri, 14 Jul 2017 23:51:12 -0400 Subject: [PATCH 13/28] Allow repeat statements to be saved in a conversation --- .../migrations/0007_create_phrase.py | 41 +++++++++++++++++++ chatterbot/ext/django_chatterbot/models.py | 39 ++++++++++++++++-- chatterbot/ext/django_chatterbot/views.py | 12 ++++-- chatterbot/storage/django_storage.py | 18 ++++++-- tests_django/test_api_view.py | 7 ++-- tests_django/test_views.py | 11 +++-- 6 files changed, 111 insertions(+), 17 deletions(-) create mode 100644 chatterbot/ext/django_chatterbot/migrations/0007_create_phrase.py diff --git a/chatterbot/ext/django_chatterbot/migrations/0007_create_phrase.py b/chatterbot/ext/django_chatterbot/migrations/0007_create_phrase.py new file mode 100644 index 000000000..b2b656188 --- /dev/null +++ b/chatterbot/ext/django_chatterbot/migrations/0007_create_phrase.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2017-07-14 21:58 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('django_chatterbot', '0006_create_conversation'), + ] + + operations = [ + migrations.CreateModel( + name='Phrase', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('text', models.CharField(blank=True, max_length=255)), + ], + options={ + 'abstract': False, + }, + ), + migrations.AlterField( + model_name='conversation', + name='statements', + field=models.ManyToManyField(help_text='The phrases in this conversation.', related_name='conversations', to='django_chatterbot.Phrase'), + ), + migrations.AlterField( + model_name='statement', + name='text', + field=models.CharField(max_length=255), + ), + migrations.AddField( + model_name='statement', + name='phrase', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='statements', to='django_chatterbot.Phrase'), + ), + ] diff --git a/chatterbot/ext/django_chatterbot/models.py b/chatterbot/ext/django_chatterbot/models.py index 2f6794408..50a5f9d10 100644 --- a/chatterbot/ext/django_chatterbot/models.py +++ b/chatterbot/ext/django_chatterbot/models.py @@ -2,6 +2,24 @@ from django.utils import timezone +class AbstractBasePhrase(models.Model): + """ + A small group of words representing a conceptual unit. + """ + + text = models.CharField( + # unique=True, + blank=True, + null=False, + max_length=255 + ) + + class Meta: + abstract = True + + def __str__(self): + return self.text + class AbstractBaseStatement(models.Model): """ The abstract base statement allows other models to @@ -10,12 +28,18 @@ class AbstractBaseStatement(models.Model): """ text = models.CharField( - unique=True, blank=False, null=False, max_length=255 ) + phrase = models.ForeignKey( + 'Phrase', + related_name='statements', + blank=True, + null=True + ) + created_at = models.DateTimeField( default=timezone.now, help_text='The date and time that this statement was created at.' @@ -185,9 +209,9 @@ class AbstractBaseConversation(models.Model): """ statements = models.ManyToManyField( - 'Statement', - related_name='conversation', - help_text='The statements in this conversation.' + 'Phrase', + related_name='conversations', + help_text='The phrases in this conversation.' ) class Meta: @@ -219,4 +243,11 @@ class Conversation(AbstractBaseConversation): """ A sequence of statements representing a conversation. """ + pass + + +class Phrase(AbstractBasePhrase): + """ + A small group of words representing a conceptual unit. + """ pass \ No newline at end of file diff --git a/chatterbot/ext/django_chatterbot/views.py b/chatterbot/ext/django_chatterbot/views.py index b3410d3d4..30a708442 100644 --- a/chatterbot/ext/django_chatterbot/views.py +++ b/chatterbot/ext/django_chatterbot/views.py @@ -28,6 +28,8 @@ def get_chat_session(self, request): Return the current session for the chat if one exists. Create a new session if one does not exist. """ + from chatterbot.ext.django_chatterbot.models import Phrase + class Obj(object): def __init__(self): self.id = None @@ -38,16 +40,20 @@ def __init__(self): chat_session_id = request.session.get('chat_session_id', None) statements = self.chatterbot.storage.filter( - conversation__id=chat_session_id + phrase__conversations__id=chat_session_id + ) + + phrases = Phrase.objects.filter( + conversations__id=chat_session_id ) - if not statements: + if not phrases: chat_session_id = self.chatterbot.storage.create_conversation() request.session['chat_session_id'] = chat_session_id conversation.id = chat_session_id conversation.statements = [ - statement.serialize() for statement in statements + {'text': phrase.text} for phrase in phrases ] return conversation diff --git a/chatterbot/storage/django_storage.py b/chatterbot/storage/django_storage.py index 9f1f87a50..8d0e9690f 100644 --- a/chatterbot/storage/django_storage.py +++ b/chatterbot/storage/django_storage.py @@ -148,7 +148,7 @@ def get_latest_response(self, conversation_id): Statement = apps.get_model(self.django_app_name, 'Statement') return Statement.objects.filter( - conversation__id=conversation_id + phrase__conversations__id=conversation_id ).order_by( 'created_at' )[:2].first() @@ -169,12 +169,24 @@ def add_to_converation(self, conversation_id, statement, response): from django.apps import apps Statement = apps.get_model(self.django_app_name, 'Statement') + Phrase = apps.get_model(self.django_app_name, 'Phrase') first_statement = Statement.objects.filter(text=statement.text).first() first_response = Statement.objects.filter(text=response.text).first() - first_statement.conversation.add(conversation_id) - first_response.conversation.add(conversation_id) + statement_phrase = Phrase.objects.create( + text=statement.text + + ) + response_phrase = Phrase.objects.create( + text=response.text + ) + + statement_phrase.conversations.add(conversation_id) + statement_phrase.statements.add(first_statement) + + response_phrase.conversations.add(conversation_id) + response_phrase.statements.add(first_response) def drop(self): """ diff --git a/tests_django/test_api_view.py b/tests_django/test_api_view.py index c6fdaf22e..6028b344b 100644 --- a/tests_django/test_api_view.py +++ b/tests_django/test_api_view.py @@ -33,7 +33,6 @@ def test_get_conversation(self): data = self._get_json(response) self.assertIn('conversation', data) - self.assertEqual(len(data['conversation']), 1) - self.assertEqual(len(data['conversation'][0]), 2) - self.assertIn('text', data['conversation'][0][0]) - self.assertIn('text', data['conversation'][0][1]) + self.assertEqual(len(data['conversation']), 2) + self.assertIn('text', data['conversation'][0]) + self.assertIn('text', data['conversation'][1]) diff --git a/tests_django/test_views.py b/tests_django/test_views.py index e16ee4bd1..582449569 100644 --- a/tests_django/test_views.py +++ b/tests_django/test_views.py @@ -30,12 +30,17 @@ def test_validate_invalid_text(self): }) def test_get_chat_session(self): - from chatterbot.ext.django_chatterbot.models import Statement + from chatterbot.ext.django_chatterbot.models import Statement, Phrase conversation_id = self.view.chatterbot.storage.create_conversation() - statement = Statement.objects.create(text='Test statement') - statement.conversation.add(conversation_id) + statement = Statement.objects.create( + text='Test statement', + phrase=Phrase.objects.create( + text='Test statement' + ) + ) + statement.phrase.conversations.add(conversation_id) mock_response = MockResponse(conversation_id) get_session = self.view.get_chat_session(mock_response) From 551a902ff1c5c9e69f2c08df1916ea0702082fe2 Mon Sep 17 00:00:00 2001 From: Gunther Cox Date: Sat, 15 Jul 2017 00:17:51 -0400 Subject: [PATCH 14/28] Remove session module --- chatterbot/conversation/session.py | 51 ------------------------------ tests/test_sessions.py | 50 ----------------------------- 2 files changed, 101 deletions(-) delete mode 100644 chatterbot/conversation/session.py delete mode 100644 tests/test_sessions.py diff --git a/chatterbot/conversation/session.py b/chatterbot/conversation/session.py deleted file mode 100644 index 1b642a32b..000000000 --- a/chatterbot/conversation/session.py +++ /dev/null @@ -1,51 +0,0 @@ -import uuid -from chatterbot.queues import ResponseQueue - - -class Session(object): - """ - A session is an ordered collection of statements - that are related to each other. - """ - - def __init__(self): - # A unique identifier for the chat session - self.uuid = uuid.uuid1() - self.id_string = str(self.uuid) - self.id = str(self.uuid) - - # The last 10 statement inputs and outputs - self.conversation = ResponseQueue(maxsize=10) - - -class ConversationSessionManager(object): - """ - Object to hold and manage multiple chat sessions. - """ - - def __init__(self): - self.sessions = {} - - def new(self): - """ - Create a new conversation. - """ - session = Session() - - self.sessions[session.id_string] = session - - return session - - def get(self, session_id, default=None): - """ - Return a session given a unique identifier. - """ - return self.sessions.get(str(session_id), default) - - def update(self, session_id, conversance): - """ - Add a conversance to a given session if the session exists. - """ - session_id = str(session_id) - if session_id in self.sessions: - self.sessions[session_id].conversation.append(conversance) diff --git a/tests/test_sessions.py b/tests/test_sessions.py deleted file mode 100644 index 741319851..000000000 --- a/tests/test_sessions.py +++ /dev/null @@ -1,50 +0,0 @@ -from unittest import TestCase -from chatterbot.conversation.session import Session, ConversationSessionManager - - -class SessionTestCase(TestCase): - - def test_id_string(self): - session = Session() - self.assertEqual(str(session.uuid), session.id_string) - - -class ConversationSessionManagerTestCase(TestCase): - - def setUp(self): - super(ConversationSessionManagerTestCase, self).setUp() - self.manager = ConversationSessionManager() - - def test_new(self): - session = self.manager.new() - - self.assertTrue(isinstance(session, Session)) - self.assertIn(session.id_string, self.manager.sessions) - self.assertEqual(session, self.manager.sessions[session.id_string]) - - def test_get(self): - session = self.manager.new() - returned_session = self.manager.get(session.id_string) - - self.assertEqual(session.id_string, returned_session.id_string) - - def test_get_invalid_id(self): - returned_session = self.manager.get('--invalid--') - - self.assertIsNone(returned_session) - - def test_get_invalid_id_with_deafult(self): - returned_session = self.manager.get('--invalid--', 'default_value') - - self.assertEqual(returned_session, 'default_value') - - def test_update(self): - session = self.manager.new() - self.manager.update(session.id_string, ('A', 'B', )) - - session_ids = list(self.manager.sessions.keys()) - session_id = session_ids[0] - - self.assertEqual(len(session_ids), 1) - self.assertEqual(len(self.manager.get(session_id).conversation), 1) - self.assertEqual(('A', 'B', ), self.manager.get(session_id).conversation[0]) From 60c5cce06c7bb568176605c0663a11c608a0c00b Mon Sep 17 00:00:00 2001 From: Gunther Cox Date: Sat, 15 Jul 2017 00:30:26 -0400 Subject: [PATCH 15/28] No longer need to clear response queue after tests --- chatterbot/ext/django_chatterbot/models.py | 3 ++- chatterbot/ext/django_chatterbot/views.py | 4 ---- examples/django_app/tests/test_example.py | 14 -------------- tests_django/test_api_view.py | 1 - 4 files changed, 2 insertions(+), 20 deletions(-) diff --git a/chatterbot/ext/django_chatterbot/models.py b/chatterbot/ext/django_chatterbot/models.py index 50a5f9d10..d8c1c0d48 100644 --- a/chatterbot/ext/django_chatterbot/models.py +++ b/chatterbot/ext/django_chatterbot/models.py @@ -20,6 +20,7 @@ class Meta: def __str__(self): return self.text + class AbstractBaseStatement(models.Model): """ The abstract base statement allows other models to @@ -250,4 +251,4 @@ class Phrase(AbstractBasePhrase): """ A small group of words representing a conceptual unit. """ - pass \ No newline at end of file + pass diff --git a/chatterbot/ext/django_chatterbot/views.py b/chatterbot/ext/django_chatterbot/views.py index 30a708442..0ddf6816e 100644 --- a/chatterbot/ext/django_chatterbot/views.py +++ b/chatterbot/ext/django_chatterbot/views.py @@ -39,10 +39,6 @@ def __init__(self): chat_session_id = request.session.get('chat_session_id', None) - statements = self.chatterbot.storage.filter( - phrase__conversations__id=chat_session_id - ) - phrases = Phrase.objects.filter( conversations__id=chat_session_id ) diff --git a/examples/django_app/tests/test_example.py b/examples/django_app/tests/test_example.py index 7f39b6a88..c087f940f 100644 --- a/examples/django_app/tests/test_example.py +++ b/examples/django_app/tests/test_example.py @@ -2,7 +2,6 @@ from django.test import TestCase from django.core.urlresolvers import reverse from django.utils.encoding import force_text -from chatterbot.ext.django_chatterbot.views import ChatterBotView class ViewTestCase(TestCase): @@ -77,19 +76,6 @@ def setUp(self): super(ApiIntegrationTestCase, self).setUp() self.api_url = reverse('chatterbot:chatterbot') - # Clear the response queue before tests - ChatterBotView.chatterbot.conversation_sessions.get( - ChatterBotView.chatterbot.default_session.id_string - ).conversation.flush() - - def tearDown(self): - super(ApiIntegrationTestCase, self).tearDown() - - # Clear the response queue after tests - ChatterBotView.chatterbot.conversation_sessions.get( - ChatterBotView.chatterbot.default_session.id_string - ).conversation.flush() - def _get_json(self, response): return json.loads(force_text(response.content)) diff --git a/tests_django/test_api_view.py b/tests_django/test_api_view.py index 6028b344b..28507adf8 100644 --- a/tests_django/test_api_view.py +++ b/tests_django/test_api_view.py @@ -1,7 +1,6 @@ import json from django.test import TestCase from django.core.urlresolvers import reverse -from chatterbot.ext.django_chatterbot.views import ChatterBotView class ApiIntegrationTestCase(TestCase): From b940a6aebb5ee037e7803dfb6d2c031479cdf183 Mon Sep 17 00:00:00 2001 From: Gunther Cox Date: Sat, 15 Jul 2017 00:54:26 -0400 Subject: [PATCH 16/28] Update test response values --- examples/django_app/tests/test_example.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/django_app/tests/test_example.py b/examples/django_app/tests/test_example.py index c087f940f..b418fd481 100644 --- a/examples/django_app/tests/test_example.py +++ b/examples/django_app/tests/test_example.py @@ -98,7 +98,6 @@ def test_get_conversation(self): data = self._get_json(response) self.assertIn('conversation', data) - self.assertEqual(len(data['conversation']), 1) - self.assertEqual(len(data['conversation'][0]), 2) - self.assertIn('text', data['conversation'][0][0]) - self.assertIn('text', data['conversation'][0][1]) + self.assertEqual(len(data['conversation']), 2) + self.assertIn('text', data['conversation'][0]) + self.assertIn('text', data['conversation'][1]) From 2c04a689c8bfb5cb7353db6d3e2bc38454a08780 Mon Sep 17 00:00:00 2001 From: Gunther Cox Date: Sat, 15 Jul 2017 01:09:32 -0400 Subject: [PATCH 17/28] Rename session variables --- chatterbot/chatterbot.py | 24 +++++++++++------------ chatterbot/ext/django_chatterbot/views.py | 20 +++++++++---------- chatterbot/filters.py | 8 ++++---- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/chatterbot/chatterbot.py b/chatterbot/chatterbot.py index a0b1b0734..5ec5bff30 100644 --- a/chatterbot/chatterbot.py +++ b/chatterbot/chatterbot.py @@ -75,7 +75,7 @@ def __init__(self, name, **kwargs): self.trainer = TrainerClass(self.storage, **kwargs) self.training_data = kwargs.get('training_data') - self.default_session_id = None + self.default_conversation_id = None self.logger = kwargs.get('logger', logging.getLogger(__name__)) @@ -91,7 +91,7 @@ def initialize(self): """ self.logic.initialize() - def get_response(self, input_item, session_id=None): + def get_response(self, input_item, conversation_id=None): """ Return the bot's response based on the input. @@ -99,10 +99,10 @@ def get_response(self, input_item, session_id=None): :returns: A response to the input. :rtype: Statement """ - if not session_id: - if not self.default_session_id: - self.default_session_id = self.storage.create_conversation() - session_id = self.default_session_id + if not conversation_id: + if not self.default_conversation_id: + self.default_conversation_id = self.storage.create_conversation() + conversation_id = self.default_conversation_id input_statement = self.input.process_input_statement(input_item) @@ -110,23 +110,23 @@ def get_response(self, input_item, session_id=None): for preprocessor in self.preprocessors: input_statement = preprocessor(self, input_statement) - statement, response = self.generate_response(input_statement, session_id) + statement, response = self.generate_response(input_statement, conversation_id) # Learn that the user's input was a valid response to the chat bot's previous output - previous_statement = self.storage.get_latest_response(session_id) + previous_statement = self.storage.get_latest_response(conversation_id) if not self.read_only: self.learn_response(statement, previous_statement) - self.storage.add_to_converation(session_id, statement, response) + self.storage.add_to_converation(conversation_id, statement, response) # Process the response output with the output adapter - return self.output.process_response(response, session_id) + return self.output.process_response(response, conversation_id) - def generate_response(self, input_statement, session_id): + def generate_response(self, input_statement, conversation_id): """ Return a response based on a given input statement. """ - self.storage.generate_base_query(self, session_id) + self.storage.generate_base_query(self, conversation_id) # Select a response to the input statement response = self.logic.process(input_statement) diff --git a/chatterbot/ext/django_chatterbot/views.py b/chatterbot/ext/django_chatterbot/views.py index 0ddf6816e..dd9620c80 100644 --- a/chatterbot/ext/django_chatterbot/views.py +++ b/chatterbot/ext/django_chatterbot/views.py @@ -23,10 +23,10 @@ def validate(self, data): if 'text' not in data: raise ValidationError('The attribute "text" is required.') - def get_chat_session(self, request): + def get_conversation(self, request): """ - Return the current session for the chat if one exists. - Create a new session if one does not exist. + Return the conversation for the session if one exists. + Create a new conversation if one does not exist. """ from chatterbot.ext.django_chatterbot.models import Phrase @@ -37,17 +37,17 @@ def __init__(self): conversation = Obj() - chat_session_id = request.session.get('chat_session_id', None) + conversation_id = request.session.get('conversation_id', None) phrases = Phrase.objects.filter( - conversations__id=chat_session_id + conversations__id=conversation_id ) if not phrases: - chat_session_id = self.chatterbot.storage.create_conversation() - request.session['chat_session_id'] = chat_session_id + conversation_id = self.chatterbot.storage.create_conversation() + request.session['conversation_id'] = conversation_id - conversation.id = chat_session_id + conversation.id = conversation_id conversation.statements = [ {'text': phrase.text} for phrase in phrases ] @@ -68,7 +68,7 @@ def post(self, request, *args, **kwargs): self.validate(input_data) - conversation = self.get_chat_session(request) + conversation = self.get_conversation(request) response = self.chatterbot.get_response(input_data, conversation.id) response_data = response.serialize() @@ -79,7 +79,7 @@ def get(self, request, *args, **kwargs): """ Return data corresponding to the current conversation. """ - conversation = self.get_chat_session(request) + conversation = self.get_conversation(request) data = { 'detail': 'You should make a POST request to this endpoint.', diff --git a/chatterbot/filters.py b/chatterbot/filters.py index 9f13cbc34..9a07a0980 100644 --- a/chatterbot/filters.py +++ b/chatterbot/filters.py @@ -9,7 +9,7 @@ class Filter(object): filters should be subclassed. """ - def filter_selection(self, chatterbot, session_id): + def filter_selection(self, chatterbot, conversation_id): """ Because this is the base filter class, this method just returns the storage adapter's base query. Other filters @@ -24,12 +24,12 @@ class RepetitiveResponseFilter(Filter): a chat bot from repeating statements that it has recently said. """ - def filter_selection(self, chatterbot, session_id): + def filter_selection(self, chatterbot, conversation_id): text_of_recent_responses = [] # TODO: Add a larger quantity of response history - latest_response = chatterbot.storage.get_latest_response(session_id) + latest_response = chatterbot.storage.get_latest_response(conversation_id) if latest_response: text_of_recent_responses.append(latest_response.text) @@ -37,7 +37,7 @@ def filter_selection(self, chatterbot, session_id): if not text_of_recent_responses: return super(RepetitiveResponseFilter, self).filter_selection( chatterbot, - session_id + conversation_id ) query = chatterbot.storage.base_query.statement_text_not_in( From e37688781324bb3ae76bf57c2af794c14a729e41 Mon Sep 17 00:00:00 2001 From: Gunther Cox Date: Sat, 15 Jul 2017 01:38:50 -0400 Subject: [PATCH 18/28] Remove session documentation --- docs/comparisons.rst | 51 +++++++++++++++++++++++++++++ docs/conversations.rst | 74 +++++++++++++++--------------------------- docs/index.rst | 2 +- docs/sessions.rst | 42 ------------------------ docs/statements.txt | 0 5 files changed, 78 insertions(+), 91 deletions(-) create mode 100644 docs/comparisons.rst delete mode 100644 docs/sessions.rst create mode 100644 docs/statements.txt diff --git a/docs/comparisons.rst b/docs/comparisons.rst new file mode 100644 index 000000000..caa2f3ecb --- /dev/null +++ b/docs/comparisons.rst @@ -0,0 +1,51 @@ +=========== +Comparisons +=========== + +.. _statement-comparison: + +Statement comparison +==================== + +ChatterBot uses :code:`Statement` objects to hold information +about things that can be said. An important part of how a chat bot +selects a response is based on its ability to compare two statements +to each other. There are a number of ways to do this, and ChatterBot +comes with a handful of methods built in for you to use. + +.. 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 possible 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 +----------------------------- + +To set the statement comparison method for your chat bot, you +will need to pass the :code:`statement_comparison_function` parameter +to your chat bot when you initialize it. An example of this +is shown below. + +.. code-block:: python + + from chatterbot import ChatBot + from chatterbot.comparisons import levenshtein_distance + + chatbot = ChatBot( + # ... + statement_comparison_function=levenshtein_distance + ) diff --git a/docs/conversations.rst b/docs/conversations.rst index 8893c604b..a004bde8b 100644 --- a/docs/conversations.rst +++ b/docs/conversations.rst @@ -2,6 +2,32 @@ Conversations ============= +ChatterBot supports the ability to have multiple concurrent conversations. +A conversations is where the chat bot interacts with a person, and supporting +multiple concurrent conversations means that the chat bot can have multiple +different conversations with different people at the same time. + +Conversation scope +------------------ + +If two :code:`ChatBot` instances are created, each will have conversations separate from each other. + +An adapter can access any conversation as long as the unique identifier for the conversation is provided. + +Conversation example +-------------------- + +The following example is taken from the Django :code:`ChatterBotView` built into ChatterBot. +In this method, the unique identifiers for each chat session are being stored in Django's +session objects. This allows different users who interact with the bot through different +web browsers to have separate conversations with the chat bot. + +.. literalinclude:: ../chatterbot/ext/django_chatterbot/views.py + :language: python + :pyobject: ChatterBotView.post + :dedent: 4 + + .. _conversation_statements: Statements @@ -53,51 +79,3 @@ of the current statement. The :code:`Response` object's :code:`occurrence` attribute indicates the number of times 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 -==================== - -ChatterBot uses :code:`Statement` objects to hold information -about things that can be said. An important part of how a chat bot -selects a response is based on its ability to compare two statements -to each other. There are a number of ways to do this, and ChatterBot -comes with a handful of methods built in for you to use. - -.. 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 possible 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 ------------------------------ - -To set the statement comparison method for your chat bot, you -will need to pass the :code:`statement_comparison_function` parameter -to your chat bot when you initialize it. An example of this -is shown below. - -.. code-block:: python - - from chatterbot import ChatBot - from chatterbot.comparisons import levenshtein_distance - - chatbot = ChatBot( - # ... - statement_comparison_function=levenshtein_distance - ) diff --git a/docs/index.rst b/docs/index.rst index 6e7dbb7b5..0c42b4daf 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -67,7 +67,7 @@ Contents: filters/index chatterbot conversations - sessions + comparisons utils corpus django/index diff --git a/docs/sessions.rst b/docs/sessions.rst deleted file mode 100644 index eef8680c8..000000000 --- a/docs/sessions.rst +++ /dev/null @@ -1,42 +0,0 @@ -======== -Sessions -======== - -ChatterBot supports the ability to have multiple concurrent chat sessions. -A chat session is where the chat bot interacts with a person, and supporting -multiple chat sessions means that your chat bot can have multiple different -conversations with different people at the same time. - -.. autoclass:: chatterbot.conversation.session.Session - :members: - -.. autoclass:: chatterbot.conversation.session.ConversationSessionManager - :members: - -Each session object holds a queue of the most recent communications that have -occurred during that session. The queue holds tuples with two values each, -the first value is the input that the bot received and the second value is the -response that the bot returned. - -.. autoclass:: chatterbot.queues.ResponseQueue - :members: - -Session scope -------------- - -If two :code:`ChatBot` instances are created, each will have sessions separate from each other. - -An adapter can access any session as long as the unique identifier for the session is provided. - -Session example ---------------- - -The following example is taken from the Django :code:`ChatterBotView` built into ChatterBot. -In this method, the unique identifiers for each chat session are being stored in Django's -session objects. This allows different users who interact with the bot through different -web browsers to have separate conversations with the chat bot. - -.. literalinclude:: ../chatterbot/ext/django_chatterbot/views.py - :language: python - :pyobject: ChatterBotView.post - :dedent: 4 diff --git a/docs/statements.txt b/docs/statements.txt new file mode 100644 index 000000000..e69de29bb From d7df50ce78cec4cc0d092d25fb747afb3a8a2793 Mon Sep 17 00:00:00 2001 From: Gunther Cox Date: Sat, 15 Jul 2017 02:53:32 -0400 Subject: [PATCH 19/28] Update variables in tests --- tests/test_chatbot.py | 4 ++-- tests_django/test_views.py | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/test_chatbot.py b/tests/test_chatbot.py index 767b9a16f..69bece366 100644 --- a/tests/test_chatbot.py +++ b/tests/test_chatbot.py @@ -43,7 +43,7 @@ def test_statement_added_to_recent_response_list(self): statement_text = 'Wow!' response = self.chatbot.get_response(statement_text) response_statement = self.chatbot.storage.get_latest_response( - self.chatbot.default_session_id + self.chatbot.default_conversation_id ) self.assertEqual(statement_text, response_statement.text) @@ -138,7 +138,7 @@ def test_generate_response(self): statement = Statement('Many insects adopt a tripedal gait for rapid yet stable walking.') input_statement, response = self.chatbot.generate_response( statement, - self.chatbot.default_session_id + self.chatbot.default_conversation_id ) self.assertEqual(input_statement, statement) diff --git a/tests_django/test_views.py b/tests_django/test_views.py index 582449569..565adb468 100644 --- a/tests_django/test_views.py +++ b/tests_django/test_views.py @@ -29,7 +29,7 @@ def test_validate_invalid_text(self): 'type': 'classmethod' }) - def test_get_chat_session(self): + def test_get_conversation(self): from chatterbot.ext.django_chatterbot.models import Statement, Phrase conversation_id = self.view.chatterbot.storage.create_conversation() @@ -43,19 +43,19 @@ def test_get_chat_session(self): statement.phrase.conversations.add(conversation_id) mock_response = MockResponse(conversation_id) - get_session = self.view.get_chat_session(mock_response) + get_session = self.view.get_conversation(mock_response) self.assertEqual(conversation_id, get_session.id) - def test_get_chat_session_invalid(self): + def test_get_conversation_invalid(self): mock_response = MockResponse(0) - session = self.view.get_chat_session(mock_response) + session = self.view.get_conversation(mock_response) self.assertNotEqual(session.id, 'test-session-id') - def test_get_chat_session_no_session(self): + def test_get_conversation_no_session(self): mock_response = MockResponse(None) mock_response.session = {} - session = self.view.get_chat_session(mock_response) + session = self.view.get_conversation(mock_response) self.assertNotEqual(session.id, 'test-session-id') From 807fbd68dfe359af23cd4b2e8b6175b837448bda Mon Sep 17 00:00:00 2001 From: Gunther Cox Date: Sat, 15 Jul 2017 10:24:35 -0400 Subject: [PATCH 20/28] Fail at connecting conversations and responses --- .../migrations/0008_remove_phrase.py | 50 ++++++++++++++ chatterbot/ext/django_chatterbot/models.py | 67 ++++++------------- chatterbot/ext/django_chatterbot/views.py | 18 ++--- chatterbot/storage/django_storage.py | 40 +++++------ tests_django/test_django_adapter.py | 8 +-- tests_django/test_views.py | 18 +++-- 6 files changed, 110 insertions(+), 91 deletions(-) create mode 100644 chatterbot/ext/django_chatterbot/migrations/0008_remove_phrase.py diff --git a/chatterbot/ext/django_chatterbot/migrations/0008_remove_phrase.py b/chatterbot/ext/django_chatterbot/migrations/0008_remove_phrase.py new file mode 100644 index 000000000..6746a0793 --- /dev/null +++ b/chatterbot/ext/django_chatterbot/migrations/0008_remove_phrase.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2017-07-15 13:44 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('django_chatterbot', '0007_create_phrase'), + ] + + operations = [ + migrations.RemoveField( + model_name='conversation', + name='statements', + ), + migrations.RemoveField( + model_name='response', + name='occurrence', + ), + migrations.RemoveField( + model_name='statement', + name='created_at', + ), + migrations.RemoveField( + model_name='statement', + name='phrase', + ), + migrations.AddField( + model_name='conversation', + name='responses', + field=models.ManyToManyField(help_text='The responses in this conversation.', related_name='conversations', to='django_chatterbot.Response'), + ), + migrations.AddField( + model_name='response', + name='created_at', + field=models.DateTimeField(default=django.utils.timezone.now, help_text='The date and time that this statement was created at.'), + ), + migrations.AlterField( + model_name='statement', + name='text', + field=models.CharField(max_length=255, unique=True), + ), + migrations.DeleteModel( + name='Phrase', + ), + ] diff --git a/chatterbot/ext/django_chatterbot/models.py b/chatterbot/ext/django_chatterbot/models.py index d8c1c0d48..0d6eba207 100644 --- a/chatterbot/ext/django_chatterbot/models.py +++ b/chatterbot/ext/django_chatterbot/models.py @@ -2,25 +2,6 @@ from django.utils import timezone -class AbstractBasePhrase(models.Model): - """ - A small group of words representing a conceptual unit. - """ - - text = models.CharField( - # unique=True, - blank=True, - null=False, - max_length=255 - ) - - class Meta: - abstract = True - - def __str__(self): - return self.text - - class AbstractBaseStatement(models.Model): """ The abstract base statement allows other models to @@ -29,23 +10,12 @@ class AbstractBaseStatement(models.Model): """ text = models.CharField( + unique=True, blank=False, null=False, max_length=255 ) - phrase = models.ForeignKey( - 'Phrase', - related_name='statements', - blank=True, - null=True - ) - - created_at = models.DateTimeField( - default=timezone.now, - help_text='The date and time that this statement was created at.' - ) - extra_data = models.CharField(max_length=500) # This is the confidence with which the chat bot believes @@ -142,7 +112,6 @@ def serialize(self): data['text'] = self.text data['in_response_to'] = [] - data['created_at'] = self.created_at data['extra_data'] = json.loads(self.extra_data) for response in self.in_response.all(): @@ -168,9 +137,10 @@ class AbstractBaseResponse(models.Model): related_name='responses' ) - unique_together = (('statement', 'response'),) - - occurrence = models.PositiveIntegerField(default=1) + created_at = models.DateTimeField( + default=timezone.now, + help_text='The date and time that this statement was created at.' + ) created_at = models.DateTimeField( default=timezone.now, @@ -180,6 +150,20 @@ class AbstractBaseResponse(models.Model): class Meta: abstract = True + @property + def occurrence(self): + """ + Return a count of the number of times this response has occurred. + """ + from django.apps import apps + + response = apps.get_model('django_chatterbot', self.__class__.__name__) + + return response.objects.filter( + statement__text=self.statement.text, + response__text=self.response.text + ).count() + def __str__(self): statement = self.statement.text response = self.response.text @@ -209,10 +193,10 @@ class AbstractBaseConversation(models.Model): default models. """ - statements = models.ManyToManyField( - 'Phrase', + responses = models.ManyToManyField( + 'Response', related_name='conversations', - help_text='The phrases in this conversation.' + help_text='The responses in this conversation.' ) class Meta: @@ -245,10 +229,3 @@ class Conversation(AbstractBaseConversation): A sequence of statements representing a conversation. """ pass - - -class Phrase(AbstractBasePhrase): - """ - A small group of words representing a conceptual unit. - """ - pass diff --git a/chatterbot/ext/django_chatterbot/views.py b/chatterbot/ext/django_chatterbot/views.py index dd9620c80..f23625872 100644 --- a/chatterbot/ext/django_chatterbot/views.py +++ b/chatterbot/ext/django_chatterbot/views.py @@ -28,7 +28,7 @@ def get_conversation(self, request): Return the conversation for the session if one exists. Create a new conversation if one does not exist. """ - from chatterbot.ext.django_chatterbot.models import Phrase + from chatterbot.ext.django_chatterbot.models import Response class Obj(object): def __init__(self): @@ -37,20 +37,20 @@ def __init__(self): conversation = Obj() - conversation_id = request.session.get('conversation_id', None) + conversation.id = request.session.get('conversation_id', None) - phrases = Phrase.objects.filter( - conversations__id=conversation_id + responses = Response.objects.filter( + conversations__id=conversation.id ) - if not phrases: + if not conversation.id: conversation_id = self.chatterbot.storage.create_conversation() request.session['conversation_id'] = conversation_id + conversation.id = conversation_id - conversation.id = conversation_id - conversation.statements = [ - {'text': phrase.text} for phrase in phrases - ] + for response in responses: + conversation.statements.append(response.statement.serialize()) + conversation.statements.append(response.response.serialize()) return conversation diff --git a/chatterbot/storage/django_storage.py b/chatterbot/storage/django_storage.py index 8d0e9690f..c484f78f9 100644 --- a/chatterbot/storage/django_storage.py +++ b/chatterbot/storage/django_storage.py @@ -98,15 +98,11 @@ def update(self, statement): response_statement.extra_data = getattr(_response_statement, 'extra_data', '') response_statement.save() - response, created = Response.objects.get_or_create( + response = Response.objects.create( statement=response_statement, response=statement ) - if not created: - response.occurrence += 1 - response.save() - return statement def get_random(self): @@ -145,13 +141,18 @@ def get_latest_response(self, conversation_id): """ from django.apps import apps - Statement = apps.get_model(self.django_app_name, 'Statement') + Response = apps.get_model(self.django_app_name, 'Response') - return Statement.objects.filter( - phrase__conversations__id=conversation_id + response = Response.objects.filter( + conversations__id=conversation_id ).order_by( 'created_at' - )[:2].first() + ).first() + + if not response: + return None + + return response.response def create_conversation(self): """ @@ -169,24 +170,17 @@ def add_to_converation(self, conversation_id, statement, response): from django.apps import apps Statement = apps.get_model(self.django_app_name, 'Statement') - Phrase = apps.get_model(self.django_app_name, 'Phrase') - - first_statement = Statement.objects.filter(text=statement.text).first() - first_response = Statement.objects.filter(text=response.text).first() + Response = apps.get_model(self.django_app_name, 'Response') - statement_phrase = Phrase.objects.create( - text=statement.text + first_statement = Statement.objects.get(text=statement.text) + first_response = Statement.objects.get(text=response.text) + response = Response.objects.create( + statement=first_statement, + response=first_response ) - response_phrase = Phrase.objects.create( - text=response.text - ) - - statement_phrase.conversations.add(conversation_id) - statement_phrase.statements.add(first_statement) - response_phrase.conversations.add(conversation_id) - response_phrase.statements.add(first_response) + response.conversations.add(conversation_id) def drop(self): """ diff --git a/tests_django/test_django_adapter.py b/tests_django/test_django_adapter.py index 20cf55237..892d4f889 100644 --- a/tests_django/test_django_adapter.py +++ b/tests_django/test_django_adapter.py @@ -340,12 +340,12 @@ def test_order_by_text(self): self.assertEqual(results[0], statement_a) self.assertEqual(results[1], statement_b) - def test_order_by_created_at(self): + def test_reverse_order_by_text(self): statement_a = StatementModel.objects.create(text='A is the first letter of the alphabet.') statement_b = StatementModel.objects.create(text='B is the second letter of the alphabet.') - results = self.adapter.filter(order_by='created_at') + results = self.adapter.filter(order_by='-text') self.assertEqual(len(results), 2) - self.assertEqual(results[0], statement_a) - self.assertEqual(results[1], statement_b) + self.assertEqual(results[1], statement_a) + self.assertEqual(results[0], statement_b) diff --git a/tests_django/test_views.py b/tests_django/test_views.py index 565adb468..4a12db0eb 100644 --- a/tests_django/test_views.py +++ b/tests_django/test_views.py @@ -30,22 +30,20 @@ def test_validate_invalid_text(self): }) def test_get_conversation(self): - from chatterbot.ext.django_chatterbot.models import Statement, Phrase + from chatterbot.ext.django_chatterbot.models import Statement, Response conversation_id = self.view.chatterbot.storage.create_conversation() - statement = Statement.objects.create( - text='Test statement', - phrase=Phrase.objects.create( - text='Test statement' - ) + statement = Statement.objects.create(text='Hello') + Response.objects.create( + statement=statement, + response=statement ) - statement.phrase.conversations.add(conversation_id) mock_response = MockResponse(conversation_id) - get_session = self.view.get_conversation(mock_response) + conversation = self.view.get_conversation(mock_response) - self.assertEqual(conversation_id, get_session.id) + self.assertEqual(conversation_id, conversation.id) def test_get_conversation_invalid(self): mock_response = MockResponse(0) @@ -53,7 +51,7 @@ def test_get_conversation_invalid(self): self.assertNotEqual(session.id, 'test-session-id') - def test_get_conversation_no_session(self): + def test_get_conversation_nonexistent(self): mock_response = MockResponse(None) mock_response.session = {} session = self.view.get_conversation(mock_response) From 44c8565b3f8543cfd5391a9cdb07b27507f791ef Mon Sep 17 00:00:00 2001 From: Gunther Cox Date: Tue, 18 Jul 2017 07:27:25 -0400 Subject: [PATCH 21/28] Update migrations --- .../migrations/0007_create_phrase.py | 41 ------------------- ...phrase.py => 0008_update_conversations.py} | 22 +--------- 2 files changed, 2 insertions(+), 61 deletions(-) delete mode 100644 chatterbot/ext/django_chatterbot/migrations/0007_create_phrase.py rename chatterbot/ext/django_chatterbot/migrations/{0008_remove_phrase.py => 0008_update_conversations.py} (52%) diff --git a/chatterbot/ext/django_chatterbot/migrations/0007_create_phrase.py b/chatterbot/ext/django_chatterbot/migrations/0007_create_phrase.py deleted file mode 100644 index b2b656188..000000000 --- a/chatterbot/ext/django_chatterbot/migrations/0007_create_phrase.py +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2017-07-14 21:58 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('django_chatterbot', '0006_create_conversation'), - ] - - operations = [ - migrations.CreateModel( - name='Phrase', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('text', models.CharField(blank=True, max_length=255)), - ], - options={ - 'abstract': False, - }, - ), - migrations.AlterField( - model_name='conversation', - name='statements', - field=models.ManyToManyField(help_text='The phrases in this conversation.', related_name='conversations', to='django_chatterbot.Phrase'), - ), - migrations.AlterField( - model_name='statement', - name='text', - field=models.CharField(max_length=255), - ), - migrations.AddField( - model_name='statement', - name='phrase', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='statements', to='django_chatterbot.Phrase'), - ), - ] diff --git a/chatterbot/ext/django_chatterbot/migrations/0008_remove_phrase.py b/chatterbot/ext/django_chatterbot/migrations/0008_update_conversations.py similarity index 52% rename from chatterbot/ext/django_chatterbot/migrations/0008_remove_phrase.py rename to chatterbot/ext/django_chatterbot/migrations/0008_update_conversations.py index 6746a0793..f3bd72049 100644 --- a/chatterbot/ext/django_chatterbot/migrations/0008_remove_phrase.py +++ b/chatterbot/ext/django_chatterbot/migrations/0008_update_conversations.py @@ -1,15 +1,14 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2017-07-15 13:44 +# Generated by Django 1.11 on 2017-07-18 11:25 from __future__ import unicode_literals from django.db import migrations, models -import django.utils.timezone class Migration(migrations.Migration): dependencies = [ - ('django_chatterbot', '0007_create_phrase'), + ('django_chatterbot', '0007_response_created_at'), ] operations = [ @@ -25,26 +24,9 @@ class Migration(migrations.Migration): model_name='statement', name='created_at', ), - migrations.RemoveField( - model_name='statement', - name='phrase', - ), migrations.AddField( model_name='conversation', name='responses', field=models.ManyToManyField(help_text='The responses in this conversation.', related_name='conversations', to='django_chatterbot.Response'), ), - migrations.AddField( - model_name='response', - name='created_at', - field=models.DateTimeField(default=django.utils.timezone.now, help_text='The date and time that this statement was created at.'), - ), - migrations.AlterField( - model_name='statement', - name='text', - field=models.CharField(max_length=255, unique=True), - ), - migrations.DeleteModel( - name='Phrase', - ), ] From dc4a9e670170e0e5745b7e651b0129bea1ac42b8 Mon Sep 17 00:00:00 2001 From: Gunther Cox Date: Wed, 19 Jul 2017 21:01:56 -0400 Subject: [PATCH 22/28] Remove created_at attribute from the statement object --- chatterbot/conversation/statement.py | 4 ---- chatterbot/ext/django_chatterbot/models.py | 6 +---- .../test_json_file_storage_adapter.py | 24 ------------------- .../test_statement_integration.py | 21 +++++++--------- tests_django/test_api.py | 4 ---- 5 files changed, 9 insertions(+), 50 deletions(-) diff --git a/chatterbot/conversation/statement.py b/chatterbot/conversation/statement.py index 851611ab2..bac9c9026 100644 --- a/chatterbot/conversation/statement.py +++ b/chatterbot/conversation/statement.py @@ -20,9 +20,6 @@ def __init__(self, text, **kwargs): self.text = text self.in_response_to = kwargs.pop('in_response_to', []) - # The date and time that this statement was created at - self.created_at = kwargs.pop('created_at', datetime.now()) - self.extra_data = kwargs.pop('extra_data', {}) # This is the confidence with which the chat bot believes @@ -139,7 +136,6 @@ def serialize(self): data['text'] = self.text data['in_response_to'] = [] - data['created_at'] = self.created_at data['extra_data'] = self.extra_data for response in self.in_response_to: diff --git a/chatterbot/ext/django_chatterbot/models.py b/chatterbot/ext/django_chatterbot/models.py index 0d6eba207..b506c9d26 100644 --- a/chatterbot/ext/django_chatterbot/models.py +++ b/chatterbot/ext/django_chatterbot/models.py @@ -93,11 +93,7 @@ def get_response_count(self, statement): :returns: Return the number of times the statement has been used as a response. :rtype: int """ - try: - response = self.in_response.get(response__text=statement.text) - return response.occurrence - except Response.DoesNotExist: - return 0 + return self.in_response.filter(response__text=statement.text).count() def serialize(self): """ diff --git a/tests/storage_adapter_tests/test_json_file_storage_adapter.py b/tests/storage_adapter_tests/test_json_file_storage_adapter.py index 26b0ffce1..95579e83a 100644 --- a/tests/storage_adapter_tests/test_json_file_storage_adapter.py +++ b/tests/storage_adapter_tests/test_json_file_storage_adapter.py @@ -380,27 +380,3 @@ def test_order_by_text(self): self.assertEqual(len(results), 2) self.assertEqual(results[0], statement_a) self.assertEqual(results[1], statement_b) - - def test_order_by_created_at(self): - from datetime import datetime, timedelta - - today = datetime.now() - yesterday = datetime.now() - timedelta(days=1) - - statement_a = Statement( - text='A is the first letter of the alphabet.', - created_at=today - ) - statement_b = Statement( - text='B is the second letter of the alphabet.', - created_at=yesterday - ) - - self.adapter.update(statement_a) - self.adapter.update(statement_b) - - results = self.adapter.filter(order_by='created_at') - - self.assertEqual(len(results), 2) - self.assertEqual(results[0], statement_a) - self.assertEqual(results[1], statement_b) diff --git a/tests_django/integration_tests/test_statement_integration.py b/tests_django/integration_tests/test_statement_integration.py index 51f813e31..e050d241a 100644 --- a/tests_django/integration_tests/test_statement_integration.py +++ b/tests_django/integration_tests/test_statement_integration.py @@ -14,9 +14,8 @@ class StatementIntegrationTestCase(TestCase): def setUp(self): super(StatementIntegrationTestCase, self).setUp() - date_created = timezone.now() - self.object = StatementObject(text='_', created_at=date_created) - self.model = StatementModel(text='_', created_at=date_created) + self.object = StatementObject(text='_') + self.model = StatementModel(text='_') def test_text(self): self.assertTrue(hasattr(self.object, 'text')) @@ -61,7 +60,10 @@ def test_get_response_count(self): model_response_statement = StatementModel.objects.create(text='Hello') self.model.save() self.model.in_response.create( - statement=self.model, response=model_response_statement, occurrence=2 + statement=self.model, response=model_response_statement + ) + self.model.in_response.create( + statement=self.model, response=model_response_statement ) object_count = self.object.get_response_count(StatementObject(text='Hello')) @@ -74,11 +76,7 @@ def test_serialize(self): object_data = self.object.serialize() model_data = self.model.serialize() - object_data_created_at = object_data.pop('created_at') - model_data_created_at = model_data.pop('created_at') - self.assertEqual(object_data, model_data) - self.assertEqual(object_data_created_at.date(), model_data_created_at.date()) def test_response_statement_cache(self): self.assertTrue(hasattr(self.object, 'response_statement_cache')) @@ -94,9 +92,8 @@ class ResponseIntegrationTestCase(TestCase): def setUp(self): super(ResponseIntegrationTestCase, self).setUp() - date_created = timezone.now() - statement_object = StatementObject(text='_', created_at=date_created) - statement_model = StatementModel.objects.create(text='_', created_at=date_created) + statement_object = StatementObject(text='_') + statement_model = StatementModel.objects.create(text='_') self.object = ResponseObject(statement_object.text) self.model = ResponseModel(statement=statement_model, response=statement_model) @@ -111,5 +108,3 @@ def test_serialize(self): self.assertIn('occurrence', object_data) self.assertIn('occurrence', model_data) self.assertEqual(object_data['occurrence'], model_data['occurrence']) - self.assertIn('created_at', object_data) - self.assertIn('created_at', model_data) diff --git a/tests_django/test_api.py b/tests_django/test_api.py index a629ae76f..84c86d99e 100644 --- a/tests_django/test_api.py +++ b/tests_django/test_api.py @@ -33,10 +33,6 @@ def test_post(self): self.assertIn('text', content) self.assertGreater(len(content['text']), 1) self.assertIn('in_response_to', content) - self.assertIn('created_at', content) - self.assertTrue( - isinstance(parse(content['created_at']), datetime) - ) def test_post_unicode(self): """ From aa2da7147c320441120cfbcd885773bbd9db03f1 Mon Sep 17 00:00:00 2001 From: Gunther Cox Date: Wed, 19 Jul 2017 21:21:43 -0400 Subject: [PATCH 23/28] Add check for if conversation exists --- chatterbot/ext/django_chatterbot/views.py | 25 ++++++++++++++--------- tests_django/test_views.py | 10 +-------- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/chatterbot/ext/django_chatterbot/views.py b/chatterbot/ext/django_chatterbot/views.py index f23625872..ff4d12b4b 100644 --- a/chatterbot/ext/django_chatterbot/views.py +++ b/chatterbot/ext/django_chatterbot/views.py @@ -28,7 +28,7 @@ def get_conversation(self, request): Return the conversation for the session if one exists. Create a new conversation if one does not exist. """ - from chatterbot.ext.django_chatterbot.models import Response + from chatterbot.ext.django_chatterbot.models import Conversation, Response class Obj(object): def __init__(self): @@ -37,20 +37,25 @@ def __init__(self): conversation = Obj() - conversation.id = request.session.get('conversation_id', None) + conversation.id = request.session.get('conversation_id', 0) + existing_conversation = False + try: + Conversation.objects.get(id=conversation.id) + existing_conversation = True - responses = Response.objects.filter( - conversations__id=conversation.id - ) - - if not conversation.id: + except Conversation.DoesNotExist: conversation_id = self.chatterbot.storage.create_conversation() request.session['conversation_id'] = conversation_id conversation.id = conversation_id - for response in responses: - conversation.statements.append(response.statement.serialize()) - conversation.statements.append(response.response.serialize()) + if existing_conversation: + responses = Response.objects.filter( + conversations__id=conversation.id + ) + + for response in responses: + conversation.statements.append(response.statement.serialize()) + conversation.statements.append(response.response.serialize()) return conversation diff --git a/tests_django/test_views.py b/tests_django/test_views.py index 4a12db0eb..c07caa3a2 100644 --- a/tests_django/test_views.py +++ b/tests_django/test_views.py @@ -6,7 +6,7 @@ class MockResponse(object): def __init__(self, pk): - self.session = {'chat_session_id': pk} + self.session = {'conversation_id': pk} class ViewTestCase(TestCase): @@ -30,16 +30,8 @@ def test_validate_invalid_text(self): }) def test_get_conversation(self): - from chatterbot.ext.django_chatterbot.models import Statement, Response - conversation_id = self.view.chatterbot.storage.create_conversation() - statement = Statement.objects.create(text='Hello') - Response.objects.create( - statement=statement, - response=statement - ) - mock_response = MockResponse(conversation_id) conversation = self.view.get_conversation(mock_response) From 176fec0ec9dd343279c27e3e327dfea0ba0f7ae3 Mon Sep 17 00:00:00 2001 From: Gunther Cox Date: Wed, 19 Jul 2017 21:24:34 -0400 Subject: [PATCH 24/28] Properly create Responses for testing --- tests_django/integration_tests/test_statement_integration.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests_django/integration_tests/test_statement_integration.py b/tests_django/integration_tests/test_statement_integration.py index e050d241a..5ab11618d 100644 --- a/tests_django/integration_tests/test_statement_integration.py +++ b/tests_django/integration_tests/test_statement_integration.py @@ -59,10 +59,10 @@ def test_get_response_count(self): self.object.add_response(ResponseObject('Hello', occurrence=2)) model_response_statement = StatementModel.objects.create(text='Hello') self.model.save() - self.model.in_response.create( + ResponseModel.objects.create( statement=self.model, response=model_response_statement ) - self.model.in_response.create( + ResponseModel.objects.create( statement=self.model, response=model_response_statement ) From e67735b2e50b2ade6ce2d9f630378793569b5863 Mon Sep 17 00:00:00 2001 From: Gunther Cox Date: Wed, 19 Jul 2017 21:40:15 -0400 Subject: [PATCH 25/28] Correct PEP8 issues --- chatterbot/chatterbot.py | 3 +-- chatterbot/conversation/statement.py | 1 - chatterbot/ext/sqlalchemy_app/models.py | 2 +- chatterbot/storage/django_storage.py | 2 +- chatterbot/storage/sql_storage.py | 1 - tests_django/integration_tests/test_statement_integration.py | 2 +- tests_django/test_api.py | 3 --- tests_django/test_django_adapter.py | 2 +- 8 files changed, 5 insertions(+), 11 deletions(-) diff --git a/chatterbot/chatterbot.py b/chatterbot/chatterbot.py index 5ec5bff30..346fa8eca 100644 --- a/chatterbot/chatterbot.py +++ b/chatterbot/chatterbot.py @@ -18,8 +18,7 @@ def __init__(self, name, **kwargs): kwargs['name'] = name kwargs['chatbot'] = self - self.conversation_sessions = ConversationSessionManager() - self.default_session = self.conversation_sessions.new() + self.default_session = None storage_adapter = kwargs.get('storage_adapter', 'chatterbot.storage.SQLStorageAdapter') diff --git a/chatterbot/conversation/statement.py b/chatterbot/conversation/statement.py index bac9c9026..3c3b52df3 100644 --- a/chatterbot/conversation/statement.py +++ b/chatterbot/conversation/statement.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- from .response import Response -from datetime import datetime class Statement(object): diff --git a/chatterbot/ext/sqlalchemy_app/models.py b/chatterbot/ext/sqlalchemy_app/models.py index 3c9402ffb..1b8b74c8b 100644 --- a/chatterbot/ext/sqlalchemy_app/models.py +++ b/chatterbot/ext/sqlalchemy_app/models.py @@ -23,4 +23,4 @@ def __tablename__(cls): ) -Base = declarative_base(cls=ModelBase) \ No newline at end of file +Base = declarative_base(cls=ModelBase) diff --git a/chatterbot/storage/django_storage.py b/chatterbot/storage/django_storage.py index c484f78f9..12b9409d3 100644 --- a/chatterbot/storage/django_storage.py +++ b/chatterbot/storage/django_storage.py @@ -98,7 +98,7 @@ def update(self, statement): response_statement.extra_data = getattr(_response_statement, 'extra_data', '') response_statement.save() - response = Response.objects.create( + Response.objects.create( statement=response_statement, response=statement ) diff --git a/chatterbot/storage/sql_storage.py b/chatterbot/storage/sql_storage.py index 0253a69c9..8aed94133 100644 --- a/chatterbot/storage/sql_storage.py +++ b/chatterbot/storage/sql_storage.py @@ -10,7 +10,6 @@ from sqlalchemy.sql import func from sqlalchemy import Table, Column, Integer, String, DateTime, ForeignKey, PickleType - class StatementTable(Base): """ StatementTable, placeholder for a sentence or phrase. diff --git a/tests_django/integration_tests/test_statement_integration.py b/tests_django/integration_tests/test_statement_integration.py index 5ab11618d..919bdbdd8 100644 --- a/tests_django/integration_tests/test_statement_integration.py +++ b/tests_django/integration_tests/test_statement_integration.py @@ -1,5 +1,4 @@ from django.test import TestCase -from django.utils import timezone from chatterbot.conversation import Statement as StatementObject from chatterbot.conversation import Response as ResponseObject from chatterbot.ext.django_chatterbot.models import Statement as StatementModel @@ -96,6 +95,7 @@ def setUp(self): statement_model = StatementModel.objects.create(text='_') self.object = ResponseObject(statement_object.text) self.model = ResponseModel(statement=statement_model, response=statement_model) + self.model.save() def test_serialize(self): object_data = self.object.serialize() diff --git a/tests_django/test_api.py b/tests_django/test_api.py index 84c86d99e..0a3e2a425 100644 --- a/tests_django/test_api.py +++ b/tests_django/test_api.py @@ -14,9 +14,6 @@ def test_post(self): """ Test that a response is returned. """ - from datetime import datetime - from dateutil.parser import parse - data = { 'text': 'How are you?' } diff --git a/tests_django/test_django_adapter.py b/tests_django/test_django_adapter.py index 892d4f889..9ec54a1b8 100644 --- a/tests_django/test_django_adapter.py +++ b/tests_django/test_django_adapter.py @@ -133,7 +133,7 @@ def test_getting_and_updating_statement(self): response = self.adapter.find(statement.text) - self.assertEqual(response.responses.count(), 1) + self.assertEqual(response.responses.count(), 2) self.assertEqual(response.responses.first().occurrence, 2) def test_remove(self): From 2f9b73690bf088606914259acef00a6078e62701 Mon Sep 17 00:00:00 2001 From: Gunther Cox Date: Mon, 7 Aug 2017 20:58:33 -0400 Subject: [PATCH 26/28] Remove ResponseQueue clas --- chatterbot/queues.py | 27 ------------------- examples/learning_feedback_example.py | 5 +--- tests/test_queues.py | 38 --------------------------- 3 files changed, 1 insertion(+), 69 deletions(-) diff --git a/chatterbot/queues.py b/chatterbot/queues.py index 37cdf2647..16321137d 100644 --- a/chatterbot/queues.py +++ b/chatterbot/queues.py @@ -51,30 +51,3 @@ def flush(self): Remove all elements from the queue. """ self.queue = [] - - -class ResponseQueue(FixedSizeQueue): - """ - An extension of the FixedSizeQueue class with - utility methods to help manage the conversation. - """ - - def get_last_response_statement(self): - """ - Return the last statement that was received. - """ - previous_interaction = self.peek() - if previous_interaction: - # Return the output statement - return previous_interaction[1] - return None - - def get_last_input_statement(self): - """ - Return the last response that was given. - """ - previous_interaction = self.peek() - if previous_interaction: - # Return the input statement - return previous_interaction[0] - return None diff --git a/examples/learning_feedback_example.py b/examples/learning_feedback_example.py index 689796a8a..7ae25bd3c 100644 --- a/examples/learning_feedback_example.py +++ b/examples/learning_feedback_example.py @@ -56,10 +56,7 @@ def get_feedback(): # Update the conversation history for the bot # It is important that this happens last, after the learning step - bot.conversation_sessions.update( - bot.default_session.id_string, - (statement, response, ) - ) + bot.storage.add_to_converation(bot.default_session, statement, response) # Press ctrl-c or ctrl-d on the keyboard to exit except (KeyboardInterrupt, EOFError, SystemExit): diff --git a/tests/test_queues.py b/tests/test_queues.py index 7dd30caca..6208b8e78 100644 --- a/tests/test_queues.py +++ b/tests/test_queues.py @@ -40,41 +40,3 @@ def test_peek(self): self.queue.append(6) self.assertEqual(self.queue.peek(), 6) - - -class ResponseQueueTests(TestCase): - """ - The response view is a version of the FixedSizeQueue with - additional utility methods to help manage the conversation. - """ - - def setUp(self): - self.queue = queues.ResponseQueue(maxsize=2) - - def test_no_last_response_statement(self): - self.assertIsNone(self.queue.get_last_response_statement()) - - def test_get_last_response_statement(self): - """ - Make sure that the get last statement method - returns the last statement that was issued. - """ - self.queue.append(('Test statement 1', 'Test response 1', )) - self.queue.append(('Test statement 2', 'Test response 2', )) - - last_statement = self.queue.get_last_response_statement() - self.assertEqual(last_statement, 'Test response 2') - - def test_no_last_input_statement(self): - self.assertIsNone(self.queue.get_last_input_statement()) - - def test_get_last_input_statement(self): - """ - Make sure that the get last statement method - returns the last statement that was issued. - """ - self.queue.append(('Test statement 1', 'Test response 1', )) - self.queue.append(('Test statement 2', 'Test response 2', )) - - last_statement = self.queue.get_last_input_statement() - self.assertEqual(last_statement, 'Test statement 2') From f88f7864e7568c78a5a0ef6a75b43b24556a5cff Mon Sep 17 00:00:00 2001 From: Gunther Cox Date: Mon, 7 Aug 2017 21:00:39 -0400 Subject: [PATCH 27/28] Remove unused fixed-size queue class --- chatterbot/queues.py | 53 -------------------------------------------- tests/test_queues.py | 42 ----------------------------------- 2 files changed, 95 deletions(-) delete mode 100644 chatterbot/queues.py delete mode 100644 tests/test_queues.py diff --git a/chatterbot/queues.py b/chatterbot/queues.py deleted file mode 100644 index 16321137d..000000000 --- a/chatterbot/queues.py +++ /dev/null @@ -1,53 +0,0 @@ -class FixedSizeQueue(object): - """ - This is a data structure like a queue. - Only a fixed number of items can be added. - Once the maximum is reached, when a new item is - added the oldest item in the queue will be removed. - """ - - def __init__(self, maxsize=10): - self.maxsize = maxsize - self.queue = [] - - def append(self, item): - """ - Append an element at the end of the queue. - """ - if len(self.queue) == self.maxsize: - # Remove an element from the top of the list - self.queue.pop(0) - - self.queue.append(item) - - def __len__(self): - return len(self.queue) - - def __getitem__(self, index): - return self.queue[index] - - def __contains__(self, item): - """ - Check if an element is in this queue. - """ - return item in self.queue - - def empty(self): - """ - Return True if the queue is empty, False otherwise. - """ - return len(self.queue) == 0 - - def peek(self): - """ - Return the most recent item put in the queue. - """ - if self.empty(): - return None - return self.queue[-1] - - def flush(self): - """ - Remove all elements from the queue. - """ - self.queue = [] diff --git a/tests/test_queues.py b/tests/test_queues.py deleted file mode 100644 index 6208b8e78..000000000 --- a/tests/test_queues.py +++ /dev/null @@ -1,42 +0,0 @@ -from unittest import TestCase -from chatterbot import queues - - -class FixedSizeQueueTests(TestCase): - - def setUp(self): - self.queue = queues.FixedSizeQueue(maxsize=2) - - def test_append(self): - self.queue.append(0) - self.assertIn(0, self.queue) - - def test_contains(self): - self.queue.queue.append(0) - self.assertIn(0, self.queue) - - def test_empty(self): - self.assertTrue(self.queue.empty()) - - def test_not_empty(self): - self.queue.append(0) - self.assertFalse(self.queue.empty()) - - def test_maxsize(self): - self.queue.append(0) - self.queue.append(1) - self.queue.append(2) - - self.assertNotIn(0, self.queue) - self.assertIn(1, self.queue) - self.assertIn(2, self.queue) - - def test_peek_empty_queue(self): - self.assertIsNone(self.queue.peek()) - - def test_peek(self): - self.queue.append(4) - self.queue.append(5) - self.queue.append(6) - - self.assertEqual(self.queue.peek(), 6) From a5d1228b7577bf6508ba5a17bb38c56651e69e3f Mon Sep 17 00:00:00 2001 From: Gunther Cox Date: Mon, 7 Aug 2017 21:24:05 -0400 Subject: [PATCH 28/28] Remove old session methods from Hipchat adapter --- chatterbot/input/hipchat.py | 14 +++----------- chatterbot/output/hipchat.py | 4 ++-- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/chatterbot/input/hipchat.py b/chatterbot/input/hipchat.py index 18edb2829..57cf4d683 100644 --- a/chatterbot/input/hipchat.py +++ b/chatterbot/input/hipchat.py @@ -84,17 +84,9 @@ def process_input(self, statement): """ new_message = False - input_statement = self.chatbot.conversation_sessions.get( - self.session_id).conversation.get_last_input_statement() - response_statement = self.chatbot.conversation_sessions.get( - self.session_id).conversation.get_last_response_statement() - - if input_statement: - last_message_id = input_statement.extra_data.get( - 'hipchat_message_id', None - ) - if last_message_id: - self.recent_message_ids.add(last_message_id) + response_statement = self.chatbot.storage.get_latest_response( + self.session_id + ) if response_statement: last_message_id = response_statement.extra_data.get( diff --git a/chatterbot/output/hipchat.py b/chatterbot/output/hipchat.py index 1bdb75895..4eaa9a737 100644 --- a/chatterbot/output/hipchat.py +++ b/chatterbot/output/hipchat.py @@ -60,8 +60,8 @@ def process_response(self, statement, session_id=None): data = self.send_message(self.hipchat_room, statement.text) # Update the output statement with the message id - self.chatbot.conversation_sessions.get(session_id).conversation[-1][1].add_extra_data( - 'hipchat_message_id', data['id'] + self.chatbot.storage.update( + statement.add_extra_data('hipchat_message_id', data['id']) ) return statement