ChatterBot 1.2.7__tar.gz → 1.2.9__tar.gz

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.
Files changed (94) hide show
  1. {chatterbot-1.2.7 → chatterbot-1.2.9}/ChatterBot.egg-info/PKG-INFO +18 -7
  2. {chatterbot-1.2.7 → chatterbot-1.2.9}/ChatterBot.egg-info/requires.txt +6 -5
  3. {chatterbot-1.2.7 → chatterbot-1.2.9}/PKG-INFO +18 -7
  4. {chatterbot-1.2.7 → chatterbot-1.2.9}/README.md +10 -0
  5. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/__init__.py +1 -1
  6. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/chatterbot.py +6 -4
  7. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/comparisons.py +4 -1
  8. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/components.py +3 -0
  9. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/ext/django_chatterbot/abstract_models.py +46 -20
  10. chatterbot-1.2.9/chatterbot/ext/django_chatterbot/models.py +26 -0
  11. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/logic/best_match.py +0 -1
  12. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/logic/specific_response.py +13 -3
  13. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/logic/unit_conversion.py +1 -1
  14. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/parsing.py +25 -15
  15. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/response_selection.py +18 -7
  16. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/search.py +8 -0
  17. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/storage/django_storage.py +45 -16
  18. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/storage/mongodb.py +34 -10
  19. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/storage/redis.py +198 -33
  20. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/storage/sql_storage.py +43 -10
  21. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/storage/storage_adapter.py +8 -0
  22. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/tagging.py +44 -7
  23. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/trainers.py +118 -50
  24. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/vectorstores.py +1 -2
  25. {chatterbot-1.2.7 → chatterbot-1.2.9}/pyproject.toml +7 -6
  26. {chatterbot-1.2.7 → chatterbot-1.2.9}/tests/test_chatbot.py +5 -4
  27. {chatterbot-1.2.7 → chatterbot-1.2.9}/tests/test_parsing.py +197 -0
  28. chatterbot-1.2.7/chatterbot/ext/django_chatterbot/models.py +0 -16
  29. {chatterbot-1.2.7 → chatterbot-1.2.9}/ChatterBot.egg-info/SOURCES.txt +0 -0
  30. {chatterbot-1.2.7 → chatterbot-1.2.9}/ChatterBot.egg-info/dependency_links.txt +0 -0
  31. {chatterbot-1.2.7 → chatterbot-1.2.9}/ChatterBot.egg-info/top_level.txt +0 -0
  32. {chatterbot-1.2.7 → chatterbot-1.2.9}/LICENSE +0 -0
  33. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/__main__.py +0 -0
  34. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/adapters.py +0 -0
  35. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/constants.py +0 -0
  36. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/conversation.py +0 -0
  37. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/corpus.py +0 -0
  38. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/exceptions.py +0 -0
  39. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/ext/__init__.py +0 -0
  40. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/ext/django_chatterbot/__init__.py +0 -0
  41. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/ext/django_chatterbot/admin.py +0 -0
  42. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/ext/django_chatterbot/apps.py +0 -0
  43. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/ext/django_chatterbot/migrations/0001_initial.py +0 -0
  44. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/ext/django_chatterbot/migrations/0002_statement_extra_data.py +0 -0
  45. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/ext/django_chatterbot/migrations/0003_change_occurrence_default.py +0 -0
  46. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/ext/django_chatterbot/migrations/0004_rename_in_response_to.py +0 -0
  47. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/ext/django_chatterbot/migrations/0005_statement_created_at.py +0 -0
  48. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/ext/django_chatterbot/migrations/0006_create_conversation.py +0 -0
  49. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/ext/django_chatterbot/migrations/0007_response_created_at.py +0 -0
  50. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/ext/django_chatterbot/migrations/0008_update_conversations.py +0 -0
  51. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/ext/django_chatterbot/migrations/0009_tags.py +0 -0
  52. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/ext/django_chatterbot/migrations/0010_statement_text.py +0 -0
  53. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/ext/django_chatterbot/migrations/0011_blank_extra_data.py +0 -0
  54. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/ext/django_chatterbot/migrations/0012_statement_created_at.py +0 -0
  55. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/ext/django_chatterbot/migrations/0013_change_conversations.py +0 -0
  56. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/ext/django_chatterbot/migrations/0014_remove_statement_extra_data.py +0 -0
  57. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/ext/django_chatterbot/migrations/0015_statement_persona.py +0 -0
  58. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/ext/django_chatterbot/migrations/0016_statement_stemmed_text.py +0 -0
  59. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/ext/django_chatterbot/migrations/0017_tags_unique.py +0 -0
  60. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/ext/django_chatterbot/migrations/0018_text_max_length.py +0 -0
  61. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/ext/django_chatterbot/migrations/0019_alter_statement_id_alter_tag_id_and_more.py +0 -0
  62. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/ext/django_chatterbot/migrations/0020_alter_statement_conversation_and_more.py +0 -0
  63. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/ext/django_chatterbot/migrations/__init__.py +0 -0
  64. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/ext/django_chatterbot/model_admin.py +0 -0
  65. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/ext/django_chatterbot/settings.py +0 -0
  66. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/ext/sqlalchemy_app/__init__.py +0 -0
  67. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/ext/sqlalchemy_app/models.py +0 -0
  68. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/filters.py +0 -0
  69. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/languages.py +0 -0
  70. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/llm.py +0 -0
  71. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/logic/__init__.py +0 -0
  72. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/logic/logic_adapter.py +0 -0
  73. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/logic/mathematical_evaluation.py +0 -0
  74. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/logic/time_adapter.py +0 -0
  75. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/preprocessors.py +0 -0
  76. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/storage/__init__.py +0 -0
  77. {chatterbot-1.2.7 → chatterbot-1.2.9}/chatterbot/utils.py +0 -0
  78. {chatterbot-1.2.7 → chatterbot-1.2.9}/setup.cfg +0 -0
  79. {chatterbot-1.2.7 → chatterbot-1.2.9}/tests/test_adapter_validation.py +0 -0
  80. {chatterbot-1.2.7 → chatterbot-1.2.9}/tests/test_benchmarks.py +0 -0
  81. {chatterbot-1.2.7 → chatterbot-1.2.9}/tests/test_cli.py +0 -0
  82. {chatterbot-1.2.7 → chatterbot-1.2.9}/tests/test_comparisons.py +0 -0
  83. {chatterbot-1.2.7 → chatterbot-1.2.9}/tests/test_conversations.py +0 -0
  84. {chatterbot-1.2.7 → chatterbot-1.2.9}/tests/test_corpus.py +0 -0
  85. {chatterbot-1.2.7 → chatterbot-1.2.9}/tests/test_examples.py +0 -0
  86. {chatterbot-1.2.7 → chatterbot-1.2.9}/tests/test_filters.py +0 -0
  87. {chatterbot-1.2.7 → chatterbot-1.2.9}/tests/test_initialization.py +0 -0
  88. {chatterbot-1.2.7 → chatterbot-1.2.9}/tests/test_languages.py +0 -0
  89. {chatterbot-1.2.7 → chatterbot-1.2.9}/tests/test_preprocessors.py +0 -0
  90. {chatterbot-1.2.7 → chatterbot-1.2.9}/tests/test_response_selection.py +0 -0
  91. {chatterbot-1.2.7 → chatterbot-1.2.9}/tests/test_search.py +0 -0
  92. {chatterbot-1.2.7 → chatterbot-1.2.9}/tests/test_tagging.py +0 -0
  93. {chatterbot-1.2.7 → chatterbot-1.2.9}/tests/test_turing.py +0 -0
  94. {chatterbot-1.2.7 → chatterbot-1.2.9}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ChatterBot
