ChatterBot 1.2.5__py3-none-any.whl → 1.2.7__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.5'
7
+ __version__ = '1.2.7'
8
8
 
9
9
  __all__ = (
10
10
  'ChatBot',
chatterbot/chatterbot.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ from typing import Union
2
3
  from chatterbot.storage import StorageAdapter
3
4
  from chatterbot.logic import LogicAdapter
4
5
  from chatterbot.search import TextSearch, IndexedTextSearch
@@ -40,11 +41,21 @@ class ChatBot(object):
40
41
 
41
42
  :param logger: A ``Logger`` object.
42
43
  :type logger: logging.Logger
44
+
45
+ :param model: A definition used to load a large language model.
46
+ Defaults to ``None``.
47
+ (Added in version 1.2.7)
48
+ :type model: dict
49
+
50
+ :param stream: Return output as a streaming responses when a ``model`` is defined.
51
+ (Added in version 1.2.7)
43
52
  """
44
53
 
45
- def __init__(self, name, **kwargs):
54
+ def __init__(self, name, stream=False, **kwargs):
46
55
  self.name = name
47
56
 
57
+ self.stream = stream
58
+
48
59
  self.logger = kwargs.get('logger', logging.getLogger(__name__))
49
60
 
50
61
  storage_adapter = kwargs.get('storage_adapter', 'chatterbot.storage.SQLStorageAdapter')
@@ -112,10 +123,15 @@ class ChatBot(object):
112
123
  # NOTE: 'xx' is the language code for a multi-language model
113
124
  self.nlp = spacy.blank(self.tagger.language.ISO_639_1)
114
125
 
126
+ self.model = None
127
+ if model := kwargs.get('model'):
128
+ import_path = model.pop('client')
129
+ self.model = utils.initialize_class(import_path, self, **model)
130
+
115
131
  # Allow the bot to save input it receives so that it can learn
116
132
  self.read_only = kwargs.get('read_only', False)
117
133
 
118
- def get_response(self, statement=None, **kwargs) -> Statement:
134
+ def get_response(self, statement: Union[Statement, str, dict] = None, **kwargs) -> Statement:
119
135
  """
120
136
  Return the bot's response based on the input.
121
137
 
@@ -185,6 +201,10 @@ class ChatBot(object):
185
201
  additional_response_selection_parameters
186
202
  )
187
203
 
204
+ # If streaming is enabled return the response immediately
205
+ if self.stream:
206
+ return response
207
+
188
208
  # Update any response data that needs to be changed
189
209
  if persist_values_to_response:
190
210
  for response_key in persist_values_to_response:
@@ -219,6 +239,12 @@ class ChatBot(object):
219
239
  result = None
220
240
  max_confidence = -1
221
241
 
242
+ # If a model is provided, use it to process the input statement
243
+ # instead of the logic adapters
244
+ if self.model:
245
+ model_response = self.model.process(input_statement)
246
+ return model_response
247
+
222
248
  for adapter in self.logic_adapters:
223
249
  if adapter.can_process(input_statement):
224
250
 
@@ -22,13 +22,13 @@ class StatementMixin(object):
22
22
 
23
23
  extra_statement_field_names = []
24
24
 
25
- def get_statement_field_names(self):
25
+ def get_statement_field_names(self) -> list[str]:
26
26
  """
27
27
  Return the list of field names for the statement.
28
28
  """
29
29
  return self.statement_field_names + self.extra_statement_field_names
30
30
 
31
- def get_tags(self):
31
+ def get_tags(self) -> list[str]:
32
32
  """
33
33
  Return the list of tags for this statement.
34
34
  """
@@ -79,7 +79,7 @@ class Statement(StatementMixin):
79
79
  'storage',
80
80
  )
81
81
 
82
- def __init__(self, text, in_response_to=None, **kwargs):
82
+ def __init__(self, text: str, in_response_to=None, **kwargs):
83
83
 
84
84
  self.id = kwargs.get('id')
85
85
  self.text = str(text)
chatterbot/corpus.py CHANGED
@@ -18,7 +18,7 @@ except (ImportError, ModuleNotFoundError):
18
18
  CORPUS_EXTENSION = 'yml'
19
19
 
20
20
 
21
- def get_file_path(dotted_path, extension='json'):
21
+ def get_file_path(dotted_path, extension='json') -> str:
22
22
  """
23
23
  Reads a dotted file path and returns the file path.
24
24
  """
@@ -41,7 +41,7 @@ def get_file_path(dotted_path, extension='json'):
41
41
  return corpus_path
42
42
 
43
43
 
44
- def read_corpus(file_name):
44
+ def read_corpus(file_name) -> dict:
45
45
  """
46
46
  Read and return the data from a corpus json file.
47
47
  """
@@ -59,7 +59,7 @@ def read_corpus(file_name):
59
59
  return yaml.safe_load(data_file)
60
60
 
61
61
 
62
- def list_corpus_files(dotted_path):
62
+ def list_corpus_files(dotted_path) -> list[str]:
63
63
  """
64
64
  Return a list of file paths to each data file in the specified corpus.
65
65
  """
@@ -117,7 +117,7 @@ class AbstractBaseStatement(models.Model, StatementMixin):
117
117
  return self.text
118
118
  return '<empty>'
119
119
 
120
- def get_tags(self):
120
+ def get_tags(self) -> list[str]:
121
121
  """
122
122
  Return the list of tags for this statement.
123
123
  (Overrides the method from StatementMixin)
@@ -13,7 +13,7 @@ class ModelBase(object):
13
13
  """
14
14
 
15
15
  @declared_attr
16
- def __tablename__(cls):
16
+ def __tablename__(cls) -> str:
17
17
  """
18
18
  Return the lowercase class name as the name of the table.
19
19
  """
@@ -99,7 +99,7 @@ class Statement(Base, StatementMixin):
99
99
  server_default=''
100
100
  )
101
101
 
102
- def get_tags(self):
102
+ def get_tags(self) -> list[str]:
103
103
  """
104
104
  Return a list of tags for this statement.
105
105
  """
