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

|
63
70
|
|
@@ -70,7 +77,6 @@ to be trained to speak any language.
|
|
70
77
|
|
71
78
|
[](https://pypi.python.org/pypi/chatterbot/)
|
72
79
|
[](https://www.python.org/downloads/release/python-360/)
|
73
|
-
[](https://docs.djangoproject.com/en/2.1/releases/2.0/)
|
74
80
|
[](https://coveralls.io/r/gunthercox/ChatterBot)
|
75
81
|
[](https://codeclimate.com/github/gunthercox/ChatterBot)
|
76
82
|
[](https://gitter.im/chatterbot/Lobby?utm_source=badge&utm_medium=badge&utm_content=badge)
|
@@ -1,4 +1,4 @@
|
|
1
|
-
chatterbot/__init__.py,sha256=
|
1
|
+
chatterbot/__init__.py,sha256=PhC2oXazQN3HNYXvWb33IAqEzwyt5QtqcfESq8eg3sg,158
|
2
2
|
chatterbot/__main__.py,sha256=nk19D56TlPT9Zdqkq4qZZrOnLKEc4YTwUVWmXYwSyHg,207
|
3
3
|
chatterbot/adapters.py,sha256=LJ_KqLpHKPdYAFpMGK63RVH4weV5X0Zh5uGyan6qdVU,878
|
4
4
|
chatterbot/chatterbot.py,sha256=YLKLkQ-XI4Unr3rbzjpGIupOqenuevm21tAnx-yFFgQ,10400
|
@@ -13,10 +13,11 @@ chatterbot/languages.py,sha256=XSenfc5FxHk_JWG5gGHsZvjvrPBbCaVCm_OU-BeER_M,32784
|
|
13
13
|
chatterbot/parsing.py,sha256=vS-w70cMkjq4YEpDOv_pXWhAI6Zj06WYDAcMDhYDj0M,23174
|
14
14
|
chatterbot/preprocessors.py,sha256=aI4v987dZc7GOKhO43i0i73EX748hehYSpzikFHpEXs,1271
|
15
15
|
chatterbot/response_selection.py,sha256=aYeZ54jpGIcQnI-1-TDcua_f1p3PiM5_iMg4hF5ZaIU,2951
|
16
|
-
chatterbot/search.py,sha256=
|
16
|
+
chatterbot/search.py,sha256=S4MFL1JzPqT-pv7tCgd-MIqf0T9Ia_KOLoNgzdoCP4Y,7035
|
17
17
|
chatterbot/tagging.py,sha256=GLY9wg_rvn6pSYVML-HcxkIo_3BZ3SAyj-q1oNZY8pI,2584
|
18
18
|
chatterbot/trainers.py,sha256=U1yh0_V7FFL51MeQe1P1Q59weceDbkHh_2kDiDYpSEc,13315
|
19
19
|
chatterbot/utils.py,sha256=ckQXvsjp2FO9GcWxziY67JovN7mShnE4RlzdYarQY5k,3277
|
20
|
+
chatterbot/vectorstores.py,sha256=-S1NB8PrZzoFIu95n2W7N4UaXuCUpyDUXIGYFebjv08,2056
|
20
21
|
chatterbot/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
21
22
|
chatterbot/ext/django_chatterbot/__init__.py,sha256=iWzmBzpAsYwkwi1faxAPFY9L1bbL97RgVXK2uqULIMc,92
|
22
23
|
chatterbot/ext/django_chatterbot/abstract_models.py,sha256=PkuBGS0uv2toL3jGE1U6HJLCLXoKwpRNohm9JbVT_y4,3303
|
@@ -51,16 +52,17 @@ chatterbot/logic/__init__.py,sha256=28-5swBCPfSVMl8xB5C8frOKZ2oj28rQfenbd9E4r-4,
|
|
51
52
|
chatterbot/logic/best_match.py,sha256=8TNW0uZ_Uq-XPfaZUMUZDVH6KzDT65j59xblxQBv-dQ,4820
|
52
53
|
chatterbot/logic/logic_adapter.py,sha256=5kNEirh5fiF5hhSMFXD7bIkKwXHmrSsSS4qDm-6xry0,4694
|
53
54
|
chatterbot/logic/mathematical_evaluation.py,sha256=GPDKUwNFajERof2R-MkPGi2jJRP-rKAGm_f0V9JHDHE,2282
|
54
|
-
chatterbot/logic/specific_response.py,sha256=
|
55
|
+
chatterbot/logic/specific_response.py,sha256=o17YIeu9DzucO8MXMP3kwNIBb1b8br60bbAhSE7AZWc,2386
|
55
56
|
chatterbot/logic/time_adapter.py,sha256=mxdoQGeC5IjREH4PU5iHYOIPEvnYnzgysocR8xMYWXc,2406
|
56
|
-
chatterbot/logic/unit_conversion.py,sha256
|
57
|
-
chatterbot/storage/__init__.py,sha256=
|
57
|
+
chatterbot/logic/unit_conversion.py,sha256=-ENMLqZqtZx0riUi0guda2oJECST0M7pZG4cSIv3ieM,5898
|
58
|
+
chatterbot/storage/__init__.py,sha256=ADw0WQe0YKr1UIDQLaxwf0mHDnuKW_CSzgz11K4TM-4,465
|
58
59
|
chatterbot/storage/django_storage.py,sha256=S5S4GipD7FyNJy4RWu5-S8sLPuSJIObwTtqTpnJu-ok,6159
|
59
60
|
chatterbot/storage/mongodb.py,sha256=Ozvdvcjb3LGZxcvbSQGzwP9VloYQbmsa2FaKunFpMyU,7934
|
61
|
+
chatterbot/storage/redis.py,sha256=FKROrzZ-7WXZ8ZoK0dKmTDdS45TxL04XOSeu0p3Jrak,12675
|
60
62
|
chatterbot/storage/sql_storage.py,sha256=VVYZvclG_74IN-MrG0edc-RQ2gUO6gRQyCWWSO0MmCk,13082
|
61
63
|
chatterbot/storage/storage_adapter.py,sha256=fvyb-qNiB0HMJ0siVMCWUIY--6d-C47N1_kKZVFZAv4,6110
|
62
|
-
chatterbot-1.2.
|
63
|
-
chatterbot-1.2.
|
64
|
-
chatterbot-1.2.
|
65
|
-
chatterbot-1.2.
|
66
|
-
chatterbot-1.2.
|
64
|
+
chatterbot-1.2.3.dist-info/LICENSE,sha256=5b04U8mi0wp5gJMYlKi49EalnD9Q2nwY_6UEI_Avgu4,1476
|
65
|
+
chatterbot-1.2.3.dist-info/METADATA,sha256=xnofLrmf6knmhcwBVcodzvxpZQ-eb4tbLB970dXQG8I,8503
|
66
|
+
chatterbot-1.2.3.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
|
67
|
+
chatterbot-1.2.3.dist-info/top_level.txt,sha256=W2TzAbAJ-eBXTIKZZhVlkrh87msJNmBQpyhkrHqjSrE,11
|
68
|
+
chatterbot-1.2.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|