3
- Version: 1.2.7
3
+ Version: 1.2.9
4
4
  Summary: ChatterBot is a machine learning, conversational dialog engine
5
5
  Author: Gunther Cox
6
6
  License-Expression: BSD-3-Clause
@@ -28,10 +28,10 @@ Classifier: Programming Language :: Python
28
28
  Classifier: Programming Language :: Python :: 3
29
29
  Classifier: Programming Language :: Python :: 3.9
30
30
  Classifier: Programming Language :: Python :: 3 :: Only
31
- Requires-Python: <3.13,>=3.9
31
+ Requires-Python: <3.14,>=3.9
32
32
  Description-Content-Type: text/markdown
33
33
  License-File: LICENSE
34
- Requires-Dist: mathparse<0.2,>=0.1
34
+ Requires-Dist: mathparse<0.3,>=0.2
35
35
  Requires-Dist: python-dateutil<2.10,>=2.9
36
36
  Requires-Dist: sqlalchemy<2.1,>=2.0
37
37
  Requires-Dist: spacy<3.9,>=3.8
@@ -42,20 +42,21 @@ Requires-Dist: coverage; extra == "test"
42
42
  Requires-Dist: sphinx<8.2,>=5.3; extra == "test"
43
43
  Requires-Dist: sphinx-sitemap>=2.6.0; extra == "test"
