vector-vault 4.2.2__tar.gz → 4.2.4__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.
- {vector_vault-4.2.2 → vector_vault-4.2.4}/PKG-INFO +1 -1
- {vector_vault-4.2.2 → vector_vault-4.2.4}/setup.py +1 -1
- {vector_vault-4.2.2 → vector_vault-4.2.4}/vector_vault.egg-info/PKG-INFO +1 -1
- {vector_vault-4.2.2 → vector_vault-4.2.4}/vectorvault/ai.py +8 -8
- {vector_vault-4.2.2 → vector_vault-4.2.4}/vectorvault/tools_gpt.py +82 -76
- {vector_vault-4.2.2 → vector_vault-4.2.4}/vectorvault/vault.py +74 -54
- {vector_vault-4.2.2 → vector_vault-4.2.4}/LICENSE +0 -0
- {vector_vault-4.2.2 → vector_vault-4.2.4}/README.md +0 -0
- {vector_vault-4.2.2 → vector_vault-4.2.4}/setup.cfg +0 -0
- {vector_vault-4.2.2 → vector_vault-4.2.4}/vector_vault.egg-info/SOURCES.txt +0 -0
- {vector_vault-4.2.2 → vector_vault-4.2.4}/vector_vault.egg-info/dependency_links.txt +0 -0
- {vector_vault-4.2.2 → vector_vault-4.2.4}/vector_vault.egg-info/requires.txt +0 -0
- {vector_vault-4.2.2 → vector_vault-4.2.4}/vector_vault.egg-info/top_level.txt +0 -0
- {vector_vault-4.2.2 → vector_vault-4.2.4}/vectorvault/__init__.py +0 -0
- {vector_vault-4.2.2 → vector_vault-4.2.4}/vectorvault/cloud_api.py +0 -0
- {vector_vault-4.2.2 → vector_vault-4.2.4}/vectorvault/cloudmanager.py +0 -0
- {vector_vault-4.2.2 → vector_vault-4.2.4}/vectorvault/creds.py +0 -0
- {vector_vault-4.2.2 → vector_vault-4.2.4}/vectorvault/download.py +0 -0
- {vector_vault-4.2.2 → vector_vault-4.2.4}/vectorvault/itemize.py +0 -0
- {vector_vault-4.2.2 → vector_vault-4.2.4}/vectorvault/vecreq.py +0 -0
- {vector_vault-4.2.2 → vector_vault-4.2.4}/vectorvault/wrap.py +0 -0
|
@@ -7,8 +7,9 @@ import tempfile
|
|
|
7
7
|
stock_sys_msg = "You are an AI assistant that excels at following instructions exactly."
|
|
8
8
|
|
|
9
9
|
class AI:
|
|
10
|
-
def __init__(self, personality_message: str = None, main_prompt: str = None, verbose: bool = False) -> None:
|
|
10
|
+
def __init__(self, personality_message: str = None, main_prompt: str = None, verbose: bool = False, timeout: int = 300) -> None:
|
|
11
11
|
self.verbose = verbose
|
|
12
|
+
self.timeout = timeout
|
|
12
13
|
self.model_token_limits = {
|
|
13
14
|
'gpt-3.5-turbo': 16000,
|
|
14
15
|
'gpt-3.5-turbo-0125': 16000,
|
|
@@ -50,8 +51,9 @@ class AI:
|
|
|
50
51
|
|
|
51
52
|
return new_model
|
|
52
53
|
|
|
53
|
-
def make_call(self, messages, model, temperature, timeout=
|
|
54
|
+
def make_call(self, messages, model, temperature, timeout=None):
|
|
54
55
|
# This function will be run in a separate thread
|
|
56
|
+
timeout = self.timeout if not timeout else timeout
|
|
55
57
|
def call_api(response_queue):
|
|
56
58
|
try:
|
|
57
59
|
response = openai.chat.completions.create(
|
|
@@ -76,18 +78,17 @@ class AI:
|
|
|
76
78
|
|
|
77
79
|
|
|
78
80
|
# This function returns a ChatGPT completion based on a provided input
|
|
79
|
-
def llm(self, user_input: str = '', history: str = '', model='gpt-3.5-turbo', max_tokens = 4000, custom_prompt = False, temperature = 0, timeout =
|
|
81
|
+
def llm(self, user_input: str = '', history: str = '', model='gpt-3.5-turbo', max_tokens = 4000, custom_prompt = False, temperature = 0, timeout = None, max_retries = 5):
|
|
80
82
|
'''
|
|
81
83
|
If you pass in a custom_prompt, make sure you format your inputs - this function will not change it
|
|
82
84
|
If you want a custom_prompt but also want to pass `user_input` to take advantage of this function's formatting, then save your custom prompt as default with `save_custom_prompt` in vault.py
|
|
83
85
|
'''
|
|
86
|
+
timeout = self.timeout if not timeout else timeout
|
|
84
87
|
prompt_template = custom_prompt if custom_prompt else self.prompt
|
|
85
88
|
|
|
86
89
|
# Use token_model_check to select the suitable model based on token count
|
|
87
90
|
model = self.model_check(self.get_tokens(history + user_input + prompt_template), model)
|
|
88
91
|
max_tokens = self.model_token_limits.get(model, 4000)
|
|
89
|
-
timeout = timeout * 2 if model in ['gpt-4-1106-preview','gpt-4-turbo-preview','gpt-4-0125-preview'] else timeout
|
|
90
|
-
timeout = timeout * .67 if model in ['gpt-3.5-turbo','gpt-3.5-turbo-0125','gpt-3.5-turbo-16k'] else timeout
|
|
91
92
|
|
|
92
93
|
if user_input:
|
|
93
94
|
new_texts = self.truncate_text(user_input, history, prompt_template, max_tokens=max_tokens)
|
|
@@ -161,12 +162,11 @@ class AI:
|
|
|
161
162
|
}]).choices[0].message.content
|
|
162
163
|
|
|
163
164
|
|
|
164
|
-
def llm_w_context(self, user_input = '', context = '', history = '', model = 'gpt-3.5-turbo', max_tokens = 4000, custom_prompt = False, temperature = 0, timeout =
|
|
165
|
+
def llm_w_context(self, user_input = '', context = '', history = '', model = 'gpt-3.5-turbo', max_tokens = 4000, custom_prompt = False, temperature = 0, timeout = None, max_retries = 5):
|
|
166
|
+
timeout = self.timeout if not timeout else timeout
|
|
165
167
|
prompt_template = custom_prompt if custom_prompt else self.context_prompt
|
|
166
168
|
model = self.model_check(self.get_tokens(history + user_input + prompt_template + context), model)
|
|
167
169
|
max_tokens = self.model_token_limits.get(model, 4000)
|
|
168
|
-
timeout = timeout * 2 if model in ['gpt-4-1106-preview','gpt-4-turbo-preview','gpt-4-0125-preview'] else timeout
|
|
169
|
-
timeout = timeout * .67 if model in ['gpt-3.5-turbo','gpt-3.5-turbo-0125','gpt-3.5-turbo-16k'] else timeout
|
|
170
170
|
|
|
171
171
|
if user_input and context:
|
|
172
172
|
new_texts = self.truncate_text(user_input, history, prompt_template, context, max_tokens=max_tokens)
|
|
@@ -4,41 +4,43 @@ import ast
|
|
|
4
4
|
|
|
5
5
|
'''
|
|
6
6
|
ToolsGPT is a set of tools special to large language models.
|
|
7
|
-
Every tool turns
|
|
7
|
+
Every tool turns unstructured data into structured data.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
Structure data is required to run programs.
|
|
10
|
+
Unstructured data is the primary human output.
|
|
11
|
+
AI tools convert unstructured data into structured data,
|
|
12
|
+
bridging the gap between humans and programs.
|
|
13
|
+
Therefore, in the future, everything will be programmable.
|
|
12
14
|
|
|
13
15
|
ToolsGPT allows you to get the following for any input:
|
|
14
16
|
1. `get_rating` - returns a rating out of 10 for any input
|
|
15
17
|
2. `get_yes_no` - returns a 'yes' or a 'no' to any question
|
|
16
|
-
3. `
|
|
17
|
-
4. `get_match` - returns a match to a list options
|
|
18
|
-
5. `
|
|
18
|
+
3. `get_number` - returns an integer based on your instructions
|
|
19
|
+
4. `get_match` - returns a match to a list options - (single choice)
|
|
20
|
+
5. `get_multi_match` - returns one or many matches to a list of options - (multiple choice)
|
|
19
21
|
6. `get_topic` - returns the topic subject matter of any input
|
|
20
22
|
|
|
21
23
|
1. `get_rating`:
|
|
22
|
-
|
|
24
|
+
Get a numeric rating out of 10 for anything
|
|
23
25
|
|
|
24
26
|
2. `get_yes_no`:
|
|
25
|
-
|
|
27
|
+
Get an exact 'yes' or 'no' to any question
|
|
26
28
|
|
|
27
|
-
3. `
|
|
28
|
-
|
|
29
|
+
3. `get_number`:
|
|
30
|
+
Get an integer output for any input
|
|
29
31
|
|
|
30
32
|
4. `get_match`:
|
|
31
|
-
|
|
33
|
+
Get an exact match to a single item within a list
|
|
34
|
+
-> in: (text and list of strings)
|
|
35
|
+
-> out: (one exact match to an answer in list -> string type)
|
|
36
|
+
|
|
37
|
+
5. `get_multi_match`:
|
|
38
|
+
Get many matches to items within a list
|
|
32
39
|
-> in: (text and list of answers)
|
|
33
|
-
-> out: (
|
|
40
|
+
-> out: (list of exact matches -> list type)
|
|
34
41
|
|
|
35
|
-
|
|
42
|
+
6. `get_topic`:
|
|
36
43
|
Useful to classify the topic of conversation
|
|
37
|
-
|
|
38
|
-
6. `match_or_make` (M&M):
|
|
39
|
-
Get a match to a list of options, or make a new one if unrelated
|
|
40
|
-
Useful if you aren't sure if the input will match one of your existing list options, and need flexibility of creating a new one
|
|
41
|
-
Also useful when starting from an empty list. - will create it from scratch
|
|
42
44
|
'''
|
|
43
45
|
|
|
44
46
|
class ToolsGPT():
|
|
@@ -46,6 +48,7 @@ class ToolsGPT():
|
|
|
46
48
|
self.verbose = verbose
|
|
47
49
|
self.llm = AI(verbose=verbose).llm
|
|
48
50
|
|
|
51
|
+
|
|
49
52
|
def get_rating(self, text: str = None, concept_to_rate_for: str = None, model='gpt-3.5-turbo', loop_limit=20) -> int:
|
|
50
53
|
'''
|
|
51
54
|
Get a numeric rating out of 10. Input plain text, and concept to rate for. Defualts to 'quality'
|
|
@@ -70,16 +73,25 @@ that this number has been carefully considered given your concept. My rating out
|
|
|
70
73
|
try:
|
|
71
74
|
return int(answer) # try to return an int zero shot
|
|
72
75
|
except:
|
|
73
|
-
return self.
|
|
76
|
+
return self.retry_until_its_a_number(answer, model=model, loop_limit=loop_limit) # force the integer if zero shot fails
|
|
74
77
|
|
|
75
78
|
|
|
76
|
-
def get_number(self, content: str, model='gpt-3.5-turbo', loop_limit=5) -> int:
|
|
79
|
+
def get_number(self, concept, content: str, model='gpt-3.5-turbo', loop_limit=5) -> int:
|
|
80
|
+
'''
|
|
81
|
+
param: `concept` - is the idea used to generate a number with - i.e. "How many students are in the professor's class?"
|
|
82
|
+
param: `content` - is the content to generate a number for - i.e. "I had 400 students in my class last year, but this year, I have 10% more."
|
|
83
|
+
'''
|
|
84
|
+
response = self.retry_llm(f'{concept} \n\n{content}', model=model, loop_limit=loop_limit)
|
|
85
|
+
print("Initial number extraction response:", response) if self.verbose else 0
|
|
86
|
+
return response if type(response) is int else self.retry_until_its_a_number(response, model=model, loop_limit=loop_limit)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def retry_until_its_a_number(self, content: str, model='gpt-3.5-turbo', loop_limit=5) -> int:
|
|
77
90
|
'''
|
|
78
|
-
|
|
79
|
-
`...User: The following content should be a number - Content: "{content}"`
|
|
91
|
+
param: `content` - the AI will return an integer based on the content you input
|
|
80
92
|
'''
|
|
81
|
-
|
|
82
|
-
|
|
93
|
+
|
|
94
|
+
prompt_template = """Respond only with a number in integer format...
|
|
83
95
|
Example content: 'The revenue for the last fiscal year was $1,200,000.' Example answer: '1200000'
|
|
84
96
|
Example content 2: 'The company has been in business for twenty years.' Example answer 2: '20'
|
|
85
97
|
Example content 3: 'The recipe calls for three cups of flour.' Example answer 3: '3'
|
|
@@ -90,27 +102,31 @@ Example content 7: 'The project deadline is in 45 days.' Example answer 7: '45'
|
|
|
90
102
|
User: The following content should be a number - Content: "{content}"
|
|
91
103
|
\nAgent:"""
|
|
92
104
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
105
|
+
prompt = prompt_template.format(content=content)
|
|
106
|
+
response = self.retry_llm(custom_prompt=prompt, model=model, loop_limit=loop_limit)
|
|
107
|
+
print("Initial number extraction response:", response) if self.verbose else 0
|
|
108
|
+
|
|
109
|
+
if type(response) is int:
|
|
110
|
+
return response
|
|
111
|
+
|
|
112
|
+
else: # loop until its a number
|
|
113
|
+
loops = 0
|
|
114
|
+
answer = content
|
|
115
|
+
|
|
116
|
+
while True:
|
|
117
|
+
if loops >= loop_limit:
|
|
118
|
+
break
|
|
119
|
+
|
|
120
|
+
prompt = prompt_template.format(content=answer)
|
|
121
|
+
answer = self.retry_llm(custom_prompt=prompt, model=model, loop_limit=loop_limit)
|
|
122
|
+
print(f"Loops: {loops} | Number: {answer}") if self.verbose else 0
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
answer = int(answer)
|
|
126
|
+
return answer # exit loop and return the integer
|
|
127
|
+
except:
|
|
128
|
+
loops += 1
|
|
112
129
|
|
|
113
|
-
return answer # return the integer
|
|
114
130
|
|
|
115
131
|
def get_yes_no(self, text: str, question: str = None, model='gpt-3.5-turbo', loop_limit=20) -> str:
|
|
116
132
|
'''
|
|
@@ -118,36 +134,16 @@ User: The following content should be a number - Content: "{content}"
|
|
|
118
134
|
Be sure to input text to get a yes or no to, then ask the question to answer
|
|
119
135
|
'''
|
|
120
136
|
answer = self.yay_or_nay(text, question, model=model) if question else self.yay_or_nay_question_in_content(text, model=model)
|
|
121
|
-
if self.verbose == True
|
|
122
|
-
|
|
123
|
-
|
|
137
|
+
print(f"Y/N Initial Answer: {answer}") if self.verbose == True else 0
|
|
138
|
+
|
|
124
139
|
loops = 0
|
|
125
140
|
while loops < loop_limit and answer not in ['yes', 'no']:
|
|
126
141
|
answer = self.isolate_yes_no(answer)
|
|
127
|
-
if self.verbose
|
|
128
|
-
print(f"Y/N Answer {loops}: {answer}")
|
|
142
|
+
print(f"Y/N Answer {loops}: {answer}") if self.verbose else 0
|
|
129
143
|
loops += 1
|
|
130
144
|
|
|
131
145
|
return answer
|
|
132
146
|
|
|
133
|
-
def get_binary(self, text: str, zero_if: str, one_if: str, model='gpt-3.5-turbo', loop_limit=20) -> str:
|
|
134
|
-
'''
|
|
135
|
-
Get an exact "0" or "1" to any question, given an input.
|
|
136
|
-
Input text to get a decision on, then tell why to pick 0 and why to pick 1.
|
|
137
|
-
Prompt starts with "Repond '0' if"...
|
|
138
|
-
'''
|
|
139
|
-
answer = self.zero_or_one(text, zero_if, one_if, model=model)
|
|
140
|
-
if self.verbose == True:
|
|
141
|
-
print(f"0/1 Initial Answer: {answer}")
|
|
142
|
-
|
|
143
|
-
loops = 0
|
|
144
|
-
while loops < loop_limit and int(answer) not in [0, 1]:
|
|
145
|
-
answer = self.isolate_zero_one(answer)
|
|
146
|
-
if self.verbose:
|
|
147
|
-
print(f"0/1 Answer {loops}: {answer}")
|
|
148
|
-
loops += 1
|
|
149
|
-
|
|
150
|
-
return int(answer)
|
|
151
147
|
|
|
152
148
|
def get_match(self, text: str, list_of_options: list, model='gpt-3.5-turbo', loop_limit=4) -> str:
|
|
153
149
|
'''
|
|
@@ -185,6 +181,7 @@ Content to classify: "{text}" \n\nDo not respond with anything other than the o
|
|
|
185
181
|
|
|
186
182
|
return new_answer
|
|
187
183
|
|
|
184
|
+
|
|
188
185
|
def get_multi_match(self, text: str, list_of_options: list, model='gpt-3.5-turbo', loop_limit=4) -> str:
|
|
189
186
|
'''
|
|
190
187
|
This function can be used in a variety of Natural Language Processing (NLP) tasks,
|
|
@@ -203,6 +200,7 @@ Content to classify: "{text}" \n\n Respond with a subset list that matches the
|
|
|
203
200
|
|
|
204
201
|
return [i if i in list_of_options else self.get_match(i, list_of_options, model, loop_limit) for i in ast.literal_eval(answer)]
|
|
205
202
|
|
|
203
|
+
|
|
206
204
|
def get_topic(self, text: str, list_of_options: list, model='gpt-3.5-turbo', loop_limit=4) -> str:
|
|
207
205
|
'''
|
|
208
206
|
Like get_match, default optimized for topic recognition
|
|
@@ -213,13 +211,11 @@ Content to classify: "{text}" \n\n Respond with a subset list that matches the
|
|
|
213
211
|
list_copy.append(option.strip().replace('.', '').lower().strip('"').strip("'"))
|
|
214
212
|
prompt_template = """Respond with one of the options on this list: {list_of_options}
|
|
215
213
|
Content to classify: "{content}" \n\nClassifiy the content above based on which topic it is mostly related to one topic: {list_of_options}"""
|
|
214
|
+
|
|
216
215
|
prompt = prompt_template.format(content=text, list_of_options=list_copy)
|
|
217
|
-
|
|
218
216
|
topic = self.retry_llm_in_list(prompt, list_copy, model, loop_limit)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
print(f"Topic Answer: {topic}")
|
|
222
|
-
|
|
217
|
+
print(f"Topic Answer: {topic}") if self.verbose else 0
|
|
218
|
+
|
|
223
219
|
if topic is not None:
|
|
224
220
|
topic = list_of_options[list_copy.index(topic)] # return original topic
|
|
225
221
|
|
|
@@ -268,6 +264,7 @@ Respond "Yes" if the right category already exists in the list'''
|
|
|
268
264
|
|
|
269
265
|
return answer
|
|
270
266
|
|
|
267
|
+
|
|
271
268
|
# internal function
|
|
272
269
|
def make_option(self, text, list_of_options: list, model='gpt-3.5-turbo') -> str:
|
|
273
270
|
prompt_template = """Content to classify: "{content}" \n\n
|
|
@@ -277,6 +274,7 @@ Create a new category for the content based on these other categories in this li
|
|
|
277
274
|
|
|
278
275
|
return self.retry_llm(custom_prompt=prompt, model=model)
|
|
279
276
|
|
|
277
|
+
|
|
280
278
|
# internal function
|
|
281
279
|
def make_option_from_zero(self, text, model='gpt-3.5-turbo'):
|
|
282
280
|
'''
|
|
@@ -290,6 +288,7 @@ no explination before or after, just the name of the catagory. \n\nThe name of t
|
|
|
290
288
|
|
|
291
289
|
return self.retry_llm(custom_prompt=prompt, model=model)
|
|
292
290
|
|
|
291
|
+
|
|
293
292
|
# internal function
|
|
294
293
|
def finalize_category(self, text, prev_answer):
|
|
295
294
|
prompt_template = """Given the following category suggestion: "{prev_answer}"
|
|
@@ -299,6 +298,7 @@ no explination before or after, just the name of the catagory. \n\nThe name of t
|
|
|
299
298
|
|
|
300
299
|
return self.retry_llm(custom_prompt=prompt)
|
|
301
300
|
|
|
301
|
+
|
|
302
302
|
# internal function
|
|
303
303
|
def isolate_yes_no(self, content, question: str, model='gpt-3.5-turbo'):
|
|
304
304
|
'''Not recommended for external use. Internal function'''
|
|
@@ -313,6 +313,7 @@ Example question 5: 'Will this happen if it's 19 percent likely to happen?' Exam
|
|
|
313
313
|
|
|
314
314
|
return self.retry_llm(custom_prompt=prompt, model=model)
|
|
315
315
|
|
|
316
|
+
|
|
316
317
|
# internal function
|
|
317
318
|
def yay_or_nay(self, content, question: str, model='gpt-3.5-turbo'):
|
|
318
319
|
'''Not recommended for external use. Internal function'''
|
|
@@ -327,6 +328,7 @@ Example question 5: 'Will this happen if it's 19 percent likely to happen?' Exam
|
|
|
327
328
|
|
|
328
329
|
return self.retry_llm(custom_prompt=prompt, model=model)
|
|
329
330
|
|
|
331
|
+
|
|
330
332
|
# internal function
|
|
331
333
|
def yay_or_nay_question_in_content(self, content: str, model='gpt-3.5-turbo'):
|
|
332
334
|
'''Not recommended for external use. Internal function'''
|
|
@@ -344,6 +346,7 @@ Agent:"""
|
|
|
344
346
|
|
|
345
347
|
return self.retry_llm(custom_prompt=prompt, model=model)
|
|
346
348
|
|
|
349
|
+
|
|
347
350
|
# internal function
|
|
348
351
|
def zero_or_one(self, content, zero_if: str, one_if: str, model='gpt-3.5-turbo'):
|
|
349
352
|
'''Not recommended for external use. Internal function'''
|
|
@@ -358,6 +361,7 @@ Example question 5: 'Will this happen if it's 19 percent likely to happen?' Exam
|
|
|
358
361
|
|
|
359
362
|
return self.retry_llm(custom_prompt=prompt, model=model)
|
|
360
363
|
|
|
364
|
+
|
|
361
365
|
# internal function
|
|
362
366
|
def isolate_zero_one(self, content, model='gpt-3.5-turbo'):
|
|
363
367
|
'''Not recommended for external use. Internal function'''
|
|
@@ -372,6 +376,7 @@ Example question 5: 'Will this happen if it's 19 percent likely to happen?' Exam
|
|
|
372
376
|
|
|
373
377
|
return self.retry_llm(custom_prompt=prompt, model=model)
|
|
374
378
|
|
|
379
|
+
|
|
375
380
|
# This function is called by the others to handle retries:
|
|
376
381
|
def retry_llm(self, custom_prompt, model='gpt-3.5-turbo', loop_limit=2, temperature=0):
|
|
377
382
|
for i in range(loop_limit):
|
|
@@ -383,11 +388,12 @@ Example question 5: 'Will this happen if it's 19 percent likely to happen?' Exam
|
|
|
383
388
|
print(f"Attempt {i+1} failed with error: {str(e)}. Retrying...")
|
|
384
389
|
else:
|
|
385
390
|
raise f"Attempt {i+1} failed with error: {str(e)}. No more retries."
|
|
386
|
-
|
|
391
|
+
|
|
392
|
+
|
|
387
393
|
def perfect(self, content, instructions: str, model='gpt-3.5-turbo'):
|
|
388
394
|
'''Wrapper function ensures that you get exactly what you wanted, and will not return until you get what you wanted'''
|
|
389
|
-
output = self.get_yes_no(model=model, text=f"These are the instructions: {instructions}",
|
|
390
|
-
question=f"
|
|
395
|
+
output = self.get_yes_no(model=model, text=f"These are the instructions: {instructions} and this is the Output: {content}",
|
|
396
|
+
question=f"Does the output match the instructions?", ) == 'no'
|
|
391
397
|
|
|
392
398
|
while output:
|
|
393
399
|
new_intstructions = f'''The following content does not match the instructions exactly. Your job is to return the content exactly as the instructions direct:
|
|
@@ -96,16 +96,18 @@ class Vault:
|
|
|
96
96
|
self.rate_limiter = RateLimiter(max_attempts=30)
|
|
97
97
|
t.join()
|
|
98
98
|
|
|
99
|
+
|
|
99
100
|
def connect_to_cloud(self):
|
|
100
101
|
try:
|
|
101
102
|
self.cloud_manager = CloudManager(self.user, self.api, self.vault)
|
|
102
|
-
if self.verbose
|
|
103
|
-
|
|
103
|
+
print(f'Connected vault: {self.vault}') if self.verbose else 0
|
|
104
|
+
|
|
104
105
|
except Exception as e:
|
|
105
106
|
print('API KEY NOT FOUND or no internet connection!', e)
|
|
106
107
|
# user can still use the get_chat() function without an api key
|
|
107
108
|
self.cloud_manager = None
|
|
108
109
|
|
|
110
|
+
|
|
109
111
|
def get_vaults(self, vault: str = None):
|
|
110
112
|
'''
|
|
111
113
|
Returns a list of vaults within the current vault directory
|
|
@@ -113,6 +115,7 @@ class Vault:
|
|
|
113
115
|
vault = self.vault if vault is None else vault
|
|
114
116
|
return self.cloud_manager.list_vaults(vault)
|
|
115
117
|
|
|
118
|
+
|
|
116
119
|
def get_total_items(self, vault: str = None):
|
|
117
120
|
'''
|
|
118
121
|
Returns the total number of vectored items in the Vault
|
|
@@ -130,6 +133,7 @@ class Vault:
|
|
|
130
133
|
except: # it doesn't exist
|
|
131
134
|
return 0
|
|
132
135
|
|
|
136
|
+
|
|
133
137
|
def get_tokens(self, text: str):
|
|
134
138
|
'''
|
|
135
139
|
Returns the number of tokens for any given text
|
|
@@ -137,6 +141,7 @@ class Vault:
|
|
|
137
141
|
self.load_ai()
|
|
138
142
|
return self.ai.get_tokens(text)
|
|
139
143
|
|
|
144
|
+
|
|
140
145
|
def get_distance(self, id1: int, id2: int):
|
|
141
146
|
'''
|
|
142
147
|
Returns the distance between two vectors - item ids are needed to compare
|
|
@@ -144,6 +149,7 @@ class Vault:
|
|
|
144
149
|
self.check_index()
|
|
145
150
|
return self.vectors.get_distance(id1, id2)
|
|
146
151
|
|
|
152
|
+
|
|
147
153
|
def get_item_vector(self, item_id: int):
|
|
148
154
|
'''
|
|
149
155
|
Returns the vector from an item id
|
|
@@ -151,6 +157,7 @@ class Vault:
|
|
|
151
157
|
self.check_index()
|
|
152
158
|
return self.vectors.get_item_vector(item_id)
|
|
153
159
|
|
|
160
|
+
|
|
154
161
|
def load_ai(self):
|
|
155
162
|
self.ai_loaded = True
|
|
156
163
|
self.ai = AI(verbose=self.verbose)
|
|
@@ -158,15 +165,16 @@ class Vault:
|
|
|
158
165
|
self.ai.prompt = self.fetch_custom_prompt(context=False)
|
|
159
166
|
self.ai.personality_message = self.fetch_personality_message()
|
|
160
167
|
|
|
168
|
+
|
|
161
169
|
def save_personality_message(self, text: str):
|
|
162
170
|
'''
|
|
163
171
|
Saves personality_message to the vault and use it by default from now on
|
|
164
172
|
'''
|
|
165
173
|
self.cloud_manager.upload_personality_message(text)
|
|
166
174
|
|
|
167
|
-
if self.verbose
|
|
168
|
-
|
|
169
|
-
|
|
175
|
+
print(f"Personality message saved") if self.verbose else 0
|
|
176
|
+
|
|
177
|
+
|
|
170
178
|
def fetch_personality_message(self):
|
|
171
179
|
'''
|
|
172
180
|
Retrieves personality_message from the vault if it is there or else use the defualt
|
|
@@ -182,6 +190,7 @@ class Vault:
|
|
|
182
190
|
|
|
183
191
|
return personality_message
|
|
184
192
|
|
|
193
|
+
|
|
185
194
|
def save_custom_prompt(self, text: str, context=True):
|
|
186
195
|
'''
|
|
187
196
|
Saves custom_prompt to the vault and use it by default from now on
|
|
@@ -189,9 +198,9 @@ class Vault:
|
|
|
189
198
|
'''
|
|
190
199
|
self.cloud_manager.upload_custom_prompt(text) if context else self.cloud_manager.upload_no_context_prompt(text)
|
|
191
200
|
|
|
192
|
-
if self.verbose
|
|
193
|
-
|
|
194
|
-
|
|
201
|
+
print(f"Custom prompt saved") if self.verbose else 0
|
|
202
|
+
|
|
203
|
+
|
|
195
204
|
def fetch_custom_prompt(self, context=True):
|
|
196
205
|
'''
|
|
197
206
|
Retrieves custom_prompt from the vault if there or eles use defualt - (used for get_context = True responses)
|
|
@@ -208,6 +217,7 @@ class Vault:
|
|
|
208
217
|
|
|
209
218
|
return prompt
|
|
210
219
|
|
|
220
|
+
|
|
211
221
|
def save(self, trees: int = 10):
|
|
212
222
|
'''
|
|
213
223
|
Saves all the data added locally to the Cloud. All Vault references are Cloud references.
|
|
@@ -229,10 +239,9 @@ class Vault:
|
|
|
229
239
|
total_saved_items += 1
|
|
230
240
|
|
|
231
241
|
self.upload_vectors()
|
|
232
|
-
|
|
233
|
-
if self.verbose:
|
|
234
|
-
print(f"upload time --- {(time.time() - start_time)} seconds --- {total_saved_items} items saved")
|
|
242
|
+
print(f"upload time --- {(time.time() - start_time)} seconds --- {total_saved_items} items saved") if self.verbose else 0
|
|
235
243
|
|
|
244
|
+
|
|
236
245
|
def clear_cache(self):
|
|
237
246
|
'''
|
|
238
247
|
Clears the cache for all the loaded items
|
|
@@ -242,12 +251,13 @@ class Vault:
|
|
|
242
251
|
self.vecs_loaded = True
|
|
243
252
|
self.saved_already = False
|
|
244
253
|
|
|
254
|
+
|
|
245
255
|
def delete(self):
|
|
246
256
|
'''
|
|
247
257
|
Deletes the entire Vault and all contents
|
|
248
258
|
'''
|
|
249
|
-
if self.verbose
|
|
250
|
-
|
|
259
|
+
print('Deleting started. Note: this can take a while for large datasets') if self.verbose else 0
|
|
260
|
+
|
|
251
261
|
# Clear the local vector data
|
|
252
262
|
self.vectors = get_vectors(self.dims)
|
|
253
263
|
self.items.clear()
|
|
@@ -255,11 +265,13 @@ class Vault:
|
|
|
255
265
|
self.x = 0
|
|
256
266
|
print('Vault deleted')
|
|
257
267
|
|
|
268
|
+
|
|
258
269
|
def remap(self, item_id):
|
|
259
270
|
for i in range(item_id, len(self.map) - 1):
|
|
260
271
|
self.map[str(i)] = self.map[str(i + 1)]
|
|
261
272
|
self.map.popitem()
|
|
262
273
|
|
|
274
|
+
|
|
263
275
|
def update_vault_data(self):
|
|
264
276
|
nary = []
|
|
265
277
|
try:
|
|
@@ -296,6 +308,7 @@ class Vault:
|
|
|
296
308
|
|
|
297
309
|
self.cloud_manager.upload_temp_file(nary_temp_file_path, f'{self.cloud_manager.username}.json')
|
|
298
310
|
|
|
311
|
+
|
|
299
312
|
def delete_items(self, item_ids: List[int], trees: int = 10) -> None:
|
|
300
313
|
'''
|
|
301
314
|
Deletes one or more items from item_id(s) passed in.
|
|
@@ -334,8 +347,8 @@ class Vault:
|
|
|
334
347
|
self.remap(item_id)
|
|
335
348
|
rebuild_vectors(item_id)
|
|
336
349
|
|
|
337
|
-
if self.verbose
|
|
338
|
-
|
|
350
|
+
print(f'Item {item_id} deleted') if self.verbose else 0
|
|
351
|
+
|
|
339
352
|
|
|
340
353
|
def edit_item(self, item_id: int, new_text: str, trees: int = 10) -> None:
|
|
341
354
|
'''
|
|
@@ -362,17 +375,16 @@ class Vault:
|
|
|
362
375
|
self.cloud_manager.upload_to_cloud(cloud_name(self.vault, self.map[str(item_id)], self.user, self.api, item=True), new_text)
|
|
363
376
|
edit_vector(item_id, self.process_batch([new_text], never_stop=False, loop_timeout=180)[0])
|
|
364
377
|
|
|
365
|
-
if self.verbose
|
|
366
|
-
|
|
378
|
+
print(f'Item {item_id} edited') if self.verbose else 0
|
|
379
|
+
|
|
367
380
|
|
|
368
381
|
def edit_item_meta(self, item_id: int, metadata) -> None:
|
|
369
382
|
'''
|
|
370
383
|
Edit and save any item's metadata
|
|
371
384
|
'''
|
|
372
385
|
self.cloud_manager.upload_to_cloud(cloud_name(self.vault, self.map[str(item_id)], self.user, self.api, meta=True), json.dumps(metadata))
|
|
386
|
+
print(f'Item {item_id} metadata saved') if self.verbose else 0
|
|
373
387
|
|
|
374
|
-
if self.verbose:
|
|
375
|
-
print(f'Item {item_id} metadata saved')
|
|
376
388
|
|
|
377
389
|
def check_index(self):
|
|
378
390
|
if not self.x_checked:
|
|
@@ -383,8 +395,8 @@ class Vault:
|
|
|
383
395
|
self.reload_vectors()
|
|
384
396
|
|
|
385
397
|
self.x_checked = True
|
|
386
|
-
if self.verbose
|
|
387
|
-
|
|
398
|
+
print("initialize index --- %s seconds ---" % (time.time() - start_time)) if self.verbose else 0
|
|
399
|
+
|
|
388
400
|
|
|
389
401
|
def load_mapping(self):
|
|
390
402
|
'''Internal function only'''
|
|
@@ -401,6 +413,7 @@ class Vault:
|
|
|
401
413
|
self.map[str(self.x)] = str(uuid.uuid4())
|
|
402
414
|
self.x +=1
|
|
403
415
|
|
|
416
|
+
|
|
404
417
|
def load_vectors(self):
|
|
405
418
|
start_time = time.time()
|
|
406
419
|
t = T(target=self.load_mapping())
|
|
@@ -410,8 +423,8 @@ class Vault:
|
|
|
410
423
|
os.remove(temp_file_path)
|
|
411
424
|
t.join()
|
|
412
425
|
self.vecs_loaded = True
|
|
413
|
-
if self.verbose
|
|
414
|
-
|
|
426
|
+
print("get load vectors --- %s seconds ---" % (time.time() - start_time)) if self.verbose else 0
|
|
427
|
+
|
|
415
428
|
|
|
416
429
|
def make_3d_map(self, highlight_id: int = None, return_html: bool = False):
|
|
417
430
|
from kneed import KneeLocator
|
|
@@ -509,6 +522,7 @@ class Vault:
|
|
|
509
522
|
else:
|
|
510
523
|
fig.show()
|
|
511
524
|
|
|
525
|
+
|
|
512
526
|
def reload_vectors(self):
|
|
513
527
|
num_existing_items = self.vectors.get_n_items()
|
|
514
528
|
new_index = get_vectors(self.dims)
|
|
@@ -520,6 +534,7 @@ class Vault:
|
|
|
520
534
|
self.x = count + 1
|
|
521
535
|
self.vectors = new_index
|
|
522
536
|
|
|
537
|
+
|
|
523
538
|
def upload_vectors(self):
|
|
524
539
|
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
|
|
525
540
|
vector_temp_file_path = temp_file.name
|
|
@@ -542,6 +557,7 @@ class Vault:
|
|
|
542
557
|
self.vecs_loaded = False
|
|
543
558
|
self.saved_already = False
|
|
544
559
|
|
|
560
|
+
|
|
545
561
|
def split_text(self, text: str, min_threshold: int = 1000, max_threshold: int = 16000):
|
|
546
562
|
'''
|
|
547
563
|
Internal function
|
|
@@ -584,11 +600,11 @@ class Vault:
|
|
|
584
600
|
if current_segment and (current_length >= min_threshold or not segments):
|
|
585
601
|
segments.append(" ".join(current_segment))
|
|
586
602
|
|
|
587
|
-
if self.verbose
|
|
588
|
-
print(f'split_text chunks: {len(segments)}')
|
|
603
|
+
print(f'split_text chunks: {len(segments)}') if self.verbose else 0
|
|
589
604
|
|
|
590
605
|
return segments
|
|
591
606
|
|
|
607
|
+
|
|
592
608
|
def get_items(self, ids: List[int] = [], vault: str = None) -> list:
|
|
593
609
|
'''
|
|
594
610
|
Get one or more items from the database.
|
|
@@ -641,11 +657,11 @@ class Vault:
|
|
|
641
657
|
index, result = future.result()
|
|
642
658
|
results[index] = result # Insert each result at its corresponding index
|
|
643
659
|
|
|
644
|
-
if self.verbose
|
|
645
|
-
|
|
646
|
-
|
|
660
|
+
print(f"Retrieved {len(ids)} items --- %s seconds ---" % (time.time() - start_time)) if self.verbose else 0
|
|
661
|
+
|
|
647
662
|
return results
|
|
648
663
|
|
|
664
|
+
|
|
649
665
|
def get_items_by_vector(self, vector: list, n: int = 4, include_distances: bool = False):
|
|
650
666
|
'''
|
|
651
667
|
Internal function that returns vector similar items. Requires input vector, returns similar items
|
|
@@ -670,8 +686,8 @@ class Vault:
|
|
|
670
686
|
index, result = future.result()
|
|
671
687
|
results[index] = result # Insert each result at its corresponding index
|
|
672
688
|
|
|
673
|
-
if self.verbose
|
|
674
|
-
|
|
689
|
+
print(f"get {n} items back --- %s seconds ---" % (time.time() - start_time)) if self.verbose else 0
|
|
690
|
+
|
|
675
691
|
return results
|
|
676
692
|
else:
|
|
677
693
|
vecs, distances = self.vectors.get_nns_by_vector(vector, n, include_distances=include_distances)
|
|
@@ -690,12 +706,13 @@ class Vault:
|
|
|
690
706
|
index, result = future.result()
|
|
691
707
|
results[index] = result # Insert each result at its corresponding index
|
|
692
708
|
|
|
693
|
-
if self.verbose
|
|
694
|
-
|
|
709
|
+
print(f"get {n} items back --- %s seconds ---" % (time.time() - start_time))if self.verbose else 0
|
|
710
|
+
|
|
695
711
|
return results
|
|
696
712
|
except:
|
|
697
713
|
return [{'data': 'No data has been added', 'metadata': {'no meta': 'No metadata has been added'}}]
|
|
698
714
|
|
|
715
|
+
|
|
699
716
|
def get_similar_local(self, text: str, n: int = 4, include_distances: bool = False):
|
|
700
717
|
'''
|
|
701
718
|
Returns similar items from the Vault as the one you entered, but locally
|
|
@@ -705,6 +722,7 @@ class Vault:
|
|
|
705
722
|
vector = self.process_batch([text], never_stop=False, loop_timeout=180)[0]
|
|
706
723
|
return self.get_items_by_vector(vector, n, include_distances=include_distances)
|
|
707
724
|
|
|
725
|
+
|
|
708
726
|
def get_similar(self, text: str, n: int = 4, include_distances: bool = False):
|
|
709
727
|
'''
|
|
710
728
|
Returns similar items from the Vault as the text you enter.
|
|
@@ -712,11 +730,10 @@ class Vault:
|
|
|
712
730
|
Param `include_distances = True` adds the "distance" field to the return.
|
|
713
731
|
The distance can be useful for assessing similarity differences in the items returned.
|
|
714
732
|
Each item has its' own distance number, and this changes the structure of the output.
|
|
715
|
-
|
|
716
|
-
|
|
717
733
|
'''
|
|
718
734
|
return call_get_similar(self.user, self.vault, self.api, self.openai_key, text, n, include_distances=include_distances, verbose=self.verbose)
|
|
719
735
|
|
|
736
|
+
|
|
720
737
|
def add_item(self, text: str, meta: dict = None, name: str = None):
|
|
721
738
|
"""
|
|
722
739
|
If your text length is greater than 15000 characters, you should use Vault.split_text(your_text) to
|
|
@@ -727,6 +744,7 @@ class Vault:
|
|
|
727
744
|
self.items.append(new_item)
|
|
728
745
|
self.add_to_map()
|
|
729
746
|
|
|
747
|
+
|
|
730
748
|
def add(self, text: str, meta: dict = None, name: str = None, split: bool = False, split_size: int = 1000, max_threshold: int = 16000):
|
|
731
749
|
"""
|
|
732
750
|
If your text length is greater than 4000 tokens, Vault.split_text(your_text)
|
|
@@ -735,14 +753,15 @@ class Vault:
|
|
|
735
753
|
self.check_index()
|
|
736
754
|
|
|
737
755
|
if len(text) > 15000 or split:
|
|
738
|
-
if self.verbose
|
|
739
|
-
|
|
756
|
+
print('Using the built-in "split_text()" function to get a list of texts') if self.verbose else 0
|
|
757
|
+
|
|
740
758
|
texts = self.split_text(text, min_threshold=split_size, max_threshold=max_threshold) # returns list of text segments
|
|
741
759
|
else:
|
|
742
760
|
texts = [text]
|
|
743
761
|
for text in texts:
|
|
744
762
|
self.add_item(text, meta, name)
|
|
745
763
|
|
|
764
|
+
|
|
746
765
|
def add_n_save(self, text: str, meta: dict = None, name: str = None, split: bool = False, split_size: int = 1000, max_threshold: int = 16000):
|
|
747
766
|
"""
|
|
748
767
|
Adds data, gets vectors, then saves the data to the cloud in one call
|
|
@@ -753,6 +772,7 @@ class Vault:
|
|
|
753
772
|
self.get_vectors()
|
|
754
773
|
self.save()
|
|
755
774
|
|
|
775
|
+
|
|
756
776
|
def add_item_with_vector(self, text: str, vector: list, meta: dict = None, name: str = None):
|
|
757
777
|
"""
|
|
758
778
|
If your text length is greater than 15000 characters, you should use Vault.split_text(your_text) to
|
|
@@ -769,9 +789,9 @@ class Vault:
|
|
|
769
789
|
self.items.append(itemize(self.vault, self.x, meta, text, name))
|
|
770
790
|
self.add_to_map()
|
|
771
791
|
|
|
772
|
-
if self.verbose
|
|
773
|
-
|
|
774
|
-
|
|
792
|
+
print("add item time --- %s seconds ---" % (time.time() - start_time)) if self.verbose else 0
|
|
793
|
+
|
|
794
|
+
|
|
775
795
|
def process_batch(self, batch_text_chunks, never_stop, loop_timeout):
|
|
776
796
|
'''
|
|
777
797
|
Internal function
|
|
@@ -799,9 +819,10 @@ class Vault:
|
|
|
799
819
|
raise TimeoutError("Loop timed out")
|
|
800
820
|
return [record.embedding for record in res.data]
|
|
801
821
|
|
|
822
|
+
|
|
802
823
|
def get_vectors(self, batch_size: int = 32, never_stop: bool = False, loop_timeout: int = 777):
|
|
803
824
|
'''
|
|
804
|
-
|
|
825
|
+
Takes text data added to the vault, and gets vectors for them
|
|
805
826
|
'''
|
|
806
827
|
self.check_index()
|
|
807
828
|
start_time = time.time()
|
|
@@ -827,14 +848,13 @@ class Vault:
|
|
|
827
848
|
current_item_index += 1
|
|
828
849
|
|
|
829
850
|
self.last_time = time.time()
|
|
830
|
-
if self.verbose
|
|
831
|
-
|
|
832
|
-
|
|
851
|
+
print("get vectors time --- %s seconds ---" % (time.time() - start_time)) if self.verbose else 0
|
|
852
|
+
|
|
833
853
|
|
|
834
854
|
def get_chat(self, text: str = None, history: str = None, summary: bool = False, get_context: bool = False,
|
|
835
855
|
n_context: int = 4, return_context: bool = False, history_search: bool = False, smart_history_search: bool = False,
|
|
836
856
|
model: str = 'gpt-3.5-turbo', include_context_meta: bool = False, custom_prompt: bool = False,
|
|
837
|
-
local: bool =False, temperature: int = 0, timeout: int =
|
|
857
|
+
local: bool =False, temperature: int = 0, timeout: int = 300):
|
|
838
858
|
'''
|
|
839
859
|
Chat get response from OpenAI's ChatGPT.
|
|
840
860
|
Models: ChatGPT = "gpt-3.5-turbo" • GPT4 = "gpt-4"
|
|
@@ -984,19 +1004,16 @@ class Vault:
|
|
|
984
1004
|
print(f"API Failed too many times, exiting loop: {e}.")
|
|
985
1005
|
break
|
|
986
1006
|
|
|
987
|
-
if self.verbose
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
if not return_context:
|
|
991
|
-
return response
|
|
992
|
-
elif return_context:
|
|
993
|
-
return {'response': response, 'context': context}
|
|
1007
|
+
print("get chat time --- %s seconds ---" % (time.time() - start_time)) if self.verbose else 0
|
|
1008
|
+
|
|
1009
|
+
return {'response': response, 'context': context} if return_context else response
|
|
994
1010
|
|
|
1011
|
+
|
|
995
1012
|
def get_chat_stream(self, text: str = None, history: str = None, summary: bool = False, get_context: bool = False,
|
|
996
1013
|
n_context: int = 4, return_context: bool = False, history_search: bool = False, smart_history_search: bool = False,
|
|
997
1014
|
model: str ='gpt-3.5-turbo', include_context_meta: bool = False, metatag: bool = False,
|
|
998
1015
|
metatag_prefixes: bool = False, metatag_suffixes: bool = False, custom_prompt: bool = False,
|
|
999
|
-
local: bool = False, temperature: int = 0, timeout: int =
|
|
1016
|
+
local: bool = False, temperature: int = 0, timeout: int = 300):
|
|
1000
1017
|
'''
|
|
1001
1018
|
Always use this get_chat_stream() wrapped by either print_stream(), or cloud_stream().
|
|
1002
1019
|
cloud_stream() is for cloud functions, like a flask app serving a front end elsewhere.
|
|
@@ -1164,8 +1181,8 @@ class Vault:
|
|
|
1164
1181
|
print(f"API Failed too many times, exiting loop: {e}.")
|
|
1165
1182
|
break
|
|
1166
1183
|
|
|
1167
|
-
if self.verbose
|
|
1168
|
-
|
|
1184
|
+
print("get chat time --- %s seconds ---" % (time.time() - start_time)) if self.verbose else 0
|
|
1185
|
+
|
|
1169
1186
|
|
|
1170
1187
|
def print_stream(self, function, printing=True):
|
|
1171
1188
|
'''
|
|
@@ -1185,6 +1202,7 @@ class Vault:
|
|
|
1185
1202
|
else:
|
|
1186
1203
|
return full_text
|
|
1187
1204
|
|
|
1205
|
+
|
|
1188
1206
|
def print_vault_data(self, print_data: bool = True, return_data: bool = False):
|
|
1189
1207
|
'''
|
|
1190
1208
|
Function to print vault data
|
|
@@ -1223,6 +1241,7 @@ class Vault:
|
|
|
1223
1241
|
|
|
1224
1242
|
return vd if return_data else None
|
|
1225
1243
|
|
|
1244
|
+
|
|
1226
1245
|
def cloud_stream(self, function):
|
|
1227
1246
|
'''
|
|
1228
1247
|
For cloud application yielding the chat stream, like a flask app
|
|
@@ -1230,6 +1249,7 @@ class Vault:
|
|
|
1230
1249
|
for word in function:
|
|
1231
1250
|
yield f"data: {json.dumps({'data': word})} \n\n"
|
|
1232
1251
|
|
|
1252
|
+
|
|
1233
1253
|
class RateLimiter:
|
|
1234
1254
|
def __init__(self, max_attempts=30):
|
|
1235
1255
|
self.base_delay = 1 # Base delay of 1 second
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|