ChatterBot 1.2.10__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 (94) hide show
  1. {chatterbot-1.2.10 → chatterbot-1.2.12}/ChatterBot.egg-info/PKG-INFO +10 -10
  2. {chatterbot-1.2.10 → chatterbot-1.2.12}/ChatterBot.egg-info/SOURCES.txt +1 -0
  3. {chatterbot-1.2.10 → chatterbot-1.2.12}/ChatterBot.egg-info/requires.txt +7 -7
  4. {chatterbot-1.2.10 → chatterbot-1.2.12}/PKG-INFO +10 -10
  5. {chatterbot-1.2.10 → chatterbot-1.2.12}/README.md +2 -2
  6. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/__init__.py +1 -1
  7. {chatterbot-1.2.10 → 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.10 → chatterbot-1.2.12}/chatterbot/storage/redis.py +31 -137
  10. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/storage/sql_storage.py +153 -112
  11. {chatterbot-1.2.10 → chatterbot-1.2.12}/pyproject.toml +7 -7
  12. {chatterbot-1.2.10 → chatterbot-1.2.12}/ChatterBot.egg-info/dependency_links.txt +0 -0
  13. {chatterbot-1.2.10 → chatterbot-1.2.12}/ChatterBot.egg-info/top_level.txt +0 -0
  14. {chatterbot-1.2.10 → chatterbot-1.2.12}/LICENSE +0 -0
  15. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/__main__.py +0 -0
  16. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/adapters.py +0 -0
  17. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/chatterbot.py +0 -0
  18. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/comparisons.py +0 -0
  19. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/components.py +0 -0
  20. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/conversation.py +0 -0
  21. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/corpus.py +0 -0
  22. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/exceptions.py +0 -0
  23. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/ext/__init__.py +0 -0
  24. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/__init__.py +0 -0
  25. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/abstract_models.py +0 -0
  26. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/admin.py +0 -0
  27. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/apps.py +0 -0
  28. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0001_initial.py +0 -0
  29. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0002_statement_extra_data.py +0 -0
  30. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0003_change_occurrence_default.py +0 -0
  31. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0004_rename_in_response_to.py +0 -0
  32. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0005_statement_created_at.py +0 -0
  33. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0006_create_conversation.py +0 -0
  34. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0007_response_created_at.py +0 -0
  35. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0008_update_conversations.py +0 -0
  36. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0009_tags.py +0 -0
  37. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0010_statement_text.py +0 -0
  38. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0011_blank_extra_data.py +0 -0
  39. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0012_statement_created_at.py +0 -0
  40. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0013_change_conversations.py +0 -0
  41. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0014_remove_statement_extra_data.py +0 -0
  42. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0015_statement_persona.py +0 -0
  43. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0016_statement_stemmed_text.py +0 -0
  44. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0017_tags_unique.py +0 -0
  45. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0018_text_max_length.py +0 -0
  46. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0019_alter_statement_id_alter_tag_id_and_more.py +0 -0
  47. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/0020_alter_statement_conversation_and_more.py +0 -0
  48. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/migrations/__init__.py +0 -0
  49. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/model_admin.py +0 -0
  50. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/models.py +0 -0
  51. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/ext/django_chatterbot/settings.py +0 -0
  52. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/ext/sqlalchemy_app/__init__.py +0 -0
  53. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/ext/sqlalchemy_app/models.py +0 -0
  54. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/filters.py +0 -0
  55. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/languages.py +0 -0
  56. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/llm.py +0 -0
  57. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/logic/__init__.py +0 -0
  58. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/logic/best_match.py +0 -0
  59. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/logic/logic_adapter.py +0 -0
  60. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/logic/mathematical_evaluation.py +0 -0
  61. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/logic/specific_response.py +0 -0
  62. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/logic/time_adapter.py +0 -0
  63. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/logic/unit_conversion.py +0 -0
  64. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/parsing.py +0 -0
  65. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/preprocessors.py +0 -0
  66. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/response_selection.py +0 -0
  67. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/search.py +0 -0
  68. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/storage/__init__.py +0 -0
  69. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/storage/django_storage.py +0 -0
  70. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/storage/mongodb.py +0 -0
  71. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/storage/storage_adapter.py +0 -0
  72. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/tagging.py +0 -0
  73. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/trainers.py +0 -0
  74. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/utils.py +0 -0
  75. {chatterbot-1.2.10 → chatterbot-1.2.12}/chatterbot/vectorstores.py +0 -0
  76. {chatterbot-1.2.10 → chatterbot-1.2.12}/setup.cfg +0 -0
  77. {chatterbot-1.2.10 → chatterbot-1.2.12}/tests/test_adapter_validation.py +0 -0
  78. {chatterbot-1.2.10 → chatterbot-1.2.12}/tests/test_benchmarks.py +0 -0
  79. {chatterbot-1.2.10 → chatterbot-1.2.12}/tests/test_chatbot.py +0 -0
  80. {chatterbot-1.2.10 → chatterbot-1.2.12}/tests/test_cli.py +0 -0
  81. {chatterbot-1.2.10 → chatterbot-1.2.12}/tests/test_comparisons.py +0 -0
  82. {chatterbot-1.2.10 → chatterbot-1.2.12}/tests/test_conversations.py +0 -0
  83. {chatterbot-1.2.10 → chatterbot-1.2.12}/tests/test_corpus.py +0 -0
  84. {chatterbot-1.2.10 → chatterbot-1.2.12}/tests/test_examples.py +0 -0
  85. {chatterbot-1.2.10 → chatterbot-1.2.12}/tests/test_filters.py +0 -0
  86. {chatterbot-1.2.10 → chatterbot-1.2.12}/tests/test_initialization.py +0 -0
  87. {chatterbot-1.2.10 → chatterbot-1.2.12}/tests/test_languages.py +0 -0
  88. {chatterbot-1.2.10 → chatterbot-1.2.12}/tests/test_parsing.py +0 -0
  89. {chatterbot-1.2.10 → chatterbot-1.2.12}/tests/test_preprocessors.py +0 -0
  90. {chatterbot-1.2.10 → chatterbot-1.2.12}/tests/test_response_selection.py +0 -0
  91. {chatterbot-1.2.10 → chatterbot-1.2.12}/tests/test_search.py +0 -0
  92. {chatterbot-1.2.10 → chatterbot-1.2.12}/tests/test_tagging.py +0 -0
  93. {chatterbot-1.2.10 → chatterbot-1.2.12}/tests/test_turing.py +0 -0
  94. {chatterbot-1.2.10 → 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.10
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
@@ -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.10
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.10'
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
 
@@ -23,7 +23,7 @@ class SQLStorageAdapter(StorageAdapter):
23
23
  from sqlalchemy import create_engine, inspect, event
24
24
  from sqlalchemy import Index
25
25
  from sqlalchemy.engine import Engine
26
- from sqlalchemy.orm import sessionmaker
26
+ from sqlalchemy.orm import sessionmaker, scoped_session
27
27
 
28
28
  self.database_uri = kwargs.get('database_uri', False)
29
29
 
@@ -35,7 +35,10 @@ class SQLStorageAdapter(StorageAdapter):
35
35
  if not self.database_uri:
36
36
  self.database_uri = 'sqlite:///db.sqlite3'
37
37
 
38
- self.engine = create_engine(self.database_uri)
38
+ # Configure connection pool with safe defaults to prevent exhaustion
39
+ # Note: SQLite uses SingletonThreadPool which doesn't support these params
40
+ # PostgreSQL, MySQL, etc. use QueuePool which does support them
41
+ pool_config = {}
39
42
 
40
43
  if self.database_uri.startswith('sqlite://'):
41
44
 
@@ -66,6 +69,23 @@ class SQLStorageAdapter(StorageAdapter):
66
69
  cursor.execute('PRAGMA synchronous=NORMAL')
67
70
  cursor.close()
68
71
 
72
+ else:
73
+ # Only apply pool configuration for databases that support QueuePool
74
+ # pool_size: Maximum persistent connections (10)
75
+ # max_overflow: Additional connections during peak load (20)
76
+ # pool_timeout: Seconds to wait for connection before error (30)
77
+ # pool_recycle: Recycle connections after 1 hour to prevent stale connections
78
+ # pool_pre_ping: Test connections before using to detect disconnects
79
+ pool_config = {
80
+ 'pool_size': kwargs.get('pool_size', 10),
81
+ 'max_overflow': kwargs.get('max_overflow', 20),
82
+ 'pool_timeout': kwargs.get('pool_timeout', 30),
83
+ 'pool_recycle': kwargs.get('pool_recycle', 3600),
84
+ 'pool_pre_ping': kwargs.get('pool_pre_ping', True),
85
+ }
86
+
87
+ self.engine = create_engine(self.database_uri, **pool_config)
88
+
69
89
  if not inspect(self.engine).has_table('statement'):
70
90
  self.create_database()
71
91
 
@@ -91,7 +111,10 @@ class SQLStorageAdapter(StorageAdapter):
91
111
 
92
112
  search_in_response_to_index.create(bind=self.engine)
93
113
 
94
- self.Session = sessionmaker(bind=self.engine, expire_on_commit=True)
114
+ # Use a scoped session for thread-safe session management
115
+ # This provides thread-local session storage to prevent session sharing across threads
116
+ session_factory = sessionmaker(bind=self.engine, expire_on_commit=True)
117
+ self.Session = scoped_session(session_factory)
95
118
 
96
119
  def get_statement_model(self):
97
120
  """
@@ -119,9 +142,11 @@ class SQLStorageAdapter(StorageAdapter):
119
142
  Statement = self.get_model('statement')
120
143
 
121
144
  session = self.Session()
122
- statement_count = session.query(Statement).count()
123
- session.close()
124
- return statement_count
145
+ try:
146
+ statement_count = session.query(Statement).count()
147
+ return statement_count
148
+ finally:
149
+ session.close()
125
150
 
126
151
  def remove(self, statement_text):
127
152
  """
@@ -131,13 +156,14 @@ class SQLStorageAdapter(StorageAdapter):
131
156
  """
132
157
  Statement = self.get_model('statement')
133
158
  session = self.Session()
159
+ try:
160
+ query = session.query(Statement).filter_by(text=statement_text)
161
+ record = query.first()
134
162
 
135
- query = session.query(Statement).filter_by(text=statement_text)
136
- record = query.first()
137
-
138
- session.delete(record)
139
- session.commit()
140
- session.close()
163
+ session.delete(record)
164
+ session.commit()
165
+ finally:
166
+ session.close()
141
167
 
142
168
  def filter(self, **kwargs):
143
169
  """
@@ -152,8 +178,6 @@ class SQLStorageAdapter(StorageAdapter):
152
178
  Statement = self.get_model('statement')
153
179
  Tag = self.get_model('tag')
154
180
 
155
- session = self.Session()
156
-
157
181
  page_size = kwargs.pop('page_size', 1000)
158
182
  order_by = kwargs.pop('order_by', None)
159
183
  tags = kwargs.pop('tags', [])
@@ -167,65 +191,69 @@ class SQLStorageAdapter(StorageAdapter):
167
191
  if isinstance(tags, str):
168
192
  tags = [tags]
169
193
 
170
- if len(kwargs) == 0:
171
- statements = session.query(Statement).filter()
172
- else:
173
- statements = session.query(Statement).filter_by(**kwargs)
174
-
175
- if tags:
176
- statements = statements.join(Statement.tags).filter(
177
- Tag.name.in_(tags)
178
- )
179
-
180
- if exclude_text:
181
- statements = statements.filter(
182
- ~Statement.text.in_(exclude_text)
183
- )
194
+ # Use context manager to ensure session cleanup even if generator is partially consumed
195
+ session = self.Session()
196
+ try:
197
+ if len(kwargs) == 0:
198
+ statements = session.query(Statement).filter()
199
+ else:
200
+ statements = session.query(Statement).filter_by(**kwargs)
201
+
202
+ if tags:
203
+ statements = statements.join(Statement.tags).filter(
204
+ Tag.name.in_(tags)
205
+ )
184
206
 
185
- if exclude_text_words:
186
- or_word_query = [
187
- Statement.text.ilike('%' + word + '%') for word in exclude_text_words
188
- ]
189
- statements = statements.filter(
190
- ~or_(*or_word_query)
191
- )
207
+ if exclude_text:
208
+ statements = statements.filter(
209
+ ~Statement.text.in_(exclude_text)
210
+ )
192
211
 
193
- if persona_not_startswith:
194
- statements = statements.filter(
195
- ~Statement.persona.startswith('bot:')
196
- )
212
+ if exclude_text_words:
213
+ or_word_query = [
214
+ Statement.text.ilike('%' + word + '%') for word in exclude_text_words
215
+ ]
216
+ statements = statements.filter(
217
+ ~or_(*or_word_query)
218
+ )
197
219
 
198
- if search_text_contains:
199
- or_query = [
200
- Statement.search_text.contains(word) for word in search_text_contains.split(' ')
201
- ]
202
- statements = statements.filter(
203
- or_(*or_query)
204
- )
220
+ if persona_not_startswith:
221
+ statements = statements.filter(
222
+ ~Statement.persona.startswith('bot:')
223
+ )
205
224
 
206
- if search_in_response_to_contains:
207
- or_query = [
208
- Statement.search_in_response_to.contains(word) for word in search_in_response_to_contains.split(' ')
209
- ]
210
- statements = statements.filter(
211
- or_(*or_query)
212
- )
225
+ if search_text_contains:
226
+ or_query = [
227
+ Statement.search_text.contains(word) for word in search_text_contains.split(' ')
228
+ ]
229
+ statements = statements.filter(
230
+ or_(*or_query)
231
+ )
213
232
 
214
- if order_by:
233
+ if search_in_response_to_contains:
234
+ or_query = [
235
+ Statement.search_in_response_to.contains(word) for word in search_in_response_to_contains.split(' ')
236
+ ]
237
+ statements = statements.filter(
238
+ or_(*or_query)
239
+ )
215
240
 
216
- if 'created_at' in order_by:
217
- index = order_by.index('created_at')
218
- order_by[index] = Statement.created_at.asc()
241
+ if order_by:
219
242
 
220
- statements = statements.order_by(*order_by)
243
+ if 'created_at' in order_by:
244
+ index = order_by.index('created_at')
245
+ order_by[index] = Statement.created_at.asc()
221
246
 
222
- total_statements = statements.count()
247
+ statements = statements.order_by(*order_by)
223
248
 
224
- for start_index in range(0, total_statements, page_size):
225
- for statement in statements.slice(start_index, start_index + page_size):
226
- yield self.model_to_object(statement)
249
+ total_statements = statements.count()
227
250
 
228
- session.close()
251
+ for start_index in range(0, total_statements, page_size):
252
+ for statement in statements.slice(start_index, start_index + page_size):
253
+ yield self.model_to_object(statement)
254
+ finally:
255
+ # Always close session, even if generator is abandoned or exception occurs
256
+ session.close()
229
257
 
230
258
  def create(
231
259
  self,
@@ -336,8 +364,11 @@ class SQLStorageAdapter(StorageAdapter):
336
364
  statement_model_object.tags.append(tag)
337
365
  create_statements.append(statement_model_object)
338
366
 
339
- session.add_all(create_statements)
340
- session.commit()
367
+ try:
368
+ session.add_all(create_statements)
369
+ session.commit()
370
+ finally:
371
+ session.close()
341
372
 
342
373
  def update(self, statement):
343
374
  """
@@ -348,49 +379,51 @@ class SQLStorageAdapter(StorageAdapter):
348
379
  Tag = self.get_model('tag')
349
380
 
350
381
  session = self.Session()
351
- record = None
382
+ try:
383
+ record = None
352
384
 
353
- if hasattr(statement, 'id') and statement.id is not None:
354
- record = session.get(Statement, statement.id)
355
- else:
356
- record = session.query(Statement).filter(
357
- Statement.text == statement.text,
358
- Statement.conversation == statement.conversation,
359
- ).first()
360
-
361
- # Create a new statement entry if one does not already exist
362
- if not record:
363
- record = Statement(
364
- text=statement.text,
365
- conversation=statement.conversation,
366
- persona=statement.persona
367
- )
385
+ if hasattr(statement, 'id') and statement.id is not None:
386
+ record = session.get(Statement, statement.id)
387
+ else:
388
+ record = session.query(Statement).filter(
389
+ Statement.text == statement.text,
390
+ Statement.conversation == statement.conversation,
391
+ ).first()
368
392
 
369
- # Update the response value
370
- record.in_response_to = statement.in_response_to
393
+ # Create a new statement entry if one does not already exist
394
+ if not record:
395
+ record = Statement(
396
+ text=statement.text,
397
+ conversation=statement.conversation,
398
+ persona=statement.persona
399
+ )
371
400
 
372
- record.created_at = statement.created_at
401
+ # Update the response value
402
+ record.in_response_to = statement.in_response_to
373
403
 
374
- if not statement.search_text:
375
- if self.raise_on_missing_search_text:
376
- raise Exception('update issued without search_text value')
404
+ record.created_at = statement.created_at
377
405
 
378
- if statement.in_response_to and not statement.search_in_response_to:
379
- if self.raise_on_missing_search_text:
380
- raise Exception('update issued without search_in_response_to value')
406
+ if not statement.search_text:
407
+ if self.raise_on_missing_search_text:
408
+ raise Exception('update issued without search_text value')
381
409
 
382
- for tag_name in statement.get_tags():
383
- tag = session.query(Tag).filter_by(name=tag_name).first()
410
+ if statement.in_response_to and not statement.search_in_response_to:
411
+ if self.raise_on_missing_search_text:
412
+ raise Exception('update issued without search_in_response_to value')
384
413
 
385
- if not tag:
386
- # Create the record
387
- tag = Tag(name=tag_name)
414
+ for tag_name in statement.get_tags():
415
+ tag = session.query(Tag).filter_by(name=tag_name).first()
388
416
 
389
- record.tags.append(tag)
417
+ if not tag:
418
+ # Create the record
419
+ tag = Tag(name=tag_name)
390
420
 
391
- session.add(record)
392
- session.commit()
393
- session.close()
421
+ record.tags.append(tag)
422
+
423
+ session.add(record)
424
+ session.commit()
425
+ finally:
426
+ session.close()
394
427
 
395
428
  def get_random(self):
396
429
  """
@@ -399,17 +432,19 @@ class SQLStorageAdapter(StorageAdapter):
399
432
  Statement = self.get_model('statement')
400
433
 
401
434
  session = self.Session()
402
- count = self.count()
403
- if count < 1:
404
- raise self.EmptyDatabaseException()
435
+ try:
436
+ count = self.count()
437
+ if count < 1:
438
+ raise self.EmptyDatabaseException()
405
439
 
406
- random_index = random.randrange(0, count)
407
- random_statement = session.query(Statement)[random_index]
440
+ random_index = random.randrange(0, count)
441
+ random_statement = session.query(Statement)[random_index]
408
442
 
409
- statement = self.model_to_object(random_statement)
443
+ statement = self.model_to_object(random_statement)
410
444
 
411
- session.close()
412
- return statement
445
+ return statement
446
+ finally:
447
+ session.close()
413
448
 
414
449
  def drop(self):
415
450
  """
@@ -419,12 +454,13 @@ class SQLStorageAdapter(StorageAdapter):
419
454
  Tag = self.get_model('tag')
420
455
 
421
456
  session = self.Session()
457
+ try:
458
+ session.query(Statement).delete()
459
+ session.query(Tag).delete()
422
460
 
423
- session.query(Statement).delete()
424
- session.query(Tag).delete()
425
-
426
- session.commit()
427
- session.close()
461
+ session.commit()
462
+ finally:
463
+ session.close()
428
464
 
429
465
  def create_database(self):
430
466
  """
@@ -438,5 +474,10 @@ class SQLStorageAdapter(StorageAdapter):
438
474
  Close the database connection and dispose of the engine.
439
475
  This ensures proper cleanup of resources.
440
476
  """
477
+ # Remove thread-local sessions from scoped_session registry
478
+ if hasattr(self, 'Session'):
479
+ self.Session.remove()
480
+
481
+ # Dispose of the connection pool
441
482
  if hasattr(self, 'engine'):
442
483
  self.engine.dispose()
@@ -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
  ]
File without changes
File without changes