44
44
  Requires-Dist: huggingface_hub; extra == "test"
45
+ Requires-Dist: django<6.0,<=4.1; extra == "test"
45
46
  Provides-Extra: dev
46
47
  Requires-Dist: pint>=0.8.1; extra == "dev"
47
48
  Requires-Dist: pyyaml<7.0,>=6.0; extra == "dev"
48
49
  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: ollama<1.0,>=0.6.0; extra == "dev"
50
51
  Requires-Dist: openai; extra == "dev"
51
52
  Provides-Extra: redis
52
- Requires-Dist: redis[hiredis]<5.3; extra == "redis"
53
- Requires-Dist: langchain-redis<=0.2.0; extra == "redis"
53
+ Requires-Dist: redis[hiredis]<7.0; extra == "redis"
54
+ Requires-Dist: langchain-redis<=0.2.5; extra == "redis"
54
55
  Requires-Dist: langchain-huggingface<=0.1.2; extra == "redis"
55
56
  Requires-Dist: accelerate<=1.6.0; extra == "redis"
56
57
  Requires-Dist: sentence-transformers<=4.0.2; extra == "redis"
57
58
  Provides-Extra: mongodb
58
- Requires-Dist: pymongo<4.12,>=4.11; extra == "mongodb"
59
+ Requires-Dist: pymongo<4.16,>=4.11; extra == "mongodb"
59
60
  Dynamic: license-file
60
61
 