chatterbot/filters.py CHANGED
@@ -1,4 +1,4 @@
1
- def get_recent_repeated_responses(chatbot, conversation, sample=10, threshold=3, quantity=3):
1
+ def get_recent_repeated_responses(chatbot, conversation, sample=10, threshold=3, quantity=3) -> list:
2
2
  """
3
3
  A filter that eliminates possibly repetitive responses to prevent
4
4
  a chat bot from repeating statements that it has recently said.
chatterbot/llm.py ADDED
@@ -0,0 +1,145 @@
1
+ """
2
+ Large Language Model (LLM) clients.
3
+
4
+ .. note::
5
+ As a part of the development process when choosing models it is
6
+ important to research and understand the models you are using.
7
+
8
+ A good example of why this is important can be found in the
9
+ description of the Phi-3 model from Microsoft which discusses
10
+ responsible AI considerations such as the limitations of the
11
+ model, and appropriate use cases.
12
+ https://huggingface.co/microsoft/Phi-3-mini-4k-instruct-gguf#responsible-ai-considerations
13
+ """
14
+
15
+
16
+ class ModelClient:
17
+ """
18
+ A base class to define the interface for language model clients.
19
+ """
20
+
21
+ def __init__(self, chatbot, model: str, **kwargs):
22
+ self.chatbot = chatbot
23
+ self.model = model
24
+
25
+
26
+ class Ollama(ModelClient):
27
+ """
28
+ This client class allows the use of Ollama models for chatbot responses.
29
+
30
+ .. warning::
31
+ This is a new and experimental class. It may not work as expected
32
+ and its functionality may change in future releases.
33
+
34
+ .. note::
35
+ Added in version 1.2.7
36
+ """
37
+
38
+ def __init__(self, chatbot, model: str, **kwargs):
39
+ """
40
+ keyword arguments:
41
+ host: The host URL for the Ollama server.
42
+ Default is 'http://localhost:11434'.
43
+ """
44
+ super().__init__(chatbot, model)
45
+ from ollama import Client, AsyncClient
46
+
47
+ self.host = kwargs.get('host', 'http://localhost:11434')
48
+
49
+ # TODO: Look into supporting the async client
50
+ self.async_mode = False
51
+
52
+ # https://github.com/ollama/ollama-python
53
+ if self.async_mode:
54
+ self.client = AsyncClient(
55
+ host=self.host,
56
+ )
57
+ else:
58
+ self.client = Client(
59
+ host=self.host,
60
+ )
61
+
62
+ def process(self, statement):
63
+
64
+ system_message = {
65
+ 'role': 'system',
66
+ 'content': 'Please keep responses short and concise.'
67
+ }
68
+ message = {
69
+ 'role': 'user',
70
+ 'content': statement.text
71
+ }
72
+
73
+ if self.chatbot.stream:
74
+ for part in self.client.chat(
75
+ model=self.model,
76
+ messages=[system_message, message],
77
+ stream=True
78
+ ):
79
+ yield part['message']['content']
80
+ else:
81
+ response = self.client.chat(
82
+ model=self.model,
83
+ messages=[system_message, message]
84
+ )
85
+
86
+ return response.message.content
87
+
88
+
89
+ class OpenAI(ModelClient):
90
+ """
91
+ This client class allows the use of the OpenAI API to generate chatbot responses.
92
+
93
+ .. warning::
94
+ This is a new and experimental class. It may not work as expected
95
+ and its functionality may change in future releases.
96
+
97
+ .. note::
98
+ Added in version 1.2.7
99
+ """
100
+
101
+ def __init__(self, chatbot, model, **kwargs):
102
+ super().__init__(chatbot, model, **kwargs)
103
+ from openai import OpenAI as OpenAIClient
104
+ from openai import AsyncOpenAI as AsyncOpenAIClient
105
+
106
+ self.host = kwargs.get('host', None)
107
+
108
+ # TODO: Look into supporting the async client
109
+ self.async_mode = False
110
+
111
+ # https://github.com/openai/openai-python
112
+ if self.async_mode:
113
+ self.client = AsyncOpenAIClient(
114
+ base_url=self.host,
115
+ )
116
+ else:
117
+ self.client = OpenAIClient(
118
+ base_url=self.host,
119
+ )
120
+
121
+ def process(self, statement):
122
+
123
+ system_message = {
124
+ 'role': 'developer',
125
+ 'content': 'Please keep responses short and concise.'
126
+ }
127
+ message = {
128
+ 'role': 'user',
129
+ 'content': statement.text
130
+ }
131
+
132
+ if self.chatbot.stream:
133
+ for part in self.client.chat.completions.create(
134
+ model=self.model,
135
+ messages=[system_message, message],
136
+ stream=True
137
+ ):
138
+ yield part
139
+ else:
140
+ response = self.client.chat.completions.create(
141
+ model=self.model,
142
+ messages=[system_message, message]
143
+ )
144
+
145
+ return response.output_text
@@ -5,6 +5,7 @@ from chatterbot.logic.specific_response import SpecificResponseAdapter
5
5
  from chatterbot.logic.time_adapter import TimeLogicAdapter
6
6
  from chatterbot.logic.unit_conversion import UnitConversion
7
7
 
8
+
8
9
  __all__ = (
9
10
  'LogicAdapter',
10
11
  'BestMatch',
@@ -1,4 +1,5 @@
1
1
  from chatterbot.logic import LogicAdapter
2
+ from chatterbot.conversation import Statement
2
3
  from chatterbot import filters
3
4
 
4
5
 
@@ -22,7 +23,7 @@ class BestMatch(LogicAdapter):
22
23
 
23
24
  self.excluded_words = kwargs.get('excluded_words')
24
25
 
25
- def process(self, input_statement, additional_response_selection_parameters=None):
26
+ def process(self, input_statement: Statement, additional_response_selection_parameters=None) -> Statement:
26
27
 
27
28
  # Get all statements that have a response text similar to the input statement
28
29
  search_results = self.search_algorithm.search(input_statement)
@@ -86,7 +86,7 @@ class LogicAdapter(Adapter):
86
86
  """
87
87
  return True
88
88
 
89
- def process(self, statement, additional_response_selection_parameters=None) -> Statement:
89
+ def process(self, statement: Statement, additional_response_selection_parameters: dict = None) -> Statement:
90
90
  """
91
91
  Override this method and implement your logic for selecting a response to an input statement.
92
92
 
@@ -99,11 +99,9 @@ class LogicAdapter(Adapter):
99
99
  lowest confidence level and 1 is the highest.
100
100
 
101
101
  :param statement: An input statement to be processed by the logic adapter.
102
- :type statement: Statement
103
102
 
104
103
  :param additional_response_selection_parameters: Parameters to be used when
105
104
  filtering results to choose a response from.
106
- :type additional_response_selection_parameters: dict
107
105
  """
108
106
  raise self.AdapterMethodNotImplementedError()
