ChatterBot 1.2.11__tar.gz → 1.2.12__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 (96) hide show
  1. {chatterbot-1.2.11 → chatterbot-1.2.12}/ChatterBot.egg-info/PKG-INFO +10 -10
  2. {chatterbot-1.2.11 → chatterbot-1.2.12}/ChatterBot.egg-info/SOURCES.txt +1 -2
  3. {chatterbot-1.2.11 → chatterbot-1.2.12}/ChatterBot.egg-info/requires.txt +7 -7
  4. {chatterbot-1.2.11 → chatterbot-1.2.12}/PKG-INFO +10 -10
  5. {chatterbot-1.2.11 → chatterbot-1.2.12}/README.md +2 -2
  6. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/__init__.py +1 -1
  7. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/constants.py +4 -4
  8. chatterbot-1.2.12/chatterbot/ext/django_chatterbot/migrations/0021_increase_text_max_length_to_1100.py +55 -0
  9. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/storage/redis.py +31 -137
  10. {chatterbot-1.2.11 → chatterbot-1.2.12}/pyproject.toml +7 -7
  11. chatterbot-1.2.11/tests/test_connection_pool.py +0 -268
  12. chatterbot-1.2.11/tests/test_poc_vulnerability.py +0 -152
  13. {chatterbot-1.2.11 → chatterbot-1.2.12}/ChatterBot.egg-info/dependency_links.txt +0 -0
  14. {chatterbot-1.2.11 → chatterbot-1.2.12}/ChatterBot.egg-info/top_level.txt +0 -0
  15. {chatterbot-1.2.11 → chatterbot-1.2.12}/LICENSE +0 -0
  16. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/__main__.py +0 -0
  17. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/adapters.py +0 -0
  18. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/chatterbot.py +0 -0
  19. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/comparisons.py +0 -0
  20. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/components.py +0 -0
  21. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/conversation.py +0 -0
  22. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/corpus.py +0 -0
  23. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/exceptions.py +0 -0
  24. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/ext/__init__.py +0 -0
  25. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/__init__.py +0 -0
  26. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/abstract_models.py +0 -0
  27. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/admin.py +0 -0
  28. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/apps.py +0 -0
  29. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0001_initial.py +0 -0
  30. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0002_statement_extra_data.py +0 -0
  31. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0003_change_occurrence_default.py +0 -0
  32. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0004_rename_in_response_to.py +0 -0
  33. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0005_statement_created_at.py +0 -0
  34. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0006_create_conversation.py +0 -0
  35. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0007_response_created_at.py +0 -0
  36. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0008_update_conversations.py +0 -0
  37. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0009_tags.py +0 -0
  38. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0010_statement_text.py +0 -0
  39. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0011_blank_extra_data.py +0 -0
  40. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0012_statement_created_at.py +0 -0
  41. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0013_change_conversations.py +0 -0
  42. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0014_remove_statement_extra_data.py +0 -0
  43. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0015_statement_persona.py +0 -0
  44. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0016_statement_stemmed_text.py +0 -0
  45. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0017_tags_unique.py +0 -0
  46. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0018_text_max_length.py +0 -0
  47. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0019_alter_statement_id_alter_tag_id_and_more.py +0 -0
  48. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0020_alter_statement_conversation_and_more.py +0 -0
  49. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/__init__.py +0 -0
  50. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/model_admin.py +0 -0
  51. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/models.py +0 -0
  52. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/settings.py +0 -0
  53. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/ext/sqlalchemy_app/__init__.py +0 -0
  54. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/ext/sqlalchemy_app/models.py +0 -0
  55. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/filters.py +0 -0
  56. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/languages.py +0 -0
  57. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/llm.py +0 -0
  58. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/logic/__init__.py +0 -0
  59. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/logic/best_match.py +0 -0
  60. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/logic/logic_adapter.py +0 -0
  61. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/logic/mathematical_evaluation.py +0 -0
  62. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/logic/specific_response.py +0 -0
  63. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/logic/time_adapter.py +0 -0
  64. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/logic/unit_conversion.py +0 -0
  65. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/parsing.py +0 -0
  66. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/preprocessors.py +0 -0
  67. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/response_selection.py +0 -0
  68. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/search.py +0 -0
  69. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/storage/__init__.py +0 -0
  70. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/storage/django_storage.py +0 -0
  71. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/storage/mongodb.py +0 -0
  72. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/storage/sql_storage.py +0 -0
  73. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/storage/storage_adapter.py +0 -0
  74. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/tagging.py +0 -0
  75. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/trainers.py +0 -0
  76. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/utils.py +0 -0
  77. {chatterbot-1.2.11 → chatterbot-1.2.12}/chatterbot/vectorstores.py +0 -0
  78. {chatterbot-1.2.11 → chatterbot-1.2.12}/setup.cfg +0 -0
  79. {chatterbot-1.2.11 → chatterbot-1.2.12}/tests/test_adapter_validation.py +0 -0
  80. {chatterbot-1.2.11 → chatterbot-1.2.12}/tests/test_benchmarks.py +0 -0
  81. {chatterbot-1.2.11 → chatterbot-1.2.12}/tests/test_chatbot.py +0 -0
  82. {chatterbot-1.2.11 → chatterbot-1.2.12}/tests/test_cli.py +0 -0
  83. {chatterbot-1.2.11 → chatterbot-1.2.12}/tests/test_comparisons.py +0 -0
  84. {chatterbot-1.2.11 → chatterbot-1.2.12}/tests/test_conversations.py +0 -0
  85. {chatterbot-1.2.11 → chatterbot-1.2.12}/tests/test_corpus.py +0 -0
  86. {chatterbot-1.2.11 → chatterbot-1.2.12}/tests/test_examples.py +0 -0
  87. {chatterbot-1.2.11 → chatterbot-1.2.12}/tests/test_filters.py +0 -0
  88. {chatterbot-1.2.11 → chatterbot-1.2.12}/tests/test_initialization.py +0 -0
  89. {chatterbot-1.2.11 → chatterbot-1.2.12}/tests/test_languages.py +0 -0
  90. {chatterbot-1.2.11 → chatterbot-1.2.12}/tests/test_parsing.py +0 -0
  91. {chatterbot-1.2.11 → chatterbot-1.2.12}/tests/test_preprocessors.py +0 -0
  92. {chatterbot-1.2.11 → chatterbot-1.2.12}/tests/test_response_selection.py +0 -0
  93. {chatterbot-1.2.11 → chatterbot-1.2.12}/tests/test_search.py +0 -0
  94. {chatterbot-1.2.11 → chatterbot-1.2.12}/tests/test_tagging.py +0 -0
  95. {chatterbot-1.2.11 → chatterbot-1.2.12}/tests/test_turing.py +0 -0
  96. {chatterbot-1.2.11 → chatterbot-1.2.12}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ChatterBot
