ChatterBot 1.2.2__py3-none-any.whl → 1.2.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
chatterbot/__init__.py CHANGED
@@ -4,7 +4,7 @@ ChatterBot is a machine learning, conversational dialog engine.
4
4
  from .chatterbot import ChatBot
5
5
 
6
6
 
7
- __version__ = '1.2.2'
7
+ __version__ = '1.2.3'
8
8
 
9
9
  __all__ = (
10
10
  'ChatBot',
@@ -1,4 +1,7 @@
1
1
  from chatterbot.logic import LogicAdapter
2
+ from chatterbot.conversation import Statement
3
+ from chatterbot import constants, languages
4
+ import spacy
2
5
 
3
6
 
4
7
  class SpecificResponseAdapter(LogicAdapter):
@@ -8,30 +11,70 @@ class SpecificResponseAdapter(LogicAdapter):
8
11
  :kwargs:
9
12
  * *input_text* (``str``) --
10
13
  The input text that triggers this logic adapter.
11
- * *output_text* (``str``) --
14
+ * *output_text* (``str`` or ``function``) --
12
15
  The output text returned by this logic adapter.
16
+ If a function is provided, it should return a string.
13
17
  """
14
18
 
15
19
  def __init__(self, chatbot, **kwargs):
16
20
  super().__init__(chatbot, **kwargs)
17
- from chatterbot.conversation import Statement
18
21
 
19
22
  self.input_text = kwargs.get('input_text')
20
23
 
21
- output_text = kwargs.get('output_text')
22
- self.response_statement = Statement(text=output_text)
24
+ self.matcher = None
25
+
26
+ if MatcherClass := kwargs.get('matcher'):
27
+ language = kwargs.get('language', languages.ENG)
28
+
29
+ self.nlp = self._initialize_nlp(language)
30
+
31
+ self.matcher = MatcherClass(self.nlp.vocab)
32
+
33
+ self.matcher.add('SpecificResponse', [self.input_text])
34
+
35
+ self._output_text = kwargs.get('output_text')
36
+
37
+ def _initialize_nlp(self, language):
38
+ try:
39
+ model = constants.DEFAULT_LANGUAGE_TO_SPACY_MODEL_MAP[language]
40
+ except KeyError as e:
41
+ raise KeyError(
42
+ f'Spacy model is not available for language {language}'
43
+ ) from e
44
+
45
+ return spacy.load(model)
23
46
 
24
47
  def can_process(self, statement):
25
- if statement.text == self.input_text:
48
+ if self.matcher:
49
+ doc = self.nlp(statement.text)
50
+ matches = self.matcher(doc)
51
+
52
+ if matches:
53
+ return True
54
+ elif statement.text == self.input_text:
26
55
  return True
27
56
 
28
57
  return False
29
58
 
30
59
  def process(self, statement, additional_response_selection_parameters=None):
31
60
 
32
- if statement.text == self.input_text:
33
- self.response_statement.confidence = 1
61
+ if callable(self._output_text):
62
+ response_statement = Statement(text=self._output_text())
63
+ else:
64
+ response_statement = Statement(text=self._output_text)
65
+
66
+ if self.matcher:
67
+ doc = self.nlp(statement.text)
68
+ matches = self.matcher(doc)
69
+
70
+ if matches:
71
+ response_statement.confidence = 1
72
+ else:
73
+ response_statement.confidence = 0
74
+
75
+ elif statement.text == self.input_text:
76
+ response_statement.confidence = 1
34
77
  else:
35
- self.response_statement.confidence = 0
78
+ response_statement.confidence = 0
36
79
 
37
- return self.response_statement
80
+ return response_statement
@@ -158,7 +158,8 @@ class UnitConversion(LogicAdapter):
158
158
  response = func(p)
159
159
  if response.confidence == 1.0:
160
160
  break
161
- except Exception:
161
+ except Exception as e:
162
+ self.chatbot.logger.warning('Error during UnitConversion: {}'.format(str(e)))
162
163
  response.confidence = 0.0
163
- finally:
164
- return response
164
+
165
+ return response
chatterbot/search.py CHANGED
@@ -149,3 +149,58 @@ class TextSearch:
149
149
  ))
150
150
 
151
151
  yield statement
152
+
153
+
154
+ class VectorSearch:
155
+ """
156
+ .. note:: BETA feature: this search method is new and experimental.
157
+
158
+ Search for similar text based on a :term:`vector database`.
159
+ """
160
+
161
+ name = 'vector_search'
162
+
163
+ def __init__(self, chatbot, **kwargs):
164
+ from chatterbot.storage import RedisVectorStorageAdapter
165
+
166
+ # Good documentation:
167
+ # https://python.langchain.com/docs/integrations/vectorstores/redis/
168
+ #
169
+ # https://hub.docker.com/r/redis/redis-stack
170
+
171
+ # Mondodb:
172
+ # > Vector Search is only supported on Atlas Clusters
173
+ # https://www.mongodb.com/community/forums/t/can-a-local-mongodb-instance-be-used-when-working-with-langchain-mongodbatlasvectorsearch/265356
174
+
175
+ # FAISS:
176
+ # https://python.langchain.com/docs/integrations/vectorstores/faiss/
177
+
178
+ print("Starting Redis Vector Store")
179
+
180
+ # TODO: look into:
181
+ # https://python.langchain.com/api_reference/redis/chat_message_history/langchain_redis.chat_message_history.RedisChatMessageHistory.html
182
+
183
+ # The VectorSearch class is only compatible with the RedisVectorStorageAdapter
184
+ if not isinstance(chatbot.storage, RedisVectorStorageAdapter):
185
+ raise Exception(
186
+ 'The VectorSearch search method requires the RedisVectorStorageAdapter storage adapter.'
187
+ )
188
+
189
+ def search(self, input_statement, **additional_parameters):
190
+ print("Querying Vector Store")
191
+
192
+ # Similarity search with score and filter
193
+ # NOTE: It looks like `return_all` is needed to return the full document
194
+ # specifically what we need here is the ID
195
+ scored_results = self.storage.vector_store.similarity_search_with_score(
196
+ input_statement.text, k=2, return_all=True
197
+ )
198
+ # sort_by="score", filter={"category": "likes"})
199
+
200
+ print("Similarity Search with Score Results:\n")
201
+ for doc, score in scored_results:
202
+ print(f"Content: {doc.page_content[:150]}...")
203
+ print(f"ID: {doc.id}")
204
+ print(f"Metadata: {doc.metadata}")
205
+ print(f"Score: {score}")
206
+ print()
@@ -2,6 +2,7 @@ from chatterbot.storage.storage_adapter import StorageAdapter
2
2
  from chatterbot.storage.django_storage import DjangoStorageAdapter
3
3
  from chatterbot.storage.mongodb import MongoDatabaseAdapter
4
4
  from chatterbot.storage.sql_storage import SQLStorageAdapter
5
+ from chatterbot.storage.redis import RedisVectorStorageAdapter
5
6
 
6
7
 
7
8
  __all__ = (
@@ -9,4 +10,5 @@ __all__ = (
9
10
  'DjangoStorageAdapter',
10
11
  'MongoDatabaseAdapter',
11
12
  'SQLStorageAdapter',
13
+ 'RedisVectorStorageAdapter',
12
14
  )
@@ -0,0 +1,390 @@
1
+ from datetime import datetime
2
+ from chatterbot.storage import StorageAdapter
3
+ from chatterbot.conversation import Statement as StatementObject
4
+
5
+
6
+ # TODO: This list may not be exhaustive.
7
+ # Is there a full list of characters reserved by redis?
8
+ REDIS_ESCAPE_CHARACTERS = {
9
+ '\\': '\\\\',
10
+ ':': '\\:',
11
+ '|': '\\|',
12
+ '%': '\\%',
13
+ '!': '\\!',
14
+ '-': '\\-',
15
+ }
16
+
17
+ REDIS_TRANSLATION_TABLE = str.maketrans(REDIS_ESCAPE_CHARACTERS)
18
+
19
+ def _escape_redis_special_characters(text):
20
+ """
21
+ Escape special characters in a string that are used in redis queries.
22
+ """
23
+ return text.translate(REDIS_TRANSLATION_TABLE)
24
+
25
+
26
+ class RedisVectorStorageAdapter(StorageAdapter):
27
+ """
28
+ .. warning:: BETA feature (Released March, 2025): this storage adapter is new
29
+ and experimental. Its functionality and default parameters might change
30
+ in the future and its behavior has not yet been finalized.
31
+
32
+ The RedisVectorStorageAdapter allows ChatterBot to store conversation
33
+ data in a redis instance.
34
+
35
+ All parameters are optional, by default a redis instance on localhost is assumed.
36
+
37
+ :keyword database_uri: eg: redis://localhost:6379/0',
38
+ The database_uri can be specified to choose a redis instance.
39
+ :type database_uri: str
40
+ """
41
+
42
+ class RedisMetaDataType:
43
+ """
44
+ Subclass for redis config metadata type enumerator.
45
+ """
46
+ TAG = 'tag'
47
+ TEXT = 'text'
48
+ NUMERIC = 'numeric'
49
+
50
+ def __init__(self, **kwargs):
51
+ super().__init__(**kwargs)
52
+ from chatterbot.vectorstores import RedisVectorStore
53
+ from langchain_redis import RedisConfig # RedisVectorStore
54
+ from langchain_huggingface import HuggingFaceEmbeddings
55
+
56
+ self.database_uri = kwargs.get('database_uri', 'redis://localhost:6379/0')
57
+
58
+ config = RedisConfig(
59
+ index_name='chatterbot',
60
+ redis_url=self.database_uri,
61
+ content_field='in_response_to',
62
+ metadata_schema=[
63
+ {
64
+ 'name': 'conversation',
65
+ 'type': self.RedisMetaDataType.TAG,
66
+ },
67
+ {
68
+ 'name': 'text',
69
+ 'type': self.RedisMetaDataType.TEXT,
70
+ },
71
+ {
72
+ 'name': 'created_at',
73
+ 'type': self.RedisMetaDataType.NUMERIC,
74
+ },
75
+ {
76
+ 'name': 'persona',
77
+ 'type': self.RedisMetaDataType.TEXT,
78
+ },
79
+ {
80
+ 'name': 'tags',
81
+ 'type': self.RedisMetaDataType.TAG,
82
+ # 'separator': '|'
83
+ },
84
+ ],
85
+ )
86
+
87
+ # TODO should this call from_existing_index if connecting to
88
+ # a redis instance that already contains data?
89
+
90
+ self.logger.info('Loading HuggingFace embeddings')
91
+
92
+ # TODO: Research different embeddings
93
+ # https://python.langchain.com/docs/integrations/vectorstores/mongodb_atlas/#initialization
94
+
95
+ embeddings = HuggingFaceEmbeddings(
96
+ model_name='sentence-transformers/all-mpnet-base-v2'
97
+ )
98
+
99
+ self.logger.info('Creating Redis Vector Store')
100
+
101
+ self.vector_store = RedisVectorStore(embeddings, config=config)
102
+
103
+ def get_statement_model(self):
104
+ """
105
+ Return the statement model.
106
+ """
107
+ from langchain_core.documents import Document
108
+
109
+ return Document
110
+
111
+ def model_to_object(self, document):
112
+
113
+ in_response_to = document.page_content
114
+
115
+ # If the value is an empty string, set it to None
116
+ # to match the expected type (the vector store does
117
+ # not use null values)
118
+ if in_response_to == '':
119
+ in_response_to = None
120
+
121
+ values = {
122
+ 'in_response_to': in_response_to,
123
+ }
124
+
125
+ if document.id:
126
+ values['id'] = document.id
127
+
128
+ values.update(document.metadata)
129
+
130
+ tags = values['tags']
131
+ values['tags'] = list(set(tags.split('|') if tags else []))
132
+
133
+ return StatementObject(**values)
134
+
135
+ def count(self):
136
+ """
137
+ Return the number of entries in the database.
138
+ """
139
+
140
+ '''
141
+ TODO
142
+ faiss_vector_store = FAISS(
143
+ embedding_function=embedding_function,
144
+ index=IndexFlatL2(embedding_size),
145
+ docstore=InMemoryDocstore(),
146
+ index_to_docstore_id={}
147
+ )
148
+ doc_count = faiss_vector_store.index.ntotal
149
+ '''
150
+
151
+ client = self.vector_store.index.client
152
+ return client.dbsize()
153
+
154
+ def remove(self, statement):
155
+ """
156
+ Removes the statement that matches the input text.
157
+ Removes any responses from statements where the response text matches
158
+ the input text.
159
+ """
160
+ self.vector_store.delete(ids=[statement.id.split(':')[1]])
161
+
162
+ def filter(self, page_size=4, **kwargs):
163
+ """
164
+ Returns a list of objects from the database.
165
+ The kwargs parameter can contain any number
166
+ of attributes. Only objects which contain all
167
+ listed attributes and in which all values match
168
+ for all listed attributes will be returned.
169
+
170
+ kwargs:
171
+ - conversation
172
+ - persona
173
+ - tags
174
+ - in_response_to
175
+ - text
176
+ - exclude_text
177
+ - exclude_text_words
178
+ - persona_not_startswith
179
+ - search_in_response_to_contains
180
+ - order_by
181
+ """
182
+ from redisvl.query.filter import Tag, Text
183
+
184
+ # https://redis.io/docs/latest/develop/interact/search-and-query/advanced-concepts/query_syntax/
185
+ filter_condition = None
186
+
187
+ if 'in_response_to' in kwargs:
188
+ filter_condition = Text('in_response_to') == kwargs['in_response_to']
189
+
190
+ if 'conversation' in kwargs:
191
+ query = Tag('conversation') == kwargs['conversation']
192
+ if filter_condition:
193
+ filter_condition &= query
194
+ else:
195
+ filter_condition = query
196
+
197
+ if 'persona' in kwargs:
198
+ query = Tag('persona') == kwargs['persona']
199
+ if filter_condition:
200
+ filter_condition &= query
201
+ else:
202
+ filter_condition = query
203
+
204
+ if 'tags' in kwargs:
205
+ query = Tag('tags') == kwargs['tags']
206
+ if filter_condition:
207
+ filter_condition &= query
208
+ else:
209
+ filter_condition = query
210
+
211
+ if 'exclude_text' in kwargs:
212
+ query = Text('text') != '|'.join([
213
+ f'%%{text}%%' for text in kwargs['exclude_text']
214
+ ])
215
+ if filter_condition:
216
+ filter_condition &= query
217
+ else:
218
+ filter_condition = query
219
+
220
+ if 'exclude_text_words' in kwargs:
221
+ _query = '|'.join([
222
+ f'%%{text}%%' for text in kwargs['exclude_text_words']
223
+ ])
224
+ query = Text('text') % f'-({ _query })'
225
+ if filter_condition:
226
+ filter_condition &= query
227
+ else:
228
+ filter_condition = query
229
+
230
+ if 'persona_not_startswith' in kwargs:
231
+ _query = _escape_redis_special_characters(kwargs['persona_not_startswith'])
232
+ query = Text('persona') % f'-(%%{_query}%%)'
233
+ if filter_condition:
234
+ filter_condition &= query
235
+ else:
236
+ filter_condition = query
237
+
238
+ if 'text' in kwargs:
239
+ _query = _escape_redis_special_characters(kwargs['text'])
240
+ query = Text('text') % '|'.join([f'%%{_q}%%' for _q in _query.split()])
241
+ if filter_condition:
242
+ filter_condition &= query
243
+ else:
244
+ filter_condition = query
245
+
246
+ ordering = kwargs.get('order_by', None)
247
+
248
+ if ordering:
249
+ ordering = ','.join(ordering)
250
+
251
+ if 'search_in_response_to_contains' in kwargs:
252
+ _search_text = kwargs.get('search_in_response_to_contains', '')
253
+
254
+ # TODO similarity_search_with_score
255
+ documents = self.vector_store.similarity_search(
256
+ _search_text,
257
+ k=page_size, # The number of results to return
258
+ return_all=True, # Include the full document with IDs
259
+ filter=filter_condition,
260
+ sort_by=ordering
261
+ )
262
+ else:
263
+ documents = self.vector_store.query_search(
264
+ k=page_size,
265
+ filter=filter_condition,
266
+ sort_by=ordering
267
+ )
268
+
269
+ return [self.model_to_object(document) for document in documents]
270
+
271
+ def create(
272
+ self,
273
+ text,
274
+ in_response_to=None,
275
+ tags=None,
276
+ **kwargs
277
+ ):
278
+ """
279
+ Creates a new statement matching the keyword arguments specified.
280
+ Returns the created statement.
281
+ """
282
+ # from langchain_community.vectorstores.redis.constants import REDIS_TAG_SEPARATOR
283
+
284
+ _default_date = datetime.now()
285
+
286
+ metadata = {
287
+ 'text': text,
288
+ 'category': kwargs.get('category', ''),
289
+ # NOTE: `created_at` must have a valid numeric value or results will
290
+ # not be returned for similarity_search for some reason
291
+ 'created_at': kwargs.get('created_at') or int(_default_date.strftime('%y%m%d')),
292
+ 'tags': '|'.join(tags) if tags else '',
293
+ 'conversation': kwargs.get('conversation', ''),
294
+ 'persona': kwargs.get('persona', ''),
295
+ }
296
+
297
+ ids = self.vector_store.add_texts([in_response_to or ''], [metadata])
298
+
299
+ metadata['created_at'] = _default_date
300
+ metadata['tags'] = tags or []
301
+ metadata.pop('text')
302
+ statement = StatementObject(
303
+ id=ids[0],
304
+ text=text,
305
+ **metadata
306
+ )
307
+ return statement
308
+
309
+ def create_many(self, statements):
310
+ """
311
+ Creates multiple statement entries.
312
+ """
313
+ Document = self.get_statement_model()
314
+ documents = [
315
+ Document(
316
+ page_content=statement.in_response_to or '',
317
+ metadata={
318
+ 'text': statement.text,
319
+ 'conversation': statement.conversation or '',
320
+ 'created_at': int(statement.created_at.strftime('%y%m%d')),
321
+ 'persona': statement.persona or '',
322
+ 'tags': '|'.join(statement.tags) if statement.tags else '',
323
+ }
324
+ ) for statement in statements
325
+ ]
326
+
327
+ self.logger.info('Adding documents to the vector store')
328
+
329
+ self.vector_store.add_documents(documents)
330
+
331
+ def update(self, statement):
332
+ """
333
+ Modifies an entry in the database.
334
+ Creates an entry if one does not exist.
335
+ """
336
+ metadata = {
337
+ 'text': statement.text,
338
+ 'conversation': statement.conversation or '',
339
+ 'created_at': int(statement.created_at.strftime('%y%m%d')),
340
+ 'persona': statement.persona or '',
341
+ 'tags': '|'.join(statement.tags) if statement.tags else '',
342
+ }
343
+
344
+ Document = self.get_statement_model()
345
+ document = Document(
346
+ page_content=statement.in_response_to or '',
347
+ metadata=metadata,
348
+ )
349
+
350
+ if statement.id:
351
+ self.vector_store.add_texts(
352
+ [document.page_content], [metadata], keys=[statement.id.split(':')[1]]
353
+ )
354
+ else:
355
+ self.vector_store.add_documents([document])
356
+
357
+ def get_random(self):
358
+ """
359
+ Returns a random statement from the database.
360
+ """
361
+ client = self.vector_store.index.client
362
+
363
+ random_key = client.randomkey()
364
+
365
+ if random_key:
366
+ random_id = random_key.decode().split(':')[1]
367
+
368
+ documents = self.vector_store.get_by_ids([random_id])
369
+
370
+ if documents:
371
+ return self.model_to_object(documents[0])
372
+
373
+ raise self.EmptyDatabaseException()
374
+
375
+ def drop(self):
376
+ """
377
+ Remove all existing documents from the database.
378
+ """
379
+ index_name = self.vector_store.config.index_name
380
+ client = self.vector_store.index.client
381
+
382
+ for key in client.scan_iter(f'{index_name}:*'):
383
+ # self.vector_store.index.drop_keys(key)
384
+ client.delete(key)
385
+
386
+ # Commenting this out for now because there is no step
387
+ # to recreate the index after it is dropped (really what
388
+ # we want is to delete all the keys in the index, but
389
+ # keep the index itself)
390
+ # self.vector_store.index.delete(drop=True)
@@ -0,0 +1,74 @@
1
+ """
2
+ Redis vector store.
3
+ """
4
+ from __future__ import annotations
5
+
6
+ from typing import Any, List, Sequence
7
+
8
+ from langchain_core.documents import Document
9
+ from redisvl.redis.utils import convert_bytes
10
+ from redisvl.query import FilterQuery
11
+
12
+ from langchain_core.documents import Document
13
+ from langchain_redis.vectorstores import RedisVectorStore as LangChainRedisVectorStore
14
+
15
+
16
+ class RedisVectorStore(LangChainRedisVectorStore):
17
+ """
18
+ Redis vector store integration.
19
+ """
20
+
21
+ def query_search(
22
+ self,
23
+ k=4,
24
+ filter=None,
25
+ sort_by=None,
26
+ ) -> List[Document]:
27
+ """
28
+ Return docs based on the provided query.
29
+
30
+ k: int, default=4
31
+ Number of documents to return.
32
+ filter: str, default=None
33
+ A filter expression to apply to the query.
34
+ sort_by: str, default=None
35
+ A field to sort the results by.
36
+
37
+ returns:
38
+ A list of Documents most matching the query.
39
+ """
40
+ from chatterbot import ChatBot
41
+
42
+ return_fields = [
43
+ self.config.content_field
44
+ ]
45
+ return_fields += [
46
+ field.name
47
+ for field in self._index.schema.fields.values()
48
+ if field.name
49
+ not in [self.config.embedding_field, self.config.content_field]
50
+ ]
51
+
52
+ query = FilterQuery(
53
+ return_fields=return_fields,
54
+ num_results=k,
55
+ filter_expression=filter,
56
+ sort_by=sort_by,
57
+ )
58
+
59
+ try:
60
+ results = self._index.query(query)
61
+ except Exception as e:
62
+ raise ChatBot.ChatBotException(f'Error querying index: {query}') from e
63
+
64
+ if results:
65
+ with self._index.client.pipeline(transaction=False) as pipe:
66
+ for document in results:
67
+ pipe.hgetall(document['id'])
68
+ full_documents = convert_bytes(pipe.execute())
69
+ else:
70
+ full_documents = []
71
+
72
+ return self._prepare_docs_full(
73
+ True, results, full_documents, True
74
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: ChatterBot
3
- Version: 1.2.2
3
+ Version: 1.2.3
4
4
  Summary: ChatterBot is a machine learning, conversational dialog engine
5
5
  Author: Gunther Cox
6
6
  License: Copyright (c) 2016 - 2025, Gunther Cox
@@ -53,11 +53,18 @@ Requires-Dist: flake8; extra == "test"
53
53
  Requires-Dist: coverage; extra == "test"
54
54
  Requires-Dist: nose; extra == "test"
55
55
  Requires-Dist: sphinx<8.2,>=5.3; extra == "test"
56
+ Requires-Dist: sphinx-sitemap>=2.6.0; extra == "test"
56
57
  Provides-Extra: dev
57
58
  Requires-Dist: pint>=0.8.1; extra == "dev"
58
- Requires-Dist: pymongo<4.12,>=4.11; extra == "dev"
59
59
  Requires-Dist: pyyaml<7.0,>=6.0; extra == "dev"
60
60
  Requires-Dist: chatterbot-corpus>=1.2.2; extra == "dev"
61
+ Provides-Extra: redis
62
+ Requires-Dist: redis[hiredis]; extra == "redis"
63
+ Requires-Dist: langchain-redis; extra == "redis"
64
+ Requires-Dist: langchain-huggingface; extra == "redis"
65
+ Requires-Dist: sentence-transformers; extra == "redis"
66
+ Provides-Extra: mongodb
67
+ Requires-Dist: pymongo<4.12,>=4.11; extra == "mongodb"
61
68
 
62
69
  ![ChatterBot: Machine learning in Python](https://i.imgur.com/b3SCmGT.png)
63
70
 
@@ -70,7 +77,6 @@ to be trained to speak any language.
70
77
 
71
78
  [![Package Version](https://img.shields.io/pypi/v/chatterbot.svg)](https://pypi.python.org/pypi/chatterbot/)
72
79
  [![Python 3.9](https://img.shields.io/badge/python-3.9-blue.svg)](https://www.python.org/downloads/release/python-360/)
73
- [![Django 2.0](https://img.shields.io/badge/Django-2.0-blue.svg)](https://docs.djangoproject.com/en/2.1/releases/2.0/)
74
80
  [![Coverage Status](https://img.shields.io/coveralls/gunthercox/ChatterBot.svg)](https://coveralls.io/r/gunthercox/ChatterBot)
75
81
  [![Code Climate](https://codeclimate.com/github/gunthercox/ChatterBot/badges/gpa.svg)](https://codeclimate.com/github/gunthercox/ChatterBot)
76
82
  [![Join the chat at https://gitter.im/chatterbot/Lobby](https://badges.gitter.im/chatterbot/Lobby.svg)](https://gitter.im/chatterbot/Lobby?utm_source=badge&utm_medium=badge&utm_content=badge)
@@ -1,4 +1,4 @@
1
- chatterbot/__init__.py,sha256=a8HpoSxm94mcHPYO24QiFVYS4WzYafqOGq7j_VuTIr8,158
1
+ chatterbot/__init__.py,sha256=PhC2oXazQN3HNYXvWb33IAqEzwyt5QtqcfESq8eg3sg,158
2
2
  chatterbot/__main__.py,sha256=nk19D56TlPT9Zdqkq4qZZrOnLKEc4YTwUVWmXYwSyHg,207
3
3
  chatterbot/adapters.py,sha256=LJ_KqLpHKPdYAFpMGK63RVH4weV5X0Zh5uGyan6qdVU,878
4
4
  chatterbot/chatterbot.py,sha256=YLKLkQ-XI4Unr3rbzjpGIupOqenuevm21tAnx-yFFgQ,10400
@@ -13,10 +13,11 @@ chatterbot/languages.py,sha256=XSenfc5FxHk_JWG5gGHsZvjvrPBbCaVCm_OU-BeER_M,32784
13
13
  chatterbot/parsing.py,sha256=vS-w70cMkjq4YEpDOv_pXWhAI6Zj06WYDAcMDhYDj0M,23174
14
14
  chatterbot/preprocessors.py,sha256=aI4v987dZc7GOKhO43i0i73EX748hehYSpzikFHpEXs,1271
15
15
  chatterbot/response_selection.py,sha256=aYeZ54jpGIcQnI-1-TDcua_f1p3PiM5_iMg4hF5ZaIU,2951
16
- chatterbot/search.py,sha256=FTwwON2eKPWqoc5uoKh4AUmuXDCqyfMcMcXB4wijpxg,4910
16
+ chatterbot/search.py,sha256=S4MFL1JzPqT-pv7tCgd-MIqf0T9Ia_KOLoNgzdoCP4Y,7035
17
17
  chatterbot/tagging.py,sha256=GLY9wg_rvn6pSYVML-HcxkIo_3BZ3SAyj-q1oNZY8pI,2584
18
18
  chatterbot/trainers.py,sha256=U1yh0_V7FFL51MeQe1P1Q59weceDbkHh_2kDiDYpSEc,13315
19
19
  chatterbot/utils.py,sha256=ckQXvsjp2FO9GcWxziY67JovN7mShnE4RlzdYarQY5k,3277
20
+ chatterbot/vectorstores.py,sha256=-S1NB8PrZzoFIu95n2W7N4UaXuCUpyDUXIGYFebjv08,2056
20
21
  chatterbot/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
22
  chatterbot/ext/django_chatterbot/__init__.py,sha256=iWzmBzpAsYwkwi1faxAPFY9L1bbL97RgVXK2uqULIMc,92
22
23
  chatterbot/ext/django_chatterbot/abstract_models.py,sha256=PkuBGS0uv2toL3jGE1U6HJLCLXoKwpRNohm9JbVT_y4,3303
@@ -51,16 +52,17 @@ chatterbot/logic/__init__.py,sha256=28-5swBCPfSVMl8xB5C8frOKZ2oj28rQfenbd9E4r-4,
51
52
  chatterbot/logic/best_match.py,sha256=8TNW0uZ_Uq-XPfaZUMUZDVH6KzDT65j59xblxQBv-dQ,4820
52
53
  chatterbot/logic/logic_adapter.py,sha256=5kNEirh5fiF5hhSMFXD7bIkKwXHmrSsSS4qDm-6xry0,4694
53
54
  chatterbot/logic/mathematical_evaluation.py,sha256=GPDKUwNFajERof2R-MkPGi2jJRP-rKAGm_f0V9JHDHE,2282
54
- chatterbot/logic/specific_response.py,sha256=_VeJaa3kun0J7cVzLOlTYK1tBpth0B6UWms7QwtcNpY,1082
55
+ chatterbot/logic/specific_response.py,sha256=o17YIeu9DzucO8MXMP3kwNIBb1b8br60bbAhSE7AZWc,2386
55
56
  chatterbot/logic/time_adapter.py,sha256=mxdoQGeC5IjREH4PU5iHYOIPEvnYnzgysocR8xMYWXc,2406
56
- chatterbot/logic/unit_conversion.py,sha256=DT50HHE3njUo_ttDSU8S-fwBylarhDF3l_McRLSX6Ic,5823
57
- chatterbot/storage/__init__.py,sha256=IymIHfeisvULQzUYsQSiUBbWIZ1m5EzyMVI082tTw5w,369
57
+ chatterbot/logic/unit_conversion.py,sha256=-ENMLqZqtZx0riUi0guda2oJECST0M7pZG4cSIv3ieM,5898
58
+ chatterbot/storage/__init__.py,sha256=ADw0WQe0YKr1UIDQLaxwf0mHDnuKW_CSzgz11K4TM-4,465
58
59
  chatterbot/storage/django_storage.py,sha256=S5S4GipD7FyNJy4RWu5-S8sLPuSJIObwTtqTpnJu-ok,6159
59
60
  chatterbot/storage/mongodb.py,sha256=Ozvdvcjb3LGZxcvbSQGzwP9VloYQbmsa2FaKunFpMyU,7934
61
+ chatterbot/storage/redis.py,sha256=FKROrzZ-7WXZ8ZoK0dKmTDdS45TxL04XOSeu0p3Jrak,12675
60
62
  chatterbot/storage/sql_storage.py,sha256=VVYZvclG_74IN-MrG0edc-RQ2gUO6gRQyCWWSO0MmCk,13082
61
63
  chatterbot/storage/storage_adapter.py,sha256=fvyb-qNiB0HMJ0siVMCWUIY--6d-C47N1_kKZVFZAv4,6110
62
- chatterbot-1.2.2.dist-info/LICENSE,sha256=5b04U8mi0wp5gJMYlKi49EalnD9Q2nwY_6UEI_Avgu4,1476
63
- chatterbot-1.2.2.dist-info/METADATA,sha256=EGYwvpQjhqJOfjlQWI83memJr0sXKo9QwM_wbp1wtrg,8311
64
- chatterbot-1.2.2.dist-info/WHEEL,sha256=nn6H5-ilmfVryoAQl3ZQ2l8SH5imPWFpm1A5FgEuFV4,91
65
- chatterbot-1.2.2.dist-info/top_level.txt,sha256=W2TzAbAJ-eBXTIKZZhVlkrh87msJNmBQpyhkrHqjSrE,11
66
- chatterbot-1.2.2.dist-info/RECORD,,
64
+ chatterbot-1.2.3.dist-info/LICENSE,sha256=5b04U8mi0wp5gJMYlKi49EalnD9Q2nwY_6UEI_Avgu4,1476
65
+ chatterbot-1.2.3.dist-info/METADATA,sha256=xnofLrmf6knmhcwBVcodzvxpZQ-eb4tbLB970dXQG8I,8503
66
+ chatterbot-1.2.3.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
67
+ chatterbot-1.2.3.dist-info/top_level.txt,sha256=W2TzAbAJ-eBXTIKZZhVlkrh87msJNmBQpyhkrHqjSrE,11
68
+ chatterbot-1.2.3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.1)
2
+ Generator: setuptools (76.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5