61
62
  ![ChatterBot: Machine learning in Python](https://i.imgur.com/b3SCmGT.png)
@@ -163,6 +164,16 @@ See release notes for changes https://github.com/gunthercox/ChatterBot/releases
163
164
  5. Use the projects [built-in automated testing](https://docs.chatterbot.us/testing/).
164
165
  to help make sure that your contribution is free from errors.
165
166
 
167
+ # Sponsors
168
+
169
+ ChatterBot is sponsored by:
170
+
171
+ <p style="font-size:21px; color:black;">Browser testing via
172
+ <a href="https://www.lambdatest.com/?utm_source=chatterbot&utm_medium=sponsor" target="_blank">
173
+ <img src="https://www.lambdatest.com/blue-logo.png" style="vertical-align: middle;" width="250" height="45" />
174
+ </a>
175
+ </p>
176
+
166
177
  # License
167
178
 
168
179
  ChatterBot is licensed under the [BSD 3-clause license](https://opensource.org/licenses/BSD-3-Clause).
@@ -1,4 +1,4 @@
1
- mathparse<0.2,>=0.1
1
+ mathparse<0.3,>=0.2
2
2
  python-dateutil<2.10,>=2.9
3
3
  sqlalchemy<2.1,>=2.0
4
4
  spacy<3.9,>=3.8
@@ -8,15 +8,15 @@ tqdm
8
8
  pint>=0.8.1
9
9
  pyyaml<7.0,>=6.0
10
10
  chatterbot-corpus<1.3.0,>=1.2.2
11
- ollama<1.0,>=0.4.7
11
+ ollama<1.0,>=0.6.0
12
12
  openai
13
13
 
14
14
  [mongodb]
15
- pymongo<4.12,>=4.11
15
+ pymongo<4.16,>=4.11
16
16
 
17
17
  [redis]
18
- redis[hiredis]<5.3
19
- langchain-redis<=0.2.0
18
+ redis[hiredis]<7.0
19
+ langchain-redis<=0.2.5
20
20
  langchain-huggingface<=0.1.2
21
21
  accelerate<=1.6.0
22
22
  sentence-transformers<=4.0.2
@@ -27,3 +27,4 @@ coverage
27
27
  sphinx<8.2,>=5.3
28
28
  sphinx-sitemap>=2.6.0
29
29
  huggingface_hub
30
+ django<6.0,<=4.1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ChatterBot
3
- Version: 1.2.7
3
+ Version: 1.2.9
4
4
  Summary: ChatterBot is a machine learning, conversational dialog engine
5
5
  Author: Gunther Cox
6
6
  License-Expression: BSD-3-Clause
@@ -28,10 +28,10 @@ Classifier: Programming Language :: Python
28
28
  Classifier: Programming Language :: Python :: 3
29
29
  Classifier: Programming Language :: Python :: 3.9
30
30
  Classifier: Programming Language :: Python :: 3 :: Only
31
- Requires-Python: <3.13,>=3.9
31
+ Requires-Python: <3.14,>=3.9
32
32
  Description-Content-Type: text/markdown
33
33
  License-File: LICENSE
34
- Requires-Dist: mathparse<0.2,>=0.1
34
+ Requires-Dist: mathparse<0.3,>=0.2
35
35
  Requires-Dist: python-dateutil<2.10,>=2.9
36
36
  Requires-Dist: sqlalchemy<2.1,>=2.0
37
37
  Requires-Dist: spacy<3.9,>=3.8
@@ -42,20 +42,21 @@ Requires-Dist: coverage; extra == "test"
42
42
  Requires-Dist: sphinx<8.2,>=5.3; extra == "test"
43
43
  Requires-Dist: sphinx-sitemap>=2.6.0; extra == "test"
44
44
  Requires-Dist: huggingface_hub; extra == "test"
45
+ Requires-Dist: django<6.0,<=4.1; extra == "test"
45
46
  Provides-Extra: dev
46
47
  Requires-Dist: pint>=0.8.1; extra == "dev"
47
48
  Requires-Dist: pyyaml<7.0,>=6.0; extra == "dev"
48
49
  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: ollama<1.0,>=0.6.0; extra == "dev"
50
51
  Requires-Dist: openai; extra == "dev"
51
52
  Provides-Extra: redis
52
- Requires-Dist: redis[hiredis]<5.3; extra == "redis"
53
- Requires-Dist: langchain-redis<=0.2.0; extra == "redis"
53
+ Requires-Dist: redis[hiredis]<7.0; extra == "redis"
54
+ Requires-Dist: langchain-redis<=0.2.5; extra == "redis"
54
55
  Requires-Dist: langchain-huggingface<=0.1.2; extra == "redis"
55
56
  Requires-Dist: accelerate<=1.6.0; extra == "redis"
56
57
  Requires-Dist: sentence-transformers<=4.0.2; extra == "redis"
57
58
  Provides-Extra: mongodb
58
- Requires-Dist: pymongo<4.12,>=4.11; extra == "mongodb"
59
+ Requires-Dist: pymongo<4.16,>=4.11; extra == "mongodb"
59
60
  Dynamic: license-file
60
61
 
61
62
  ![ChatterBot: Machine learning in Python](https://i.imgur.com/b3SCmGT.png)
@@ -163,6 +164,16 @@ See release notes for changes https://github.com/gunthercox/ChatterBot/releases
163
164
  5. Use the projects [built-in automated testing](https://docs.chatterbot.us/testing/).
164
165
  to help make sure that your contribution is free from errors.
165
166
 
167
+ # Sponsors
168
+
169
+ ChatterBot is sponsored by:
170
+
171
+ <p style="font-size:21px; color:black;">Browser testing via
172
+ <a href="https://www.lambdatest.com/?utm_source=chatterbot&utm_medium=sponsor" target="_blank">
173
+ <img src="https://www.lambdatest.com/blue-logo.png" style="vertical-align: middle;" width="250" height="45" />
174
+ </a>
175
+ </p>
176
+
166
177
  # License
167
178
 
168
179
  ChatterBot is licensed under the [BSD 3-clause license](https://opensource.org/licenses/BSD-3-Clause).
@@ -103,6 +103,16 @@ See release notes for changes https://github.com/gunthercox/ChatterBot/releases
103
103
  5. Use the projects [built-in automated testing](https://docs.chatterbot.us/testing/).
104
104
  to help make sure that your contribution is free from errors.
105
105
 
106
+ # Sponsors
107
+
108
+ ChatterBot is sponsored by:
109
+
110
+ <p style="font-size:21px; color:black;">Browser testing via
111
+ <a href="https://www.lambdatest.com/?utm_source=chatterbot&utm_medium=sponsor" target="_blank">
112
+ <img src="https://www.lambdatest.com/blue-logo.png" style="vertical-align: middle;" width="250" height="45" />
113
+ </a>
114
+ </p>
115
+
106
116
  # License
107
117
 
108
118
  ChatterBot is licensed under the [BSD 3-clause license](https://opensource.org/licenses/BSD-3-Clause).
@@ -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.7'
7
+ __version__ = '1.2.9'
8
8
 
9
9
  __all__ = (
10
10
  'ChatBot',
@@ -77,7 +77,12 @@ class ChatBot(object):
77
77
  try:
78
78
  Tagger = kwargs.get('tagger', PosLemmaTagger)
79
79
 
80
- self.tagger = Tagger(language=tagger_language)
80
+ # Allow instances to be provided for performance optimization
81
+ # (Example: a pre-loaded model in a tagger when unit testing)
82
+ if not isinstance(Tagger, type):
83
+ self.tagger = Tagger
84
+ else:
85
+ self.tagger = Tagger(language=tagger_language)
81
86
  except IOError as io_error:
82
87
  # Return a more helpful error message if possible
83
88
  if "Can't find model" in str(io_error):
@@ -224,7 +229,6 @@ class ChatBot(object):
224
229
  # Save the response generated for the input
225
230
  self.learn_response(response, previous_statement=input_statement)
226
231
 
227
-
228
232
  return response
229
233
 
230
234
  def generate_response(self, input_statement, additional_response_selection_parameters=None):
@@ -345,8 +349,6 @@ class ChatBot(object):
345
349
  Returns the latest response in a conversation if it exists.
346
350
  Returns None if a matching conversation cannot be found.
347
351
  """
348
- from chatterbot.conversation import Statement as StatementObject
349
-
350
352
  conversation_statements = list(self.storage.filter(
351
353
  conversation=conversation,
352
354
  order_by=['id']
@@ -85,7 +85,10 @@ class SpacySimilarity(Comparator):
85
85
  python -m spacy download en_core_web_sm
86
86
  python -m spacy download de_core_news_sm
87
87
 
88
- Alternatively, the ``spacy`` models can be installed as Python packages. The following lines could be included in a ``requirements.txt`` or ``pyproject.yml`` file if you needed to pin specific versions:
88
+ Alternatively, the ``spacy`` models can be installed as Python
89
+ packages. The following lines could be included in a
90
+ ``requirements.txt`` or ``pyproject.yml`` file if you needed to pin
91
+ specific versions:
89
92
 
90
93
  .. code-block:: text
91
94
 
@@ -29,6 +29,9 @@ def chatterbot_bigram_indexer(document):
29
29
  token for token in document if not (token.is_punct)
30
30
  ]
31
31
 
32
+ # Pairs consist of the part-of-speech of the first token and the
33
+ # lemma of the second token in the bigram. This provides a good
34
+ # balance of generalization and specificity for matching.
32
35
  bigram_pairs = [
33
36
  f"{tokens[i - 1].pos_}:{tokens[i].lemma_.lower()}"
34
37
  for i in range(1, len(tokens))
@@ -3,25 +3,15 @@ from chatterbot import constants
3
3
  from django.db import models
4
4
  from django.utils import timezone
5
5
  from django.conf import settings
6
+ from django.apps import apps
6
7
 
7
8
 
8
9
  DJANGO_APP_NAME = constants.DEFAULT_DJANGO_APP_NAME
9
- STATEMENT_MODEL = 'Statement'
10
- TAG_MODEL = 'Tag'
11
10
 
12
- if hasattr(settings, 'CHATTERBOT'):
13
- """
14
- Allow related models to be overridden in the project settings.
15
- Default to the original settings if one is not defined.
16
- """
17
- DJANGO_APP_NAME = settings.CHATTERBOT.get(
18
- 'django_app_name',
19
- DJANGO_APP_NAME
20
- )
21
- STATEMENT_MODEL = settings.CHATTERBOT.get(
22
- 'statement_model',
23
- STATEMENT_MODEL
24
- )
11
+ # Default model paths for swappable models
12
+ # These can be overridden via CHATTERBOT_STATEMENT_MODEL and CHATTERBOT_TAG_MODEL settings
13
+ DEFAULT_STATEMENT_MODEL = f'{DJANGO_APP_NAME}.Statement'
14
+ DEFAULT_TAG_MODEL = f'{DJANGO_APP_NAME}.Tag'
25
15
 
26
16
 
27
17
  class AbstractBaseTag(models.Model):
@@ -88,7 +78,9 @@ class AbstractBaseStatement(models.Model, StatementMixin):
88
78
  )
89
79
 
90
80
  tags = models.ManyToManyField(
91
- TAG_MODEL,
81
+ settings.CHATTERBOT_TAG_MODEL if hasattr(
82
+ settings, 'CHATTERBOT_TAG_MODEL'
83
+ ) else DEFAULT_TAG_MODEL,
92
84
  related_name='statements',
93
85
  help_text='The tags that are associated with this statement.'
94
86
  )
@@ -117,17 +109,51 @@ class AbstractBaseStatement(models.Model, StatementMixin):
117
109
  return self.text
118
110
  return '<empty>'
119
111
 
112
+ @classmethod
113
+ def get_tag_model(cls):
114
+ """
115
+ Return the Tag model class, respecting the swappable setting.
116
+
117
+ This method checks:
118
+ 1. Django settings (CHATTERBOT_TAG_MODEL) - project-wide configuration
119
+ 2. The model referenced by the 'tags' field - handles custom models via kwargs
120
+ 3. Falls back to DEFAULT_TAG_MODEL if introspection fails
121
+
122
+ This ensures the correct Tag model is used even when custom models
123
+ are specified via storage adapter kwargs rather than Django settings.
124
+ """
125
+ tag_model_path = getattr(settings, 'CHATTERBOT_TAG_MODEL', None)
126
+
127
+ if tag_model_path:
128
+ return apps.get_model(tag_model_path)
129
+
130
+ # If no setting, infer from the ManyToManyField relationship for
131
+ # cases where custom models are specified via kwargs
132
+ try:
133
+ # Get the model that this class's 'tags' field points to
134
+ tags_field = cls._meta.get_field('tags')
135
+ related_model = tags_field.related_model
136
+
137
+ # Resolve strings (lazy references)
138
+ if isinstance(related_model, str):
139
+ return apps.get_model(related_model)
140
+ return related_model
141
+ except Exception:
142
+ # Fallback to default if introspection fails
143
+ return apps.get_model(DEFAULT_TAG_MODEL)
144
+
120
145
  def get_tags(self) -> list[str]:
121
146
  """
122
147
  Return the list of tags for this statement.
123
- (Overrides the method from StatementMixin)
124
148
  """
125
149
  return list(self.tags.values_list('name', flat=True))
126
150
 
127
151
  def add_tags(self, *tags):
128
152
  """
129
153
  Add a list of strings to the statement as tags.
130
- (Overrides the method from StatementMixin)
131
154
  """
132
- for _tag in tags:
133
- self.tags.get_or_create(name=_tag)
155
+ TagModel = self.get_tag_model()
156
+
157
+ for tag_name in tags:
158
+ tag_obj, _created = TagModel.objects.get_or_create(name=tag_name)
159
+ self.tags.add(tag_obj)
@@ -0,0 +1,26 @@
1
+ from chatterbot.ext.django_chatterbot.abstract_models import AbstractBaseStatement, AbstractBaseTag
2
+
3
+
4
+ class Statement(AbstractBaseStatement):
5
+ """
6
+ A statement represents a single spoken entity, sentence or
7
+ phrase that someone can say.
8
+
9
+ This model can be swapped for a custom model by setting
10
+ CHATTERBOT_STATEMENT_MODEL in your Django settings.
11
+ """
12
+
13
+ class Meta:
14
+ swappable = 'CHATTERBOT_STATEMENT_MODEL'
15
+
16
+
17
+ class Tag(AbstractBaseTag):
18
+ """
19
+ A label that categorizes a statement.
20
+
21
+ This model can be swapped for a custom model by setting
22
+ CHATTERBOT_TAG_MODEL in your Django settings.
23
+ """
24
+
25
+ class Meta:
26
+ swappable = 'CHATTERBOT_TAG_MODEL'
@@ -78,7 +78,6 @@ class BestMatch(LogicAdapter):
78
78
  additional_response_selection_parameters
79
79
  )
80
80
 
81
-
82
81
  # Get all statements with text similar to the closest match
83
82
  response_list = list(self.chatbot.storage.filter(**response_selection_parameters))
84
83
 
@@ -20,7 +20,19 @@ class SpecificResponseAdapter(LogicAdapter):
20
20
  def __init__(self, chatbot, **kwargs):
21
21
  super().__init__(chatbot, **kwargs)
22
22
 
23
- self.input_text = kwargs.get('input_text')
23
+ try:
24
+ self.input_text = kwargs['input_text']
25
+ except KeyError:
26
+ raise chatbot.ChatBotException(
27
+ 'The SpecificResponseAdapter requires an input_text parameter.'
28
+ )
29
+
30
+ try:
31
+ self._output_text = kwargs['output_text']
32
+ except KeyError:
33
+ raise chatbot.ChatBotException(
34
+ 'The SpecificResponseAdapter requires an output_text parameter.'
35
+ )
24
36
 
25
37
  self.matcher = None
26
38
 
@@ -33,8 +45,6 @@ class SpecificResponseAdapter(LogicAdapter):
33
45
 
34
46
  self.matcher.add('SpecificResponse', [self.input_text])
35
47
 
36
- self._output_text = kwargs.get('output_text')
37
-
38
48
  def _initialize_nlp(self, language):
39
49
  model = get_model_for_language(language)
40
50
 
@@ -85,7 +85,7 @@ class UnitConversion(LogicAdapter):
85
85
  for unit in unit_variations:
86
86
  try:
87
87
  return getattr(self.unit_registry, unit)
88
- except Exception:
88
+ except AttributeError:
89
89
  continue
90
90
  return None
91
91
 
@@ -503,7 +503,7 @@ regex = [
503
503
  ]
504
504
 
505
505
 
506
- def convert_string_to_number(value):
506
+ def convert_string_to_number(value: str) -> int:
507
507
  """
508
508
  Convert strings to numbers
509
509
  """
@@ -517,7 +517,7 @@ def convert_string_to_number(value):
517
517
  return sum(num_list)
518
518
 
519
519
 
520
- def convert_time_to_hour_minute(hour, minute, convention):
520
+ def convert_time_to_hour_minute(hour: str, minute: str, convention: str) -> dict:
521
521
  """
522
522
  Convert time to hour, minute
523
523
  """
@@ -532,12 +532,19 @@ def convert_time_to_hour_minute(hour, minute, convention):
532
532
  minute = int(minute)
533
533
 
534
534
  if convention.lower() == 'pm':
535
- hour += 12
535
+ # Handle 12 PM (noon) - it stays as 12
536
+ # Handle 1-11 PM - add 12
537
+ if hour != 12:
538
+ hour += 12
539
+ else:
540
+ # Handle 12 AM (midnight) - convert to 0
541
+ if hour == 12:
542
+ hour = 0
536
543
 
537
544
  return {'hours': hour, 'minutes': minute}
538
545
 
539
546
 
540
- def date_from_quarter(base_date, ordinal, year):
547
+ def date_from_quarter(base_date: datetime, ordinal: int, year: int) -> list[datetime]:
541
548
  """
542
549
  Extract date from quarter of a year
543
550
  """
@@ -554,7 +561,7 @@ def date_from_quarter(base_date, ordinal, year):
554
561
  ]
555
562
 
556
563
 
557
- def date_from_relative_day(base_date, time, dow):
564
+ def date_from_relative_day(base_date: datetime, time: str, dow: str) -> datetime:
558
565
  """
559
566
  Converts relative day to time
560
567
  Ex: this tuesday, last tuesday
@@ -577,7 +584,7 @@ def date_from_relative_day(base_date, time, dow):
577
584
  return next_week_day(base_date, num)
578
585
 
579
586
 
580
- def date_from_relative_week_year(base_date, time, dow, ordinal=1):
587
+ def date_from_relative_week_year(base_date: datetime, time: str, dow: str, ordinal: int = 1) -> datetime:
581
588
  """
582
589
  Converts relative day to time
583
590
  Eg. this tuesday, last tuesday
@@ -608,7 +615,10 @@ def date_from_relative_week_year(base_date, time, dow, ordinal=1):
608
615
  day = min(relative_date.day, calendar.monthrange(year, month)[1])
609
616
  return datetime(year, month, day)
610
617
  else:
611
- return datetime(relative_date.year, relative_date.month + ord, relative_date.day)
618
+ # Base the day to valid range on the target month
619
+ target_month = relative_date.month + ord
620
+ day = min(relative_date.day, calendar.monthrange(relative_date.year, target_month)[1])
621
+ return datetime(relative_date.year, target_month, day)
612
622
  elif time == 'end of the':
613
623
  return datetime(
614
624
  relative_date.year,
@@ -636,7 +646,7 @@ def date_from_relative_week_year(base_date, time, dow, ordinal=1):
636
646
  return datetime(relative_date.year, relative_date.month, relative_date.day, 23, 59, 59)
637
647
 
638
648
 
639
- def date_from_adverb(base_date, name):
649
+ def date_from_adverb(base_date: datetime, name: str) -> datetime:
640
650
  """
641
651
  Convert Day adverbs to dates
642
652
  Tomorrow => Date
@@ -645,14 +655,14 @@ def date_from_adverb(base_date, name):
645
655
  # Reset date to start of the day
646
656
  adverb_date = datetime(base_date.year, base_date.month, base_date.day)
647
657
  if name == 'today' or name == 'tonite' or name == 'tonight':
648
- return adverb_date.today().replace(hour=0, minute=0, second=0, microsecond=0)
658
+ return adverb_date
649
659
  elif name == 'yesterday':
650
660
  return adverb_date - timedelta(days=1)
651
661
  elif name == 'tomorrow' or name == 'tom':
652
662
  return adverb_date + timedelta(days=1)
653
663
 
654
664
 
655
- def date_from_duration(base_date, number_as_string, unit, duration, base_time=None):
665
+ def date_from_duration(base_date: datetime, number_as_string: str, unit: str, duration: str, base_time: str = None) -> datetime:
656
666
  """
657
667
  Find dates from duration
658
668
  Eg: 20 days from now
@@ -682,7 +692,7 @@ def date_from_duration(base_date, number_as_string, unit, duration, base_time=No
682
692
  return base_date + timedelta(**args)
683
693
 
684
694
 
685
- def this_week_day(base_date, weekday):
695
+ def this_week_day(base_date: datetime, weekday: int) -> datetime:
686
696
  """
687
697
  Finds coming weekday
688
698
  """
@@ -698,7 +708,7 @@ def this_week_day(base_date, weekday):
698
708
  return day
699
709
 
700
710
 
701
- def previous_week_day(base_date, weekday):
711
+ def previous_week_day(base_date: datetime, weekday: int) -> datetime:
702
712
  """
703
713
  Finds previous weekday
704
714
  """
@@ -708,9 +718,9 @@ def previous_week_day(base_date, weekday):
708
718
  return day
709
719
 
710
720
 
711
- def next_week_day(base_date, weekday):
721
+ def next_week_day(base_date: datetime, weekday: int) -> datetime:
712
722
  """
713
- Finds next weekday
723
+ Finds the next weekday.
714
724
  """
715
725
  day_of_week = base_date.weekday()
716
726
  end_of_this_week = base_date + timedelta(days=6 - day_of_week)
@@ -720,7 +730,7 @@ def next_week_day(base_date, weekday):
720
730
  return day
721
731
 
722
732
 
723
- def datetime_parsing(text, base_date=datetime.now()):
733
+ def datetime_parsing(text: str, base_date: datetime = datetime.now()) -> list[tuple[str, datetime, tuple[int, int]]]:
724
734
  """
725
735
  Extract datetime objects from a string of text.
726
736
  """
@@ -18,17 +18,28 @@ def get_most_frequent_response(input_statement: Statement, response_list: list[S
18
18
 
19
19
  :return: The response statement with the greatest number of occurrences.
20
20
  """
21
- matching_response = None
22
- occurrence_count = -1
23
-
24
21
  logger = logging.getLogger(__name__)
25
22
  logger.info('Selecting response with greatest number of occurrences.')
26
23
 
24
+ # Collect all unique text values from response_list
25
+ response_texts = set(statement.text for statement in response_list)
26
+
27
+ # Fetch all statements matching the input in a single query
28
+ # Then count occurrences in memory
29
+ all_matching = list(storage.filter(in_response_to=input_statement.text))
30
+
31
+ # Count how many times each response text appears in the database
32
+ occurrence_counts = {}
33
+ for statement in all_matching:
34
+ if statement.text in response_texts:
35
+ occurrence_counts[statement.text] = occurrence_counts.get(statement.text, 0) + 1
36
+
37
+ # Find the response with the highest occurrence count
38
+ matching_response = None
39
+ occurrence_count = -1
40
+
27
41
  for statement in response_list:
28
- count = len(list(storage.filter(
29
- text=statement.text,
30
- in_response_to=input_statement.text)
31
- ))
42
+ count = occurrence_counts.get(statement.text, 0)
32
43
 
33
44
  # Keep the more common statement
34
45
  if count >= occurrence_count:
@@ -74,6 +74,10 @@ class IndexedTextSearch:
74
74
 
75
75
  yield statement
76
76
 
77
+ if confidence >= 1.0:
78
+ self.chatbot.logger.info('Exact match found, stopping search')
79
+ break
80
+
77
81
 
78
82
  class TextSearch:
79
83
  """
@@ -149,3 +153,7 @@ class TextSearch:
149
153
  ))
150
154
 
151
155
  yield statement
156
+
157
+ if confidence >= 1.0:
158
+ self.chatbot.logger.info('Exact match found, stopping search')
159
+ break