3
- Version: 1.2.11
3
+ Version: 1.2.12
4
4
  Summary: ChatterBot is a machine learning, conversational dialog engine
5
5
  Author: Gunther Cox
6
6
  License-Expression: BSD-3-Clause
@@ -39,7 +39,7 @@ 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: sphinx<8.2,>=5.3; extra == "test"
42
+ Requires-Dist: sphinx<9.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
45
  Requires-Dist: django<6.0,<=4.1; extra == "test"
@@ -50,13 +50,13 @@ Requires-Dist: chatterbot-corpus<1.3.0,>=1.2.2; extra == "dev"
50
50
  Requires-Dist: ollama<1.0,>=0.6.0; extra == "dev"
51
51
  Requires-Dist: openai; extra == "dev"
52
52
  Provides-Extra: redis
53
- Requires-Dist: redis[hiredis]<7.0; extra == "redis"
54
- Requires-Dist: langchain-redis<=0.2.5; extra == "redis"
55
- Requires-Dist: langchain-huggingface<=0.1.2; extra == "redis"
56
- Requires-Dist: accelerate<=1.6.0; extra == "redis"
57
- Requires-Dist: sentence-transformers<=4.0.2; extra == "redis"
53
+ Requires-Dist: redis[hiredis]<7.2,>=7.0; extra == "redis"
54
+ Requires-Dist: langchain-redis<0.3.0; extra == "redis"
55
+ Requires-Dist: langchain-huggingface<1.3.0,>=0.1.2; extra == "redis"
56
+ Requires-Dist: accelerate<1.13,>=1.6.0; extra == "redis"
57
+ Requires-Dist: sentence-transformers<5.3.0,>=4.0.2; extra == "redis"
58
58
  Provides-Extra: mongodb
59
- Requires-Dist: pymongo<4.16,>=4.11; extra == "mongodb"
59
+ Requires-Dist: pymongo<4.17,>=4.11; extra == "mongodb"
60
60
  Dynamic: license-file
61
61
 