109
107
 
@@ -25,7 +25,7 @@ class MathematicalEvaluation(LogicAdapter):
25
25
  self.language = kwargs.get('language', languages.ENG)
26
26
  self.cache = {}
27
27
 
28
- def can_process(self, statement):
28
+ def can_process(self, statement) -> bool:
29
29
  """
30
30
  Determines whether it is appropriate for this
31
31
  adapter to respond to the user input.
@@ -34,7 +34,7 @@ class MathematicalEvaluation(LogicAdapter):
34
34
  self.cache[statement.text] = response
35
35
  return response.confidence == 1
36
36
 
37
- def process(self, statement, additional_response_selection_parameters=None):
37
+ def process(self, statement: Statement, additional_response_selection_parameters: dict = None) -> Statement:
38
38
  """
39
39
  Takes a statement string.
40
40
  Returns the equation from the statement with the mathematical terms solved.
@@ -40,7 +40,7 @@ class SpecificResponseAdapter(LogicAdapter):
40
40
 
41
41
  return spacy.load(model)
42
42
 
43
- def can_process(self, statement):
43
+ def can_process(self, statement) -> bool:
44
44
  if self.matcher:
45
45
  doc = self.nlp(statement.text)
46
46
  matches = self.matcher(doc)
@@ -52,7 +52,7 @@ class SpecificResponseAdapter(LogicAdapter):
52
52
 
53
53
  return False
54
54
 
55
- def process(self, statement, additional_response_selection_parameters=None):
55
+ def process(self, statement: Statement, additional_response_selection_parameters: dict = None) -> Statement:
56
56
 
57
57
  if callable(self._output_text):
58
58
  response_statement = Statement(text=self._output_text())
@@ -51,7 +51,7 @@ class TimeLogicAdapter(LogicAdapter):
51
51
  # Add the patterns to the matcher
52
52
  self.matcher.add('TimeQuestionList', patterns)
53
53
 
54
- def process(self, statement, additional_response_selection_parameters=None):
54
+ def process(self, statement: Statement, additional_response_selection_parameters: dict = None) -> Statement:
55
55
  now = datetime.now()
56
56
 
57
57
  # Check if the input statement contains a time-related question
@@ -137,12 +137,12 @@ class UnitConversion(LogicAdapter):
137
137
 
138
138
  return response
139
139
 
140
- def can_process(self, statement):
140
+ def can_process(self, statement) -> bool:
141
141
  response = self.process(statement)
142
142
  self.cache[statement.text] = response
143
143
  return response.confidence == 1.0
144
144
 
145
- def process(self, statement, additional_response_selection_parameters=None):
145
+ def process(self, statement: Statement, additional_response_selection_parameters: dict = None) -> Statement:
146
146
  response = Statement(text='')
147
147
  input_text = statement.text
148
148
  try:
@@ -6,13 +6,11 @@ from chatterbot.conversation import Statement
6
6
  import logging
7
7
 
8
8
 
9
- def get_most_frequent_response(input_statement, response_list, storage=None) -> Statement:
9
+ def get_most_frequent_response(input_statement: Statement, response_list: list[Statement], storage=None) -> Statement:
10
10
  """
11
11
  :param input_statement: A statement, that closely matches an input to the chat bot.
12
- :type input_statement: Statement
13
12
 
14
13
  :param response_list: A list of statement options to choose a response from.
15
- :type response_list: list
16
14
 
17
15
  :param storage: An instance of a storage adapter to allow the response selection
18
16
  method to access other statements if needed.
@@ -41,13 +39,11 @@ def get_most_frequent_response(input_statement, response_list, storage=None) ->
41
39
  return matching_response
42
40
 
43
41
 
44
- def get_first_response(input_statement, response_list, storage=None) -> Statement:
42
+ def get_first_response(input_statement: Statement, response_list: list[Statement], storage=None) -> Statement:
45
43
  """
46
44
  :param input_statement: A statement, that closely matches an input to the chat bot.
47
- :type input_statement: Statement
48
45
 
49
46
  :param response_list: A list of statement options to choose a response from.
50
- :type response_list: list
51
47
 
52
48
  :param storage: An instance of a storage adapter to allow the response selection
53
49
  method to access other statements if needed.
@@ -62,7 +58,7 @@ def get_first_response(input_statement, response_list, storage=None) -> Statemen
62
58
  return response_list[0]
63
59
 
64
60
 
65
- def get_random_response(input_statement, response_list, storage=None) -> Statement:
61
+ def get_random_response(input_statement: Statement, response_list: list[Statement], storage=None) -> Statement:
66
62
  """
67
63
  :param input_statement: A statement, that closely matches an input to the chat bot.
68
64
  :type input_statement: Statement
@@ -24,7 +24,7 @@ class DjangoStorageAdapter(StorageAdapter):
24
24
  from django.apps import apps
25
25
  return apps.get_model(self.django_app_name, 'Tag')
26
26
 
27
- def count(self):
27
+ def count(self) -> int:
28
28
  Statement = self.get_model('statement')
29
29
  return Statement.objects.count()
30
30
 
@@ -54,7 +54,7 @@ class MongoDatabaseAdapter(StorageAdapter):
54
54
 
55
55
  return statement
56
56
 
57
- def count(self):
57
+ def count(self) -> int:
58
58
  return self.statements.count_documents({})
59
59
 
60
60
  def mongo_to_object(self, statement_data):
@@ -132,7 +132,7 @@ class RedisVectorStorageAdapter(StorageAdapter):
132
132
 
133
133
  return StatementObject(**values)
134
134
 
135
- def count(self):
135
+ def count(self) -> int:
136
136
  """
137
137
  Return the number of entries in the database.
138
138
  """
@@ -44,7 +44,7 @@ class SQLStorageAdapter(StorageAdapter):
44
44
  dbapi_connection.execute('PRAGMA journal_mode=WAL')
45
45
  dbapi_connection.execute('PRAGMA synchronous=NORMAL')
46
46
 
47
- if not inspect(self.engine).has_table(self.engine, 'statement'):
47
+ if not inspect(self.engine).has_table('statement'):
48
48
  self.create_database()
49
49
 
50
50
  # Check if the expected index exists on the text field of the statement table
@@ -90,7 +90,7 @@ class SQLStorageAdapter(StorageAdapter):
90
90
 
91
91
  return StatementObject(**statement.serialize())
92
92
 
93
- def count(self):
93
+ def count(self) -> int:
94
94
  """
95
95
  Return the number of entries in the database.
96
96
  """
@@ -54,7 +54,7 @@ class StorageAdapter(object):
54
54
 
55
55
  return Statement
56
56
 
57
- def count(self):
57
+ def count(self) -> int:
58
58
  """
59
59
  Return the number of entries in the database.
60
60
  """
chatterbot/tagging.py CHANGED
@@ -1,3 +1,4 @@
1
+ from typing import List, Union, Tuple
1
2
  from chatterbot import languages
2
3
  from chatterbot.utils import get_model_for_language
3
4
  import spacy
@@ -20,7 +21,7 @@ class LowercaseTagger(object):
20
21
  'chatterbot_lowercase_indexer', name='chatterbot_lowercase_indexer', last=True
21
22
  )
22
23
 
23
- def get_text_index_string(self, text):
24
+ def get_text_index_string(self, text: Union[str, List[str]]):
24
25
  if isinstance(text, list):
25
26
  documents = self.nlp.pipe(text)
26
27
  return [document._.search_index for document in documents]
@@ -28,7 +29,7 @@ class LowercaseTagger(object):
28
29
  document = self.nlp(text)
29
30
  return document._.search_index
30
31
 
31
- def as_nlp_pipeline(self, texts):
32
+ def as_nlp_pipeline(self, texts: Union[List[str], Tuple[str, dict]]):
32
33
 
33
34
  process_as_tuples = texts and isinstance(texts[0], tuple)
34
35
 
@@ -52,7 +53,7 @@ class PosLemmaTagger(object):
52
53
  'chatterbot_bigram_indexer', name='chatterbot_bigram_indexer', last=True
53
54
  )
54
55
 
55
- def get_text_index_string(self, text):
56
+ def get_text_index_string(self, text: Union[str, List[str]]) -> str:
56
57
  """
57
58
  Return a string of text containing part-of-speech, lemma pairs.
58
59
  """
@@ -63,7 +64,7 @@ class PosLemmaTagger(object):
63
64
  document = self.nlp(text)
64
65
  return document._.search_index
65
66
 
66
- def as_nlp_pipeline(self, texts):
67
+ def as_nlp_pipeline(self, texts: Union[List[str], Tuple[str, dict]]):
67
68
  """
68
69
  Accepts a single string or a list of strings, or a list of tuples
69
70
  where the first element is the text and the second element is a
chatterbot/trainers.py CHANGED
@@ -1,12 +1,13 @@
1
1
  import os
2
- import sys
3
2
  import csv
4
3
  import time
5
4
  import glob
6
5
  import json
7
6
  import tarfile
7
+ from typing import List, Union
8
8
  from tqdm import tqdm
9
9
  from dateutil import parser as date_parser
10
+ from chatterbot.chatterbot import ChatBot
10
11
  from chatterbot.conversation import Statement
11
12
 
12
13
 
@@ -20,7 +21,7 @@ class Trainer(object):
20
21
  the environment variable if it is set.
21
22
  """
22
23
 
23
- def __init__(self, chatbot, **kwargs):
24
+ def __init__(self, chatbot: ChatBot, **kwargs):
24
25
  self.chatbot = chatbot
25
26
 
26
27
  environment_default = bool(int(os.environ.get('CHATTERBOT_SHOW_TRAINING_PROGRESS', True)))
@@ -30,7 +31,7 @@ class Trainer(object):
30
31
  environment_default
31
32
  )
32
33
 
33
- def get_preprocessed_statement(self, input_statement):
34
+ def get_preprocessed_statement(self, input_statement: Statement) -> Statement:
34
35
  """
35
36
  Preprocess the input statement.
36
37
  """
@@ -58,7 +59,7 @@ class Trainer(object):
58
59
  )
59
60
  super().__init__(message or default)
60
61
 
61
- def _generate_export_data(self):
62
+ def _generate_export_data(self) -> list:
62
63
  result = []
63
64
  for statement in self.chatbot.storage.filter():
64
65
  if statement.in_response_to:
@@ -82,7 +83,7 @@ class ListTrainer(Trainer):
82
83
  where the list represents a conversation.
83
84
  """
84
85
 
85
- def train(self, conversation: list):
86
+ def train(self, conversation: List[str]):
86
87
  """
87
88
  Train the chat bot based on the provided list of
88
89
  statements that represents a single conversation.
@@ -95,7 +96,6 @@ class ListTrainer(Trainer):
95
96
  # Run the pipeline in bulk to improve performance
96
97
  documents = self.chatbot.tagger.as_nlp_pipeline(conversation)
97
98
 
98
- # for text in enumerate(conversation):
99
99
  for document in tqdm(documents, desc='List Trainer', disable=self.disable_progress):
100
100
  statement_search_text = document._.search_index
101
101
 
@@ -123,7 +123,7 @@ class ChatterBotCorpusTrainer(Trainer):
123
123
  ChatterBot dialog corpus.
124
124
  """
125
125
 
126
- def train(self, *corpus_paths):
126
+ def train(self, *corpus_paths: Union[str, List[str]]):
127
127
  from chatterbot.corpus import load_corpus, list_corpus_files
128
128
 
129
129
  data_file_paths = []
@@ -178,7 +178,17 @@ class GenericFileTrainer(Trainer):
178
178
  or directory of those file types.
179
179
  """
180
180
 
181
- def __init__(self, chatbot, **kwargs):
181
+ # NOTE: If the value is an integer, this be the
182
+ # column index instead of the key or header
183
+ DEFAULT_STATEMENT_TO_HEADER_MAPPING = {
184
+ 'text': 'text',
185
+ 'conversation': 'conversation',
186
+ 'created_at': 'created_at',
187
+ 'persona': 'persona',
188
+ 'tags': 'tags'
189
+ }
190
+
191
+ def __init__(self, chatbot: ChatBot, **kwargs):
182
192
  """
183
193
  data_path: str The path to the data file or directory.
184
194
  field_map: dict A dictionary containing the column name to header mapping.
@@ -187,22 +197,12 @@ class GenericFileTrainer(Trainer):
187
197
 
188
198
  self.file_extension = None
189
199
 
190
- # NOTE: If the key is an integer, this be the
191
- # column index instead of the key or header
192
- DEFAULT_STATEMENT_TO_HEADER_MAPPING = {
193
- 'text': 'text',
194
- 'conversation': 'conversation',
195
- 'created_at': 'created_at',
196
- 'persona': 'persona',
197
- 'tags': 'tags'
198
- }
199
-
200
200
  self.field_map = kwargs.get(
201
201
  'field_map',
202
- DEFAULT_STATEMENT_TO_HEADER_MAPPING
202
+ self.DEFAULT_STATEMENT_TO_HEADER_MAPPING
203
203
  )