62
62
  ![ChatterBot: Machine learning in Python](https://i.imgur.com/b3SCmGT.png)
@@ -162,9 +162,9 @@ https://docs.chatterbot.us/contributing/
162
162
 
163
163
  ChatterBot is sponsored by:
164
164
 
165
- <p style="font-size:21px; color:black;">Browser testing via
165
+ <p>
166
166
  <a href="https://www.lambdatest.com/?utm_source=chatterbot&utm_medium=sponsor" target="_blank">
167
- <img src="https://www.lambdatest.com/blue-logo.png" style="vertical-align: middle;" width="250" height="45" />
167
+ <img src="docs/_static/testmu-ai-white-logo.png" style="vertical-align: middle;" width="250" height="80" />
168
168
  </a>
169
169
  </p>
170
170
 
@@ -56,6 +56,7 @@ chatterbot/ext/django_chatterbot/migrations/0017_tags_unique.py
56
56
  chatterbot/ext/django_chatterbot/migrations/0018_text_max_length.py
57
57
  chatterbot/ext/django_chatterbot/migrations/0019_alter_statement_id_alter_tag_id_and_more.py
58
58
  chatterbot/ext/django_chatterbot/migrations/0020_alter_statement_conversation_and_more.py
59
+ chatterbot/ext/django_chatterbot/migrations/0021_increase_text_max_length_to_1100.py
59
60
  chatterbot/ext/django_chatterbot/migrations/__init__.py
60
61
  chatterbot/ext/sqlalchemy_app/__init__.py
61
62
  chatterbot/ext/sqlalchemy_app/models.py
@@ -77,7 +78,6 @@ tests/test_benchmarks.py
77
78
  tests/test_chatbot.py
78
79
  tests/test_cli.py
79
80
  tests/test_comparisons.py
80
- tests/test_connection_pool.py
81
81
  tests/test_conversations.py
82
82
  tests/test_corpus.py
83
83
  tests/test_examples.py
@@ -85,7 +85,6 @@ tests/test_filters.py
85
85
  tests/test_initialization.py
86
86
  tests/test_languages.py
87
87
  tests/test_parsing.py
88
- tests/test_poc_vulnerability.py
89
88
  tests/test_preprocessors.py
90
89
  tests/test_response_selection.py
91
90
  tests/test_search.py
@@ -12,19 +12,19 @@ ollama<1.0,>=0.6.0
12
12
  openai
13
13
 
14
14
  [mongodb]
15
- pymongo<4.16,>=4.11
15
+ pymongo<4.17,>=4.11
16
16
 
17
17
  [redis]
18
- redis[hiredis]<7.0
19
- langchain-redis<=0.2.5
20
- langchain-huggingface<=0.1.2
21
- accelerate<=1.6.0
22
- sentence-transformers<=4.0.2
18
+ redis[hiredis]<7.2,>=7.0
19
+ langchain-redis<0.3.0
20
+ langchain-huggingface<1.3.0,>=0.1.2
21
+ accelerate<1.13,>=1.6.0
22
+ sentence-transformers<5.3.0,>=4.0.2
23
23
 
24
24
  [test]
25
25
  flake8
26
26
  coverage
27
- sphinx<8.2,>=5.3
27
+ sphinx<9.2,>=5.3
28
28
  sphinx-sitemap>=2.6.0
29
29
  huggingface_hub
30
30
  django<6.0,<=4.1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ChatterBot
3
- Version: 1.2.11
3
+ Version: 1.2.12
4
4
  Summary: ChatterBot is a machine learning, conversational dialog engine
5
5
  Author: Gunther Cox
6
6
  License-Expression: BSD-3-Clause
@@ -39,7 +39,7 @@ 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: sphinx<8.2,>=5.3; extra == "test"
42
+ Requires-Dist: sphinx<9.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
45
  Requires-Dist: django<6.0,<=4.1; extra == "test"
@@ -50,13 +50,13 @@ Requires-Dist: chatterbot-corpus<1.3.0,>=1.2.2; extra == "dev"
50
50
  Requires-Dist: ollama<1.0,>=0.6.0; extra == "dev"
51
51
  Requires-Dist: openai; extra == "dev"
52
52
  Provides-Extra: redis
53
- Requires-Dist: redis[hiredis]<7.0; extra == "redis"
54
- Requires-Dist: langchain-redis<=0.2.5; extra == "redis"
55
- Requires-Dist: langchain-huggingface<=0.1.2; extra == "redis"
56
- Requires-Dist: accelerate<=1.6.0; extra == "redis"
57
- Requires-Dist: sentence-transformers<=4.0.2; extra == "redis"
53
+ Requires-Dist: redis[hiredis]<7.2,>=7.0; extra == "redis"
54
+ Requires-Dist: langchain-redis<0.3.0; extra == "redis"
55
+ Requires-Dist: langchain-huggingface<1.3.0,>=0.1.2; extra == "redis"
56
+ Requires-Dist: accelerate<1.13,>=1.6.0; extra == "redis"
57
+ Requires-Dist: sentence-transformers<5.3.0,>=4.0.2; extra == "redis"
58
58
  Provides-Extra: mongodb
59
- Requires-Dist: pymongo<4.16,>=4.11; extra == "mongodb"
59
+ Requires-Dist: pymongo<4.17,>=4.11; extra == "mongodb"
60
60
  Dynamic: license-file
61
61
 
62
62
  ![ChatterBot: Machine learning in Python](https://i.imgur.com/b3SCmGT.png)
@@ -162,9 +162,9 @@ https://docs.chatterbot.us/contributing/
162
162
 
163
163
  ChatterBot is sponsored by:
164
164
 
165
- <p style="font-size:21px; color:black;">Browser testing via
165
+ <p>
166
166
  <a href="https://www.lambdatest.com/?utm_source=chatterbot&utm_medium=sponsor" target="_blank">
167
- <img src="https://www.lambdatest.com/blue-logo.png" style="vertical-align: middle;" width="250" height="45" />
167
+ <img src="docs/_static/testmu-ai-white-logo.png" style="vertical-align: middle;" width="250" height="80" />
168
168
  </a>
169
169
  </p>
170
170
 
@@ -101,9 +101,9 @@ https://docs.chatterbot.us/contributing/
101
101
 
102
102
  ChatterBot is sponsored by:
103
103
 
104
- <p style="font-size:21px; color:black;">Browser testing via
104
+ <p>
105
105
  <a href="https://www.lambdatest.com/?utm_source=chatterbot&utm_medium=sponsor" target="_blank">
106
- <img src="https://www.lambdatest.com/blue-logo.png" style="vertical-align: middle;" width="250" height="45" />
106
+ <img src="docs/_static/testmu-ai-white-logo.png" style="vertical-align: middle;" width="250" height="80" />
107
107
  </a>
108
108
  </p>
109
109
 
@@ -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.11'
7
+ __version__ = '1.2.12'
8
8
 
9
9
  __all__ = (
10
10
  'ChatBot',
@@ -5,11 +5,11 @@ from chatterbot import languages
5
5
 
6
6
  '''
7
7
  The maximum length of characters that the text of a statement can contain.
8
- The number 255 is used because that is the maximum length of a char field
9
- in most databases. This value should be enforced on a per-model basis by
10
- the data model for each storage adapter.
8
+ The number 1100 is used to support longer conversational statements while
9
+ remaining within VARCHAR limits for most databases. This value should be
10
+ enforced on a per-model basis by the data model for each storage adapter.
11
11
  '''
12
- STATEMENT_TEXT_MAX_LENGTH = 255
12
+ STATEMENT_TEXT_MAX_LENGTH = 1100
13
13
 
14
14
  '''
15
15
  The maximum length of characters that the text label of a conversation can contain.
@@ -0,0 +1,55 @@
1
+ """
2
+ Django migration to increase text field max_length from 255 to 1100.
3
+
4
+ This migration alters all text-related fields in the Statement model:
5
+ - text
6
+ - search_text
7
+ - in_response_to
8
+ - search_in_response_to
9
+
10
+ This change supports longer conversational statements while remaining
11
+ within VARCHAR limits for most databases.
12
+ """
13
+ from django.db import migrations, models
14
+
15
+
16
+ class Migration(migrations.Migration):
17
+
18
+ dependencies = [
19
+ ('django_chatterbot', '0020_alter_statement_conversation_and_more'),
20
+ ]
21
+
22
+ operations = [
23
+ migrations.AlterField(
24
+ model_name='statement',
25
+ name='text',
26
+ field=models.CharField(max_length=1100, help_text='The text of the statement.'),
27
+ ),
28
+ migrations.AlterField(
29
+ model_name='statement',
30
+ name='search_text',
31
+ field=models.CharField(
32
+ blank=True,
33
+ max_length=1100,
34
+ help_text='A modified version of the statement text optimized for searching.'
35
+ ),
36
+ ),
37
+ migrations.AlterField(
38
+ model_name='statement',
39
+ name='in_response_to',
40
+ field=models.CharField(
41
+ max_length=1100,
42
+ null=True,
43
+ help_text='The text of the statement that this statement is in response to.'
44
+ ),
45
+ ),
46
+ migrations.AlterField(
47
+ model_name='statement',
48
+ name='search_in_response_to',
49
+ field=models.CharField(
50
+ blank=True,
51
+ max_length=1100,
52
+ help_text='A modified version of the in_response_to text optimized for searching.'
53
+ ),
54
+ ),
55
+ ]
@@ -65,6 +65,7 @@ class RedisVectorStorageAdapter(StorageAdapter):
65
65
  index_name='chatterbot',
66
66
  redis_url=self.database_uri,
67
67
  content_field='in_response_to',
68
+ legacy_key_format=False,
68
69
  metadata_schema=[
69
70
  {
70
71
  'name': 'conversation',
@@ -212,12 +213,16 @@ class RedisVectorStorageAdapter(StorageAdapter):
212
213
  - search_in_response_to_contains
213
214
  - order_by
214
215
  """
215
- from redisvl.query import VectorQuery
216
216
  from redisvl.query.filter import Tag, Text
217
217
 
218
218
  # https://redis.io/docs/latest/develop/interact/search-and-query/advanced-concepts/query_syntax/
219
219
  filter_condition = None
220
220
 
221
+ ordering = kwargs.get('order_by', None)
222
+
223
+ if ordering:
224
+ ordering = ','.join(ordering)
225
+
221
226
  if 'in_response_to' in kwargs:
222
227
  filter_condition = Text('in_response_to') == kwargs['in_response_to']
223
228
 
@@ -255,7 +260,7 @@ class RedisVectorStorageAdapter(StorageAdapter):
255
260
  _query = '|'.join([
256
261
  f'%%{text}%%' for text in kwargs['exclude_text_words']
257
262
  ])
258
- query = Text('text') % f'-({ _query })'
263
+ query = Text('text') % f'-({_query})'
259
264
  if filter_condition:
260
265
  filter_condition &= query
261
266
  else:
@@ -290,67 +295,20 @@ class RedisVectorStorageAdapter(StorageAdapter):
290
295
  # similar responses.
291
296
  _search_query = kwargs['search_text_contains']
292
297
 
293
- # Use vector similarity to find statements responding to similar contexts
294
- embedding = self.vector_store.embeddings.embed_query(_search_query)
295
-
296
- return_fields = [
297
- 'text', 'in_response_to', 'conversation', 'persona', 'tags', 'created_at'
298
- ]
299
-
300
- query = VectorQuery(
301
- vector=embedding,
302
- vector_field_name='embedding',
303
- return_fields=return_fields,
304
- num_results=page_size,
305
- filter_expression=filter_condition
298
+ documents = self.vector_store.similarity_search(
299
+ _search_query,
300
+ k=page_size, # The number of results to return
301
+ return_all=True, # Include the full document with IDs
302
+ filter=filter_condition,
303
+ sort_by=ordering
306
304
  )
307
305
 
308
- results = self.vector_store.index.query(query)
309
-
310
- Document = self.get_statement_model()
311
- documents = []
312
-
313
- # Calculate confidence from vector distances
306
+ # Add confidence scores based on similarity ordering
314
307
  # Results are ordered by similarity (best match first)
315
- for idx, result in enumerate(results):
316
- in_response_to = result.get('in_response_to', '')
317
-
318
- # Redis vector_score is cosine distance (lower is better)
319
- # Convert to confidence: confidence = 1 - distance
320
- # If vector_score not available, use result order
321
- vector_score = result.get('vector_score')
322
- if vector_score is not None:
323
- # Cosine distance ranges from 0 (identical) to 2 (opposite)
324
- # Normalize to confidence: 1.0 (identical) to 0.0 (opposite)
325
- confidence = max(0.0, 1.0 - (float(vector_score) / 2.0))
326
- else:
327
- # Fallback: use result order (first result = highest confidence)
328
- # Start at 0.95 for first result, decay by 0.05 per position
329
- confidence = max(0.0, 0.95 - (idx * 0.05))
330
-
331
- # Parse timestamp
332
- created_at_value = result.get('created_at', 0)
333
- if isinstance(created_at_value, str):
334
- created_at = datetime.fromtimestamp(float(created_at_value))
335
- elif created_at_value:
336
- created_at = datetime.fromtimestamp(float(created_at_value))
337
- else:
338
- created_at = datetime.now()
339
-
340
- metadata = {
341
- 'text': result.get('text', ''),
342
- 'conversation': result.get('conversation', ''),
343
- 'persona': result.get('persona', ''),
344
- 'tags': result.get('tags', ''),
345
- 'created_at': created_at,
346
- 'confidence': confidence,
347
- }
348
- doc = Document(
349
- page_content=in_response_to,
350
- metadata=metadata,
351
- id=result['id']
352
- )
353
- documents.append(doc)
308
+ for idx, doc in enumerate(documents):
309
+ # Start at 0.95 for first result, decay by 0.05 per position
310
+ confidence = max(0.0, 0.95 - (idx * 0.05))
311
+ doc.metadata['confidence'] = confidence
354
312
 
355
313
  return [self.model_to_object(document) for document in documents]
356
314
 
@@ -379,74 +337,20 @@ class RedisVectorStorageAdapter(StorageAdapter):
379
337
  if 'search_in_response_to_contains' in kwargs:
380
338
  _search_text = kwargs.get('search_in_response_to_contains', '')
381
339
 
382
- # Get embedding for the search text
383
- embedding = self.vector_store.embeddings.embed_query(_search_text)
384
-
385
- # Build return fields from metadata schema
386
- return_fields = [
387
- 'text', 'in_response_to', 'conversation', 'persona', 'tags', 'created_at'
388
- ]
389
-
390
- # Use direct index query via RedisVL
391
- # langchain's similarity_search has issues with filters in v0.2.4
392
- # and may not work properly with existing indexes
393
- # TODO: Look into similarity_search_with_score implementation
394
- query = VectorQuery(
395
- vector=embedding,
396
- vector_field_name='embedding',
397
- return_fields=return_fields,
398
- num_results=page_size,
399
- filter_expression=filter_condition
340
+ documents = self.vector_store.similarity_search(
341
+ _search_text,
342
+ k=page_size, # The number of results to return
343
+ return_all=True, # Include the full document with IDs
344
+ filter=filter_condition,
345
+ sort_by=ordering
400
346
  )
401
347
 
402
- # Execute query
403
- results = self.vector_store.index.query(query)
404
-
405
- # Convert results to Document objects
406
- Document = self.get_statement_model()
407
- documents = []
408
-
409
- # Calculate confidence from vector distances
348
+ # Add confidence scores based on similarity ordering
410
349
  # Results are ordered by similarity (best match first)
411
- for idx, result in enumerate(results):
412
- # Extract metadata and content
413
- in_response_to = result.get('in_response_to', '')
414
-
415
- # Redis vector_score is cosine distance (lower is better)
416
- # Convert to confidence: confidence = 1 - distance
417
- # If vector_score not available, use result order
418
- vector_score = result.get('vector_score')
419
- if vector_score is not None:
420
- # Cosine distance ranges from 0 (identical) to 2 (opposite)
421
- # Normalize to confidence: 1.0 (identical) to 0.0 (opposite)
422
- confidence = max(0.0, 1.0 - (float(vector_score) / 2.0))
423
- else:
424
- # Fallback: use result order (first result = highest confidence)
425
- # Start at 0.95 for first result, decay by 0.05 per position
426
- confidence = max(0.0, 0.95 - (idx * 0.05))
427
-
428
- # Convert Unix timestamp back to datetime
429
- # Redis returns numeric fields as strings
430
- created_at_timestamp = result.get('created_at', '0')
431
- if created_at_timestamp and created_at_timestamp != '0':
432
- created_at = datetime.fromtimestamp(float(created_at_timestamp))
433
- else:
434
- created_at = datetime.now()
435
-
436
- metadata = {
437
- 'text': result.get('text', ''),
438
- 'conversation': result.get('conversation', ''),
439
- 'persona': result.get('persona', ''),
440
- 'tags': result.get('tags', ''),
441
- 'created_at': created_at,
442
- 'confidence': confidence,
443
- }
444
- doc = Document(
445
- page_content=in_response_to,
446
- metadata=metadata,
447
- id=result['id']
448
- )
449
- documents.append(doc)
350
+ for idx, doc in enumerate(documents):
351
+ # Start at 0.95 for first result, decay by 0.05 per position
352
+ confidence = max(0.0, 0.95 - (idx * 0.05))
353
+ doc.metadata['confidence'] = confidence
450
354
  else:
451
355
  documents = self.vector_store.query_search(
452
356
  k=page_size,
@@ -551,11 +455,8 @@ class RedisVectorStorageAdapter(StorageAdapter):
551
455
  client = self.vector_store.index.client
552
456
  client.delete(statement.id)
553
457
 
554
- # NOTE: langchain-redis has an inconsistency - it uses :: for auto-generated
555
- # IDs but : (single colon) when keys are explicitly provided
556
- if '::' in statement.id:
557
- key = statement.id.split('::', 1)[1]
558
- elif ':' in statement.id:
458
+ # Extract the key from the full ID (format: prefix:key)
459
+ if ':' in statement.id:
559
460
  key = statement.id.split(':', 1)[1]
560
461
  else:
561
462
  # If no delimiter found, use the entire ID as the key
@@ -564,13 +465,6 @@ class RedisVectorStorageAdapter(StorageAdapter):
564
465
  ids = self.vector_store.add_texts(
565
466
  [document.page_content], [metadata], keys=[key]
566
467
  )
567
-
568
- # Normalize the ID to use :: delimiter (if langchain-redis returned single colon)
569
- if ids and ':' in ids[0] and '::' not in ids[0]:
570
- # Replace first occurrence of single colon with double colon
571
- normalized_id = ids[0].replace(':', '::', 1)
572
- # Update the key in Redis to use the correct format
573
- client.rename(ids[0], normalized_id)
574
468
  else:
575
469
  self.vector_store.add_documents([document])
576
470
 
@@ -71,7 +71,7 @@ dependencies = [
71
71
  test = [
72
72
  "flake8",
73
73
  "coverage",
74
- "sphinx>=5.3,<8.2",
74
+ "sphinx>=5.3,<9.2",
75
75
  "sphinx-sitemap>=2.6.0",
76
76
  "huggingface_hub",
77
77
  "django<=4.1,<6.0"
@@ -84,12 +84,12 @@ dev = [
84
84
  "openai"
85
85
  ]
86
86
  redis = [
87
- "redis[hiredis]<7.0",
88
- "langchain-redis<=0.2.5",
89
- "langchain-huggingface<=0.1.2",
90
- "accelerate<=1.6.0",
91
- "sentence-transformers<=4.0.2",
87
+ "redis[hiredis]>=7.0,<7.2",
88
+ "langchain-redis<0.3.0",
89
+ "langchain-huggingface>=0.1.2,<1.3.0",
90
+ "accelerate>=1.6.0,<1.13",
91
+ "sentence-transformers>=4.0.2,<5.3.0",
92
92
  ]
93
93
  mongodb = [
94
- "pymongo>=4.11,<4.16",
94
+ "pymongo>=4.11,<4.17",
95
95
  ]
@@ -1,268 +0,0 @@
1
- """
2
- Tests for database connection pool management and concurrency.
3
-
4
- These tests verify that the fixes for the connection pool exhaustion
5
- vulnerability (CVE-TBD) are working correctly.
6
- """
7
- import threading
8
- import time
9
- import unittest
10
- from chatterbot import ChatBot
11
- from chatterbot.trainers import ListTrainer
12
-
13
-
14
- class ConnectionPoolTestCase(unittest.TestCase):
15
- """
16
- Test cases for database connection pool management.
17
- """
18
-
19
- def setUp(self):
20
- """
21
- Set up test fixtures before each test.
22
- """
23
- # Use in-memory SQLite for fast testing
24
- # Note: SQLite doesn't use QueuePool, so pool params are ignored
25
- self.chatbot = ChatBot(
26
- 'TestBot',
27
- database_uri='sqlite://',
28
- )
29
-
30
- # Train with some basic responses
31
- trainer = ListTrainer(self.chatbot)
32
- trainer.train([
33
- 'Hi',
34
- 'Hello!',
35
- 'How are you?',
36
- 'I am doing well.',
37
- 'What is your name?',
38
- 'My name is TestBot.',
39
- ])
40
-
41
- def tearDown(self):
42
- """
43
- Clean up after each test.
44
- """
45
- self.chatbot.storage.drop()
46
- self.chatbot.storage.close()
47
-
48
- def test_concurrent_requests_no_exhaustion(self):
49
- """
50
- Test that concurrent requests don't exhaust the connection pool.
51
-
52
- This was the original vulnerability - concurrent get_response() calls
53
- would leak sessions and exhaust the pool.
54
- """
55
- num_threads = 30 # More than pool_size + max_overflow
56
- responses = []
57
- errors = []
58
-
59
- def make_request():
60
- try:
61
- response = self.chatbot.get_response('Hi')
62
- responses.append(str(response))
63
- except Exception as e:
64
- errors.append(e)
65
-
66
- threads = []
67
- for _ in range(num_threads):
68
- t = threading.Thread(target=make_request)
69
- threads.append(t)
70
- t.start()
71
-
72
- # Wait for all threads to complete
73
- for t in threads:
74
- t.join(timeout=10)
75
-
76
- # Verify no errors occurred
77
- self.assertEqual(len(errors), 0,
78
- f"Connection pool exhaustion occurred: {errors}")
79
-
80
- # Verify all threads got responses
81
- self.assertEqual(len(responses), num_threads,
82
- "Not all threads received responses")
83
-
84
- def test_rapid_sequential_requests(self):
85
- """
86
- Test that rapid sequential requests properly release connections.
87
- """
88
- num_requests = 50 # More than pool size
89
-
90
- for i in range(num_requests):
91
- response = self.chatbot.get_response(f'Request {i}')
92
- self.assertIsNotNone(response)
93
-
94
- def test_partial_filter_consumption(self):
95
- """
96
- Test that partially consuming filter() results doesn't leak sessions.
97
-
98
- This was a key part of the vulnerability - the filter() generator
99
- would not close the session if iteration stopped early.
100
- """
101
- # Create many statements
102
- trainer = ListTrainer(self.chatbot)
103
- for i in range(100):
104
- trainer.train([f'Question {i}', f'Answer {i}'])
105
-
106
- # Partially consume filter results many times
107
- for _ in range(50):
108
- results = self.chatbot.storage.filter()
109
- # Only consume first result
110
- first = next(results, None)
111
- self.assertIsNotNone(first)
112
- # Don't consume the rest - this should still clean up the session
113
-
114
- # If sessions weren't cleaned up, this would fail
115
- response = self.chatbot.get_response('Hi')
116
- self.assertIsNotNone(response)
117
-
118
- def test_concurrent_training(self):
119
- """
120
- Test that concurrent training operations don't leak connections.
121
- """
122
- errors = []
123
-
124
- def train_batch(batch_id):
125
- try:
126
- trainer = ListTrainer(self.chatbot)
127
- trainer.train([
128
- f'Training question {batch_id}',
129
- f'Training answer {batch_id}',
130
- ])
131
- except Exception as e:
132
- errors.append(e)
133
-
134
- threads = []
135
- for i in range(20):
136
- t = threading.Thread(target=train_batch, args=(i,))
137
- threads.append(t)
138
- t.start()
139
-
140
- for t in threads:
141
- t.join(timeout=10)
142
-
143
- self.assertEqual(len(errors), 0,
144
- f"Errors during concurrent training: {errors}")
145
-
146
- def test_session_cleanup_on_exception(self):
147
- """
148
- Test that sessions are cleaned up even when exceptions occur.
149
- """
150
- # Force an error during a database operation
151
- try:
152
- # Create a statement with invalid data
153
- self.chatbot.storage.create(
154
- text='', # Empty text might cause issues
155
- in_response_to=None
156
- )
157
- except Exception:
158
- pass # Expected to fail
159
-
160
- # Verify the pool is still usable
161
- response = self.chatbot.get_response('Hi')
162
- self.assertIsNotNone(response)
163
-
164
- def test_scoped_session_thread_safety(self):
165
- """
166
- Test that scoped_session provides proper thread isolation.
167
- """
168
- results = {}
169
-
170
- def check_session_isolation(thread_id):
171
- # Each thread should get its own session
172
- session1 = self.chatbot.storage.Session()
173
- time.sleep(0.01) # Small delay to encourage thread interleaving
174
- session2 = self.chatbot.storage.Session()
175
-
176
- # In the same thread, scoped_session should return the same session
177
- results[thread_id] = (id(session1) == id(session2))
178
-
179
- session1.close()
180
- # After close, scoped_session should return the same instance
181
- # (it doesn't create a new one, just reuses the thread-local one)
182
-
183
- threads = []
184
- for i in range(5):
185
- t = threading.Thread(target=check_session_isolation, args=(i,))
186
- threads.append(t)
187
- t.start()
188
-
189
- for t in threads:
190
- t.join()
191
-
192
- # All threads should have gotten consistent session behavior
193
- for thread_id, same_session in results.items():
194
- self.assertTrue(same_session,
195
- f"Thread {thread_id} got different sessions")
196
-
197
-
198
- class ConnectionPoolConfigTestCase(unittest.TestCase):
199
- """
200
- Test cases for connection pool configuration options.
201
- Note: These tests are skipped for SQLite since it uses SingletonThreadPool.
202
- """
203
-
204
- def test_pool_config_not_applied_to_sqlite(self):
205
- """
206
- Test that pool config is not applied to SQLite (uses SingletonThreadPool).
207
- """
208
- chatbot = ChatBot(
209
- 'SQLiteBot',
210
- database_uri='sqlite://',
211
- pool_size=3, # Should be ignored
212
- max_overflow=2, # Should be ignored
213
- )
214
-
215
- # SQLite uses SingletonThreadPool, not QueuePool
216
- from sqlalchemy.pool import SingletonThreadPool
217
- self.assertIsInstance(chatbot.storage.engine.pool, SingletonThreadPool)
218
-
219
- chatbot.storage.close()
220
-
221
- @unittest.skip("Requires PostgreSQL/MySQL database for testing")
222
- def test_custom_pool_size_postgres(self):
223
- """
224
- Test that custom pool_size is respected for PostgreSQL.
225
- """
226
- # This test would require a PostgreSQL connection
227
- # chatbot = ChatBot(
228
- # 'ConfigBot',
229
- # database_uri='postgresql://user:pass@localhost/test',
230
- # pool_size=3,
231
- # max_overflow=2,
232
- # )
233
- # self.assertEqual(chatbot.storage.engine.pool.size(), 3)
234
- # chatbot.storage.close()
235
- pass
236
-
237
- @unittest.skip("Requires PostgreSQL/MySQL database for testing")
238
- def test_default_pool_config_postgres(self):
239
- """
240
- Test that default pool configuration is applied for PostgreSQL.
241
- """
242
- # This test would require a PostgreSQL connection
243
- # chatbot = ChatBot(
244
- # 'DefaultBot',
245
- # database_uri='postgresql://user:pass@localhost/test',
246
- # )
247
- # pool = chatbot.storage.engine.pool
248
- # self.assertEqual(pool.size(), 10) # Default pool_size
249
- # chatbot.storage.close()
250
- pass
251
-
252
- @unittest.skip("Requires PostgreSQL/MySQL database for testing")
253
- def test_pool_pre_ping_enabled_postgres(self):
254
- """
255
- Test that pool_pre_ping is enabled by default for PostgreSQL.
256
- """
257
- # This test would require a PostgreSQL connection
258
- # chatbot = ChatBot(
259
- # 'PingBot',
260
- # database_uri='postgresql://user:pass@localhost/test',
261
- # )
262
- # self.assertTrue(chatbot.storage.engine.pool._pre_ping)
263
- # chatbot.storage.close()
264
- pass
265
-
266
-
267
- if __name__ == '__main__':
268
- unittest.main()
@@ -1,152 +0,0 @@
1
- """
2
- Proof-of-Concept test for the connection pool exhaustion vulnerability (CVE-TBD).
3
-
4
- This test demonstrates that the original vulnerability has been fixed.
5
- The PoC from the security report would cause timeout errors before the fix.
6
- """
7
- import threading
8
- import tempfile
9
- import os
10
- import unittest
11
- from chatterbot import ChatBot
12
- from chatterbot.trainers import ListTrainer
13
-
14
-
15
- class PoC_VulnerabilityTestCase(unittest.TestCase):
16
- """
17
- Test case demonstrating the fix for the connection pool exhaustion vulnerability.
18
-
19
- This uses a file-based SQLite database which doesn't have the same
20
- thread-restrictions as in-memory databases.
21
- """
22
-
23
- def setUp(self):
24
- """
25
- Set up test fixtures.
26
- """
27
- # Create a temporary database file
28
- self.db_fd, self.db_path = tempfile.mkstemp(suffix='.sqlite3')
29
-
30
- self.chatbot = ChatBot(
31
- 'TestBot',
32
- database_uri=f'sqlite:///{self.db_path}',
33
- )
34
-
35
- # Train with basic data
36
- trainer = ListTrainer(self.chatbot)
37
- trainer.train(['hello', 'hi there'])
38
-
39
- def tearDown(self):
40
- """
41
- Clean up after test.
42
- """
43
- self.chatbot.storage.close()
44
- os.close(self.db_fd)
45
- os.unlink(self.db_path)
46
-
47
- def test_original_poc_no_longer_causes_timeout(self):
48
- """
49
- Test that the original PoC from the security report no longer causes errors.
50
-
51
- Before the fix: This would cause SQLAlchemy TimeoutError due to pool exhaustion
52
- After the fix: All requests complete successfully
53
- """
54
- def attack():
55
- try:
56
- response = self.chatbot.get_response("hello")
57
- results.append(('success', str(response)))
58
- except Exception as e:
59
- results.append(('error', str(e)))
60
-
61
- results = []
62
- threads = []
63
-
64
- # Original PoC used 30 threads
65
- for _ in range(30):
66
- t = threading.Thread(target=attack)
67
- t.start()
68
- threads.append(t)
69
-
70
- for t in threads:
71
- t.join(timeout=15) # Should complete well before timeout
72
-
73
- # Count successes and errors
74
- successes = [r for r in results if r[0] == 'success']
75
- errors = [r for r in results if r[0] == 'error']
76
-
77
- # Before fix: Would have many TimeoutError exceptions
78
- # After fix: All should succeed
79
- self.assertEqual(len(errors), 0,
80
- f"Got {len(errors)} errors (expected 0). Errors: {[e[1] for e in errors][:5]}")
81
- self.assertEqual(len(successes), 30,
82
- f"Got {len(successes)} successes (expected 30)")
83
-
84
- def test_high_concurrency_sustained(self):
85
- """
86
- Test sustained high concurrency doesn't cause issues.
87
- """
88
- request_count = 0
89
- lock = threading.Lock()
90
-
91
- def make_many_requests():
92
- nonlocal request_count
93
- for _ in range(10):
94
- try:
95
- self.chatbot.get_response("hello")
96
- with lock:
97
- request_count += 1
98
- except Exception:
99
- pass
100
-
101
- threads = []
102
- for _ in range(10): # 10 threads × 10 requests = 100 total
103
- t = threading.Thread(target=make_many_requests)
104
- t.start()
105
- threads.append(t)
106
-
107
- for t in threads:
108
- t.join(timeout=30)
109
-
110
- # Should have completed all 100 requests
111
- self.assertGreater(request_count, 90, # Allow for some timing issues
112
- f"Only completed {request_count}/100 requests")
113
-
114
-
115
- class SequentialPerformanceTestCase(unittest.TestCase):
116
- """
117
- Test that the fixes don't negatively impact single-threaded performance.
118
- """
119
-
120
- def setUp(self):
121
- """
122
- Set up test fixtures.
123
- """
124
- self.db_fd, self.db_path = tempfile.mkstemp(suffix='.sqlite3')
125
-
126
- self.chatbot = ChatBot(
127
- 'PerfBot',
128
- database_uri=f'sqlite:///{self.db_path}',
129
- )
130
-
131
- trainer = ListTrainer(self.chatbot)
132
- trainer.train(['hello', 'hi', 'how are you', 'good'])
133
-
134
- def tearDown(self):
135
- """
136
- Clean up.
137
- """
138
- self.chatbot.storage.close()
139
- os.close(self.db_fd)
140
- os.unlink(self.db_path)
141
-
142
- def test_sequential_requests_still_work(self):
143
- """
144
- Test that normal sequential usage still works correctly.
145
- """
146
- for i in range(50):
147
- response = self.chatbot.get_response(f"message {i}")
148
- self.assertIsNotNone(response)
149
-
150
-
151
- if __name__ == '__main__':
152
- unittest.main()
File without changes
File without changes