204
204
 
205
- def _get_file_list(self, data_path, limit):
205
+ def _get_file_list(self, data_path: str, limit: Union[int, None]):
206
206
  """
207
207
  Get a list of files to read from the data set.
208
208
  """
@@ -302,6 +302,20 @@ class GenericFileTrainer(Trainer):
302
302
  f'Current mapping: {self.field_map}'
303
303
  )
304
304
 
305
+ response_to_search_index_mapping = {}
306
+
307
+ if 'in_response_to' in self.field_map.keys():
308
+ # Generate the search_in_response_to value for the in_response_to fields
309
+ response_documents = self.chatbot.tagger.as_nlp_pipeline([
310
+ (
311
+ row[self.field_map['in_response_to']]
312
+ ) for row in data if len(row) > 0 and row[self.field_map['in_response_to']] is not None
313
+ ])
314
+
315
+ # (Process the response values the same way as the text values)
316
+ for document in response_documents:
317
+ response_to_search_index_mapping[document.text] = document._.search_index
318
+
305
319
  for document, context in documents:
306
320
  statement = Statement(
307
321
  text=document.text,
@@ -314,14 +328,19 @@ class GenericFileTrainer(Trainer):
314
328
  statement.created_at = date_parser.parse(context['created_at'])
315
329
 
316
330
  statement.search_text = document._.search_index
317
- statement.search_in_response_to = previous_statement_search_text
318
331
 
319
332
  # Use the in_response_to attribute for the previous statement if
320
333
  # one is defined, otherwise use the last statement which was created
321
334
  if 'in_response_to' in self.field_map.keys():
322
335
  statement.in_response_to = context.get(self.field_map['in_response_to'], None)
336
+ statement.search_in_response_to = response_to_search_index_mapping.get(
337
+ context.get(self.field_map['in_response_to'], None), ''
338
+ )
323
339
  else:
340
+ # List-type data such as CSVs with no response specified can use
341
+ # the previous statement as the in_response_to value
324
342
  statement.in_response_to = previous_statement_text
343
+ statement.search_in_response_to = previous_statement_search_text
325
344
 
326
345
  for preprocessor in self.chatbot.preprocessors:
327
346
  statement = preprocessor(statement)
@@ -345,7 +364,6 @@ class GenericFileTrainer(Trainer):
345
364
  )
346
365
  )
347
366
 
348
-
349
367
  class CsvFileTrainer(GenericFileTrainer):
350
368
  """
351
369
  .. note::
@@ -358,11 +376,11 @@ class CsvFileTrainer(GenericFileTrainer):
358
376
  parameter is set to 'tsv'.
359
377
 
360
378
  :param str file_extension: The file extension to look for when searching for files (defaults to 'csv').
361
- :param str field_map: A dictionary containing the database column name to header mapping.
379
+ :param dict field_map: A dictionary containing the database column name to header mapping.
362
380
  Values can be either the header name (str) or the column index (int).
363
381
  """
364
382
 
365
- def __init__(self, chatbot, **kwargs):
383
+ def __init__(self, chatbot: ChatBot, **kwargs):
366
384
  super().__init__(chatbot, **kwargs)
367
385
 
368
386
  self.file_extension = kwargs.get('file_extension', 'csv')
@@ -376,26 +394,26 @@ class JsonFileTrainer(GenericFileTrainer):
376
394
  Allow chatbots to be trained with data from a JSON file or
377
395
  directory of JSON files.
378
396
 
379
- :param str field_map: A dictionary containing the database column name to header mapping.
397
+ :param dict field_map: A dictionary containing the database column name to header mapping.
380
398
  """
381
399
 
382
- def __init__(self, chatbot, **kwargs):
400
+ DEFAULT_STATEMENT_TO_KEY_MAPPING = {
401
+ 'text': 'text',
402
+ 'conversation': 'conversation',
403
+ 'created_at': 'created_at',
404
+ 'in_response_to': 'in_response_to',
405
+ 'persona': 'persona',
406
+ 'tags': 'tags'
407
+ }
408
+
409
+ def __init__(self, chatbot: ChatBot, **kwargs):
383
410
  super().__init__(chatbot, **kwargs)
384
411
 
385
412
  self.file_extension = 'json'
386
413
 
387
- DEFAULT_STATEMENT_TO_KEY_MAPPING = {
388
- 'text': 'text',
389
- 'conversation': 'conversation',
390
- 'created_at': 'created_at',
391
- 'in_response_to': 'in_response_to',
392
- 'persona': 'persona',
393
- 'tags': 'tags'
394
- }
395
-
396
414
  self.field_map = kwargs.get(
397
415
  'field_map',
398
- DEFAULT_STATEMENT_TO_KEY_MAPPING
416
+ self.DEFAULT_STATEMENT_TO_KEY_MAPPING
399
417
  )
400
418
 
401
419
 
@@ -412,7 +430,7 @@ class UbuntuCorpusTrainer(CsvFileTrainer):
412
430
  :param str ubuntu_corpus_data_directory: The directory where the Ubuntu corpus data is already located, or where it should be downloaded and extracted.
413
431
  """
414
432
 
415
- def __init__(self, chatbot, **kwargs):
433
+ def __init__(self, chatbot: ChatBot, **kwargs):
416
434
  super().__init__(chatbot, **kwargs)
417
435
  home_directory = os.path.expanduser('~')
418
436
 
@@ -434,7 +452,7 @@ class UbuntuCorpusTrainer(CsvFileTrainer):
434
452
  'persona': 1,
435
453
  }
436
454
 
437
- def is_downloaded(self, file_path):
455
+ def is_downloaded(self, file_path: str):
438
456
  """
439
457
  Check if the data file is already downloaded.
440
458
  """
@@ -444,7 +462,7 @@ class UbuntuCorpusTrainer(CsvFileTrainer):
444
462
 
445
463
  return False
446
464
 
447
- def is_extracted(self, file_path):
465
+ def is_extracted(self, file_path: str):
448
466
  """
449
467
  Check if the data file is already extracted.
450
468
  """
@@ -454,7 +472,7 @@ class UbuntuCorpusTrainer(CsvFileTrainer):
454
472
  return True
455
473
  return False
456
474
 
457
- def download(self, url, show_status=True):
475
+ def download(self, url: str, show_status=True):
458
476
  """
459
477
  Download a file from the given url.
460
478
  Show a progress indicator for the download status.
@@ -493,7 +511,7 @@ class UbuntuCorpusTrainer(CsvFileTrainer):
493
511
  print('Download location: %s' % file_path)
494
512
  return file_path
495
513
 
496
- def extract(self, file_path):
514
+ def extract(self, file_path: str):
497
515
  """
498
516
  Extract a tar file at the specified file path.
499
517
  """
@@ -533,7 +551,7 @@ class UbuntuCorpusTrainer(CsvFileTrainer):
533
551
 
534
552
  return True
535
553
 
536
- def _get_file_list(self, data_path, limit):
554
+ def _get_file_list(self, data_path: str, limit: Union[int, None]):
537
555
  """
538
556
  Get a list of files to read from the data set.
539
557
  """
@@ -564,7 +582,7 @@ class UbuntuCorpusTrainer(CsvFileTrainer):
564
582
 
565
583
  yield file_path
566
584
 
567
- def train(self, data_download_url, limit=None):
585
+ def train(self, data_download_url: str, limit: Union[int, None] = None):
568
586
  """
569
587
  :param str data_download_url: The URL to download the Ubuntu dialog corpus from.
570
588
  :param int limit: The maximum number of files to train from.
chatterbot/utils.py CHANGED
@@ -1,11 +1,12 @@
1
1
  """
2
2
  ChatterBot utility functions
3
3
  """
4
+ from typing import Union
4
5
  import importlib
5
6
  import time
6
7
 
7
8
 
8
- def import_module(dotted_path):
9
+ def import_module(dotted_path: str):
9
10
  """
10
11
  Imports the specified module based on the
11
12
  dot notated import path for the module.
@@ -17,7 +18,7 @@ def import_module(dotted_path):
17
18
  return getattr(module, module_parts[-1])
18
19
 
19
20
 
20
- def initialize_class(data, *args, **kwargs):
21
+ def initialize_class(data: Union[dict, str], *args, **kwargs):
21
22
  """
22
23
  :param data: A string or dictionary containing a import_path attribute.
23
24
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ChatterBot
3
- Version: 1.2.5
3
+ Version: 1.2.7
4
4
  Summary: ChatterBot is a machine learning, conversational dialog engine
5
5
  Author: Gunther Cox
6
6
  License-Expression: BSD-3-Clause
@@ -39,19 +39,21 @@ Requires-Dist: tqdm
39
39
  Provides-Extra: test
40
40
  Requires-Dist: flake8; extra == "test"
41
41
  Requires-Dist: coverage; extra == "test"
42
- Requires-Dist: nose; extra == "test"
43
42
  Requires-Dist: sphinx<8.2,>=5.3; extra == "test"
44
43
  Requires-Dist: sphinx-sitemap>=2.6.0; extra == "test"
45
44
  Requires-Dist: huggingface_hub; extra == "test"
46
45
  Provides-Extra: dev
47
46
  Requires-Dist: pint>=0.8.1; extra == "dev"
48
47
  Requires-Dist: pyyaml<7.0,>=6.0; extra == "dev"
49
- Requires-Dist: chatterbot-corpus>=1.2.2; extra == "dev"
48
+ Requires-Dist: chatterbot-corpus<1.3.0,>=1.2.2; extra == "dev"
49
+ Requires-Dist: ollama<1.0,>=0.4.7; extra == "dev"
50
+ Requires-Dist: openai; extra == "dev"
50
51
  Provides-Extra: redis
51
- Requires-Dist: redis[hiredis]; extra == "redis"
52
- Requires-Dist: langchain-redis; extra == "redis"
53
- Requires-Dist: langchain-huggingface; extra == "redis"
54
- Requires-Dist: sentence-transformers; extra == "redis"
52
+ Requires-Dist: redis[hiredis]<5.3; extra == "redis"
53
+ Requires-Dist: langchain-redis<=0.2.0; extra == "redis"
54
+ Requires-Dist: langchain-huggingface<=0.1.2; extra == "redis"
55
+ Requires-Dist: accelerate<=1.6.0; extra == "redis"
56
+ Requires-Dist: sentence-transformers<=4.0.2; extra == "redis"
55
57
  Provides-Extra: mongodb
56
58
  Requires-Dist: pymongo<4.12,>=4.11; extra == "mongodb"
57
59
  Dynamic: license-file
@@ -66,10 +68,11 @@ known conversations. The language independent design of ChatterBot allows it
66
68
  to be trained to speak any language.
67
69
 
68
70
  [![Package Version](https://img.shields.io/pypi/v/chatterbot.svg)](https://pypi.python.org/pypi/chatterbot/)
69
- [![Python 3.9](https://img.shields.io/badge/python-3.9-blue.svg)](https://www.python.org/downloads/release/python-360/)
71
+ [![Python 3.12](https://img.shields.io/badge/python-3.12-blue.svg)](https://www.python.org/downloads/release/python-360/)
70
72
  [![Coverage Status](https://img.shields.io/coveralls/gunthercox/ChatterBot.svg)](https://coveralls.io/r/gunthercox/ChatterBot)
71
- [![Code Climate](https://codeclimate.com/github/gunthercox/ChatterBot/badges/gpa.svg)](https://codeclimate.com/github/gunthercox/ChatterBot)
73
+ [![Follow on Bluesky](https://img.shields.io/badge/🦋%20Bluesky-1185fe)](https://bsky.app/profile/chatterbot.us)
72
74
  [![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)
75
+ <!-- [![Code Climate](https://codeclimate.com/github/gunthercox/ChatterBot/badges/gpa.svg)](https://codeclimate.com/github/gunthercox/ChatterBot) -->
73
76
 
74
77
  An example of typical input would be something like this:
75
78
 
@@ -80,7 +83,7 @@ An example of typical input would be something like this:
80
83
 
81
84
  ## How it works
82
85
 
83
- An untrained instance of ChatterBot starts off with no knowledge of how to communicate. Each time a user enters a statement, the library saves the text that they entered and the text that the statement was in response to. As ChatterBot receives more input the number of responses that it can reply and the accuracy of each response in relation to the input statement increase. The program selects the closest matching response by searching for the closest matching known statement that matches the input, it then returns the most likely response to that statement based on how frequently each response is issued by the people the bot communicates with.
86
+ An untrained instance of ChatterBot starts off with no knowledge of how to communicate. Each time a user enters a statement, the library saves the text that they entered and the text that the statement was in response to. As ChatterBot receives more input the number of responses that it can reply to, and the accuracy of each response in relation to the input statement increases. The program selects the closest matching response by searching for the closest matching known statement that matches the input, it then returns the most likely response to that statement based on how frequently each response is issued by the people the bot communicates with.
84
87
 
85
88
  # [Documentation](https://docs.chatterbot.us)
86
89
 
@@ -1,26 +1,27 @@
1
- chatterbot/__init__.py,sha256=QJV-6PyWrpW0rpSjVn0lKcH3dqb77TlYoU74kv8uVjc,158
1
+ chatterbot/__init__.py,sha256=fIvZMoUZCqkxB9WkmF1UB7t9qwksyGYoO_TMZwUuF0E,158
2
2
  chatterbot/__main__.py,sha256=zvH4uxtGlGrP-ht_LkhX29duzjm3hRH800SDCq4YOwg,637
3
3
  chatterbot/adapters.py,sha256=LJ_KqLpHKPdYAFpMGK63RVH4weV5X0Zh5uGyan6qdVU,878
4
- chatterbot/chatterbot.py,sha256=nqxdeTBWdA_LDIEWTMf2gphvpNfd0c9htNwrxa_7pzo,12543
4
+ chatterbot/chatterbot.py,sha256=bzBw0qAiDwA8r2BzgcL4WaWdJWcRkWqHePSGsntPq9U,13493
5
5
  chatterbot/comparisons.py,sha256=kYYR5DUipdzr7ok08m8s6r9LBez4GBQICaSmLZcmoiM,6184
6
6
  chatterbot/components.py,sha256=ld3Xam8olBClvE5QqcFYggE7Q7tODCFek7BO7lhfyeU,1782
7
7
  chatterbot/constants.py,sha256=c_KPQKc82CHX6H3maeyTYqWatx6j-N-8HJhmejoVi60,1875
8
- chatterbot/conversation.py,sha256=-JshXr4Uvsl8NVeEgm1am2e4UStBN1BXfMQHYkQFvco,3162
9
- chatterbot/corpus.py,sha256=n1sFU0KF-oRdj7g3nXft7uHOVilwbj_mVZnaeIHgC54,2409
8
+ chatterbot/conversation.py,sha256=uLLm5qa6fzHKNBGelykBEw-RD1BLdu_BoYfZUm4wbzI,3193
9
+ chatterbot/corpus.py,sha256=7TYGLU1JvoewFqIRkmyMPfl9q6TCbIyRZ-7OQFMbTZ4,2437
10
10
  chatterbot/exceptions.py,sha256=gqAlckDna6SB53kyqutmCtCqWxia40WBdUOnGVQ2Kbk,163
11
- chatterbot/filters.py,sha256=vDSDJz2FM10xT6ybs7qJiqy4X5I4gTEfwEnjBGUxZ9g,847
11
+ chatterbot/filters.py,sha256=VeXyVTa6SKmff1Ha6IHuBKEq2Y-UoAP_L9LRmykArzk,855
12
12
  chatterbot/languages.py,sha256=XSenfc5FxHk_JWG5gGHsZvjvrPBbCaVCm_OU-BeER_M,32784
13
+ chatterbot/llm.py,sha256=ojIAxuenXvX6ivXXnDfbmDbsp1ZLzWPmwFE-Szd0Yeg,4207
13
14
  chatterbot/parsing.py,sha256=vS-w70cMkjq4YEpDOv_pXWhAI6Zj06WYDAcMDhYDj0M,23174
14
15
  chatterbot/preprocessors.py,sha256=kqsgnejSj6Z1rr9U2TGHKOp-MMaFWBdNT41EwyhQFls,1389
15
- chatterbot/response_selection.py,sha256=JpUVuBYrgxhHkDMRHXyWvhluSLxQED5mAhE1-VvJSmg,2970
16
+ chatterbot/response_selection.py,sha256=T0sA3FiCc0qN5fM6co4hzd7tRmbPwq8uJHkYixLMmPw,2920
16
17
  chatterbot/search.py,sha256=FTwwON2eKPWqoc5uoKh4AUmuXDCqyfMcMcXB4wijpxg,4910
17
- chatterbot/tagging.py,sha256=czcI2g18vILujphkjvobRyEewJU8-QjS7QRzY-hCZ4o,2429
18
- chatterbot/trainers.py,sha256=S_y-Q67hgU7p1A_ixJsR91nW_FniJUsSzbdtAQ8KJQM,19749
19
- chatterbot/utils.py,sha256=ubPiBapvUvdFVhrDjxqq5IGekUh9qMUJs_dQ605xLAI,2924
18
+ chatterbot/tagging.py,sha256=HwiNrOA1c173H_lze93fgdm_Xu70f7xgTKgnWUAET2s,2592
19
+ chatterbot/trainers.py,sha256=CZezNX68Byg9gg2z-PUZbTc5pqzTzbhgSmqHx7P6Ivg,20973
20
+ chatterbot/utils.py,sha256=cPYwU5fU00HeSD_ErIIKFs8FwKwGYx08Krizhz1Hfas,2972
20
21
  chatterbot/vectorstores.py,sha256=-S1NB8PrZzoFIu95n2W7N4UaXuCUpyDUXIGYFebjv08,2056
21
22
  chatterbot/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
23
  chatterbot/ext/django_chatterbot/__init__.py,sha256=iWzmBzpAsYwkwi1faxAPFY9L1bbL97RgVXK2uqULIMc,92
23
- chatterbot/ext/django_chatterbot/abstract_models.py,sha256=skMqOqxIL_xOyfU8ZabtDqzUKTz0YtqGkaylB3TC1JA,3883
24
+ chatterbot/ext/django_chatterbot/abstract_models.py,sha256=U3KzKo1D6ahgmweFYuLoaMF6Vibk_urQuU3F-I8rg8o,3896
24
25
  chatterbot/ext/django_chatterbot/admin.py,sha256=jyCJGLRcRoeaoYWZFv-TCivcDUNTE4Li7pmYE8_hYCM,266
25
26
  chatterbot/ext/django_chatterbot/apps.py,sha256=YSqF77pD9Pl2-BP6KeQZRr22XhL8gInpHVkVTLtZsUU,464
26
27
  chatterbot/ext/django_chatterbot/model_admin.py,sha256=aJVE6u1fquMrvFTm_0aclK6FSAcUtfZcj-RPnXmm6iM,353
@@ -48,22 +49,22 @@ chatterbot/ext/django_chatterbot/migrations/0019_alter_statement_id_alter_tag_id
48
49
  chatterbot/ext/django_chatterbot/migrations/0020_alter_statement_conversation_and_more.py,sha256=PpcIvl6sO9jNBxW4UgvFMv_j0eNmUBHire5qo7u-XAg,2133
49
50
  chatterbot/ext/django_chatterbot/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
51
  chatterbot/ext/sqlalchemy_app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
- chatterbot/ext/sqlalchemy_app/models.py,sha256=ZQ-R_5rA-f1agaqYGUQhuuO7zx__BvTDUvJo5R7ZrDY,2492
52
- chatterbot/logic/__init__.py,sha256=28-5swBCPfSVMl8xB5C8frOKZ2oj28rQfenbd9E4r-4,531
53
- chatterbot/logic/best_match.py,sha256=8TNW0uZ_Uq-XPfaZUMUZDVH6KzDT65j59xblxQBv-dQ,4820
54
- chatterbot/logic/logic_adapter.py,sha256=zcYrzyywZ0kT3GsjcMRr69abvswurlNYOPLsUpIwaB8,5144
55
- chatterbot/logic/mathematical_evaluation.py,sha256=GPDKUwNFajERof2R-MkPGi2jJRP-rKAGm_f0V9JHDHE,2282
56
- chatterbot/logic/specific_response.py,sha256=akWHkfe0AjzlCUvjs_PbKFNkX4SZhu_tzY45xCRXoo0,2236
57
- chatterbot/logic/time_adapter.py,sha256=1PT6tWtGauZLRH02-Xlh2LublDpu_3hnCqHBqNGM9yg,2256
58
- chatterbot/logic/unit_conversion.py,sha256=-ENMLqZqtZx0riUi0guda2oJECST0M7pZG4cSIv3ieM,5898
52
+ chatterbot/ext/sqlalchemy_app/models.py,sha256=UUJ6cXsZRvOqXcMgxSb7n36Bw_wsapP02-HCpdZMozA,2512
53
+ chatterbot/logic/__init__.py,sha256=bqYcX4iPwTKwsL7ey9XN9zK7Z_YrLNKtOojZEpVSdA4,532
54
+ chatterbot/logic/best_match.py,sha256=QttbNGxtUt3FnoHgDo2BOpnerITlDD1hA4LoW2CWO1s,4890
55
+ chatterbot/logic/logic_adapter.py,sha256=vhu2-TtEpoQxfJE9ERu9Zm-eC-KB9KIPJUtagN7hi0U,5067
56
+ chatterbot/logic/mathematical_evaluation.py,sha256=FXBRt_OOWj72bKUip5RIdFIro79kMzy1E9sWARr5Mn8,2322
57
+ chatterbot/logic/specific_response.py,sha256=eNIpcphquPOFqxa77HriO9b0PIkZrmc2JqZ6Em34qe8,2276
58
+ chatterbot/logic/time_adapter.py,sha256=q07kob5EPZElAbIk8kzNrficBYNCnvS5MaZGz5nxBBc,2288
59
+ chatterbot/logic/unit_conversion.py,sha256=CWtAnaSHPBjsKN63rtv6xUXU2JQj2qXCAwzX1xyYnhA,5938
59
60
  chatterbot/storage/__init__.py,sha256=ADw0WQe0YKr1UIDQLaxwf0mHDnuKW_CSzgz11K4TM-4,465
60
- chatterbot/storage/django_storage.py,sha256=BpuVEO4rPOiPu7f7KW1Zyar2LqEXy6I4HgPYhyGP0kE,6305
61
- chatterbot/storage/mongodb.py,sha256=Ozvdvcjb3LGZxcvbSQGzwP9VloYQbmsa2FaKunFpMyU,7934
62
- chatterbot/storage/redis.py,sha256=FKROrzZ-7WXZ8ZoK0dKmTDdS45TxL04XOSeu0p3Jrak,12675
63
- chatterbot/storage/sql_storage.py,sha256=dAMLByFKQgbiTFoBUtKDeqadYRdwVO5fz1OONTcVCH4,13076
64
- chatterbot/storage/storage_adapter.py,sha256=fvyb-qNiB0HMJ0siVMCWUIY--6d-C47N1_kKZVFZAv4,6110
65
- chatterbot-1.2.5.dist-info/licenses/LICENSE,sha256=5b04U8mi0wp5gJMYlKi49EalnD9Q2nwY_6UEI_Avgu4,1476
66
- chatterbot-1.2.5.dist-info/METADATA,sha256=UgfV52vY3Rq4A_8rVZurYhKcU0DeNwply-UBh42aTd0,7049
67
- chatterbot-1.2.5.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
68
- chatterbot-1.2.5.dist-info/top_level.txt,sha256=W2TzAbAJ-eBXTIKZZhVlkrh87msJNmBQpyhkrHqjSrE,11
69
- chatterbot-1.2.5.dist-info/RECORD,,
61
+ chatterbot/storage/django_storage.py,sha256=Mo1shGkABulZA3LS-4ucJSK7X1I49UKgzUGuQHnG52s,6312
62
+ chatterbot/storage/mongodb.py,sha256=XVp7p3tOwjEZ0vCIFjjSuJY--Js090O5HymjU92qp8c,7941
63
+ chatterbot/storage/redis.py,sha256=45rH7lLUK8uTrQt01UGXScayQgEP-aWMV8i8934c_5w,12682
64
+ chatterbot/storage/sql_storage.py,sha256=EMOWr0Pm9_TA44WEhJ4f3gyGblFwbBII16Jeuc5xpVM,13070
65
+ chatterbot/storage/storage_adapter.py,sha256=yOZXxazJeVTRK74ao5PUC0pZPxw84ExuxjsTLT5LBj8,6117
66
+ chatterbot-1.2.7.dist-info/licenses/LICENSE,sha256=5b04U8mi0wp5gJMYlKi49EalnD9Q2nwY_6UEI_Avgu4,1476
67
+ chatterbot-1.2.7.dist-info/METADATA,sha256=i88UIutDqJz1xy4o5KiZKqKu2c9Nfl7bHlZfNKimH8c,7314
68
+ chatterbot-1.2.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
69
+ chatterbot-1.2.7.dist-info/top_level.txt,sha256=W2TzAbAJ-eBXTIKZZhVlkrh87msJNmBQpyhkrHqjSrE,11
70
+ chatterbot-1.2.7.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5