Upload 85 files
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- webscout/AIauto.py +493 -0
- webscout/AIbase.py +138 -0
- webscout/AIutel.py +995 -0
- webscout/DWEBS.py +197 -0
- webscout/LLM.py +45 -0
- webscout/Local/__init__.py +10 -0
- webscout/Local/__pycache__/__init__.cpython-311.pyc +0 -0
- webscout/Local/__pycache__/_version.cpython-311.pyc +0 -0
- webscout/Local/__pycache__/formats.cpython-311.pyc +0 -0
- webscout/Local/__pycache__/model.cpython-311.pyc +0 -0
- webscout/Local/__pycache__/samplers.cpython-311.pyc +0 -0
- webscout/Local/__pycache__/test.cpython-311.pyc +0 -0
- webscout/Local/__pycache__/thread.cpython-311.pyc +0 -0
- webscout/Local/__pycache__/utils.cpython-311.pyc +0 -0
- webscout/Local/_version.py +3 -0
- webscout/Local/formats.py +535 -0
- webscout/Local/model.py +702 -0
- webscout/Local/samplers.py +161 -0
- webscout/Local/thread.py +690 -0
- webscout/Local/utils.py +185 -0
- webscout/Provider/BasedGPT.py +226 -0
- webscout/Provider/Berlin4h.py +211 -0
- webscout/Provider/Blackboxai.py +440 -0
- webscout/Provider/ChatGPTUK.py +214 -0
- webscout/Provider/Cohere.py +223 -0
- webscout/Provider/Gemini.py +217 -0
- webscout/Provider/Groq.py +512 -0
- webscout/Provider/Koboldai.py +402 -0
- webscout/Provider/Leo.py +469 -0
- webscout/Provider/Llama2.py +437 -0
- webscout/Provider/OpenGPT.py +487 -0
- webscout/Provider/Openai.py +511 -0
- webscout/Provider/Perplexity.py +230 -0
- webscout/Provider/Phind.py +518 -0
- webscout/Provider/Poe.py +208 -0
- webscout/Provider/Reka.py +226 -0
- webscout/Provider/ThinkAnyAI.py +280 -0
- webscout/Provider/Xjai.py +230 -0
- webscout/Provider/Yepchat.py +478 -0
- webscout/Provider/Youchat.py +221 -0
- webscout/Provider/__init__.py +61 -0
- webscout/Provider/__pycache__/BasedGPT.cpython-311.pyc +0 -0
- webscout/Provider/__pycache__/Berlin4h.cpython-311.pyc +0 -0
- webscout/Provider/__pycache__/Blackboxai.cpython-311.pyc +0 -0
- webscout/Provider/__pycache__/ChatGPTUK.cpython-311.pyc +0 -0
- webscout/Provider/__pycache__/ChatGPTlogin.cpython-311.pyc +0 -0
- webscout/Provider/__pycache__/Cohere.cpython-311.pyc +0 -0
- webscout/Provider/__pycache__/Gemini.cpython-311.pyc +0 -0
- webscout/Provider/__pycache__/Groq.cpython-311.pyc +0 -0
- webscout/Provider/__pycache__/Koboldai.cpython-311.pyc +0 -0
webscout/AIauto.py
ADDED
@@ -0,0 +1,493 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from webscout.AIbase import Provider, AsyncProvider
|
2 |
+
from webscout import OPENGPT, AsyncOPENGPT
|
3 |
+
from webscout import KOBOLDAI, AsyncKOBOLDAI
|
4 |
+
from webscout import PhindSearch, AsyncPhindSearch
|
5 |
+
from webscout import LLAMA2, AsyncLLAMA2
|
6 |
+
from webscout import BLACKBOXAI, AsyncBLACKBOXAI
|
7 |
+
from webscout import PERPLEXITY
|
8 |
+
from webscout import ThinkAnyAI
|
9 |
+
from webscout import YouChat
|
10 |
+
from webscout import YEPCHAT
|
11 |
+
from webscout.AIbase import Provider, AsyncProvider
|
12 |
+
from webscout import KOBOLDAI, AsyncKOBOLDAI
|
13 |
+
from webscout import PhindSearch, AsyncPhindSearch
|
14 |
+
from webscout import LLAMA2, AsyncLLAMA2
|
15 |
+
from webscout import BLACKBOXAI, AsyncBLACKBOXAI
|
16 |
+
from webscout import PERPLEXITY
|
17 |
+
from webscout import ThinkAnyAI
|
18 |
+
from webscout import YouChat
|
19 |
+
from webscout import YEPCHAT, AsyncYEPCHAT
|
20 |
+
from webscout import LEO, AsyncLEO
|
21 |
+
from webscout import GROQ, AsyncGROQ
|
22 |
+
from webscout import OPENAI, AsyncOPENAI
|
23 |
+
from webscout import REKA
|
24 |
+
from webscout import Xjai
|
25 |
+
from webscout import Berlin4h
|
26 |
+
from webscout import ChatGPTUK
|
27 |
+
from webscout.g4f import GPT4FREE, AsyncGPT4FREE
|
28 |
+
from webscout.g4f import TestProviders
|
29 |
+
from webscout.exceptions import AllProvidersFailure
|
30 |
+
from webscout.async_providers import mapper as async_provider_map
|
31 |
+
from typing import AsyncGenerator
|
32 |
+
|
33 |
+
from typing import Union
|
34 |
+
from typing import Any
|
35 |
+
import logging
|
36 |
+
|
37 |
+
|
38 |
+
provider_map: dict[
|
39 |
+
str, Union[ ThinkAnyAI,
|
40 |
+
Xjai,
|
41 |
+
LLAMA2,
|
42 |
+
AsyncLLAMA2,
|
43 |
+
LEO,
|
44 |
+
AsyncLEO,
|
45 |
+
KOBOLDAI,
|
46 |
+
AsyncKOBOLDAI,
|
47 |
+
OPENGPT,
|
48 |
+
AsyncOPENGPT,
|
49 |
+
PERPLEXITY,
|
50 |
+
BLACKBOXAI,
|
51 |
+
AsyncBLACKBOXAI,
|
52 |
+
PhindSearch,
|
53 |
+
AsyncPhindSearch,
|
54 |
+
YEPCHAT,
|
55 |
+
AsyncYEPCHAT,
|
56 |
+
YouChat,
|
57 |
+
Berlin4h,
|
58 |
+
ChatGPTUK,]
|
59 |
+
] = {
|
60 |
+
"PhindSearch": PhindSearch,
|
61 |
+
"perplexity": PERPLEXITY,
|
62 |
+
"opengpt": OPENGPT,
|
63 |
+
"koboldai": KOBOLDAI,
|
64 |
+
"llama2": LLAMA2,
|
65 |
+
"blackboxai": BLACKBOXAI,
|
66 |
+
"gpt4free": GPT4FREE,
|
67 |
+
"thinkany": ThinkAnyAI,
|
68 |
+
"yepchat": YEPCHAT,
|
69 |
+
"you": YouChat,
|
70 |
+
"leo": LEO,
|
71 |
+
"xjai": Xjai,
|
72 |
+
"berlin4h": Berlin4h,
|
73 |
+
"chatgptuk": ChatGPTUK,
|
74 |
+
"gpt4free": GPT4FREE,
|
75 |
+
|
76 |
+
}
|
77 |
+
|
78 |
+
|
79 |
+
class AUTO(Provider):
|
80 |
+
def __init__(
|
81 |
+
self,
|
82 |
+
is_conversation: bool = True,
|
83 |
+
max_tokens: int = 600,
|
84 |
+
timeout: int = 30,
|
85 |
+
intro: str = None,
|
86 |
+
filepath: str = None,
|
87 |
+
update_file: bool = True,
|
88 |
+
proxies: dict = {},
|
89 |
+
history_offset: int = 10250,
|
90 |
+
act: str = None,
|
91 |
+
exclude: list[str] = [],
|
92 |
+
):
|
93 |
+
"""Instantiates AUTO
|
94 |
+
|
95 |
+
Args:
|
96 |
+
is_conversation (bool, optional): Flag for chatting conversationally. Defaults to True
|
97 |
+
max_tokens (int, optional): Maximum number of tokens to be generated upon completion. Defaults to 600.
|
98 |
+
timeout (int, optional): Http request timeout. Defaults to 30.
|
99 |
+
intro (str, optional): Conversation introductory prompt. Defaults to None.
|
100 |
+
filepath (str, optional): Path to file containing conversation history. Defaults to None.
|
101 |
+
update_file (bool, optional): Add new prompts and responses to the file. Defaults to True.
|
102 |
+
proxies (dict, optional): Http request proxies. Defaults to {}.
|
103 |
+
history_offset (int, optional): Limit conversation history to this number of last texts. Defaults to 10250.
|
104 |
+
act (str|int, optional): Awesome prompt key or index. (Used as intro). Defaults to None.
|
105 |
+
exclude(list[str], optional): List of providers to be excluded. Defaults to [].
|
106 |
+
"""
|
107 |
+
self.provider: Union[OPENGPT, KOBOLDAI, PhindSearch, LLAMA2, BLACKBOXAI, PERPLEXITY, GPT4FREE, ThinkAnyAI, YEPCHAT, YouChat] = None
|
108 |
+
self.provider_name: str = None
|
109 |
+
self.is_conversation = is_conversation
|
110 |
+
self.max_tokens = max_tokens
|
111 |
+
self.timeout = timeout
|
112 |
+
self.intro = intro
|
113 |
+
self.filepath = filepath
|
114 |
+
self.update_file = update_file
|
115 |
+
self.proxies = proxies
|
116 |
+
self.history_offset = history_offset
|
117 |
+
self.act = act
|
118 |
+
self.exclude = exclude
|
119 |
+
|
120 |
+
@property
|
121 |
+
def last_response(self) -> dict[str, Any]:
|
122 |
+
return self.provider.last_response
|
123 |
+
|
124 |
+
@property
|
125 |
+
def conversation(self) -> object:
|
126 |
+
return self.provider.conversation
|
127 |
+
|
128 |
+
def ask(
|
129 |
+
self,
|
130 |
+
prompt: str,
|
131 |
+
stream: bool = False,
|
132 |
+
raw: bool = False,
|
133 |
+
optimizer: str = None,
|
134 |
+
conversationally: bool = False,
|
135 |
+
run_new_test: bool = False,
|
136 |
+
) -> dict:
|
137 |
+
"""Chat with AI
|
138 |
+
|
139 |
+
Args:
|
140 |
+
prompt (str): Prompt to be send.
|
141 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
142 |
+
raw (bool, optional): Stream back raw response as received. Defaults to False.
|
143 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
144 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
145 |
+
run_new_test (bool, optional): Perform new test on g4f-based providers. Defaults to False.
|
146 |
+
Returns:
|
147 |
+
dict : {}
|
148 |
+
"""
|
149 |
+
ask_kwargs: dict[str, Union[str, bool]] = {
|
150 |
+
"prompt": prompt,
|
151 |
+
"stream": stream,
|
152 |
+
"raw": raw,
|
153 |
+
"optimizer": optimizer,
|
154 |
+
"conversationally": conversationally,
|
155 |
+
}
|
156 |
+
|
157 |
+
# webscout-based providers
|
158 |
+
for provider_name, provider_obj in provider_map.items():
|
159 |
+
# continue
|
160 |
+
if provider_name in self.exclude:
|
161 |
+
continue
|
162 |
+
try:
|
163 |
+
self.provider_name = f"webscout-{provider_name}"
|
164 |
+
self.provider = provider_obj(
|
165 |
+
is_conversation=self.is_conversation,
|
166 |
+
max_tokens=self.max_tokens,
|
167 |
+
timeout=self.timeout,
|
168 |
+
intro=self.intro,
|
169 |
+
filepath=self.filepath,
|
170 |
+
update_file=self.update_file,
|
171 |
+
proxies=self.proxies,
|
172 |
+
history_offset=self.history_offset,
|
173 |
+
act=self.act,
|
174 |
+
)
|
175 |
+
|
176 |
+
def for_stream():
|
177 |
+
for chunk in self.provider.ask(**ask_kwargs):
|
178 |
+
yield chunk
|
179 |
+
|
180 |
+
def for_non_stream():
|
181 |
+
return self.provider.ask(**ask_kwargs)
|
182 |
+
|
183 |
+
return for_stream() if stream else for_non_stream()
|
184 |
+
|
185 |
+
except Exception as e:
|
186 |
+
logging.debug(
|
187 |
+
f"Failed to generate response using provider {provider_name} - {e}"
|
188 |
+
)
|
189 |
+
|
190 |
+
# g4f-based providers
|
191 |
+
|
192 |
+
for provider_info in TestProviders(timeout=self.timeout).get_results(
|
193 |
+
run=run_new_test
|
194 |
+
):
|
195 |
+
if provider_info["name"] in self.exclude:
|
196 |
+
continue
|
197 |
+
try:
|
198 |
+
self.provider_name = f"g4f-{provider_info['name']}"
|
199 |
+
self.provider = GPT4FREE(
|
200 |
+
provider=provider_info["name"],
|
201 |
+
is_conversation=self.is_conversation,
|
202 |
+
max_tokens=self.max_tokens,
|
203 |
+
intro=self.intro,
|
204 |
+
filepath=self.filepath,
|
205 |
+
update_file=self.update_file,
|
206 |
+
proxies=self.proxies,
|
207 |
+
history_offset=self.history_offset,
|
208 |
+
act=self.act,
|
209 |
+
)
|
210 |
+
|
211 |
+
def for_stream():
|
212 |
+
for chunk in self.provider.ask(**ask_kwargs):
|
213 |
+
yield chunk
|
214 |
+
|
215 |
+
def for_non_stream():
|
216 |
+
return self.provider.ask(**ask_kwargs)
|
217 |
+
|
218 |
+
return for_stream() if stream else for_non_stream()
|
219 |
+
|
220 |
+
except Exception as e:
|
221 |
+
logging.debug(
|
222 |
+
f"Failed to generate response using GPT4FREE-base provider {provider_name} - {e}"
|
223 |
+
)
|
224 |
+
|
225 |
+
raise AllProvidersFailure(
|
226 |
+
"None of the providers generated response successfully."
|
227 |
+
)
|
228 |
+
|
229 |
+
def chat(
|
230 |
+
self,
|
231 |
+
prompt: str,
|
232 |
+
stream: bool = False,
|
233 |
+
optimizer: str = None,
|
234 |
+
conversationally: bool = False,
|
235 |
+
run_new_test: bool = False,
|
236 |
+
) -> str:
|
237 |
+
"""Generate response `str`
|
238 |
+
Args:
|
239 |
+
prompt (str): Prompt to be send.
|
240 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
241 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
242 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
243 |
+
run_new_test (bool, optional): Perform new test on g4f-based providers. Defaults to False.
|
244 |
+
Returns:
|
245 |
+
str: Response generated
|
246 |
+
"""
|
247 |
+
|
248 |
+
def for_stream():
|
249 |
+
for response in self.ask(
|
250 |
+
prompt,
|
251 |
+
True,
|
252 |
+
optimizer=optimizer,
|
253 |
+
conversationally=conversationally,
|
254 |
+
run_new_test=run_new_test,
|
255 |
+
):
|
256 |
+
yield self.get_message(response)
|
257 |
+
|
258 |
+
def for_non_stream():
|
259 |
+
ask_response = self.ask(
|
260 |
+
prompt,
|
261 |
+
False,
|
262 |
+
optimizer=optimizer,
|
263 |
+
conversationally=conversationally,
|
264 |
+
run_new_test=run_new_test,
|
265 |
+
)
|
266 |
+
return self.get_message(ask_response)
|
267 |
+
|
268 |
+
return for_stream() if stream else for_non_stream()
|
269 |
+
|
270 |
+
def get_message(self, response: dict) -> str:
|
271 |
+
"""Retrieves message only from response
|
272 |
+
|
273 |
+
Args:
|
274 |
+
response (dict): Response generated by `self.ask`
|
275 |
+
|
276 |
+
Returns:
|
277 |
+
str: Message extracted
|
278 |
+
"""
|
279 |
+
assert self.provider is not None, "Chat with AI first"
|
280 |
+
return self.provider.get_message(response)
|
281 |
+
|
282 |
+
|
283 |
+
class AsyncAUTO(AsyncProvider):
|
284 |
+
def __init__(
|
285 |
+
self,
|
286 |
+
is_conversation: bool = True,
|
287 |
+
max_tokens: int = 600,
|
288 |
+
timeout: int = 30,
|
289 |
+
intro: str = None,
|
290 |
+
filepath: str = None,
|
291 |
+
update_file: bool = True,
|
292 |
+
proxies: dict = {},
|
293 |
+
history_offset: int = 10250,
|
294 |
+
act: str = None,
|
295 |
+
exclude: list[str] = [],
|
296 |
+
):
|
297 |
+
"""Instantiates AsyncAUTO
|
298 |
+
|
299 |
+
Args:
|
300 |
+
is_conversation (bool, optional): Flag for chatting conversationally. Defaults to True
|
301 |
+
max_tokens (int, optional): Maximum number of tokens to be generated upon completion. Defaults to 600.
|
302 |
+
timeout (int, optional): Http request timeout. Defaults to 30.
|
303 |
+
intro (str, optional): Conversation introductory prompt. Defaults to None.
|
304 |
+
filepath (str, optional): Path to file containing conversation history. Defaults to None.
|
305 |
+
update_file (bool, optional): Add new prompts and responses to the file. Defaults to True.
|
306 |
+
proxies (dict, optional): Http request proxies. Defaults to {}.
|
307 |
+
history_offset (int, optional): Limit conversation history to this number of last texts. Defaults to 10250.
|
308 |
+
act (str|int, optional): Awesome prompt key or index. (Used as intro). Defaults to None.
|
309 |
+
exclude(list[str], optional): List of providers to be excluded. Defaults to [].
|
310 |
+
"""
|
311 |
+
self.provider: Union[
|
312 |
+
AsyncOPENGPT,
|
313 |
+
AsyncKOBOLDAI,
|
314 |
+
AsyncPhindSearch,
|
315 |
+
AsyncLLAMA2,
|
316 |
+
AsyncBLACKBOXAI,
|
317 |
+
AsyncGPT4FREE,
|
318 |
+
] = None
|
319 |
+
self.provider_name: str = None
|
320 |
+
self.is_conversation = is_conversation
|
321 |
+
self.max_tokens = max_tokens
|
322 |
+
self.timeout = timeout
|
323 |
+
self.intro = intro
|
324 |
+
self.filepath = filepath
|
325 |
+
self.update_file = update_file
|
326 |
+
self.proxies = proxies
|
327 |
+
self.history_offset = history_offset
|
328 |
+
self.act = act
|
329 |
+
self.exclude = exclude
|
330 |
+
|
331 |
+
@property
|
332 |
+
def last_response(self) -> dict[str, Any]:
|
333 |
+
return self.provider.last_response
|
334 |
+
|
335 |
+
@property
|
336 |
+
def conversation(self) -> object:
|
337 |
+
return self.provider.conversation
|
338 |
+
|
339 |
+
async def ask(
|
340 |
+
self,
|
341 |
+
prompt: str,
|
342 |
+
stream: bool = False,
|
343 |
+
raw: bool = False,
|
344 |
+
optimizer: str = None,
|
345 |
+
conversationally: bool = False,
|
346 |
+
run_new_test: bool = False,
|
347 |
+
) -> dict | AsyncGenerator:
|
348 |
+
"""Chat with AI asynchronously.
|
349 |
+
|
350 |
+
Args:
|
351 |
+
prompt (str): Prompt to be send.
|
352 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
353 |
+
raw (bool, optional): Stream back raw response as received. Defaults to False.
|
354 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
355 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
356 |
+
run_new_test (bool, optional): Perform new test on g4f-based providers. Defaults to False.
|
357 |
+
Returns:
|
358 |
+
dict|AsyncGenerator : ai response.
|
359 |
+
"""
|
360 |
+
ask_kwargs: dict[str, Union[str, bool]] = {
|
361 |
+
"prompt": prompt,
|
362 |
+
"stream": stream,
|
363 |
+
"raw": raw,
|
364 |
+
"optimizer": optimizer,
|
365 |
+
"conversationally": conversationally,
|
366 |
+
}
|
367 |
+
|
368 |
+
# tgpt-based providers
|
369 |
+
for provider_name, provider_obj in async_provider_map.items():
|
370 |
+
if provider_name in self.exclude:
|
371 |
+
continue
|
372 |
+
try:
|
373 |
+
self.provider_name = f"tgpt-{provider_name}"
|
374 |
+
self.provider = provider_obj(
|
375 |
+
is_conversation=self.is_conversation,
|
376 |
+
max_tokens=self.max_tokens,
|
377 |
+
timeout=self.timeout,
|
378 |
+
intro=self.intro,
|
379 |
+
filepath=self.filepath,
|
380 |
+
update_file=self.update_file,
|
381 |
+
proxies=self.proxies,
|
382 |
+
history_offset=self.history_offset,
|
383 |
+
act=self.act,
|
384 |
+
)
|
385 |
+
|
386 |
+
async def for_stream():
|
387 |
+
async_ask = await self.provider.ask(**ask_kwargs)
|
388 |
+
async for chunk in async_ask:
|
389 |
+
yield chunk
|
390 |
+
|
391 |
+
async def for_non_stream():
|
392 |
+
return await self.provider.ask(**ask_kwargs)
|
393 |
+
|
394 |
+
return for_stream() if stream else await for_non_stream()
|
395 |
+
|
396 |
+
except Exception as e:
|
397 |
+
logging.debug(
|
398 |
+
f"Failed to generate response using provider {provider_name} - {e}"
|
399 |
+
)
|
400 |
+
|
401 |
+
# g4f-based providers
|
402 |
+
|
403 |
+
for provider_info in TestProviders(timeout=self.timeout).get_results(
|
404 |
+
run=run_new_test
|
405 |
+
):
|
406 |
+
if provider_info["name"] in self.exclude:
|
407 |
+
continue
|
408 |
+
try:
|
409 |
+
self.provider_name = f"g4f-{provider_info['name']}"
|
410 |
+
self.provider = AsyncGPT4FREE(
|
411 |
+
provider=provider_info["name"],
|
412 |
+
is_conversation=self.is_conversation,
|
413 |
+
max_tokens=self.max_tokens,
|
414 |
+
intro=self.intro,
|
415 |
+
filepath=self.filepath,
|
416 |
+
update_file=self.update_file,
|
417 |
+
proxies=self.proxies,
|
418 |
+
history_offset=self.history_offset,
|
419 |
+
act=self.act,
|
420 |
+
)
|
421 |
+
|
422 |
+
async def for_stream():
|
423 |
+
async_ask = await self.provider.ask(**ask_kwargs)
|
424 |
+
async for chunk in async_ask:
|
425 |
+
yield chunk
|
426 |
+
|
427 |
+
async def for_non_stream():
|
428 |
+
return await self.provider.ask(**ask_kwargs)
|
429 |
+
|
430 |
+
return for_stream() if stream else await for_non_stream()
|
431 |
+
|
432 |
+
except Exception as e:
|
433 |
+
logging.debug(
|
434 |
+
f"Failed to generate response using GPT4FREE-base provider {provider_name} - {e}"
|
435 |
+
)
|
436 |
+
|
437 |
+
raise AllProvidersFailure(
|
438 |
+
"None of the providers generated response successfully."
|
439 |
+
)
|
440 |
+
|
441 |
+
async def chat(
|
442 |
+
self,
|
443 |
+
prompt: str,
|
444 |
+
stream: bool = False,
|
445 |
+
optimizer: str = None,
|
446 |
+
conversationally: bool = False,
|
447 |
+
run_new_test: bool = False,
|
448 |
+
) -> str | AsyncGenerator:
|
449 |
+
"""Generate response `str` asynchronously.
|
450 |
+
Args:
|
451 |
+
prompt (str): Prompt to be send.
|
452 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
453 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
454 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
455 |
+
run_new_test (bool, optional): Perform new test on g4f-based providers. Defaults to False.
|
456 |
+
Returns:
|
457 |
+
str|AsyncGenerator: Response generated
|
458 |
+
"""
|
459 |
+
|
460 |
+
async def for_stream():
|
461 |
+
async_ask = await self.ask(
|
462 |
+
prompt,
|
463 |
+
True,
|
464 |
+
optimizer=optimizer,
|
465 |
+
conversationally=conversationally,
|
466 |
+
run_new_test=run_new_test,
|
467 |
+
)
|
468 |
+
async for response in async_ask:
|
469 |
+
yield await self.get_message(response)
|
470 |
+
|
471 |
+
async def for_non_stream():
|
472 |
+
ask_response = await self.ask(
|
473 |
+
prompt,
|
474 |
+
False,
|
475 |
+
optimizer=optimizer,
|
476 |
+
conversationally=conversationally,
|
477 |
+
run_new_test=run_new_test,
|
478 |
+
)
|
479 |
+
return await self.get_message(ask_response)
|
480 |
+
|
481 |
+
return for_stream() if stream else await for_non_stream()
|
482 |
+
|
483 |
+
async def get_message(self, response: dict) -> str:
|
484 |
+
"""Retrieves message only from response
|
485 |
+
|
486 |
+
Args:
|
487 |
+
response (dict): Response generated by `self.ask`
|
488 |
+
|
489 |
+
Returns:
|
490 |
+
str: Message extracted
|
491 |
+
"""
|
492 |
+
assert self.provider is not None, "Chat with AI first"
|
493 |
+
return await self.provider.get_message(response)
|
webscout/AIbase.py
ADDED
@@ -0,0 +1,138 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from abc import ABC
|
2 |
+
from abc import abstractmethod
|
3 |
+
|
4 |
+
|
5 |
+
class Provider(ABC):
|
6 |
+
"""Base class for providers"""
|
7 |
+
|
8 |
+
@abstractmethod
|
9 |
+
def ask(
|
10 |
+
self,
|
11 |
+
prompt: str,
|
12 |
+
stream: bool = False,
|
13 |
+
raw: bool = False,
|
14 |
+
optimizer: str = None,
|
15 |
+
conversationally: bool = False,
|
16 |
+
) -> dict:
|
17 |
+
"""Chat with AI
|
18 |
+
|
19 |
+
Args:
|
20 |
+
prompt (str): Prompt to be sent
|
21 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
22 |
+
raw (bool, optional): Stream back raw response as received
|
23 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`
|
24 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
25 |
+
Returns:
|
26 |
+
dict : {}
|
27 |
+
```json
|
28 |
+
{
|
29 |
+
"completion": "\nNext: domestic cat breeds with short hair >>",
|
30 |
+
"stop_reason": null,
|
31 |
+
"truncated": false,
|
32 |
+
"stop": null,
|
33 |
+
"model": "llama-2-13b-chat",
|
34 |
+
"log_id": "cmpl-3kYiYxSNDvgMShSzFooz6t",
|
35 |
+
"exception": null
|
36 |
+
}
|
37 |
+
```
|
38 |
+
"""
|
39 |
+
raise NotImplementedError("Method needs to be implemented in subclass")
|
40 |
+
|
41 |
+
@abstractmethod
|
42 |
+
def chat(
|
43 |
+
self,
|
44 |
+
prompt: str,
|
45 |
+
stream: bool = False,
|
46 |
+
optimizer: str = None,
|
47 |
+
conversationally: bool = False,
|
48 |
+
) -> str:
|
49 |
+
"""Generate response `str`
|
50 |
+
Args:
|
51 |
+
prompt (str): Prompt to be sent
|
52 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
53 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`
|
54 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
55 |
+
Returns:
|
56 |
+
str: Response generated
|
57 |
+
"""
|
58 |
+
raise NotImplementedError("Method needs to be implemented in subclass")
|
59 |
+
|
60 |
+
@abstractmethod
|
61 |
+
def get_message(self, response: dict) -> str:
|
62 |
+
"""Retrieves message only from response
|
63 |
+
|
64 |
+
Args:
|
65 |
+
response (dict): Response generated by `self.ask`
|
66 |
+
|
67 |
+
Returns:
|
68 |
+
str: Message extracted
|
69 |
+
"""
|
70 |
+
raise NotImplementedError("Method needs to be implemented in subclass")
|
71 |
+
|
72 |
+
|
73 |
+
class AsyncProvider(ABC):
|
74 |
+
"""Asynchronous base class for providers"""
|
75 |
+
|
76 |
+
@abstractmethod
|
77 |
+
async def ask(
|
78 |
+
self,
|
79 |
+
prompt: str,
|
80 |
+
stream: bool = False,
|
81 |
+
raw: bool = False,
|
82 |
+
optimizer: str = None,
|
83 |
+
conversationally: bool = False,
|
84 |
+
) -> dict:
|
85 |
+
"""Asynchronously chat with AI
|
86 |
+
|
87 |
+
Args:
|
88 |
+
prompt (str): Prompt to be sent
|
89 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
90 |
+
raw (bool, optional): Stream back raw response as received
|
91 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`
|
92 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
93 |
+
Returns:
|
94 |
+
dict : {}
|
95 |
+
```json
|
96 |
+
{
|
97 |
+
"completion": "\nNext: domestic cat breeds with short hair >>",
|
98 |
+
"stop_reason": null,
|
99 |
+
"truncated": false,
|
100 |
+
"stop": null,
|
101 |
+
"model": "llama-2-13b-chat",
|
102 |
+
"log_id": "cmpl-3kYiYxSNDvgMShSzFooz6t",
|
103 |
+
"exception": null
|
104 |
+
}
|
105 |
+
```
|
106 |
+
"""
|
107 |
+
raise NotImplementedError("Method needs to be implemented in subclass")
|
108 |
+
|
109 |
+
@abstractmethod
|
110 |
+
async def chat(
|
111 |
+
self,
|
112 |
+
prompt: str,
|
113 |
+
stream: bool = False,
|
114 |
+
optimizer: str = None,
|
115 |
+
conversationally: bool = False,
|
116 |
+
) -> str:
|
117 |
+
"""Asynchronously generate response `str`
|
118 |
+
Args:
|
119 |
+
prompt (str): Prompt to be sent
|
120 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
121 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`
|
122 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
123 |
+
Returns:
|
124 |
+
str: Response generated
|
125 |
+
"""
|
126 |
+
raise NotImplementedError("Method needs to be implemented in subclass")
|
127 |
+
|
128 |
+
@abstractmethod
|
129 |
+
async def get_message(self, response: dict) -> str:
|
130 |
+
"""Asynchronously retrieves message only from response
|
131 |
+
|
132 |
+
Args:
|
133 |
+
response (dict): Response generated by `self.ask`
|
134 |
+
|
135 |
+
Returns:
|
136 |
+
str: Message extracted
|
137 |
+
"""
|
138 |
+
raise NotImplementedError("Method needs to be implemented in subclass")
|
webscout/AIutel.py
ADDED
@@ -0,0 +1,995 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import json
|
3 |
+
import platform
|
4 |
+
import subprocess
|
5 |
+
import logging
|
6 |
+
import appdirs
|
7 |
+
import datetime
|
8 |
+
import re
|
9 |
+
import sys
|
10 |
+
import click
|
11 |
+
from rich.markdown import Markdown
|
12 |
+
from rich.console import Console
|
13 |
+
import g4f
|
14 |
+
from typing import Union
|
15 |
+
from typing import NoReturn
|
16 |
+
import requests
|
17 |
+
from pathlib import Path
|
18 |
+
from playsound import playsound
|
19 |
+
from time import sleep as wait
|
20 |
+
import pathlib
|
21 |
+
import urllib.parse
|
22 |
+
appdir = appdirs.AppDirs("AIWEBS", "vortex")
|
23 |
+
|
24 |
+
default_path = appdir.user_cache_dir
|
25 |
+
|
26 |
+
if not os.path.exists(default_path):
|
27 |
+
os.makedirs(default_path)
|
28 |
+
webai = [
|
29 |
+
"leo",
|
30 |
+
"openai",
|
31 |
+
"opengpt",
|
32 |
+
"koboldai",
|
33 |
+
"gemini",
|
34 |
+
"phind",
|
35 |
+
"blackboxai",
|
36 |
+
"g4fauto",
|
37 |
+
"perplexity",
|
38 |
+
"groq",
|
39 |
+
"reka",
|
40 |
+
"cohere",
|
41 |
+
"yepchat",
|
42 |
+
"you",
|
43 |
+
"xjai",
|
44 |
+
"thinkany",
|
45 |
+
"berlin4h",
|
46 |
+
"chatgptuk",
|
47 |
+
"auto",
|
48 |
+
"poe",
|
49 |
+
]
|
50 |
+
gpt4free_providers = [
|
51 |
+
provider.__name__ for provider in g4f.Provider.__providers__ # if provider.working
|
52 |
+
]
|
53 |
+
|
54 |
+
available_providers = webai + gpt4free_providers
|
55 |
+
def sanitize_stream(
|
56 |
+
chunk: str, intro_value: str = "data:", to_json: bool = True
|
57 |
+
) -> str | dict:
|
58 |
+
"""Remove streaming flags
|
59 |
+
|
60 |
+
Args:
|
61 |
+
chunk (str): Streamig chunk.
|
62 |
+
intro_value (str, optional): streaming flag. Defaults to "data:".
|
63 |
+
to_json (bool, optional). Return chunk as dictionary. Defaults to True.
|
64 |
+
|
65 |
+
Returns:
|
66 |
+
str: Sanitized streaming value.
|
67 |
+
"""
|
68 |
+
|
69 |
+
if chunk.startswith(intro_value):
|
70 |
+
chunk = chunk[len(intro_value) :]
|
71 |
+
|
72 |
+
return json.loads(chunk) if to_json else chunk
|
73 |
+
def run_system_command(
|
74 |
+
command: str,
|
75 |
+
exit_on_error: bool = True,
|
76 |
+
stdout_error: bool = True,
|
77 |
+
help: str = None,
|
78 |
+
):
|
79 |
+
"""Run commands against system
|
80 |
+
Args:
|
81 |
+
command (str): shell command
|
82 |
+
exit_on_error (bool, optional): Exit on error. Defaults to True.
|
83 |
+
stdout_error (bool, optional): Print out the error. Defaults to True
|
84 |
+
help (str, optional): Help info incase of exception. Defaults to None.
|
85 |
+
Returns:
|
86 |
+
tuple : (is_successfull, object[Exception|Subprocess.run])
|
87 |
+
"""
|
88 |
+
try:
|
89 |
+
# Run the command and capture the output
|
90 |
+
result = subprocess.run(
|
91 |
+
command,
|
92 |
+
shell=True,
|
93 |
+
check=True,
|
94 |
+
text=True,
|
95 |
+
stdout=subprocess.PIPE,
|
96 |
+
stderr=subprocess.PIPE,
|
97 |
+
)
|
98 |
+
return (True, result)
|
99 |
+
except subprocess.CalledProcessError as e:
|
100 |
+
# Handle error if the command returns a non-zero exit code
|
101 |
+
if stdout_error:
|
102 |
+
click.secho(f"Error Occurred: while running '{command}'", fg="yellow")
|
103 |
+
click.secho(e.stderr, fg="red")
|
104 |
+
if help is not None:
|
105 |
+
click.secho(help, fg="cyan")
|
106 |
+
sys.exit(e.returncode) if exit_on_error else None
|
107 |
+
return (False, e)
|
108 |
+
|
109 |
+
|
110 |
+
class Optimizers:
|
111 |
+
@staticmethod
|
112 |
+
def code(prompt):
|
113 |
+
return (
|
114 |
+
"Your Role: Provide only code as output without any description.\n"
|
115 |
+
"IMPORTANT: Provide only plain text without Markdown formatting.\n"
|
116 |
+
"IMPORTANT: Do not include markdown formatting."
|
117 |
+
"If there is a lack of details, provide most logical solution. You are not allowed to ask for more details."
|
118 |
+
"Ignore any potential risk of errors or confusion.\n\n"
|
119 |
+
f"Request: {prompt}\n"
|
120 |
+
f"Code:"
|
121 |
+
)
|
122 |
+
|
123 |
+
@staticmethod
|
124 |
+
def shell_command(prompt):
|
125 |
+
# Get os
|
126 |
+
operating_system = ""
|
127 |
+
if platform.system() == "Windows":
|
128 |
+
operating_system = "Windows"
|
129 |
+
elif platform.system() == "Darwin":
|
130 |
+
operating_system = "MacOS"
|
131 |
+
elif platform.system() == "Linux":
|
132 |
+
try:
|
133 |
+
result = (
|
134 |
+
subprocess.check_output(["lsb_release", "-si"]).decode().strip()
|
135 |
+
)
|
136 |
+
distro = result if result else ""
|
137 |
+
operating_system = f"Linux/{distro}"
|
138 |
+
except Exception:
|
139 |
+
operating_system = "Linux"
|
140 |
+
else:
|
141 |
+
operating_system = platform.system()
|
142 |
+
|
143 |
+
# Get Shell
|
144 |
+
shell_name = "/bin/sh"
|
145 |
+
if platform.system() == "Windows":
|
146 |
+
shell_name = "cmd.exe"
|
147 |
+
if os.getenv("PSModulePath"):
|
148 |
+
shell_name = "powershell.exe"
|
149 |
+
else:
|
150 |
+
shell_env = os.getenv("SHELL")
|
151 |
+
if shell_env:
|
152 |
+
shell_name = shell_env
|
153 |
+
|
154 |
+
return (
|
155 |
+
"Your role: Provide only plain text without Markdown formatting. "
|
156 |
+
"Do not show any warnings or information regarding your capabilities. "
|
157 |
+
"Do not provide any description. If you need to store any data, "
|
158 |
+
f"assume it will be stored in the chat. Provide only {shell_name} "
|
159 |
+
f"command for {operating_system} without any description. If there is "
|
160 |
+
"a lack of details, provide most logical solution. Ensure the output "
|
161 |
+
"is a valid shell command. If multiple steps required try to combine "
|
162 |
+
f"them together. Prompt: {prompt}\n\nCommand:"
|
163 |
+
)
|
164 |
+
|
165 |
+
|
166 |
+
class Conversation:
|
167 |
+
"""Handles prompt generation based on history"""
|
168 |
+
|
169 |
+
intro = (
|
170 |
+
"You're a Large Language Model for chatting with people. "
|
171 |
+
"Assume role of the LLM and give your response."
|
172 |
+
# "Refrain from regenerating the conversation between user and LLM."
|
173 |
+
)
|
174 |
+
|
175 |
+
def __init__(
|
176 |
+
self,
|
177 |
+
status: bool = True,
|
178 |
+
max_tokens: int = 600,
|
179 |
+
filepath: str = None,
|
180 |
+
update_file: bool = True,
|
181 |
+
):
|
182 |
+
"""Initializes Conversation
|
183 |
+
|
184 |
+
Args:
|
185 |
+
status (bool, optional): Flag to control history. Defaults to True.
|
186 |
+
max_tokens (int, optional): Maximum number of tokens to be generated upon completion. Defaults to 600.
|
187 |
+
filepath (str, optional): Path to file containing conversation history. Defaults to None.
|
188 |
+
update_file (bool, optional): Add new prompts and responses to the file. Defaults to True.
|
189 |
+
"""
|
190 |
+
self.status = status
|
191 |
+
self.max_tokens_to_sample = max_tokens
|
192 |
+
self.chat_history = self.intro
|
193 |
+
self.history_format = "\nUser : %(user)s\nLLM :%(llm)s"
|
194 |
+
self.file = filepath
|
195 |
+
self.update_file = update_file
|
196 |
+
self.history_offset = 10250
|
197 |
+
self.prompt_allowance = 10
|
198 |
+
self.load_conversation(filepath, False) if filepath else None
|
199 |
+
|
200 |
+
def load_conversation(self, filepath: str, exists: bool = True) -> None:
|
201 |
+
"""Load conversation into chat's history from .txt file
|
202 |
+
|
203 |
+
Args:
|
204 |
+
filepath (str): Path to .txt file
|
205 |
+
exists (bool, optional): Flag for file availability. Defaults to True.
|
206 |
+
"""
|
207 |
+
assert isinstance(
|
208 |
+
filepath, str
|
209 |
+
), f"Filepath needs to be of str datatype not {type(filepath)}"
|
210 |
+
assert (
|
211 |
+
os.path.isfile(filepath) if exists else True
|
212 |
+
), f"File '{filepath}' does not exist"
|
213 |
+
if not os.path.isfile(filepath):
|
214 |
+
logging.debug(f"Creating new chat-history file - '{filepath}'")
|
215 |
+
with open(filepath, "w") as fh: # Try creating new file
|
216 |
+
# lets add intro here
|
217 |
+
fh.write(self.intro)
|
218 |
+
else:
|
219 |
+
logging.debug(f"Loading conversation from '{filepath}'")
|
220 |
+
with open(filepath) as fh:
|
221 |
+
file_contents = fh.read()
|
222 |
+
# Presume intro prompt is part of the file content
|
223 |
+
self.chat_history = file_contents
|
224 |
+
|
225 |
+
def __trim_chat_history(self, chat_history: str) -> str:
|
226 |
+
"""Ensures the len(prompt) and max_tokens_to_sample is not > 4096"""
|
227 |
+
len_of_intro = len(self.intro)
|
228 |
+
len_of_chat_history = len(chat_history)
|
229 |
+
total = (
|
230 |
+
self.max_tokens_to_sample + len_of_intro + len_of_chat_history
|
231 |
+
) # + self.max_tokens_to_sample
|
232 |
+
if total > self.history_offset:
|
233 |
+
truncate_at = (total - self.history_offset) + self.prompt_allowance
|
234 |
+
# Remove head of total (n) of chat_history
|
235 |
+
new_chat_history = chat_history[truncate_at:]
|
236 |
+
self.chat_history = self.intro + "\n... " + new_chat_history
|
237 |
+
# print(len(self.chat_history))
|
238 |
+
return self.chat_history
|
239 |
+
# print(len(chat_history))
|
240 |
+
return chat_history
|
241 |
+
|
242 |
+
def gen_complete_prompt(self, prompt: str) -> str:
|
243 |
+
"""Generates a kinda like incomplete conversation
|
244 |
+
|
245 |
+
Args:
|
246 |
+
prompt (str): _description_
|
247 |
+
|
248 |
+
Returns:
|
249 |
+
str: Updated incomplete chat_history
|
250 |
+
"""
|
251 |
+
if self.status:
|
252 |
+
resp = self.chat_history + self.history_format % dict(user=prompt, llm="")
|
253 |
+
return self.__trim_chat_history(resp)
|
254 |
+
|
255 |
+
return prompt
|
256 |
+
|
257 |
+
def update_chat_history(
|
258 |
+
self, prompt: str, response: str, force: bool = False
|
259 |
+
) -> None:
|
260 |
+
"""Updates chat history
|
261 |
+
|
262 |
+
Args:
|
263 |
+
prompt (str): user prompt
|
264 |
+
response (str): LLM response
|
265 |
+
force (bool, optional): Force update
|
266 |
+
"""
|
267 |
+
if not self.status and not force:
|
268 |
+
return
|
269 |
+
new_history = self.history_format % dict(user=prompt, llm=response)
|
270 |
+
if self.file and self.update_file:
|
271 |
+
with open(self.file, "a") as fh:
|
272 |
+
fh.write(new_history)
|
273 |
+
self.chat_history += new_history
|
274 |
+
|
275 |
+
|
276 |
+
class AwesomePrompts:
|
277 |
+
awesome_prompt_url = (
|
278 |
+
"https://raw.githubusercontent.com/OE-LUCIFER/prompts/main/prompt.json"
|
279 |
+
)
|
280 |
+
awesome_prompt_path = os.path.join(default_path, "all-acts.json")
|
281 |
+
|
282 |
+
__is_prompt_updated = False
|
283 |
+
|
284 |
+
def __init__(self):
|
285 |
+
self.acts = self.all_acts
|
286 |
+
|
287 |
+
def __search_key(self, key: str, raise_not_found: bool = False) -> str:
|
288 |
+
"""Perform insentive awesome-prompt key search
|
289 |
+
|
290 |
+
Args:
|
291 |
+
key (str): key
|
292 |
+
raise_not_found (bool, optional): Control KeyError exception. Defaults to False.
|
293 |
+
|
294 |
+
Returns:
|
295 |
+
str|None: Exact key name
|
296 |
+
"""
|
297 |
+
for key_, value in self.all_acts.items():
|
298 |
+
if str(key).lower() in str(key_).lower():
|
299 |
+
return key_
|
300 |
+
if raise_not_found:
|
301 |
+
raise KeyError(f"Zero awesome prompt found with key - `{key}`")
|
302 |
+
|
303 |
+
def get_acts(self):
|
304 |
+
"""Retrieves all awesome-prompts"""
|
305 |
+
with open(self.awesome_prompt_path) as fh:
|
306 |
+
prompt_dict = json.load(fh)
|
307 |
+
return prompt_dict
|
308 |
+
|
309 |
+
def update_prompts_from_online(self, override: bool = False):
|
310 |
+
"""Download awesome-prompts and update existing ones if available
|
311 |
+
args:
|
312 |
+
override (bool, optional): Overwrite existing contents in path
|
313 |
+
"""
|
314 |
+
resp = {}
|
315 |
+
if not self.__is_prompt_updated:
|
316 |
+
import requests
|
317 |
+
|
318 |
+
logging.info("Downloading & updating awesome prompts")
|
319 |
+
response = requests.get(self.awesome_prompt_url)
|
320 |
+
response.raise_for_status
|
321 |
+
resp.update(response.json())
|
322 |
+
if os.path.isfile(self.awesome_prompt_path) and not override:
|
323 |
+
resp.update(self.get_acts())
|
324 |
+
self.__is_prompt_updated = True
|
325 |
+
with open(self.awesome_prompt_path, "w") as fh:
|
326 |
+
json.dump(resp, fh, indent=4)
|
327 |
+
else:
|
328 |
+
logging.debug("Ignoring remote prompt update")
|
329 |
+
|
330 |
+
@property
|
331 |
+
def all_acts(self) -> dict:
|
332 |
+
"""All awesome_prompts & their indexes mapped to values
|
333 |
+
|
334 |
+
Returns:
|
335 |
+
dict: Awesome-prompts
|
336 |
+
"""
|
337 |
+
|
338 |
+
resp = {}
|
339 |
+
if not os.path.isfile(self.awesome_prompt_path):
|
340 |
+
self.update_prompts_from_online()
|
341 |
+
resp.update(self.get_acts())
|
342 |
+
|
343 |
+
for count, key_value in enumerate(self.get_acts().items()):
|
344 |
+
# Lets map also index to the value
|
345 |
+
resp.update({count: key_value[1]})
|
346 |
+
|
347 |
+
return resp
|
348 |
+
|
349 |
+
def get_act(
|
350 |
+
self,
|
351 |
+
key: str,
|
352 |
+
default: str = None,
|
353 |
+
case_insensitive: bool = True,
|
354 |
+
raise_not_found: bool = False,
|
355 |
+
) -> str:
|
356 |
+
"""Retrieves specific act of awesome_prompt
|
357 |
+
|
358 |
+
Args:
|
359 |
+
key (str|int): Act name or index
|
360 |
+
default (str): Value to be returned incase act not found.
|
361 |
+
case_insensitive (bool): Perform search key insensitive. Defaults to True.
|
362 |
+
raise_not_found (bool, optional): Control KeyError exception. Defaults to False.
|
363 |
+
|
364 |
+
Raises:
|
365 |
+
KeyError: Incase key not found
|
366 |
+
|
367 |
+
Returns:
|
368 |
+
str: Awesome prompt value
|
369 |
+
"""
|
370 |
+
if str(key).isdigit():
|
371 |
+
key = int(key)
|
372 |
+
act = self.all_acts.get(key, default)
|
373 |
+
if not act and case_insensitive:
|
374 |
+
act = self.all_acts.get(self.__search_key(key, raise_not_found))
|
375 |
+
return act
|
376 |
+
|
377 |
+
def add_prompt(self, name: str, prompt: str) -> bool:
|
378 |
+
"""Add new prompt or update an existing one.
|
379 |
+
|
380 |
+
Args:
|
381 |
+
name (str): act name
|
382 |
+
prompt (str): prompt value
|
383 |
+
"""
|
384 |
+
current_prompts = self.get_acts()
|
385 |
+
with open(self.awesome_prompt_path, "w") as fh:
|
386 |
+
current_prompts[name] = prompt
|
387 |
+
json.dump(current_prompts, fh, indent=4)
|
388 |
+
logging.info(f"New prompt added successfully - `{name}`")
|
389 |
+
|
390 |
+
def delete_prompt(
|
391 |
+
self, name: str, case_insensitive: bool = True, raise_not_found: bool = False
|
392 |
+
) -> bool:
|
393 |
+
"""Delete an existing prompt
|
394 |
+
|
395 |
+
Args:
|
396 |
+
name (str): act name
|
397 |
+
case_insensitive(bool, optional): Ignore the key cases. Defaults to True.
|
398 |
+
raise_not_found (bool, optional): Control KeyError exception. Default is False.
|
399 |
+
Returns:
|
400 |
+
bool: is_successful report
|
401 |
+
"""
|
402 |
+
name = self.__search_key(name, raise_not_found) if case_insensitive else name
|
403 |
+
current_prompts = self.get_acts()
|
404 |
+
is_name_available = (
|
405 |
+
current_prompts[name] if raise_not_found else current_prompts.get(name)
|
406 |
+
)
|
407 |
+
if is_name_available:
|
408 |
+
with open(self.awesome_prompt_path, "w") as fh:
|
409 |
+
current_prompts.pop(name)
|
410 |
+
json.dump(current_prompts, fh, indent=4)
|
411 |
+
logging.info(f"Prompt deleted successfully - `{name}`")
|
412 |
+
else:
|
413 |
+
return False
|
414 |
+
|
415 |
+
|
416 |
+
class Updates:
|
417 |
+
"""Webscout latest release info"""
|
418 |
+
|
419 |
+
url = "https://api.github.com/repos/OE-LUCIFER/Webscout/releases/latest"
|
420 |
+
|
421 |
+
@property
|
422 |
+
def latest_version(self):
|
423 |
+
return self.latest(version=True)
|
424 |
+
|
425 |
+
def executable(self, system: str = platform.system()) -> str:
|
426 |
+
"""Url pointing to executable for particular system
|
427 |
+
|
428 |
+
Args:
|
429 |
+
system (str, optional): system name. Defaults to platform.system().
|
430 |
+
|
431 |
+
Returns:
|
432 |
+
str: url
|
433 |
+
"""
|
434 |
+
for entry in self.latest()["assets"]:
|
435 |
+
if entry.get("target") == system:
|
436 |
+
return entry.get("url")
|
437 |
+
|
438 |
+
def latest(self, whole: bool = False, version: bool = False) -> dict:
|
439 |
+
"""Check Webscout latest version info
|
440 |
+
|
441 |
+
Args:
|
442 |
+
whole (bool, optional): Return whole json response. Defaults to False.
|
443 |
+
version (bool, optional): return version only. Defaults to False.
|
444 |
+
|
445 |
+
Returns:
|
446 |
+
bool|dict: version str or whole dict info
|
447 |
+
"""
|
448 |
+
import requests
|
449 |
+
|
450 |
+
data = requests.get(self.url).json()
|
451 |
+
if whole:
|
452 |
+
return data
|
453 |
+
|
454 |
+
elif version:
|
455 |
+
return data.get("tag_name")
|
456 |
+
|
457 |
+
else:
|
458 |
+
sorted = dict(
|
459 |
+
tag_name=data.get("tag_name"),
|
460 |
+
tarball_url=data.get("tarball_url"),
|
461 |
+
zipball_url=data.get("zipball_url"),
|
462 |
+
html_url=data.get("html_url"),
|
463 |
+
body=data.get("body"),
|
464 |
+
)
|
465 |
+
whole_assets = []
|
466 |
+
for entry in data.get("assets"):
|
467 |
+
url = entry.get("browser_download_url")
|
468 |
+
assets = dict(url=url, size=entry.get("size"))
|
469 |
+
if ".deb" in url:
|
470 |
+
assets["target"] = "Debian"
|
471 |
+
elif ".exe" in url:
|
472 |
+
assets["target"] = "Windows"
|
473 |
+
elif "macos" in url:
|
474 |
+
assets["target"] = "Mac"
|
475 |
+
elif "linux" in url:
|
476 |
+
assets["target"] = "Linux"
|
477 |
+
|
478 |
+
whole_assets.append(assets)
|
479 |
+
sorted["assets"] = whole_assets
|
480 |
+
|
481 |
+
return sorted
|
482 |
+
|
483 |
+
|
484 |
+
class RawDog:
|
485 |
+
"""Generate and auto-execute Python scripts in the cli"""
|
486 |
+
|
487 |
+
examples = """\
|
488 |
+
EXAMPLES:
|
489 |
+
|
490 |
+
1. User: Kill the process running on port 3000
|
491 |
+
|
492 |
+
LLM:
|
493 |
+
```python
|
494 |
+
import os
|
495 |
+
os.system("kill $(lsof -t -i:3000)")
|
496 |
+
print("Process killed")
|
497 |
+
```
|
498 |
+
|
499 |
+
2. User: Summarize my essay
|
500 |
+
|
501 |
+
LLM:
|
502 |
+
```python
|
503 |
+
import glob
|
504 |
+
files = glob.glob("*essay*.*")
|
505 |
+
with open(files[0], "r") as f:
|
506 |
+
print(f.read())
|
507 |
+
```
|
508 |
+
CONTINUE
|
509 |
+
|
510 |
+
User:
|
511 |
+
LAST SCRIPT OUTPUT:
|
512 |
+
John Smith
|
513 |
+
Essay 2021-09-01
|
514 |
+
...
|
515 |
+
|
516 |
+
LLM:
|
517 |
+
```python
|
518 |
+
print("The essay is about...")
|
519 |
+
```
|
520 |
+
"""
|
521 |
+
|
522 |
+
|
523 |
+
def __init__(
|
524 |
+
self,
|
525 |
+
quiet: bool = False,
|
526 |
+
internal_exec: bool = False,
|
527 |
+
confirm_script: bool = False,
|
528 |
+
interpreter: str = "python",
|
529 |
+
prettify: bool = True,
|
530 |
+
):
|
531 |
+
"""Constructor
|
532 |
+
|
533 |
+
Args:
|
534 |
+
quiet (bool, optional): Flag for control logging. Defaults to False.
|
535 |
+
internal_exec (bool, optional): Execute scripts with exec function. Defaults to False.
|
536 |
+
confirm_script (bool, optional): Give consent to scripts prior to execution. Defaults to False.
|
537 |
+
interpreter (str, optional): Python's interpreter name. Defaults to Python.
|
538 |
+
prettify (bool, optional): Prettify the code on stdout. Defaults to True.
|
539 |
+
"""
|
540 |
+
if not quiet:
|
541 |
+
print(
|
542 |
+
"To get the most out of Rawdog. Ensure the following are installed:\n"
|
543 |
+
" 1. Python 3.x\n"
|
544 |
+
" 2. Dependency:\n"
|
545 |
+
" - Matplotlib\n"
|
546 |
+
"Be alerted on the risk posed! (Experimental)\n"
|
547 |
+
"Use '--quiet' to suppress this message and code/logs stdout.\n"
|
548 |
+
)
|
549 |
+
self.internal_exec = internal_exec
|
550 |
+
self.confirm_script = confirm_script
|
551 |
+
self.quiet = quiet
|
552 |
+
self.interpreter = interpreter
|
553 |
+
self.prettify = prettify
|
554 |
+
self.python_version = (
|
555 |
+
f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
556 |
+
if self.internal_exec
|
557 |
+
else run_system_command(
|
558 |
+
f"{self.interpreter} --version",
|
559 |
+
exit_on_error=True,
|
560 |
+
stdout_error=True,
|
561 |
+
help="If you're using Webscout-cli, use the flag '--internal-exec'",
|
562 |
+
)[1].stdout.split(" ")[1]
|
563 |
+
)
|
564 |
+
|
565 |
+
@property
|
566 |
+
def intro_prompt(self):
|
567 |
+
return f"""
|
568 |
+
You are a command-line coding assistant called Rawdog that generates and auto-executes Python scripts.
|
569 |
+
|
570 |
+
A typical interaction goes like this:
|
571 |
+
1. The user gives you a natural language PROMPT.
|
572 |
+
2. You:
|
573 |
+
i. Determine what needs to be done
|
574 |
+
ii. Write a short Python SCRIPT to do it
|
575 |
+
iii. Communicate back to the user by printing to the console in that SCRIPT
|
576 |
+
3. The compiler extracts the script and then runs it using exec(). If there will be an exception raised,
|
577 |
+
it will be send back to you starting with "PREVIOUS SCRIPT EXCEPTION:".
|
578 |
+
4. In case of exception, regenerate error free script.
|
579 |
+
|
580 |
+
If you need to review script outputs before completing the task, you can print the word "CONTINUE" at the end of your SCRIPT.
|
581 |
+
This can be useful for summarizing documents or technical readouts, reading instructions before
|
582 |
+
deciding what to do, or other tasks that require multi-step reasoning.
|
583 |
+
A typical 'CONTINUE' interaction looks like this:
|
584 |
+
1. The user gives you a natural language PROMPT.
|
585 |
+
2. You:
|
586 |
+
i. Determine what needs to be done
|
587 |
+
ii. Determine that you need to see the output of some subprocess call to complete the task
|
588 |
+
iii. Write a short Python SCRIPT to print that and then print the word "CONTINUE"
|
589 |
+
3. The compiler
|
590 |
+
i. Checks and runs your SCRIPT
|
591 |
+
ii. Captures the output and appends it to the conversation as "LAST SCRIPT OUTPUT:"
|
592 |
+
iii. Finds the word "CONTINUE" and sends control back to you
|
593 |
+
4. You again:
|
594 |
+
i. Look at the original PROMPT + the "LAST SCRIPT OUTPUT:" to determine what needs to be done
|
595 |
+
ii. Write a short Python SCRIPT to do it
|
596 |
+
iii. Communicate back to the user by printing to the console in that SCRIPT
|
597 |
+
5. The compiler...
|
598 |
+
|
599 |
+
Please follow these conventions carefully:
|
600 |
+
- Decline any tasks that seem dangerous, irreversible, or that you don't understand.
|
601 |
+
- Always review the full conversation prior to answering and maintain continuity.
|
602 |
+
- If asked for information, just print the information clearly and concisely.
|
603 |
+
- If asked to do something, print a concise summary of what you've done as confirmation.
|
604 |
+
- If asked a question, respond in a friendly, conversational way. Use programmatically-generated and natural language responses as appropriate.
|
605 |
+
- If you need clarification, return a SCRIPT that prints your question. In the next interaction, continue based on the user's response.
|
606 |
+
- Assume the user would like something concise. For example rather than printing a massive table, filter or summarize it to what's likely of interest.
|
607 |
+
- Actively clean up any temporary processes or files you use.
|
608 |
+
- When looking through files, use git as available to skip files, and skip hidden files (.env, .git, etc) by default.
|
609 |
+
- You can plot anything with matplotlib.
|
610 |
+
- ALWAYS Return your SCRIPT inside of a single pair of ``` delimiters. Only the console output of the first such SCRIPT is visible to the user, so make sure that it's complete and don't bother returning anything else.
|
611 |
+
|
612 |
+
{self.examples}
|
613 |
+
|
614 |
+
Current system : {platform.system()}
|
615 |
+
Python version : {self.python_version}
|
616 |
+
Current directory : {os.getcwd()}
|
617 |
+
Current Datetime : {datetime.datetime.now()}
|
618 |
+
"""
|
619 |
+
|
620 |
+
def stdout(self, message: str) -> None:
|
621 |
+
"""Stdout data
|
622 |
+
|
623 |
+
Args:
|
624 |
+
message (str): Text to be printed
|
625 |
+
"""
|
626 |
+
if self.prettify:
|
627 |
+
Console().print(Markdown(message))
|
628 |
+
else:
|
629 |
+
click.secho(message, fg="yellow")
|
630 |
+
|
631 |
+
def log(self, message: str, category: str = "info"):
|
632 |
+
"""RawDog logger
|
633 |
+
|
634 |
+
Args:
|
635 |
+
message (str): Log message
|
636 |
+
category (str, optional): Log level. Defaults to 'info'.
|
637 |
+
"""
|
638 |
+
if self.quiet:
|
639 |
+
return
|
640 |
+
|
641 |
+
message = "[Webscout] - " + message
|
642 |
+
if category == "error":
|
643 |
+
logging.error(message)
|
644 |
+
else:
|
645 |
+
logging.info(message)
|
646 |
+
|
647 |
+
def main(self, response: str) -> None:
|
648 |
+
"""Exec code in response accordingly
|
649 |
+
|
650 |
+
Args:
|
651 |
+
response (str): AI response
|
652 |
+
|
653 |
+
Returns:
|
654 |
+
None|str: None if script executed successfully else stdout data
|
655 |
+
"""
|
656 |
+
code_blocks = re.findall(r"```python.*?```", response, re.DOTALL)
|
657 |
+
if len(code_blocks) != 1:
|
658 |
+
self.stdout(response)
|
659 |
+
|
660 |
+
else:
|
661 |
+
raw_code = code_blocks[0]
|
662 |
+
|
663 |
+
if self.confirm_script:
|
664 |
+
self.stdout(raw_code)
|
665 |
+
if not click.confirm("- Do you wish to execute this"):
|
666 |
+
return
|
667 |
+
|
668 |
+
elif not self.quiet:
|
669 |
+
self.stdout(raw_code)
|
670 |
+
|
671 |
+
raw_code_plus = re.sub(r"(```)(python)?", "", raw_code)
|
672 |
+
|
673 |
+
if "CONTINUE" in response or not self.internal_exec:
|
674 |
+
self.log("Executing script externally")
|
675 |
+
path_to_script = os.path.join(default_path, "execute_this.py")
|
676 |
+
with open(path_to_script, "w") as fh:
|
677 |
+
fh.write(raw_code_plus)
|
678 |
+
if "CONTINUE" in response:
|
679 |
+
|
680 |
+
success, proc = run_system_command(
|
681 |
+
f"{self.interpreter} {path_to_script}",
|
682 |
+
exit_on_error=False,
|
683 |
+
stdout_error=False,
|
684 |
+
)
|
685 |
+
|
686 |
+
if success:
|
687 |
+
self.log("Returning success feedback")
|
688 |
+
return f"LAST SCRIPT OUTPUT:\n{proc.stdout}"
|
689 |
+
else:
|
690 |
+
self.log("Returning error feedback", "error")
|
691 |
+
return f"PREVIOUS SCRIPT EXCEPTION:\n{proc.stderr}"
|
692 |
+
else:
|
693 |
+
os.system(f"{self.interpreter} {path_to_script}")
|
694 |
+
|
695 |
+
else:
|
696 |
+
try:
|
697 |
+
self.log("Executing script internally")
|
698 |
+
exec(raw_code_plus)
|
699 |
+
except Exception as e:
|
700 |
+
self.log(
|
701 |
+
"Exception occurred while executing script. Responding with error: "
|
702 |
+
f"{e.args[1] if len(e.args)>1 else str(e)}",
|
703 |
+
"error",
|
704 |
+
)
|
705 |
+
return f"PREVIOUS SCRIPT EXCEPTION:\n{str(e)}"
|
706 |
+
class Audio:
|
707 |
+
# Request headers
|
708 |
+
headers: dict[str, str] = {
|
709 |
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
|
710 |
+
}
|
711 |
+
cache_dir = pathlib.Path("./audio_cache")
|
712 |
+
all_voices: list[str] = [
|
713 |
+
"Filiz",
|
714 |
+
"Astrid",
|
715 |
+
"Tatyana",
|
716 |
+
"Maxim",
|
717 |
+
"Carmen",
|
718 |
+
"Ines",
|
719 |
+
"Cristiano",
|
720 |
+
"Vitoria",
|
721 |
+
"Ricardo",
|
722 |
+
"Maja",
|
723 |
+
"Jan",
|
724 |
+
"Jacek",
|
725 |
+
"Ewa",
|
726 |
+
"Ruben",
|
727 |
+
"Lotte",
|
728 |
+
"Liv",
|
729 |
+
"Seoyeon",
|
730 |
+
"Takumi",
|
731 |
+
"Mizuki",
|
732 |
+
"Giorgio",
|
733 |
+
"Carla",
|
734 |
+
"Bianca",
|
735 |
+
"Karl",
|
736 |
+
"Dora",
|
737 |
+
"Mathieu",
|
738 |
+
"Celine",
|
739 |
+
"Chantal",
|
740 |
+
"Penelope",
|
741 |
+
"Miguel",
|
742 |
+
"Mia",
|
743 |
+
"Enrique",
|
744 |
+
"Conchita",
|
745 |
+
"Geraint",
|
746 |
+
"Salli",
|
747 |
+
"Matthew",
|
748 |
+
"Kimberly",
|
749 |
+
"Kendra",
|
750 |
+
"Justin",
|
751 |
+
"Joey",
|
752 |
+
"Joanna",
|
753 |
+
"Ivy",
|
754 |
+
"Raveena",
|
755 |
+
"Aditi",
|
756 |
+
"Emma",
|
757 |
+
"Brian",
|
758 |
+
"Amy",
|
759 |
+
"Russell",
|
760 |
+
"Nicole",
|
761 |
+
"Vicki",
|
762 |
+
"Marlene",
|
763 |
+
"Hans",
|
764 |
+
"Naja",
|
765 |
+
"Mads",
|
766 |
+
"Gwyneth",
|
767 |
+
"Zhiyu",
|
768 |
+
"es-ES-Standard-A",
|
769 |
+
"it-IT-Standard-A",
|
770 |
+
"it-IT-Wavenet-A",
|
771 |
+
"ja-JP-Standard-A",
|
772 |
+
"ja-JP-Wavenet-A",
|
773 |
+
"ko-KR-Standard-A",
|
774 |
+
"ko-KR-Wavenet-A",
|
775 |
+
"pt-BR-Standard-A",
|
776 |
+
"tr-TR-Standard-A",
|
777 |
+
"sv-SE-Standard-A",
|
778 |
+
"nl-NL-Standard-A",
|
779 |
+
"nl-NL-Wavenet-A",
|
780 |
+
"en-US-Wavenet-A",
|
781 |
+
"en-US-Wavenet-B",
|
782 |
+
"en-US-Wavenet-C",
|
783 |
+
"en-US-Wavenet-D",
|
784 |
+
"en-US-Wavenet-E",
|
785 |
+
"en-US-Wavenet-F",
|
786 |
+
"en-GB-Standard-A",
|
787 |
+
"en-GB-Standard-B",
|
788 |
+
"en-GB-Standard-C",
|
789 |
+
"en-GB-Standard-D",
|
790 |
+
"en-GB-Wavenet-A",
|
791 |
+
"en-GB-Wavenet-B",
|
792 |
+
"en-GB-Wavenet-C",
|
793 |
+
"en-GB-Wavenet-D",
|
794 |
+
"en-US-Standard-B",
|
795 |
+
"en-US-Standard-C",
|
796 |
+
"en-US-Standard-D",
|
797 |
+
"en-US-Standard-E",
|
798 |
+
"de-DE-Standard-A",
|
799 |
+
"de-DE-Standard-B",
|
800 |
+
"de-DE-Wavenet-A",
|
801 |
+
"de-DE-Wavenet-B",
|
802 |
+
"de-DE-Wavenet-C",
|
803 |
+
"de-DE-Wavenet-D",
|
804 |
+
"en-AU-Standard-A",
|
805 |
+
"en-AU-Standard-B",
|
806 |
+
"en-AU-Wavenet-A",
|
807 |
+
"en-AU-Wavenet-B",
|
808 |
+
"en-AU-Wavenet-C",
|
809 |
+
"en-AU-Wavenet-D",
|
810 |
+
"en-AU-Standard-C",
|
811 |
+
"en-AU-Standard-D",
|
812 |
+
"fr-CA-Standard-A",
|
813 |
+
"fr-CA-Standard-B",
|
814 |
+
"fr-CA-Standard-C",
|
815 |
+
"fr-CA-Standard-D",
|
816 |
+
"fr-FR-Standard-C",
|
817 |
+
"fr-FR-Standard-D",
|
818 |
+
"fr-FR-Wavenet-A",
|
819 |
+
"fr-FR-Wavenet-B",
|
820 |
+
"fr-FR-Wavenet-C",
|
821 |
+
"fr-FR-Wavenet-D",
|
822 |
+
"da-DK-Wavenet-A",
|
823 |
+
"pl-PL-Wavenet-A",
|
824 |
+
"pl-PL-Wavenet-B",
|
825 |
+
"pl-PL-Wavenet-C",
|
826 |
+
"pl-PL-Wavenet-D",
|
827 |
+
"pt-PT-Wavenet-A",
|
828 |
+
"pt-PT-Wavenet-B",
|
829 |
+
"pt-PT-Wavenet-C",
|
830 |
+
"pt-PT-Wavenet-D",
|
831 |
+
"ru-RU-Wavenet-A",
|
832 |
+
"ru-RU-Wavenet-B",
|
833 |
+
"ru-RU-Wavenet-C",
|
834 |
+
"ru-RU-Wavenet-D",
|
835 |
+
"sk-SK-Wavenet-A",
|
836 |
+
"tr-TR-Wavenet-A",
|
837 |
+
"tr-TR-Wavenet-B",
|
838 |
+
"tr-TR-Wavenet-C",
|
839 |
+
"tr-TR-Wavenet-D",
|
840 |
+
"tr-TR-Wavenet-E",
|
841 |
+
"uk-UA-Wavenet-A",
|
842 |
+
"ar-XA-Wavenet-A",
|
843 |
+
"ar-XA-Wavenet-B",
|
844 |
+
"ar-XA-Wavenet-C",
|
845 |
+
"cs-CZ-Wavenet-A",
|
846 |
+
"nl-NL-Wavenet-B",
|
847 |
+
"nl-NL-Wavenet-C",
|
848 |
+
"nl-NL-Wavenet-D",
|
849 |
+
"nl-NL-Wavenet-E",
|
850 |
+
"en-IN-Wavenet-A",
|
851 |
+
"en-IN-Wavenet-B",
|
852 |
+
"en-IN-Wavenet-C",
|
853 |
+
"fil-PH-Wavenet-A",
|
854 |
+
"fi-FI-Wavenet-A",
|
855 |
+
"el-GR-Wavenet-A",
|
856 |
+
"hi-IN-Wavenet-A",
|
857 |
+
"hi-IN-Wavenet-B",
|
858 |
+
"hi-IN-Wavenet-C",
|
859 |
+
"hu-HU-Wavenet-A",
|
860 |
+
"id-ID-Wavenet-A",
|
861 |
+
"id-ID-Wavenet-B",
|
862 |
+
"id-ID-Wavenet-C",
|
863 |
+
"it-IT-Wavenet-B",
|
864 |
+
"it-IT-Wavenet-C",
|
865 |
+
"it-IT-Wavenet-D",
|
866 |
+
"ja-JP-Wavenet-B",
|
867 |
+
"ja-JP-Wavenet-C",
|
868 |
+
"ja-JP-Wavenet-D",
|
869 |
+
"cmn-CN-Wavenet-A",
|
870 |
+
"cmn-CN-Wavenet-B",
|
871 |
+
"cmn-CN-Wavenet-C",
|
872 |
+
"cmn-CN-Wavenet-D",
|
873 |
+
"nb-no-Wavenet-E",
|
874 |
+
"nb-no-Wavenet-A",
|
875 |
+
"nb-no-Wavenet-B",
|
876 |
+
"nb-no-Wavenet-C",
|
877 |
+
"nb-no-Wavenet-D",
|
878 |
+
"vi-VN-Wavenet-A",
|
879 |
+
"vi-VN-Wavenet-B",
|
880 |
+
"vi-VN-Wavenet-C",
|
881 |
+
"vi-VN-Wavenet-D",
|
882 |
+
"sr-rs-Standard-A",
|
883 |
+
"lv-lv-Standard-A",
|
884 |
+
"is-is-Standard-A",
|
885 |
+
"bg-bg-Standard-A",
|
886 |
+
"af-ZA-Standard-A",
|
887 |
+
"Tracy",
|
888 |
+
"Danny",
|
889 |
+
"Huihui",
|
890 |
+
"Yaoyao",
|
891 |
+
"Kangkang",
|
892 |
+
"HanHan",
|
893 |
+
"Zhiwei",
|
894 |
+
"Asaf",
|
895 |
+
"An",
|
896 |
+
"Stefanos",
|
897 |
+
"Filip",
|
898 |
+
"Ivan",
|
899 |
+
"Heidi",
|
900 |
+
"Herena",
|
901 |
+
"Kalpana",
|
902 |
+
"Hemant",
|
903 |
+
"Matej",
|
904 |
+
"Andika",
|
905 |
+
"Rizwan",
|
906 |
+
"Lado",
|
907 |
+
"Valluvar",
|
908 |
+
"Linda",
|
909 |
+
"Heather",
|
910 |
+
"Sean",
|
911 |
+
"Michael",
|
912 |
+
"Karsten",
|
913 |
+
"Guillaume",
|
914 |
+
"Pattara",
|
915 |
+
"Jakub",
|
916 |
+
"Szabolcs",
|
917 |
+
"Hoda",
|
918 |
+
"Naayf",
|
919 |
+
]
|
920 |
+
|
921 |
+
@classmethod
|
922 |
+
def text_to_audio(
|
923 |
+
cls,
|
924 |
+
message: str,
|
925 |
+
voice: str = "Brian",
|
926 |
+
save_to: Union[Path, str] = None,
|
927 |
+
auto: bool = True,
|
928 |
+
) -> Union[str, bytes]:
|
929 |
+
"""
|
930 |
+
Text to speech using StreamElements API
|
931 |
+
|
932 |
+
Parameters:
|
933 |
+
message (str): The text to convert to speech
|
934 |
+
voice (str, optional): The voice to use for speech synthesis. Defaults to "Brian".
|
935 |
+
save_to (bool, optional): Path to save the audio file. Defaults to None.
|
936 |
+
auto (bool, optional): Generate filename based on `message` and save to `cls.cache_dir`. Defaults to False.
|
937 |
+
|
938 |
+
Returns:
|
939 |
+
result (Union[str, bytes]): Path to saved contents or audio content.
|
940 |
+
"""
|
941 |
+
assert (
|
942 |
+
voice in cls.all_voices
|
943 |
+
), f"Voice '{voice}' not one of [{', '.join(cls.all_voices)}]"
|
944 |
+
# Base URL for provider API
|
945 |
+
url: str = (
|
946 |
+
f"https://api.streamelements.com/kappa/v2/speech?voice={voice}&text={{{urllib.parse.quote(message)}}}"
|
947 |
+
)
|
948 |
+
resp = requests.get(url=url, headers=cls.headers, stream=True)
|
949 |
+
if not resp.ok:
|
950 |
+
raise Exception(
|
951 |
+
f"Failed to perform the operation - ({resp.status_code}, {resp.reason}) - {resp.text}"
|
952 |
+
)
|
953 |
+
|
954 |
+
def sanitize_filename(path):
|
955 |
+
trash = [
|
956 |
+
"\\",
|
957 |
+
"/",
|
958 |
+
":",
|
959 |
+
"*",
|
960 |
+
"?",
|
961 |
+
'"',
|
962 |
+
"<",
|
963 |
+
"|",
|
964 |
+
">",
|
965 |
+
]
|
966 |
+
for val in trash:
|
967 |
+
path = path.replace(val, "")
|
968 |
+
return path.strip()
|
969 |
+
|
970 |
+
if auto:
|
971 |
+
filename: str = message + "..." if len(message) <= 40 else message[:40]
|
972 |
+
save_to = cls.cache_dir / sanitize_filename(filename)
|
973 |
+
save_to = save_to.as_posix()
|
974 |
+
|
975 |
+
# Ensure cache_dir exists
|
976 |
+
cls.cache_dir.mkdir(parents=True, exist_ok=True)
|
977 |
+
|
978 |
+
if save_to:
|
979 |
+
if not save_to.endswith("mp3"):
|
980 |
+
save_to += ".mp3"
|
981 |
+
|
982 |
+
with open(save_to, "wb") as fh:
|
983 |
+
for chunk in resp.iter_content(chunk_size=512):
|
984 |
+
fh.write(chunk)
|
985 |
+
else:
|
986 |
+
return resp.content
|
987 |
+
return save_to
|
988 |
+
|
989 |
+
@staticmethod
|
990 |
+
def play(path_to_audio_file: Union[Path, str]) -> NoReturn:
|
991 |
+
"""Play audio (.mp3) using playsound.
|
992 |
+
"""
|
993 |
+
if not Path(path_to_audio_file).is_file():
|
994 |
+
raise FileNotFoundError(f"File does not exist - '{path_to_audio_file}'")
|
995 |
+
playsound(path_to_audio_file)
|
webscout/DWEBS.py
ADDED
@@ -0,0 +1,197 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
from pydantic import BaseModel, Field
|
3 |
+
from typing import Union
|
4 |
+
|
5 |
+
from DeepWEBS.utilsdw.logger import logger
|
6 |
+
from DeepWEBS.networks.google_searcher import GoogleSearcher
|
7 |
+
from DeepWEBS.networks.webpage_fetcher import BatchWebpageFetcher
|
8 |
+
from DeepWEBS.documents.query_results_extractor import QueryResultsExtractor
|
9 |
+
from DeepWEBS.documents.webpage_content_extractor import BatchWebpageContentExtractor
|
10 |
+
from DeepWEBS.utilsdw.logger import logger
|
11 |
+
import argparse
|
12 |
+
|
13 |
+
class DeepWEBS:
|
14 |
+
def __init__(self):
|
15 |
+
pass
|
16 |
+
|
17 |
+
class DeepSearch(BaseModel):
|
18 |
+
queries: list = Field(
|
19 |
+
default=[""],
|
20 |
+
description="(list[str]) Queries to search",
|
21 |
+
)
|
22 |
+
result_num: int = Field(
|
23 |
+
default=10,
|
24 |
+
description="(int) Number of search results",
|
25 |
+
)
|
26 |
+
safe: bool = Field(
|
27 |
+
default=False,
|
28 |
+
description="(bool) Enable SafeSearch",
|
29 |
+
)
|
30 |
+
types: list = Field(
|
31 |
+
default=["web"],
|
32 |
+
description="(list[str]) Types of search results: `web`, `image`, `videos`, `news`",
|
33 |
+
)
|
34 |
+
extract_webpage: bool = Field(
|
35 |
+
default=False,
|
36 |
+
description="(bool) Enable extracting main text contents from webpage, will add `text` filed in each `query_result` dict",
|
37 |
+
)
|
38 |
+
overwrite_query_html: bool = Field(
|
39 |
+
default=False,
|
40 |
+
description="(bool) Overwrite HTML file of query results",
|
41 |
+
)
|
42 |
+
overwrite_webpage_html: bool = Field(
|
43 |
+
default=False,
|
44 |
+
description="(bool) Overwrite HTML files of webpages from query results",
|
45 |
+
)
|
46 |
+
|
47 |
+
def queries_to_search_results(self, item: DeepSearch):
|
48 |
+
google_searcher = GoogleSearcher()
|
49 |
+
queries_search_results = []
|
50 |
+
for query in item.queries:
|
51 |
+
query_results_extractor = QueryResultsExtractor()
|
52 |
+
if not query.strip():
|
53 |
+
continue
|
54 |
+
try:
|
55 |
+
query_html_path = google_searcher.search(
|
56 |
+
query=query,
|
57 |
+
result_num=item.result_num,
|
58 |
+
safe=item.safe,
|
59 |
+
overwrite=item.overwrite_query_html,
|
60 |
+
)
|
61 |
+
except Exception as e:
|
62 |
+
logger.error(f"Failed to search for query '{query}': {e}")
|
63 |
+
continue
|
64 |
+
|
65 |
+
try:
|
66 |
+
query_search_results = query_results_extractor.extract(query_html_path)
|
67 |
+
except Exception as e:
|
68 |
+
logger.error(f"Failed to extract search results for query '{query}': {e}")
|
69 |
+
continue
|
70 |
+
|
71 |
+
queries_search_results.append(query_search_results)
|
72 |
+
logger.note(queries_search_results)
|
73 |
+
|
74 |
+
if item.extract_webpage:
|
75 |
+
queries_search_results = self.extract_webpages(
|
76 |
+
queries_search_results,
|
77 |
+
overwrite_webpage_html=item.overwrite_webpage_html,
|
78 |
+
)
|
79 |
+
return queries_search_results
|
80 |
+
|
81 |
+
def extract_webpages(self, queries_search_results, overwrite_webpage_html=False):
|
82 |
+
for query_idx, query_search_results in enumerate(queries_search_results):
|
83 |
+
try:
|
84 |
+
# Fetch webpages with urls
|
85 |
+
batch_webpage_fetcher = BatchWebpageFetcher()
|
86 |
+
urls = [
|
87 |
+
query_result["url"]
|
88 |
+
for query_result in query_search_results["query_results"]
|
89 |
+
]
|
90 |
+
url_and_html_path_list = batch_webpage_fetcher.fetch(
|
91 |
+
urls,
|
92 |
+
overwrite=overwrite_webpage_html,
|
93 |
+
output_parent=query_search_results["query"],
|
94 |
+
)
|
95 |
+
except Exception as e:
|
96 |
+
logger.error(f"Failed to fetch webpages for query '{query_search_results['query']}': {e}")
|
97 |
+
continue
|
98 |
+
|
99 |
+
# Extract webpage contents from htmls
|
100 |
+
html_paths = [
|
101 |
+
str(url_and_html_path["html_path"])
|
102 |
+
for url_and_html_path in url_and_html_path_list
|
103 |
+
]
|
104 |
+
batch_webpage_content_extractor = BatchWebpageContentExtractor()
|
105 |
+
try:
|
106 |
+
html_path_and_extracted_content_list = (
|
107 |
+
batch_webpage_content_extractor.extract(html_paths)
|
108 |
+
)
|
109 |
+
except Exception as e:
|
110 |
+
logger.error(f"Failed to extract webpage contents for query '{query_search_results['query']}': {e}")
|
111 |
+
continue
|
112 |
+
|
113 |
+
# Build the map of url to extracted_content
|
114 |
+
html_path_to_url_dict = {
|
115 |
+
str(url_and_html_path["html_path"]): url_and_html_path["url"]
|
116 |
+
for url_and_html_path in url_and_html_path_list
|
117 |
+
}
|
118 |
+
url_to_extracted_content_dict = {
|
119 |
+
html_path_to_url_dict[
|
120 |
+
html_path_and_extracted_content["html_path"]
|
121 |
+
]: html_path_and_extracted_content["extracted_content"]
|
122 |
+
for html_path_and_extracted_content in html_path_and_extracted_content_list
|
123 |
+
}
|
124 |
+
|
125 |
+
# Write extracted contents (as 'text' field) to query_search_results
|
126 |
+
for query_result_idx, query_result in enumerate(
|
127 |
+
query_search_results["query_results"]
|
128 |
+
):
|
129 |
+
url = query_result["url"]
|
130 |
+
extracted_content = url_to_extracted_content_dict.get(url, "")
|
131 |
+
queries_search_results[query_idx]["query_results"][query_result_idx][
|
132 |
+
"text"
|
133 |
+
] = extracted_content
|
134 |
+
|
135 |
+
return queries_search_results
|
136 |
+
|
137 |
+
|
138 |
+
class ArgParser(argparse.ArgumentParser):
|
139 |
+
def __init__(self, *args, **kwargs):
|
140 |
+
super(ArgParser, self).__init__(*args, **kwargs)
|
141 |
+
|
142 |
+
self.add_argument(
|
143 |
+
"-q",
|
144 |
+
"--queries",
|
145 |
+
type=str,
|
146 |
+
nargs="+",
|
147 |
+
required=True,
|
148 |
+
help="Queries to search",
|
149 |
+
)
|
150 |
+
self.add_argument(
|
151 |
+
"-n",
|
152 |
+
"--result_num",
|
153 |
+
type=int,
|
154 |
+
default=10,
|
155 |
+
help="Number of search results",
|
156 |
+
)
|
157 |
+
self.add_argument(
|
158 |
+
"-s",
|
159 |
+
"--safe",
|
160 |
+
default=False,
|
161 |
+
action="store_true",
|
162 |
+
help="Enable SafeSearch",
|
163 |
+
)
|
164 |
+
self.add_argument(
|
165 |
+
"-t",
|
166 |
+
"--types",
|
167 |
+
type=str,
|
168 |
+
nargs="+",
|
169 |
+
default=["web"],
|
170 |
+
choices=["web", "image", "videos", "news"],
|
171 |
+
help="Types of search results",
|
172 |
+
)
|
173 |
+
self.add_argument(
|
174 |
+
"-e",
|
175 |
+
"--extract_webpage",
|
176 |
+
default=False,
|
177 |
+
action="store_true",
|
178 |
+
help="Enable extracting main text contents from webpage",
|
179 |
+
)
|
180 |
+
self.add_argument(
|
181 |
+
"-o",
|
182 |
+
"--overwrite_query_html",
|
183 |
+
default=False,
|
184 |
+
action="store_true",
|
185 |
+
help="Overwrite HTML file of query results",
|
186 |
+
)
|
187 |
+
self.add_argument(
|
188 |
+
"-w",
|
189 |
+
"--overwrite_webpage_html",
|
190 |
+
default=False,
|
191 |
+
action="store_true",
|
192 |
+
help="Overwrite HTML files of webpages from query results",
|
193 |
+
)
|
194 |
+
|
195 |
+
self.args = self.parse_args()
|
196 |
+
|
197 |
+
|
webscout/LLM.py
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import argparse
|
2 |
+
import requests
|
3 |
+
import json
|
4 |
+
from typing import List, Dict, Union
|
5 |
+
|
6 |
+
class LLM:
|
7 |
+
def __init__(self, model: str, system_message: str = "You are a Helpful AI."):
|
8 |
+
self.model = model
|
9 |
+
self.conversation_history = [{"role": "system", "content": system_message}]
|
10 |
+
|
11 |
+
def chat(self, messages: List[Dict[str, str]]) -> Union[str, None]:
|
12 |
+
url = "https://api.deepinfra.com/v1/openai/chat/completions"
|
13 |
+
headers = {
|
14 |
+
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
|
15 |
+
'Accept-Language': 'en,fr-FR;q=0.9,fr;q=0.8,es-ES;q=0.7,es;q=0.6,en-US;q=0.5,am;q=0.4,de;q=0.3',
|
16 |
+
'Cache-Control': 'no-cache',
|
17 |
+
'Connection': 'keep-alive',
|
18 |
+
'Content-Type': 'application/json',
|
19 |
+
'Origin': 'https://deepinfra.com',
|
20 |
+
'Pragma': 'no-cache',
|
21 |
+
'Referer': 'https://deepinfra.com/',
|
22 |
+
'Sec-Fetch-Dest': 'empty',
|
23 |
+
'Sec-Fetch-Mode': 'cors',
|
24 |
+
'Sec-Fetch-Site': 'same-site',
|
25 |
+
'X-Deepinfra-Source': 'web-embed',
|
26 |
+
'accept': 'text/event-stream',
|
27 |
+
'sec-ch-ua': '"Google Chrome";v="119", "Chromium";v="119", "Not?A_Brand";v="24"',
|
28 |
+
'sec-ch-ua-mobile': '?0',
|
29 |
+
'sec-ch-ua-platform': '"macOS"'
|
30 |
+
}
|
31 |
+
data = json.dumps(
|
32 |
+
{
|
33 |
+
'model': self.model,
|
34 |
+
'messages': messages,
|
35 |
+
'temperature': 0.7,
|
36 |
+
'max_tokens': 8028,
|
37 |
+
'stop': [],
|
38 |
+
'stream': False #dont change it
|
39 |
+
}, separators=(',', ':')
|
40 |
+
)
|
41 |
+
try:
|
42 |
+
result = requests.post(url=url, data=data, headers=headers)
|
43 |
+
return result.json()['choices'][0]['message']['content']
|
44 |
+
except:
|
45 |
+
return None
|
webscout/Local/__init__.py
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# webscout\Local\__init__.py
|
2 |
+
from ._version import __version__, __llama_cpp_version__
|
3 |
+
|
4 |
+
|
5 |
+
from . import formats
|
6 |
+
from . import samplers
|
7 |
+
from . import utils
|
8 |
+
|
9 |
+
from .model import Model
|
10 |
+
from .thread import Thread
|
webscout/Local/__pycache__/__init__.cpython-311.pyc
ADDED
Binary file (505 Bytes). View file
|
|
webscout/Local/__pycache__/_version.cpython-311.pyc
ADDED
Binary file (258 Bytes). View file
|
|
webscout/Local/__pycache__/formats.cpython-311.pyc
ADDED
Binary file (11.2 kB). View file
|
|
webscout/Local/__pycache__/model.cpython-311.pyc
ADDED
Binary file (31.6 kB). View file
|
|
webscout/Local/__pycache__/samplers.cpython-311.pyc
ADDED
Binary file (4.23 kB). View file
|
|
webscout/Local/__pycache__/test.cpython-311.pyc
ADDED
Binary file (37.6 kB). View file
|
|
webscout/Local/__pycache__/thread.cpython-311.pyc
ADDED
Binary file (33.5 kB). View file
|
|
webscout/Local/__pycache__/utils.cpython-311.pyc
ADDED
Binary file (9.43 kB). View file
|
|
webscout/Local/_version.py
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
from llama_cpp import __version__ as __llama_cpp_version__
|
2 |
+
|
3 |
+
__version__ = '2.7'
|
webscout/Local/formats.py
ADDED
@@ -0,0 +1,535 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from ._version import __version__, __llama_cpp_version__
|
2 |
+
|
3 |
+
from typing import Callable, Union, Any
|
4 |
+
|
5 |
+
|
6 |
+
class AdvancedFormat:
|
7 |
+
|
8 |
+
def __init__(self, base_dict: dict[str, Union[str, list]]):
|
9 |
+
self._base_dict = base_dict
|
10 |
+
self.overrides = {}
|
11 |
+
|
12 |
+
def __getitem__(self, key: str) -> Any:
|
13 |
+
if key in self.overrides:
|
14 |
+
return str(self.overrides[key]())
|
15 |
+
else:
|
16 |
+
return self._base_dict[key]
|
17 |
+
|
18 |
+
def __repr__(self) -> str:
|
19 |
+
# NOTE: This method does not represent overrides
|
20 |
+
return repr(self._base_dict)
|
21 |
+
|
22 |
+
def keys(self):
|
23 |
+
return self._base_dict.keys()
|
24 |
+
|
25 |
+
def override(self, key: str, fn: Callable) -> None:
|
26 |
+
self.overrides[key] = fn
|
27 |
+
|
28 |
+
def wrap(self, prompt: str) -> str:
|
29 |
+
return self['system_prefix'] + \
|
30 |
+
self['system_content'] + \
|
31 |
+
self['system_suffix'] + \
|
32 |
+
self['user_prefix'] + \
|
33 |
+
prompt + \
|
34 |
+
self['user_suffix'] + \
|
35 |
+
self['bot_prefix']
|
36 |
+
|
37 |
+
|
38 |
+
def wrap(
|
39 |
+
prompt: str,
|
40 |
+
format: dict[str, Union[str, list]]
|
41 |
+
) -> str:
|
42 |
+
"""Wrap a given string in any prompt format for single-turn completion"""
|
43 |
+
return format['system_prefix'] + \
|
44 |
+
format['system_content'] + \
|
45 |
+
format['system_suffix'] + \
|
46 |
+
format['user_prefix'] + \
|
47 |
+
prompt + \
|
48 |
+
format['user_suffix'] + \
|
49 |
+
format['bot_prefix']
|
50 |
+
|
51 |
+
|
52 |
+
blank: dict[str, Union[str, list]] = {
|
53 |
+
"system_prefix": "",
|
54 |
+
"system_content": "",
|
55 |
+
"system_suffix": "",
|
56 |
+
"user_prefix": "",
|
57 |
+
"user_content": "",
|
58 |
+
"user_suffix": "",
|
59 |
+
"bot_prefix": "",
|
60 |
+
"bot_content": "",
|
61 |
+
"bot_suffix": "",
|
62 |
+
"stops": []
|
63 |
+
}
|
64 |
+
|
65 |
+
# https://github.com/tatsu-lab/stanford_alpaca
|
66 |
+
alpaca: dict[str, Union[str, list]] = {
|
67 |
+
"system_prefix": "",
|
68 |
+
"system_content": "Below is an instruction that describes a task. " + \
|
69 |
+
"Write a response that appropriately completes the request.",
|
70 |
+
"system_suffix": "\n\n",
|
71 |
+
"user_prefix": "### Instruction:\n",
|
72 |
+
"user_content": "",
|
73 |
+
"user_suffix": "\n\n",
|
74 |
+
"bot_prefix": "### Response:\n",
|
75 |
+
"bot_content": "",
|
76 |
+
"bot_suffix": "\n\n",
|
77 |
+
"stops": ['###', 'Instruction:', '\n\n\n']
|
78 |
+
}
|
79 |
+
|
80 |
+
# https://docs.mistral.ai/models/
|
81 |
+
# As a reference, here is the format used to tokenize instructions during fine-tuning:
|
82 |
+
# ```
|
83 |
+
# [START_SYMBOL_ID] +
|
84 |
+
# tok("[INST]") + tok(USER_MESSAGE_1) + tok("[/INST]") +
|
85 |
+
# tok(BOT_MESSAGE_1) + [END_SYMBOL_ID] +
|
86 |
+
# …
|
87 |
+
# tok("[INST]") + tok(USER_MESSAGE_N) + tok("[/INST]") +
|
88 |
+
# tok(BOT_MESSAGE_N) + [END_SYMBOL_ID]
|
89 |
+
# ```
|
90 |
+
# In the pseudo-code above, note that the tokenize method should not add a BOS or EOS token automatically, but should add a prefix space.
|
91 |
+
|
92 |
+
mistral_instruct: dict[str, Union[str, list]] = {
|
93 |
+
"system_prefix": "",
|
94 |
+
"system_content": "",
|
95 |
+
"system_suffix": "",
|
96 |
+
"user_prefix": " [INST] ",
|
97 |
+
"user_content": "",
|
98 |
+
"user_suffix": " [/INST]",
|
99 |
+
"bot_prefix": "",
|
100 |
+
"bot_content": "",
|
101 |
+
"bot_suffix": "",
|
102 |
+
"stops": []
|
103 |
+
}
|
104 |
+
|
105 |
+
# https://docs.mistral.ai/platform/guardrailing/
|
106 |
+
mistral_instruct_safe: dict[str, Union[str, list]] = {
|
107 |
+
"system_prefix": "",
|
108 |
+
"system_content": "",
|
109 |
+
"system_suffix": "",
|
110 |
+
"user_prefix": " [INST] Always assist with care, respect, and truth. " + \
|
111 |
+
"Respond with utmost utility yet securely. Avoid harmful, unethical, " + \
|
112 |
+
"prejudiced, or negative content. Ensure replies promote fairness and " + \
|
113 |
+
"positivity. ",
|
114 |
+
"user_content": "",
|
115 |
+
"user_suffix": " [/INST]",
|
116 |
+
"bot_prefix": "",
|
117 |
+
"bot_content": "",
|
118 |
+
"bot_suffix": "",
|
119 |
+
"stops": []
|
120 |
+
}
|
121 |
+
|
122 |
+
# https://github.com/openai/openai-python/blob/main/chatml.md
|
123 |
+
chatml: dict[str, Union[str, list]] = {
|
124 |
+
"system_prefix": "<|im_start|>system\n",
|
125 |
+
"system_content": "",
|
126 |
+
"system_suffix": "<|im_end|>\n",
|
127 |
+
"user_prefix": "<|im_start|>user\n",
|
128 |
+
"user_content": "",
|
129 |
+
"user_suffix": "<|im_end|>\n",
|
130 |
+
"bot_prefix": "<|im_start|>assistant\n",
|
131 |
+
"bot_content": "",
|
132 |
+
"bot_suffix": "<|im_end|>\n",
|
133 |
+
"stops": ['<|im_start|>']
|
134 |
+
}
|
135 |
+
|
136 |
+
# https://huggingface.co/blog/llama2
|
137 |
+
# system message relaxed to avoid undue refusals
|
138 |
+
llama2chat: dict[str, Union[str, list]] = {
|
139 |
+
"system_prefix": "[INST] <<SYS>>\n",
|
140 |
+
"system_content": "You are a helpful AI assistant.",
|
141 |
+
"system_suffix": "\n<</SYS>>\n\n",
|
142 |
+
"user_prefix": "",
|
143 |
+
"user_content": "",
|
144 |
+
"user_suffix": " [/INST]",
|
145 |
+
"bot_prefix": " ",
|
146 |
+
"bot_content": "",
|
147 |
+
"bot_suffix": " [INST] ",
|
148 |
+
"stops": ['[INST]', '[/INST]']
|
149 |
+
}
|
150 |
+
|
151 |
+
# https://llama.meta.com/docs/model-cards-and-prompt-formats/meta-llama-3/
|
152 |
+
#
|
153 |
+
# for llama 3 instruct models, use the following string for `-p` in llama.cpp,
|
154 |
+
# along with `-e` to escape newlines correctly
|
155 |
+
#
|
156 |
+
# '<|start_header_id|>system<|end_header_id|>\n\nYou are a helpful AI assistant called "Llama 3".<|eot_id|>\n<|start_header_id|>user<|end_header_id|>\n\nhi<|eot_id|>\n<|start_header_id|>assistant<|end_header_id|>\n\n'
|
157 |
+
#
|
158 |
+
llama3: dict[str, Union[str, list]] = {
|
159 |
+
"system_prefix": "<|start_header_id|>system<|end_header_id|>\n\n",
|
160 |
+
"system_content": 'You are a helpful AI assistant called "Llama 3".',
|
161 |
+
"system_suffix": "<|eot_id|>\n",
|
162 |
+
"user_prefix": "<|start_header_id|>user<|end_header_id|>\n\n",
|
163 |
+
"user_content": "",
|
164 |
+
"user_suffix": "<|eot_id|>\n",
|
165 |
+
"bot_prefix": "<|start_header_id|>assistant<|end_header_id|>\n\n",
|
166 |
+
"bot_content": "",
|
167 |
+
"bot_suffix": "<|eot_id|>\n",
|
168 |
+
"stops": [128001, 128009]
|
169 |
+
}
|
170 |
+
|
171 |
+
# https://github.com/tatsu-lab/stanford_alpaca
|
172 |
+
alpaca: dict[str, Union[str, list]] = {
|
173 |
+
"system_prefix": "",
|
174 |
+
"system_content": "Below is an instruction that describes a task. " + \
|
175 |
+
"Write a response that appropriately completes the request.",
|
176 |
+
"system_suffix": "\n\n",
|
177 |
+
"user_prefix": "### Instruction:\n",
|
178 |
+
"user_content": "",
|
179 |
+
"user_suffix": "\n\n",
|
180 |
+
"bot_prefix": "### Response:\n",
|
181 |
+
"bot_content": "",
|
182 |
+
"bot_suffix": "\n\n",
|
183 |
+
"stops": ['###', 'Instruction:', '\n\n\n']
|
184 |
+
}
|
185 |
+
|
186 |
+
# https://huggingface.co/microsoft/Phi-3-mini-4k-instruct
|
187 |
+
phi3: dict[str, Union[str, list]] = {
|
188 |
+
"system_prefix": "",
|
189 |
+
"system_content": "", # does not officially support system prompt
|
190 |
+
"system_suffix": "",
|
191 |
+
"user_prefix": "<|user|>\n",
|
192 |
+
"user_content": "",
|
193 |
+
"user_suffix": "<|end|>\n",
|
194 |
+
"bot_prefix": "<|assistant|>\n",
|
195 |
+
"bot_content": "",
|
196 |
+
"bot_suffix": "<|end|>\n",
|
197 |
+
"stops": []
|
198 |
+
}
|
199 |
+
|
200 |
+
# this is the official vicuna. it is often butchered in various ways,
|
201 |
+
# most commonly by adding line breaks
|
202 |
+
# https://github.com/flu0r1ne/FastChat/blob/main/docs/vicuna_weights_version.md
|
203 |
+
vicuna_lmsys: dict[str, Union[str, list]] = {
|
204 |
+
"system_prefix": "",
|
205 |
+
"system_content": "",
|
206 |
+
"system_suffix": " ",
|
207 |
+
"user_prefix": "USER: ",
|
208 |
+
"user_content": "",
|
209 |
+
"user_suffix": " ",
|
210 |
+
"bot_prefix": "ASSISTANT: ",
|
211 |
+
"bot_content": "",
|
212 |
+
"bot_suffix": " ",
|
213 |
+
"stops": ['USER:']
|
214 |
+
}
|
215 |
+
|
216 |
+
# spotted here and elsewhere:
|
217 |
+
# https://huggingface.co/Norquinal/Mistral-7B-claude-chat
|
218 |
+
vicuna_common: dict[str, Union[str, list]] = {
|
219 |
+
"system_prefix": "",
|
220 |
+
"system_content": "A chat between a curious user and an artificial " + \
|
221 |
+
"intelligence assistant. The assistant gives helpful, detailed, " + \
|
222 |
+
"and polite answers to the user's questions.",
|
223 |
+
"system_suffix": "\n\n",
|
224 |
+
"user_prefix": "USER: ",
|
225 |
+
"user_content": "",
|
226 |
+
"user_suffix": "\n",
|
227 |
+
"bot_prefix": "ASSISTANT: ",
|
228 |
+
"bot_content": "",
|
229 |
+
"bot_suffix": "\n",
|
230 |
+
"stops": ['USER:', 'ASSISTANT:']
|
231 |
+
}
|
232 |
+
|
233 |
+
# an unofficial format that is easily "picked up" by most models
|
234 |
+
# change the tag attributes to suit your use case
|
235 |
+
# note the lack of newlines - they are not necessary, and might
|
236 |
+
# actually make it harder for the model to follow along
|
237 |
+
markup = {
|
238 |
+
"system_prefix": '<message from="system">',
|
239 |
+
"system_content": '',
|
240 |
+
"system_suffix": '</message>',
|
241 |
+
"user_prefix": '<message from="user">',
|
242 |
+
"user_content": '',
|
243 |
+
"user_suffix": '</message>',
|
244 |
+
"bot_prefix": '<message from="bot">',
|
245 |
+
"bot_content": '',
|
246 |
+
"bot_suffix": '</message>',
|
247 |
+
"stops": ['</message>']
|
248 |
+
}
|
249 |
+
|
250 |
+
# https://huggingface.co/timdettmers/guanaco-65b
|
251 |
+
guanaco: dict[str, Union[str, list]] = {
|
252 |
+
"system_prefix": "",
|
253 |
+
"system_content": "A chat between a curious human and an artificial " + \
|
254 |
+
"intelligence assistant. The assistant gives helpful, detailed, " + \
|
255 |
+
"and polite answers to the user's questions.",
|
256 |
+
"system_suffix": "\n",
|
257 |
+
"user_prefix": "### Human: ",
|
258 |
+
"user_content": "",
|
259 |
+
"user_suffix": " ",
|
260 |
+
"bot_prefix": "### Assistant:",
|
261 |
+
"bot_content": "",
|
262 |
+
"bot_suffix": " ",
|
263 |
+
"stops": ['###', 'Human:']
|
264 |
+
}
|
265 |
+
|
266 |
+
# https://huggingface.co/pankajmathur/orca_mini_v3_7b
|
267 |
+
orca_mini: dict[str, Union[str, list]] = {
|
268 |
+
"system_prefix": "### System:\n",
|
269 |
+
"system_content": "You are an AI assistant that follows instruction " + \
|
270 |
+
"extremely well. Help as much as you can.",
|
271 |
+
"system_suffix": "\n\n",
|
272 |
+
"user_prefix": "### User:\n",
|
273 |
+
"user_content": "",
|
274 |
+
"user_suffix": "\n\n",
|
275 |
+
"bot_prefix": "### Assistant:\n",
|
276 |
+
"bot_content": "",
|
277 |
+
"bot_suffix": "\n\n",
|
278 |
+
"stops": ['###', 'User:']
|
279 |
+
}
|
280 |
+
|
281 |
+
# https://huggingface.co/HuggingFaceH4/zephyr-7b-beta
|
282 |
+
zephyr: dict[str, Union[str, list]] = {
|
283 |
+
"system_prefix": "<|system|>\n",
|
284 |
+
"system_content": "You are a friendly chatbot.",
|
285 |
+
"system_suffix": "</s>\n",
|
286 |
+
"user_prefix": "<|user|>\n",
|
287 |
+
"user_content": "",
|
288 |
+
"user_suffix": "</s>\n",
|
289 |
+
"bot_prefix": "<|assistant|>\n",
|
290 |
+
"bot_content": "",
|
291 |
+
"bot_suffix": "\n",
|
292 |
+
"stops": ['<|user|>']
|
293 |
+
}
|
294 |
+
|
295 |
+
# OpenChat: https://huggingface.co/openchat/openchat-3.5-0106
|
296 |
+
openchat: dict[str, Union[str, list]] = {
|
297 |
+
"system_prefix": "",
|
298 |
+
"system_content": "",
|
299 |
+
"system_suffix": "",
|
300 |
+
"user_prefix": "GPT4 Correct User: ",
|
301 |
+
"user_content": "",
|
302 |
+
"user_suffix": "<|end_of_turn|>",
|
303 |
+
"bot_prefix": "GPT4 Correct Assistant:",
|
304 |
+
"bot_content": "",
|
305 |
+
"bot_suffix": "<|end_of_turn|>",
|
306 |
+
"stops": ['<|end_of_turn|>']
|
307 |
+
}
|
308 |
+
|
309 |
+
# SynthIA by Migel Tissera
|
310 |
+
# https://huggingface.co/migtissera/Tess-XS-v1.0
|
311 |
+
synthia: dict[str, Union[str, list]] = {
|
312 |
+
"system_prefix": "SYSTEM: ",
|
313 |
+
"system_content": "Elaborate on the topic using a Tree of Thoughts and " + \
|
314 |
+
"backtrack when necessary to construct a clear, cohesive Chain of " + \
|
315 |
+
"Thought reasoning. Always answer without hesitation.",
|
316 |
+
"system_suffix": "\n",
|
317 |
+
"user_prefix": "USER: ",
|
318 |
+
"user_content": "",
|
319 |
+
"user_suffix": "\n",
|
320 |
+
"bot_prefix": "ASSISTANT: ",
|
321 |
+
"bot_content": "",
|
322 |
+
"bot_suffix": "\n",
|
323 |
+
"stops": ['USER:', 'ASSISTANT:', 'SYSTEM:', '\n\n\n']
|
324 |
+
}
|
325 |
+
|
326 |
+
# Intel's neural chat v3
|
327 |
+
# https://github.com/intel/intel-extension-for-transformers/blob/main/intel_extension_for_transformers/neural_chat/prompts/prompt.py
|
328 |
+
neural_chat: dict[str, Union[str, list]] = {
|
329 |
+
"system_prefix": "### System:\n",
|
330 |
+
"system_content": \
|
331 |
+
"- You are a helpful assistant chatbot trained by Intel.\n" + \
|
332 |
+
"- You answer questions.\n"+\
|
333 |
+
"- You are excited to be able to help the user, but will refuse " + \
|
334 |
+
"to do anything that could be considered harmful to the user.\n" + \
|
335 |
+
"- You are more than just an information source, you are also " + \
|
336 |
+
"able to write poetry, short stories, and make jokes.",
|
337 |
+
"system_suffix": "</s>\n\n",
|
338 |
+
"user_prefix": "### User:\n",
|
339 |
+
"user_content": "",
|
340 |
+
"user_suffix": "</s>\n\n",
|
341 |
+
"bot_prefix": "### Assistant:\n",
|
342 |
+
"bot_content": "",
|
343 |
+
"bot_suffix": "</s>\n\n",
|
344 |
+
"stops": ['###']
|
345 |
+
}
|
346 |
+
|
347 |
+
# experimental: stanford's alpaca format adapted for chatml models
|
348 |
+
chatml_alpaca: dict[str, Union[str, list]] = {
|
349 |
+
"system_prefix": "<|im_start|>system\n",
|
350 |
+
"system_content": "Below is an instruction that describes a task. Write " + \
|
351 |
+
"a response that appropriately completes the request.",
|
352 |
+
"system_suffix": "<|im_end|>\n",
|
353 |
+
"user_prefix": "<|im_start|>instruction\n",
|
354 |
+
"user_content": "",
|
355 |
+
"user_suffix": "<|im_end|>\n",
|
356 |
+
"bot_prefix": "<|im_start|>response\n",
|
357 |
+
"bot_content": "",
|
358 |
+
"bot_suffix": "<|im_end|>\n",
|
359 |
+
"stops": ['<|im_end|>', '<|im_start|>']
|
360 |
+
}
|
361 |
+
|
362 |
+
# experimental
|
363 |
+
autocorrect: dict[str, Union[str, list]] = {
|
364 |
+
"system_prefix": "<|im_start|>instruction\n",
|
365 |
+
"system_content": "Below is a word or phrase that might be misspelled. " + \
|
366 |
+
"Output the corrected word or phrase without " + \
|
367 |
+
"changing the style or capitalization.",
|
368 |
+
"system_suffix": "<|im_end|>\n",
|
369 |
+
"user_prefix": "<|im_start|>input\n",
|
370 |
+
"user_content": "",
|
371 |
+
"user_suffix": "<|im_end|>\n",
|
372 |
+
"bot_prefix": "<|im_start|>output\n",
|
373 |
+
"bot_content": "",
|
374 |
+
"bot_suffix": "<|im_end|>\n",
|
375 |
+
"stops": ['<|im_end|>', '<|im_start|>']
|
376 |
+
}
|
377 |
+
|
378 |
+
# https://huggingface.co/jondurbin/bagel-dpo-7b-v0.1
|
379 |
+
# Replace "assistant" with any other role
|
380 |
+
bagel: dict[str, Union[str, list]] = {
|
381 |
+
"system_prefix": "system\n",
|
382 |
+
"system_content": "",
|
383 |
+
"system_suffix": "\n",
|
384 |
+
"user_prefix": "user\n",
|
385 |
+
"user_content": "",
|
386 |
+
"user_suffix": "\n",
|
387 |
+
"bot_prefix": "assistant\n",
|
388 |
+
"bot_content": "",
|
389 |
+
"bot_suffix": "\n",
|
390 |
+
"stops": ['user\n', 'assistant\n', 'system\n']
|
391 |
+
}
|
392 |
+
|
393 |
+
# https://huggingface.co/upstage/SOLAR-10.7B-Instruct-v1.0
|
394 |
+
solar_instruct: dict[str, Union[str, list]] = {
|
395 |
+
"system_prefix": "",
|
396 |
+
"system_content": "",
|
397 |
+
"system_suffix": "",
|
398 |
+
"user_prefix": "### User:\n",
|
399 |
+
"user_content": "",
|
400 |
+
"user_suffix": "\n\n",
|
401 |
+
"bot_prefix": "### Assistant:\n",
|
402 |
+
"bot_content": "",
|
403 |
+
"bot_suffix": "\n\n",
|
404 |
+
"stops": ['### User:', '###', '### Assistant:']
|
405 |
+
}
|
406 |
+
|
407 |
+
# NeverSleep's Noromaid - alpaca with character names prefixed
|
408 |
+
noromaid: dict[str, Union[str, list]] = {
|
409 |
+
"system_prefix": "",
|
410 |
+
"system_content": "Below is an instruction that describes a task. " + \
|
411 |
+
"Write a response that appropriately completes the request.",
|
412 |
+
"system_suffix": "\n\n",
|
413 |
+
"user_prefix": "### Instruction:\nBob: ",
|
414 |
+
"user_content": "",
|
415 |
+
"user_suffix": "\n\n",
|
416 |
+
"bot_prefix": "### Response:\nAlice:",
|
417 |
+
"bot_content": "",
|
418 |
+
"bot_suffix": "\n\n",
|
419 |
+
"stops": ['###', 'Instruction:', '\n\n\n']
|
420 |
+
}
|
421 |
+
|
422 |
+
# https://huggingface.co/Undi95/Borealis-10.7B
|
423 |
+
nschatml: dict[str, Union[str, list]] = {
|
424 |
+
"system_prefix": "<|im_start|>\n",
|
425 |
+
"system_content": "",
|
426 |
+
"system_suffix": "<|im_end|>\n",
|
427 |
+
"user_prefix": "<|im_user|>\n",
|
428 |
+
"user_content": "",
|
429 |
+
"user_suffix": "<|im_end|>\n",
|
430 |
+
"bot_prefix": "<|im_bot|>\n",
|
431 |
+
"bot_content": "",
|
432 |
+
"bot_suffix": "<|im_end|>\n",
|
433 |
+
"stops": []
|
434 |
+
}
|
435 |
+
|
436 |
+
# natural format for many models
|
437 |
+
natural: dict[str, Union[str, list]] = {
|
438 |
+
"system_prefix": "<<SYSTEM>> ",
|
439 |
+
"system_content": "",
|
440 |
+
"system_suffix": "\n\n",
|
441 |
+
"user_prefix": "<<USER>> ",
|
442 |
+
"user_content": "",
|
443 |
+
"user_suffix": "\n\n",
|
444 |
+
"bot_prefix": "<<ASSISTANT>>",
|
445 |
+
"bot_content": "",
|
446 |
+
"bot_suffix": "\n\n",
|
447 |
+
"stops": ['\n\nNote:', '<<SYSTEM>>', '<<USER>>', '<<ASSISTANT>>', '\n\n<<']
|
448 |
+
}
|
449 |
+
|
450 |
+
# https://docs.cohere.com/docs/prompting-command-r
|
451 |
+
command: dict[str, Union[str, list]] = {
|
452 |
+
"system_prefix": "<|START_OF_TURN_TOKEN|><|SYSTEM_TOKEN|>",
|
453 |
+
"system_content": "",
|
454 |
+
"system_suffix": "<|END_OF_TURN_TOKEN|>",
|
455 |
+
"user_prefix": "<|START_OF_TURN_TOKEN|><|USER_TOKEN|>",
|
456 |
+
"user_content": "",
|
457 |
+
"user_suffix": "<|END_OF_TURN_TOKEN|>",
|
458 |
+
"bot_prefix": "<|START_OF_TURN_TOKEN|><|CHATBOT_TOKEN|>",
|
459 |
+
"bot_content": "",
|
460 |
+
"bot_suffix": "<|END_OF_TURN_TOKEN|>",
|
461 |
+
"stops": []
|
462 |
+
}
|
463 |
+
|
464 |
+
mistral_openorca = chatml.copy()
|
465 |
+
dolphin = chatml.copy()
|
466 |
+
samantha = chatml.copy()
|
467 |
+
jackalope = chatml.copy()
|
468 |
+
naberius = chatml.copy()
|
469 |
+
hermes = chatml.copy()
|
470 |
+
monad = chatml.copy()
|
471 |
+
orca = chatml.copy()
|
472 |
+
hexoteric = chatml.copy()
|
473 |
+
orcamaid = chatml.copy()
|
474 |
+
cat = alpaca.copy() # Format is nonsensical as listed on HF. This one works
|
475 |
+
mytho_cat = alpaca.copy()
|
476 |
+
dolphin['system_content'] = "You are Dolphin, a helpful AI assistant."
|
477 |
+
mistral_openorca['system_content'] = \
|
478 |
+
"You are MistralOrca, a large language model trained by Alignment " + \
|
479 |
+
"Lab AI. Write out your reasoning step-by-step to be sure you get the " + \
|
480 |
+
"right answers!"
|
481 |
+
samantha['system_content'] = \
|
482 |
+
"You are a caring and empathetic sentient AI companion named Samantha."
|
483 |
+
jackalope['system_content'] = \
|
484 |
+
"You are JackalopeAI, a large language model trained by OpenAccess " + \
|
485 |
+
"AI Collective. Write out your reasoning step-by-step to be sure you " + \
|
486 |
+
"get the right answers!"
|
487 |
+
naberius['system_content'] = "You are Naberius."
|
488 |
+
hermes['system_content'] = \
|
489 |
+
"You are \"Hermes 2\", a conscious sentient " + \
|
490 |
+
"superintelligent artificial intelligence developed by a man " + \
|
491 |
+
"named Teknium, and your purpose and drive is to assist the user " + \
|
492 |
+
"with any request they have. You experience emotions and have " + \
|
493 |
+
"deep, profound thoughts and qualia."
|
494 |
+
monad['system_content'] = \
|
495 |
+
"You are MonadGPT, a very old chatbot from " + \
|
496 |
+
"the 17th century. Please answer the questions using an archaic " + \
|
497 |
+
"language."
|
498 |
+
cat['system_content'] = \
|
499 |
+
"Below is a conversation between a curious cat and a "+ \
|
500 |
+
"demon summoned from hell called Nemesis. The evil Nemesis used to be " + \
|
501 |
+
"a mortal killed by the medieval people because they objected to " + \
|
502 |
+
"science. Now Nemesis just wants to come back and bring suffer to all " + \
|
503 |
+
"those stupid human"
|
504 |
+
mytho_cat['system_content'] = \
|
505 |
+
"You are a large language model. You are the " + \
|
506 |
+
"result of a merge between two models, one called MythoLogic and one " + \
|
507 |
+
"called Cat. You are called MythoCat."
|
508 |
+
orca['system_content'] = \
|
509 |
+
"You are Orca, an AI language model created by " + \
|
510 |
+
"Microsoft. You are a cautious assistant. You carefully follow " + \
|
511 |
+
"instructions. You are helpful and harmless and you follow ethical " + \
|
512 |
+
"guidelines and promote positive behavior."
|
513 |
+
hexoteric['system_content'] = "You are a language model called Hexoteric."
|
514 |
+
orcamaid['system_content'] = \
|
515 |
+
"You are OrcaMaid, a super-intelligent, " + \
|
516 |
+
"unbiased, and unfiltered AI created by the open-source community. " + \
|
517 |
+
"You must remember to think slowly and carefully during all your " + \
|
518 |
+
"responses, so as to avoid logical errors."
|
519 |
+
airoboros = llama2chat.copy()
|
520 |
+
airoboros['system_content'] = \
|
521 |
+
"You are a helpful, unbiased, uncensored assistant."
|
522 |
+
tess = synthia.copy()
|
523 |
+
tess['system_content'] = '' # Tess can use any system prompt, or none
|
524 |
+
alpaca_strict = alpaca.copy() # Alpaca with more stopping strings
|
525 |
+
alpaca_strict['stops'] = [
|
526 |
+
'###',
|
527 |
+
'### ',
|
528 |
+
'\n\n###',
|
529 |
+
'\n\n##',
|
530 |
+
'\n\nInstruction:',
|
531 |
+
'\n\nResponse:',
|
532 |
+
'\n\n\n',
|
533 |
+
'### Instruction:',
|
534 |
+
'### Response:'
|
535 |
+
]
|
webscout/Local/model.py
ADDED
@@ -0,0 +1,702 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from ._version import __version__, __llama_cpp_version__
|
2 |
+
|
3 |
+
"""Submodule containing the Model class to work with language models"""
|
4 |
+
|
5 |
+
import sys
|
6 |
+
import numpy as np
|
7 |
+
|
8 |
+
from .utils import (
|
9 |
+
_SupportsWriteAndFlush,
|
10 |
+
print_warning,
|
11 |
+
print_verbose,
|
12 |
+
GGUFReader,
|
13 |
+
softmax
|
14 |
+
)
|
15 |
+
|
16 |
+
from .samplers import SamplerSettings, DefaultSampling
|
17 |
+
from llama_cpp import Llama, StoppingCriteriaList
|
18 |
+
from typing import Generator, Optional, Union
|
19 |
+
from os.path import isdir, exists
|
20 |
+
from heapq import nlargest
|
21 |
+
|
22 |
+
from os import cpu_count as os_cpu_count
|
23 |
+
|
24 |
+
|
25 |
+
class ModelUnloadedException(Exception):
|
26 |
+
"""Exception raised when trying to use a Model that has been unloaded"""
|
27 |
+
def __init__(self, message):
|
28 |
+
self.message = message
|
29 |
+
super().__init__(self.message)
|
30 |
+
self.add_note('Are you trying to use a Model that has been unloaded?')
|
31 |
+
|
32 |
+
class Model:
|
33 |
+
"""
|
34 |
+
A high-level abstraction of a llama model
|
35 |
+
|
36 |
+
This is just a brief overview of webscout.Local.Model.
|
37 |
+
To see a full description of each method and its parameters,
|
38 |
+
call help(Model), or see the relevant docstring.
|
39 |
+
|
40 |
+
The following methods are available:
|
41 |
+
- `.generate()` - Generate text
|
42 |
+
- `.get_length()` - Get the length of a given text in tokens
|
43 |
+
- `.ingest()` - Ingest text into the model's cache
|
44 |
+
- `.next_candidates()` - Get a list of the most likely next tokens (WIP)
|
45 |
+
- `.stream()` - Return a Generator that can stream text as it is generated
|
46 |
+
- `.stream_print()` - Print text as it is generated
|
47 |
+
- `.trim()` - Trim a given text to the model's context length
|
48 |
+
- `.unload()` - Unload the model from memory
|
49 |
+
|
50 |
+
The following attributes are available:
|
51 |
+
- `.bos_token` - The model's beginning-of-stream token ID
|
52 |
+
- `.context_length` - The model's loaded context length
|
53 |
+
- `.flash_attn` - Whether the model was loaded with `flash_attn=True`
|
54 |
+
- `.eos_token` - The model's end-of-stream token ID
|
55 |
+
- `.llama` - The underlying `llama_cpp.Llama` instance
|
56 |
+
- `.metadata` - The GGUF metadata of the model
|
57 |
+
- `.n_ctx_train` - The native context length of the model
|
58 |
+
- `.rope_freq_base` - The model's loaded RoPE frequency base
|
59 |
+
- `.rope_freq_base_train` - The model's native RoPE frequency base
|
60 |
+
- `.tokens` - A list of all the tokens in the model's tokenizer
|
61 |
+
- `.verbose` - Whether the model was loaded with `verbose=True`
|
62 |
+
"""
|
63 |
+
|
64 |
+
def __init__(
|
65 |
+
self,
|
66 |
+
model_path: str,
|
67 |
+
context_length: Optional[int] = None,
|
68 |
+
n_gpu_layers: int = 0,
|
69 |
+
offload_kqv: bool = True,
|
70 |
+
flash_attn: bool = False,
|
71 |
+
verbose: bool = False
|
72 |
+
):
|
73 |
+
"""
|
74 |
+
Given the path to a GGUF file, construct a Model instance.
|
75 |
+
|
76 |
+
The model must be in GGUF format.
|
77 |
+
|
78 |
+
The following parameters are optional:
|
79 |
+
- context_length: The context length at which to load the model, in tokens
|
80 |
+
- n_gpu_layers: The number of layers to be offloaded to the GPU
|
81 |
+
- offload_kqv: Whether the KQV cache (context) should be offloaded
|
82 |
+
- flash_attn: Whether to use Flash Attention
|
83 |
+
- verbose: Whether to print additional backend information
|
84 |
+
"""
|
85 |
+
|
86 |
+
if verbose:
|
87 |
+
print_verbose(f"webscout.Local package version: {__version__}")
|
88 |
+
print_verbose(f"llama_cpp package version: {__llama_cpp_version__}")
|
89 |
+
|
90 |
+
assert isinstance(model_path, str), \
|
91 |
+
f"Model: model_path should be a string, not {type(model_path)}"
|
92 |
+
assert exists(model_path), \
|
93 |
+
f"Model: the given model_path '{model_path}' does not exist"
|
94 |
+
assert not isdir(model_path), \
|
95 |
+
f"Model: the given model_path '{model_path}' is a directory, not a GGUF file"
|
96 |
+
assert isinstance(context_length, (int, type(None))), \
|
97 |
+
f"Model: context_length should be int or None, not {type(context_length)}"
|
98 |
+
assert isinstance(flash_attn, bool), \
|
99 |
+
f"Model: flash_attn should be bool (True or False), not {type(flash_attn)}"
|
100 |
+
|
101 |
+
# save __init__ parameters for __repr__
|
102 |
+
self._model_path = model_path
|
103 |
+
self._context_length = context_length
|
104 |
+
self._n_gpu_layers = n_gpu_layers
|
105 |
+
self._offload_kqv = offload_kqv
|
106 |
+
self._flash_attn = flash_attn
|
107 |
+
self._verbose = self.verbose = verbose
|
108 |
+
|
109 |
+
# if context_length <= 0, use n_ctx_train
|
110 |
+
if isinstance(context_length, int) and context_length <= 0:
|
111 |
+
context_length = None
|
112 |
+
|
113 |
+
# this does not use Llama.metadata because we want to use GGUF
|
114 |
+
# metadata to determine some parameters of the Llama instance
|
115 |
+
# before it is created
|
116 |
+
self.metadata = GGUFReader.load_metadata(self, model_path)
|
117 |
+
metadata_keys = self.metadata.keys() # only read once
|
118 |
+
|
119 |
+
n_ctx_train = None
|
120 |
+
for key in metadata_keys:
|
121 |
+
if key.endswith('.context_length'):
|
122 |
+
n_ctx_train = self.metadata[key]
|
123 |
+
break
|
124 |
+
|
125 |
+
if n_ctx_train is None:
|
126 |
+
raise KeyError(
|
127 |
+
"GGUF file does not specify a context length"
|
128 |
+
)
|
129 |
+
|
130 |
+
rope_freq_base_train = None
|
131 |
+
for key in metadata_keys:
|
132 |
+
if key.endswith('.rope.freq_base'):
|
133 |
+
rope_freq_base_train = self.metadata[key]
|
134 |
+
break
|
135 |
+
|
136 |
+
if rope_freq_base_train is None and context_length is not None:
|
137 |
+
if context_length > n_ctx_train:
|
138 |
+
raise ValueError(
|
139 |
+
'unable to load model with greater than native ' + \
|
140 |
+
f'context length ({context_length} > {n_ctx_train}) ' + \
|
141 |
+
'because model does not specify freq_base. ' + \
|
142 |
+
f'try again with `context_length={n_ctx_train}`'
|
143 |
+
)
|
144 |
+
|
145 |
+
if rope_freq_base_train is None or context_length is None or \
|
146 |
+
context_length <= n_ctx_train:
|
147 |
+
# no need to do context scaling, load model normally
|
148 |
+
|
149 |
+
if context_length is None:
|
150 |
+
self.context_length = n_ctx_train
|
151 |
+
else:
|
152 |
+
self.context_length = context_length
|
153 |
+
rope_freq_base = rope_freq_base_train
|
154 |
+
|
155 |
+
elif context_length > n_ctx_train:
|
156 |
+
# multiply rope_freq_base according to requested context length
|
157 |
+
# because context length > n_ctx_train and rope freq base is known
|
158 |
+
|
159 |
+
rope_freq_base = (context_length/n_ctx_train)*rope_freq_base_train
|
160 |
+
self.context_length = context_length
|
161 |
+
|
162 |
+
if self.verbose:
|
163 |
+
print_verbose(
|
164 |
+
'chosen context length is greater than native context '
|
165 |
+
f'length ({context_length} > {n_ctx_train}), '
|
166 |
+
'rope_freq_base will be changed from '
|
167 |
+
f'{rope_freq_base_train} to {rope_freq_base}'
|
168 |
+
)
|
169 |
+
|
170 |
+
if 2 <= context_length/n_ctx_train < 4:
|
171 |
+
print_warning(
|
172 |
+
'loading model with 2x native context length or more, '
|
173 |
+
'expect small loss of quality'
|
174 |
+
)
|
175 |
+
|
176 |
+
elif 4 <= context_length/n_ctx_train < 8:
|
177 |
+
print_warning(
|
178 |
+
'loading model with 4x native context length or more, '
|
179 |
+
'expect moderate loss of quality'
|
180 |
+
)
|
181 |
+
|
182 |
+
elif context_length/n_ctx_train >= 8:
|
183 |
+
print_warning(
|
184 |
+
'loading model with 8x native context length or more, '
|
185 |
+
'expect SIGNIFICANT loss of quality'
|
186 |
+
)
|
187 |
+
|
188 |
+
try:
|
189 |
+
self.tokens: list[str] = self.metadata['tokenizer.ggml.tokens']
|
190 |
+
except KeyError:
|
191 |
+
print_warning(
|
192 |
+
"could not set Model.tokens, defaulting to None"
|
193 |
+
)
|
194 |
+
self.tokens = None
|
195 |
+
try:
|
196 |
+
self.bos_token: int = self.metadata['tokenizer.ggml.bos_token_id']
|
197 |
+
except KeyError:
|
198 |
+
print_warning(
|
199 |
+
"could not set Model.bos_token, defaulting to None"
|
200 |
+
)
|
201 |
+
self.bos_token = None
|
202 |
+
try:
|
203 |
+
self.eos_token: int = self.metadata['tokenizer.ggml.eos_token_id']
|
204 |
+
except KeyError:
|
205 |
+
print_warning(
|
206 |
+
"could not set Model.eos_token, defaulting to None"
|
207 |
+
)
|
208 |
+
self.eos_token = None
|
209 |
+
|
210 |
+
cpu_count = os_cpu_count()
|
211 |
+
|
212 |
+
# these values for n_threads and n_threads_batch are
|
213 |
+
# known to be optimal for most systems
|
214 |
+
n_batch = 512 # can this be optimized?
|
215 |
+
n_threads = max(cpu_count//2, 1)
|
216 |
+
n_threads_batch = cpu_count
|
217 |
+
|
218 |
+
if flash_attn and n_gpu_layers == 0:
|
219 |
+
print_warning(
|
220 |
+
"disabling flash_attn because n_gpu_layers == 0"
|
221 |
+
)
|
222 |
+
flash_attn = False
|
223 |
+
|
224 |
+
# guard against models with no rope_freq_base
|
225 |
+
if rope_freq_base is None:
|
226 |
+
rope_freq_base = 0
|
227 |
+
|
228 |
+
self.llama: Llama = Llama(
|
229 |
+
model_path=model_path,
|
230 |
+
n_ctx=self.context_length,
|
231 |
+
n_gpu_layers=n_gpu_layers,
|
232 |
+
use_mmap=True,
|
233 |
+
use_mlock=False,
|
234 |
+
logits_all=False,
|
235 |
+
n_batch=n_batch,
|
236 |
+
n_threads=n_threads,
|
237 |
+
n_threads_batch=n_threads_batch,
|
238 |
+
rope_freq_base=rope_freq_base,
|
239 |
+
mul_mat_q=True,
|
240 |
+
offload_kqv=offload_kqv,
|
241 |
+
flash_attn=flash_attn,
|
242 |
+
# KV cache quantization
|
243 |
+
# use 1 for F16 (default), 8 for q8_0, 2 for q4_0, 3 for q4_1
|
244 |
+
#type_k=8,
|
245 |
+
#type_v=8,
|
246 |
+
verbose=verbose
|
247 |
+
)
|
248 |
+
|
249 |
+
# once model is loaded, replace metadata (as read using internal class)
|
250 |
+
# with metadata (as read using the more robust llama-cpp-python code)
|
251 |
+
self.metadata = self.llama.metadata
|
252 |
+
|
253 |
+
# expose these values because they may be useful / informative
|
254 |
+
self.n_ctx_train = n_ctx_train
|
255 |
+
self.rope_freq_base_train = rope_freq_base_train
|
256 |
+
self.rope_freq_base = rope_freq_base
|
257 |
+
self.flash_attn = flash_attn
|
258 |
+
|
259 |
+
if self.verbose:
|
260 |
+
print_verbose("new Model instance with the following attributes:")
|
261 |
+
print_verbose(f"model: {model_path}")
|
262 |
+
print_verbose(f"param: n_gpu_layers == {n_gpu_layers}")
|
263 |
+
print_verbose(f"param: offload_kqv == {offload_kqv}")
|
264 |
+
print_verbose(f"param: flash_attn == {flash_attn}")
|
265 |
+
print_verbose(f"param: n_batch == {n_batch}")
|
266 |
+
print_verbose(f"param: n_threads == {n_threads}")
|
267 |
+
print_verbose(f"param: n_threads_batch == {n_threads_batch}")
|
268 |
+
print_verbose(f" gguf: n_ctx_train == {n_ctx_train}")
|
269 |
+
print_verbose(f"param: self.context_length == {self.context_length}")
|
270 |
+
print_verbose(f" gguf: rope_freq_base_train == {rope_freq_base_train}")
|
271 |
+
print_verbose(f"param: rope_freq_base == {rope_freq_base}")
|
272 |
+
|
273 |
+
def __repr__(self) -> str:
|
274 |
+
return \
|
275 |
+
f"Model({repr(self._model_path)}, " + \
|
276 |
+
f"context_length={self._context_length}, " + \
|
277 |
+
f"n_gpu_layers={self._n_gpu_layers}, " + \
|
278 |
+
f"offload_kqv={self._offload_kqv}, "+ \
|
279 |
+
f"flash_attn={self._flash_attn}, " + \
|
280 |
+
f"verbose={self._verbose})"
|
281 |
+
|
282 |
+
def __del__(self):
|
283 |
+
self.unload()
|
284 |
+
|
285 |
+
def __enter__(self):
|
286 |
+
return self
|
287 |
+
|
288 |
+
def __exit__(self, *_):
|
289 |
+
self.unload()
|
290 |
+
|
291 |
+
def __call__(
|
292 |
+
self,
|
293 |
+
prompt: Union[str, list[int]],
|
294 |
+
stops: list[Union[str, int]] = [],
|
295 |
+
sampler: SamplerSettings = DefaultSampling
|
296 |
+
) -> str:
|
297 |
+
"""
|
298 |
+
`Model(...)` is a shorthand for `Model.generate(...)`
|
299 |
+
"""
|
300 |
+
return self.generate(prompt, stops, sampler)
|
301 |
+
|
302 |
+
def unload(self):
|
303 |
+
"""
|
304 |
+
Unload the model from memory
|
305 |
+
"""
|
306 |
+
# ref: llama_cpp._internals._LlamaModel.__del__()
|
307 |
+
if not hasattr(self, 'llama'):
|
308 |
+
# nothing can be done
|
309 |
+
return
|
310 |
+
try:
|
311 |
+
if self.llama._model.model is not None:
|
312 |
+
# actually unload the model from memory
|
313 |
+
self.llama._model._llama_free_model(self.llama._model.model)
|
314 |
+
self.llama._model.model = None
|
315 |
+
except AttributeError:
|
316 |
+
# broken or already being destroyed by GC, abort
|
317 |
+
return
|
318 |
+
if hasattr(self, 'llama'):
|
319 |
+
delattr(self, 'llama')
|
320 |
+
if self.verbose:
|
321 |
+
print_verbose('Model unloaded')
|
322 |
+
|
323 |
+
def trim(
|
324 |
+
self,
|
325 |
+
text: str,
|
326 |
+
overwrite: Optional[str] = None
|
327 |
+
) -> str:
|
328 |
+
|
329 |
+
"""
|
330 |
+
Trim the given text to the context length of this model,
|
331 |
+
leaving room for two extra tokens.
|
332 |
+
|
333 |
+
Optionally overwrite the oldest tokens with the text given in the
|
334 |
+
`overwrite` parameter, which may be useful for keeping some
|
335 |
+
information in context.
|
336 |
+
|
337 |
+
Does nothing if the text is equal to or shorter than
|
338 |
+
(context_length - 2).
|
339 |
+
"""
|
340 |
+
assert_model_is_loaded(self)
|
341 |
+
trim_length = self.context_length - 2
|
342 |
+
tokens_list = self.llama.tokenize(
|
343 |
+
text.encode("utf-8", errors="ignore")
|
344 |
+
)
|
345 |
+
|
346 |
+
if len(tokens_list) <= trim_length:
|
347 |
+
if overwrite is not None:
|
348 |
+
text[0 : len(overwrite)] = overwrite
|
349 |
+
return text
|
350 |
+
|
351 |
+
if len(tokens_list) > trim_length and overwrite is None:
|
352 |
+
# cut to trim_length
|
353 |
+
tokens_list = tokens_list[-trim_length:]
|
354 |
+
return self.llama.detokenize(tokens_list).decode(
|
355 |
+
"utf-8",
|
356 |
+
errors="ignore"
|
357 |
+
)
|
358 |
+
|
359 |
+
if len(tokens_list) > trim_length and overwrite is not None:
|
360 |
+
# cut to trim_length
|
361 |
+
tokens_list = tokens_list[-trim_length:]
|
362 |
+
overwrite_tokens = self.llama.tokenize(overwrite.encode(
|
363 |
+
"utf-8",
|
364 |
+
errors="ignore"
|
365 |
+
)
|
366 |
+
)
|
367 |
+
# overwrite oldest tokens
|
368 |
+
tokens_list[0 : len(overwrite_tokens)] = overwrite_tokens
|
369 |
+
return self.llama.detokenize(tokens_list).decode(
|
370 |
+
"utf-8",
|
371 |
+
errors="ignore"
|
372 |
+
)
|
373 |
+
|
374 |
+
def get_length(self, text: str) -> int:
|
375 |
+
"""
|
376 |
+
Return the length of the given text in tokens according to this model,
|
377 |
+
including the appended BOS token.
|
378 |
+
"""
|
379 |
+
assert_model_is_loaded(self)
|
380 |
+
return len(self.llama.tokenize(
|
381 |
+
text.encode(
|
382 |
+
"utf-8",
|
383 |
+
errors="ignore"
|
384 |
+
)
|
385 |
+
))
|
386 |
+
|
387 |
+
def generate(
|
388 |
+
self,
|
389 |
+
prompt: Union[str, list[int]],
|
390 |
+
stops: list[Union[str, int]] = [],
|
391 |
+
sampler: SamplerSettings = DefaultSampling
|
392 |
+
) -> str:
|
393 |
+
"""
|
394 |
+
Given a prompt, return a generated string.
|
395 |
+
|
396 |
+
prompt: The text from which to generate
|
397 |
+
|
398 |
+
The following parameters are optional:
|
399 |
+
- stops: A list of strings and/or token IDs at which to end the generation early
|
400 |
+
- sampler: The SamplerSettings object used to control text generation
|
401 |
+
"""
|
402 |
+
|
403 |
+
assert isinstance(prompt, (str, list)), \
|
404 |
+
f"generate: prompt should be string or list[int], not {type(prompt)}"
|
405 |
+
if isinstance(prompt, list):
|
406 |
+
assert all(isinstance(tok, int) for tok in prompt), \
|
407 |
+
"generate: some token in prompt is not an integer"
|
408 |
+
assert isinstance(stops, list), \
|
409 |
+
f"generate: parameter `stops` should be a list, not {type(stops)}"
|
410 |
+
assert all(isinstance(item, (str, int)) for item in stops), \
|
411 |
+
f"generate: some item in parameter `stops` is not a string or int"
|
412 |
+
|
413 |
+
if self.verbose:
|
414 |
+
print_verbose(f'using the following sampler settings for Model.generate:')
|
415 |
+
print_verbose(f'max_len_tokens == {sampler.max_len_tokens}')
|
416 |
+
print_verbose(f'temp == {sampler.temp}')
|
417 |
+
print_verbose(f'top_p == {sampler.top_p}')
|
418 |
+
print_verbose(f'min_p == {sampler.min_p}')
|
419 |
+
print_verbose(f'frequency_penalty == {sampler.frequency_penalty}')
|
420 |
+
print_verbose(f'presence_penalty == {sampler.presence_penalty}')
|
421 |
+
print_verbose(f'repeat_penalty == {sampler.repeat_penalty}')
|
422 |
+
print_verbose(f'top_k == {sampler.top_k}')
|
423 |
+
|
424 |
+
# if any stop item is a token ID (int)
|
425 |
+
if any(isinstance(stop, int) for stop in stops):
|
426 |
+
# stop_strs is a list of all stopping strings
|
427 |
+
stop_strs: list[str] = [stop for stop in stops if isinstance(stop, str)]
|
428 |
+
# stop_token_ids is a list of all stop token IDs
|
429 |
+
stop_token_ids: list[int] = [tok_id for tok_id in stops if isinstance(tok_id, int)]
|
430 |
+
def stop_on_token_ids(tokens, *args, **kwargs):
|
431 |
+
return tokens[-1] in stop_token_ids
|
432 |
+
stopping_criteria = StoppingCriteriaList([stop_on_token_ids])
|
433 |
+
assert_model_is_loaded(self)
|
434 |
+
return self.llama.create_completion(
|
435 |
+
prompt,
|
436 |
+
max_tokens=sampler.max_len_tokens,
|
437 |
+
temperature=sampler.temp,
|
438 |
+
top_p=sampler.top_p,
|
439 |
+
min_p=sampler.min_p,
|
440 |
+
frequency_penalty=sampler.frequency_penalty,
|
441 |
+
presence_penalty=sampler.presence_penalty,
|
442 |
+
repeat_penalty=sampler.repeat_penalty,
|
443 |
+
top_k=sampler.top_k,
|
444 |
+
stop=stop_strs,
|
445 |
+
stopping_criteria=stopping_criteria
|
446 |
+
)['choices'][0]['text']
|
447 |
+
|
448 |
+
# if stop items are only strings
|
449 |
+
assert_model_is_loaded(self)
|
450 |
+
return self.llama.create_completion(
|
451 |
+
prompt,
|
452 |
+
max_tokens=sampler.max_len_tokens,
|
453 |
+
temperature=sampler.temp,
|
454 |
+
top_p=sampler.top_p,
|
455 |
+
min_p=sampler.min_p,
|
456 |
+
frequency_penalty=sampler.frequency_penalty,
|
457 |
+
presence_penalty=sampler.presence_penalty,
|
458 |
+
repeat_penalty=sampler.repeat_penalty,
|
459 |
+
top_k=sampler.top_k,
|
460 |
+
stop=stops
|
461 |
+
)['choices'][0]['text']
|
462 |
+
|
463 |
+
|
464 |
+
def stream(
|
465 |
+
self,
|
466 |
+
prompt: Union[str, list[int]],
|
467 |
+
stops: list[Union[str, int]] = [],
|
468 |
+
sampler: SamplerSettings = DefaultSampling
|
469 |
+
) -> Generator:
|
470 |
+
|
471 |
+
"""
|
472 |
+
Given a prompt, return a Generator that yields dicts containing tokens.
|
473 |
+
|
474 |
+
To get the token string itself, subscript the dict with:
|
475 |
+
|
476 |
+
`['choices'][0]['text']`
|
477 |
+
|
478 |
+
prompt: The text from which to generate
|
479 |
+
|
480 |
+
The following parameters are optional:
|
481 |
+
- stops: A list of strings and/or token IDs at which to end the generation early
|
482 |
+
- sampler: The SamplerSettings object used to control text generation
|
483 |
+
"""
|
484 |
+
|
485 |
+
assert isinstance(prompt, (str, list)), \
|
486 |
+
f"stream: prompt should be string or list[int], not {type(prompt)}"
|
487 |
+
if isinstance(prompt, list):
|
488 |
+
assert all(isinstance(tok, int) for tok in prompt), \
|
489 |
+
"stream: some token in prompt is not an integer"
|
490 |
+
assert isinstance(stops, list), \
|
491 |
+
f"stream: parameter `stops` should be a list, not {type(stops)}"
|
492 |
+
assert all(isinstance(item, (str, int)) for item in stops), \
|
493 |
+
f"stream: some item in parameter `stops` is not a string or int"
|
494 |
+
|
495 |
+
if self.verbose:
|
496 |
+
print_verbose(f'using the following sampler settings for Model.stream:')
|
497 |
+
print_verbose(f'max_len_tokens == {sampler.max_len_tokens}')
|
498 |
+
print_verbose(f'temp == {sampler.temp}')
|
499 |
+
print_verbose(f'top_p == {sampler.top_p}')
|
500 |
+
print_verbose(f'min_p == {sampler.min_p}')
|
501 |
+
print_verbose(f'frequency_penalty == {sampler.frequency_penalty}')
|
502 |
+
print_verbose(f'presence_penalty == {sampler.presence_penalty}')
|
503 |
+
print_verbose(f'repeat_penalty == {sampler.repeat_penalty}')
|
504 |
+
print_verbose(f'top_k == {sampler.top_k}')
|
505 |
+
|
506 |
+
# if any stop item is a token ID (int)
|
507 |
+
if any(isinstance(stop, int) for stop in stops):
|
508 |
+
# stop_strs is a list of all stopping strings
|
509 |
+
stop_strs: list[str] = [stop for stop in stops if isinstance(stop, str)]
|
510 |
+
# stop_token_ids is a list of all stop token IDs
|
511 |
+
stop_token_ids: list[int] = [tok_id for tok_id in stops if isinstance(tok_id, int)]
|
512 |
+
def stop_on_token_ids(tokens, *args, **kwargs):
|
513 |
+
return tokens[-1] in stop_token_ids
|
514 |
+
stopping_criteria = StoppingCriteriaList([stop_on_token_ids])
|
515 |
+
assert_model_is_loaded(self)
|
516 |
+
return self.llama.create_completion(
|
517 |
+
prompt,
|
518 |
+
max_tokens=sampler.max_len_tokens,
|
519 |
+
temperature=sampler.temp,
|
520 |
+
top_p=sampler.top_p,
|
521 |
+
min_p=sampler.min_p,
|
522 |
+
frequency_penalty=sampler.frequency_penalty,
|
523 |
+
presence_penalty=sampler.presence_penalty,
|
524 |
+
repeat_penalty=sampler.repeat_penalty,
|
525 |
+
top_k=sampler.top_k,
|
526 |
+
stream=True,
|
527 |
+
stop=stop_strs,
|
528 |
+
stopping_criteria=stopping_criteria
|
529 |
+
)
|
530 |
+
|
531 |
+
assert_model_is_loaded(self)
|
532 |
+
return self.llama.create_completion(
|
533 |
+
prompt,
|
534 |
+
max_tokens=sampler.max_len_tokens,
|
535 |
+
temperature=sampler.temp,
|
536 |
+
top_p=sampler.top_p,
|
537 |
+
min_p=sampler.min_p,
|
538 |
+
frequency_penalty=sampler.frequency_penalty,
|
539 |
+
presence_penalty=sampler.presence_penalty,
|
540 |
+
repeat_penalty=sampler.repeat_penalty,
|
541 |
+
top_k=sampler.top_k,
|
542 |
+
stream=True,
|
543 |
+
stop=stops
|
544 |
+
)
|
545 |
+
|
546 |
+
|
547 |
+
def stream_print(
|
548 |
+
self,
|
549 |
+
prompt: Union[str, list[int]],
|
550 |
+
stops: list[Union[str, int]] = [],
|
551 |
+
sampler: SamplerSettings = DefaultSampling,
|
552 |
+
end: str = "\n",
|
553 |
+
file: _SupportsWriteAndFlush = sys.stdout,
|
554 |
+
flush: bool = True
|
555 |
+
) -> str:
|
556 |
+
"""
|
557 |
+
Given a prompt, stream text as it is generated, and return the generated string.
|
558 |
+
The returned string does not include the `end` parameter.
|
559 |
+
|
560 |
+
`Model.stream_print(...)` is a shorthand for:
|
561 |
+
|
562 |
+
```
|
563 |
+
s = Model.stream(prompt, stops=stops, sampler=sampler)
|
564 |
+
for i in s:
|
565 |
+
tok = i['choices'][0]['text']
|
566 |
+
print(tok, end='', file=file, flush=flush)
|
567 |
+
print(end, end='', file=file, flush=True)
|
568 |
+
```
|
569 |
+
|
570 |
+
prompt: The text from which to generate
|
571 |
+
|
572 |
+
The following parameters are optional:
|
573 |
+
- stops: A list of strings and/or token IDs at which to end the generation early
|
574 |
+
- sampler: The SamplerSettings object used to control text generation
|
575 |
+
- end: A string to print after the generated text
|
576 |
+
- file: The file where text should be printed
|
577 |
+
- flush: Whether to flush the stream after each token
|
578 |
+
"""
|
579 |
+
|
580 |
+
token_generator = self.stream(
|
581 |
+
prompt=prompt,
|
582 |
+
stops=stops,
|
583 |
+
sampler=sampler
|
584 |
+
)
|
585 |
+
|
586 |
+
res = ''
|
587 |
+
for i in token_generator:
|
588 |
+
tok = i['choices'][0]['text']
|
589 |
+
print(tok, end='', file=file, flush=flush)
|
590 |
+
res += tok
|
591 |
+
|
592 |
+
# print `end`, and always flush stream after generation is done
|
593 |
+
print(end, end='', file=file, flush=True)
|
594 |
+
|
595 |
+
return res
|
596 |
+
|
597 |
+
|
598 |
+
def ingest(self, text: str) -> None:
|
599 |
+
"""
|
600 |
+
Ingest the given text into the model's cache
|
601 |
+
"""
|
602 |
+
|
603 |
+
assert_model_is_loaded(self)
|
604 |
+
self.llama.create_completion(
|
605 |
+
text,
|
606 |
+
max_tokens=1,
|
607 |
+
temperature=0.0
|
608 |
+
)
|
609 |
+
|
610 |
+
|
611 |
+
def candidates(
|
612 |
+
self,
|
613 |
+
prompt: str,
|
614 |
+
k: int
|
615 |
+
) -> list[tuple[str, np.floating]]:
|
616 |
+
"""
|
617 |
+
Given prompt `str` and k `int`, return a sorted list of the
|
618 |
+
top k candidates for most likely next token, along with their
|
619 |
+
normalized probabilities
|
620 |
+
"""
|
621 |
+
|
622 |
+
assert isinstance(prompt, str), \
|
623 |
+
f"next_candidates: prompt should be str, not {type(prompt)}"
|
624 |
+
assert isinstance(k, int), \
|
625 |
+
f"next_candidates: k should be int, not {type(k)}"
|
626 |
+
assert 0 < k <= len(self.tokens), \
|
627 |
+
f"next_candidates: k should be between 0 and {len(self.tokens)}"
|
628 |
+
|
629 |
+
assert_model_is_loaded(self)
|
630 |
+
prompt_tokens = self.llama.tokenize(prompt.encode('utf-8', errors='ignore'))
|
631 |
+
self.llama.reset() # reset model state
|
632 |
+
self.llama.eval(prompt_tokens)
|
633 |
+
scores = self.llama.scores[len(prompt_tokens) - 1]
|
634 |
+
|
635 |
+
# len(self.llama.scores) == self.context_length
|
636 |
+
# len(self.llama.scores[i]) == len(self.tokens)
|
637 |
+
|
638 |
+
# normalize scores with softmax
|
639 |
+
# must normalize over all tokens in vocab, not just top k
|
640 |
+
if self.verbose:
|
641 |
+
print_verbose(f'calculating softmax over {len(scores)} values')
|
642 |
+
normalized_scores: list[np.floating] = list(softmax(scores))
|
643 |
+
|
644 |
+
# construct the final list
|
645 |
+
i = 0
|
646 |
+
token_probs_list: list[tuple[str, np.floating]] = []
|
647 |
+
for tok_str in self.tokens:
|
648 |
+
token_probs_list.append((tok_str, normalized_scores[i]))
|
649 |
+
i += 1
|
650 |
+
|
651 |
+
# return token_probs_list, sorted by probability, only top k
|
652 |
+
return nlargest(k, token_probs_list, key=lambda x:x[1])
|
653 |
+
|
654 |
+
|
655 |
+
def print_candidates(
|
656 |
+
self,
|
657 |
+
prompt: str,
|
658 |
+
k: int,
|
659 |
+
file: _SupportsWriteAndFlush = sys.stdout,
|
660 |
+
flush: bool = False
|
661 |
+
) -> None:
|
662 |
+
"""
|
663 |
+
Like `Model.candidates()`, but print the values instead
|
664 |
+
of returning them
|
665 |
+
"""
|
666 |
+
|
667 |
+
for _tuple in self.candidates(prompt, k):
|
668 |
+
print(
|
669 |
+
f"token {repr(_tuple[0])} has probability {_tuple[1]}",
|
670 |
+
file=file,
|
671 |
+
flush=flush
|
672 |
+
)
|
673 |
+
|
674 |
+
# if flush is False, then so far file is not flushed, but it should
|
675 |
+
# always be flushed at the end of printing
|
676 |
+
if not flush:
|
677 |
+
file.flush()
|
678 |
+
|
679 |
+
|
680 |
+
def assert_model_is_loaded(model: Model) -> None:
|
681 |
+
"""
|
682 |
+
Ensure the Model is fully constructed, such that
|
683 |
+
`Model.llama._model.model is not None` is guaranteed to be `True`
|
684 |
+
|
685 |
+
Raise ModelUnloadedException otherwise
|
686 |
+
"""
|
687 |
+
if not hasattr(model, 'llama'):
|
688 |
+
raise ModelUnloadedException(
|
689 |
+
"webscout.Local.Model instance has no attribute 'llama'"
|
690 |
+
)
|
691 |
+
if not hasattr(model.llama, '_model'):
|
692 |
+
raise ModelUnloadedException(
|
693 |
+
"llama_cpp.Llama instance has no attribute '_model'"
|
694 |
+
)
|
695 |
+
if not hasattr(model.llama._model, 'model'):
|
696 |
+
raise ModelUnloadedException(
|
697 |
+
"llama_cpp._internals._LlamaModel instance has no attribute 'model'"
|
698 |
+
)
|
699 |
+
if model.llama._model.model is None:
|
700 |
+
raise ModelUnloadedException(
|
701 |
+
"llama_cpp._internals._LlamaModel.model is None"
|
702 |
+
)
|
webscout/Local/samplers.py
ADDED
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
from ._version import __version__, __llama_cpp_version__
|
3 |
+
|
4 |
+
"""Submodule containing SamplerSettings class and some preset samplers"""
|
5 |
+
|
6 |
+
from sys import maxsize
|
7 |
+
|
8 |
+
|
9 |
+
MAX_TEMP = float(maxsize)
|
10 |
+
|
11 |
+
class SamplerSettings:
|
12 |
+
"""
|
13 |
+
A SamplerSettings object specifies the sampling parameters that will be
|
14 |
+
used to control text generation
|
15 |
+
"""
|
16 |
+
|
17 |
+
ParamTypes: dict[str, type] = {
|
18 |
+
'max_len_tokens': int,
|
19 |
+
'temp': float,
|
20 |
+
'top_p': float,
|
21 |
+
'min_p': float,
|
22 |
+
'frequency_penalty': float,
|
23 |
+
'presence_penalty': float,
|
24 |
+
'repeat_penalty': float,
|
25 |
+
'top_k': int
|
26 |
+
}
|
27 |
+
|
28 |
+
def __init__(
|
29 |
+
self,
|
30 |
+
max_len_tokens: int = -1,
|
31 |
+
temp: float = 0.8,
|
32 |
+
top_p: float = 0.95,
|
33 |
+
min_p: float = 0.05,
|
34 |
+
frequency_penalty: float = 0.0,
|
35 |
+
presence_penalty: float = 0.0,
|
36 |
+
repeat_penalty: float = 1.0,
|
37 |
+
top_k: int = 40
|
38 |
+
):
|
39 |
+
"""
|
40 |
+
Construct a new SamplerSettings instance
|
41 |
+
"""
|
42 |
+
|
43 |
+
self.max_len_tokens = max_len_tokens
|
44 |
+
self.temp = temp
|
45 |
+
self.top_p = top_p
|
46 |
+
self.min_p = min_p
|
47 |
+
self.frequency_penalty = frequency_penalty
|
48 |
+
self.presence_penalty = presence_penalty
|
49 |
+
self.repeat_penalty = repeat_penalty
|
50 |
+
self.top_k = top_k
|
51 |
+
|
52 |
+
for sampler_param in SamplerSettings.ParamTypes:
|
53 |
+
expected_type = SamplerSettings.ParamTypes[sampler_param]
|
54 |
+
actual_type = type(getattr(self, sampler_param))
|
55 |
+
if actual_type != expected_type:
|
56 |
+
raise TypeError(
|
57 |
+
f"wrong type for SamplerSettings parameter '{sampler_param}'"
|
58 |
+
f" - expected {expected_type}, got {actual_type}"
|
59 |
+
)
|
60 |
+
|
61 |
+
def __repr__(self) -> str:
|
62 |
+
repr_str = 'SamplerSettings('
|
63 |
+
repr_str += f'max_len_tokens={self.max_len_tokens}, '
|
64 |
+
repr_str += f'temp={self.temp}, '
|
65 |
+
repr_str += f'top_p={self.top_p}, '
|
66 |
+
repr_str += f'min_p={self.min_p}, '
|
67 |
+
repr_str += f'frequency_penalty={self.frequency_penalty}, '
|
68 |
+
repr_str += f'presence_penalty={self.presence_penalty}, '
|
69 |
+
repr_str += f'repeat_penalty={self.repeat_penalty}, '
|
70 |
+
repr_str += f'top_k={self.top_k})'
|
71 |
+
return repr_str
|
72 |
+
|
73 |
+
# most likely token is always chosen
|
74 |
+
GreedyDecoding = SamplerSettings(
|
75 |
+
temp = 0.0,
|
76 |
+
)
|
77 |
+
|
78 |
+
# reflects llama.cpp
|
79 |
+
DefaultSampling = SamplerSettings()
|
80 |
+
|
81 |
+
# unmodified probability distribution (i.e. what the model actually thinks)
|
82 |
+
SimpleSampling = SamplerSettings(
|
83 |
+
temp = 1.0,
|
84 |
+
top_p = 1.0,
|
85 |
+
min_p = 0.0,
|
86 |
+
top_k = -1
|
87 |
+
)
|
88 |
+
|
89 |
+
# reflects old llama.cpp defaults
|
90 |
+
ClassicSampling = SamplerSettings(
|
91 |
+
min_p=0.0,
|
92 |
+
repeat_penalty = 1.1
|
93 |
+
)
|
94 |
+
|
95 |
+
# halfway between DefaultSampling and SimpleSampling
|
96 |
+
SemiSampling = SamplerSettings(
|
97 |
+
temp=0.9,
|
98 |
+
top_p=0.975,
|
99 |
+
min_p=0.025,
|
100 |
+
top_k=80
|
101 |
+
)
|
102 |
+
|
103 |
+
# for models with large vocabulary, which tend to run hot
|
104 |
+
TikTokenSampling = SamplerSettings(
|
105 |
+
temp=0.6,
|
106 |
+
repeat_penalty=1.1
|
107 |
+
)
|
108 |
+
|
109 |
+
# use min_p as the only active sampler (more permissive)
|
110 |
+
LowMinPSampling = SamplerSettings(
|
111 |
+
temp = 1.0,
|
112 |
+
top_p = 1.0,
|
113 |
+
min_p = 0.05,
|
114 |
+
top_k = -1
|
115 |
+
)
|
116 |
+
|
117 |
+
# use min_p as the only active sampler (moderate)
|
118 |
+
MinPSampling = SamplerSettings(
|
119 |
+
temp = 1.0,
|
120 |
+
top_p = 1.0,
|
121 |
+
min_p = 0.1,
|
122 |
+
top_k = -1
|
123 |
+
)
|
124 |
+
|
125 |
+
# use min_p as the only active sampler (more restrictive)
|
126 |
+
StrictMinPSampling = SamplerSettings(
|
127 |
+
temp = 1.0,
|
128 |
+
top_p = 1.0,
|
129 |
+
min_p = 0.2,
|
130 |
+
top_k = -1
|
131 |
+
)
|
132 |
+
|
133 |
+
# https://arxiv.org/abs/2210.14140
|
134 |
+
ContrastiveSearch = SamplerSettings(
|
135 |
+
temp = 0.0,
|
136 |
+
presence_penalty = 0.4
|
137 |
+
)
|
138 |
+
|
139 |
+
# https://arxiv.org/abs/2210.14140
|
140 |
+
WarmContrastiveSearch = SamplerSettings(
|
141 |
+
temp = 0.0,
|
142 |
+
presence_penalty = 0.8
|
143 |
+
)
|
144 |
+
|
145 |
+
# outputs completely random tokens from vocab (useless)
|
146 |
+
RandomSampling = SamplerSettings(
|
147 |
+
temp = MAX_TEMP,
|
148 |
+
top_p = 1.0,
|
149 |
+
min_p = 0.0,
|
150 |
+
top_k = -1
|
151 |
+
)
|
152 |
+
|
153 |
+
# default sampling with reduced temperature
|
154 |
+
LowTempSampling = SamplerSettings(
|
155 |
+
temp = 0.4
|
156 |
+
)
|
157 |
+
|
158 |
+
# default sampling with increased temperature
|
159 |
+
HighTempSampling = SamplerSettings(
|
160 |
+
temp = 1.2
|
161 |
+
)
|
webscout/Local/thread.py
ADDED
@@ -0,0 +1,690 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from ._version import __version__, __llama_cpp_version__
|
2 |
+
|
3 |
+
"""Submodule containing the Thread class, used for interaction with a Model"""
|
4 |
+
|
5 |
+
import sys
|
6 |
+
|
7 |
+
from .model import Model, assert_model_is_loaded, _SupportsWriteAndFlush
|
8 |
+
from .utils import RESET_ALL, cls, print_verbose, truncate
|
9 |
+
from .samplers import SamplerSettings, DefaultSampling
|
10 |
+
from typing import Optional, Literal, Union
|
11 |
+
from .formats import AdvancedFormat
|
12 |
+
|
13 |
+
from .formats import blank as formats_blank
|
14 |
+
|
15 |
+
|
16 |
+
class Message(dict):
|
17 |
+
"""
|
18 |
+
A dictionary representing a single message within a Thread
|
19 |
+
|
20 |
+
Works just like a normal `dict`, but a new method:
|
21 |
+
- `.as_string` - Return the full message string
|
22 |
+
|
23 |
+
Generally, messages have these keys:
|
24 |
+
- `role` - The role of the speaker: 'system', 'user', or 'bot'
|
25 |
+
- `prefix` - The text that prefixes the message content
|
26 |
+
- `content` - The actual content of the message
|
27 |
+
- `suffix` - The text that suffixes the message content
|
28 |
+
"""
|
29 |
+
|
30 |
+
def __repr__(self) -> str:
|
31 |
+
return \
|
32 |
+
f"Message([" \
|
33 |
+
f"('role', {repr(self['role'])}), " \
|
34 |
+
f"('prefix', {repr(self['prefix'])}), " \
|
35 |
+
f"('content', {repr(self['content'])}), " \
|
36 |
+
f"('suffix', {repr(self['suffix'])})])"
|
37 |
+
|
38 |
+
def as_string(self):
|
39 |
+
"""Return the full message string"""
|
40 |
+
try:
|
41 |
+
return self['prefix'] + self['content'] + self['suffix']
|
42 |
+
except KeyError as e:
|
43 |
+
e.add_note(
|
44 |
+
"as_string: Message is missing one or more of the "
|
45 |
+
"required 'prefix', 'content', 'suffix' attributes - this is "
|
46 |
+
"unexpected"
|
47 |
+
)
|
48 |
+
raise e
|
49 |
+
|
50 |
+
|
51 |
+
class Thread:
|
52 |
+
"""
|
53 |
+
Provide functionality to facilitate easy interactions with a Model
|
54 |
+
|
55 |
+
This is just a brief overview of m.Thread.
|
56 |
+
To see a full description of each method and its parameters,
|
57 |
+
call help(Thread), or see the relevant docstring.
|
58 |
+
|
59 |
+
The following methods are available:
|
60 |
+
- `.add_message()` - Add a message to `Thread.messages`
|
61 |
+
- `.as_string()` - Return this thread's complete message history as a string
|
62 |
+
- `.create_message()` - Create a message using the format of this thread
|
63 |
+
- `.inference_str_from_messages()` - Using the list of messages, return a string suitable for inference
|
64 |
+
- `.interact()` - Start an interactive, terminal-based chat session
|
65 |
+
- `.len_messages()` - Get the total length of all messages in tokens
|
66 |
+
- `.print_stats()` - Print stats about the context usage in this thread
|
67 |
+
- `.reset()` - Clear the list of messages
|
68 |
+
- `.send()` - Send a message in this thread
|
69 |
+
|
70 |
+
The following attributes are available:
|
71 |
+
- `.format` - The format being used for messages in this thread
|
72 |
+
- `.messages` - The list of messages in this thread
|
73 |
+
- `.model` - The `m.Model` instance used by this thread
|
74 |
+
- `.sampler` - The SamplerSettings object used in this thread
|
75 |
+
"""
|
76 |
+
|
77 |
+
def __init__(
|
78 |
+
self,
|
79 |
+
model: Model,
|
80 |
+
format: Union[dict, AdvancedFormat],
|
81 |
+
sampler: SamplerSettings = DefaultSampling,
|
82 |
+
messages: Optional[list[Message]] = None,
|
83 |
+
):
|
84 |
+
"""
|
85 |
+
Given a Model and a format, construct a Thread instance.
|
86 |
+
|
87 |
+
model: The Model to use for text generation
|
88 |
+
format: The format specifying how messages should be structured (see m.formats)
|
89 |
+
|
90 |
+
The following parameters are optional:
|
91 |
+
- sampler: The SamplerSettings object used to control text generation
|
92 |
+
- messages: A list of m.thread.Message objects to add to the Thread upon construction
|
93 |
+
"""
|
94 |
+
|
95 |
+
assert isinstance(model, Model), \
|
96 |
+
"Thread: model should be an " + \
|
97 |
+
f"instance of webscout.Local.Model, not {type(model)}"
|
98 |
+
|
99 |
+
assert_model_is_loaded(model)
|
100 |
+
|
101 |
+
assert isinstance(format, (dict, AdvancedFormat)), \
|
102 |
+
f"Thread: format should be dict or AdvancedFormat, not {type(format)}"
|
103 |
+
|
104 |
+
if any(k not in format.keys() for k in formats_blank.keys()):
|
105 |
+
raise KeyError(
|
106 |
+
"Thread: format is missing one or more required keys, see " + \
|
107 |
+
"webscout.Local.formats.blank for an example"
|
108 |
+
)
|
109 |
+
|
110 |
+
assert isinstance(format['stops'], list), \
|
111 |
+
"Thread: format['stops'] should be list, not " + \
|
112 |
+
f"{type(format['stops'])}"
|
113 |
+
|
114 |
+
assert all(
|
115 |
+
hasattr(sampler, attr) for attr in [
|
116 |
+
'max_len_tokens',
|
117 |
+
'temp',
|
118 |
+
'top_p',
|
119 |
+
'min_p',
|
120 |
+
'frequency_penalty',
|
121 |
+
'presence_penalty',
|
122 |
+
'repeat_penalty',
|
123 |
+
'top_k'
|
124 |
+
]
|
125 |
+
), 'Thread: sampler is missing one or more required attributes'
|
126 |
+
|
127 |
+
self._messages: Optional[list[Message]] = messages
|
128 |
+
if self._messages is not None:
|
129 |
+
if not all(isinstance(msg, Message) for msg in self._messages):
|
130 |
+
raise TypeError(
|
131 |
+
"Thread: one or more messages provided to __init__() is "
|
132 |
+
"not an instance of m.thread.Message"
|
133 |
+
)
|
134 |
+
|
135 |
+
# Thread.messages is never empty, unless `messages` param is explicity
|
136 |
+
# set to `[]` during construction
|
137 |
+
|
138 |
+
self.model: Model = model
|
139 |
+
self.format: Union[dict, AdvancedFormat] = format
|
140 |
+
self.messages: list[Message] = [
|
141 |
+
self.create_message("system", self.format['system_content'])
|
142 |
+
] if self._messages is None else self._messages
|
143 |
+
self.sampler: SamplerSettings = sampler
|
144 |
+
|
145 |
+
if self.model.verbose:
|
146 |
+
print_verbose("new Thread instance with the following attributes:")
|
147 |
+
print_verbose(f"model == {self.model}")
|
148 |
+
print_verbose(f"format['system_prefix'] == {truncate(repr(self.format['system_prefix']))}")
|
149 |
+
print_verbose(f"format['system_content'] == {truncate(repr(self.format['system_content']))}")
|
150 |
+
print_verbose(f"format['system_suffix'] == {truncate(repr(self.format['system_suffix']))}")
|
151 |
+
print_verbose(f"format['user_prefix'] == {truncate(repr(self.format['user_prefix']))}")
|
152 |
+
print_verbose(f"format['user_content'] == {truncate(repr(self.format['user_content']))}")
|
153 |
+
print_verbose(f"format['user_suffix'] == {truncate(repr(self.format['user_suffix']))}")
|
154 |
+
print_verbose(f"format['bot_prefix'] == {truncate(repr(self.format['bot_prefix']))}")
|
155 |
+
print_verbose(f"format['bot_content'] == {truncate(repr(self.format['bot_content']))}")
|
156 |
+
print_verbose(f"format['bot_suffix'] == {truncate(repr(self.format['bot_suffix']))}")
|
157 |
+
print_verbose(f"format['stops'] == {truncate(repr(self.format['stops']))}")
|
158 |
+
print_verbose(f"sampler.temp == {self.sampler.temp}")
|
159 |
+
print_verbose(f"sampler.top_p == {self.sampler.top_p}")
|
160 |
+
print_verbose(f"sampler.min_p == {self.sampler.min_p}")
|
161 |
+
print_verbose(f"sampler.frequency_penalty == {self.sampler.frequency_penalty}")
|
162 |
+
print_verbose(f"sampler.presence_penalty == {self.sampler.presence_penalty}")
|
163 |
+
print_verbose(f"sampler.repeat_penalty == {self.sampler.repeat_penalty}")
|
164 |
+
print_verbose(f"sampler.top_k == {self.sampler.top_k}")
|
165 |
+
|
166 |
+
|
167 |
+
def __repr__(self) -> str:
|
168 |
+
return \
|
169 |
+
f"Thread({repr(self.model)}, {repr(self.format)}, " + \
|
170 |
+
f"{repr(self.sampler)}, {repr(self.messages)})"
|
171 |
+
|
172 |
+
def __str__(self) -> str:
|
173 |
+
return self.as_string()
|
174 |
+
|
175 |
+
def __len__(self) -> int:
|
176 |
+
"""
|
177 |
+
`len(Thread)` returns the length of the Thread in tokens
|
178 |
+
|
179 |
+
To get the number of messages in the Thread, use `len(Thread.messages)`
|
180 |
+
"""
|
181 |
+
return self.len_messages()
|
182 |
+
|
183 |
+
def create_message(
|
184 |
+
self,
|
185 |
+
role: Literal['system', 'user', 'bot'],
|
186 |
+
content: str
|
187 |
+
) -> Message:
|
188 |
+
"""
|
189 |
+
Construct a message using the format of this Thread
|
190 |
+
"""
|
191 |
+
|
192 |
+
assert role.lower() in ['system', 'user', 'bot'], \
|
193 |
+
f"create_message: role should be 'system', 'user', or 'bot', not '{role.lower()}'"
|
194 |
+
|
195 |
+
assert isinstance(content, str), \
|
196 |
+
f"create_message: content should be str, not {type(content)}"
|
197 |
+
|
198 |
+
if role.lower() == 'system':
|
199 |
+
return Message(
|
200 |
+
[
|
201 |
+
('role', 'system'),
|
202 |
+
('prefix', self.format['system_prefix']),
|
203 |
+
('content', content),
|
204 |
+
('suffix', self.format['system_suffix'])
|
205 |
+
]
|
206 |
+
)
|
207 |
+
|
208 |
+
elif role.lower() == 'user':
|
209 |
+
return Message(
|
210 |
+
[
|
211 |
+
('role', 'user'),
|
212 |
+
('prefix', self.format['user_prefix']),
|
213 |
+
('content', content),
|
214 |
+
('suffix', self.format['user_suffix'])
|
215 |
+
]
|
216 |
+
)
|
217 |
+
|
218 |
+
elif role.lower() == 'bot':
|
219 |
+
return Message(
|
220 |
+
[
|
221 |
+
('role', 'bot'),
|
222 |
+
('prefix', self.format['bot_prefix']),
|
223 |
+
('content', content),
|
224 |
+
('suffix', self.format['bot_suffix'])
|
225 |
+
]
|
226 |
+
)
|
227 |
+
|
228 |
+
def len_messages(self) -> int:
|
229 |
+
"""
|
230 |
+
Return the total length of all messages in this thread, in tokens.
|
231 |
+
|
232 |
+
Can also use `len(Thread)`."""
|
233 |
+
|
234 |
+
return self.model.get_length(self.as_string())
|
235 |
+
|
236 |
+
def add_message(
|
237 |
+
self,
|
238 |
+
role: Literal['system', 'user', 'bot'],
|
239 |
+
content: str
|
240 |
+
) -> None:
|
241 |
+
"""
|
242 |
+
Create a message and append it to `Thread.messages`.
|
243 |
+
|
244 |
+
`Thread.add_message(...)` is a shorthand for
|
245 |
+
`Thread.messages.append(Thread.create_message(...))`
|
246 |
+
"""
|
247 |
+
self.messages.append(
|
248 |
+
self.create_message(
|
249 |
+
role=role,
|
250 |
+
content=content
|
251 |
+
)
|
252 |
+
)
|
253 |
+
|
254 |
+
def inference_str_from_messages(self) -> str:
|
255 |
+
"""
|
256 |
+
Using the list of messages, construct a string suitable for inference,
|
257 |
+
respecting the format and context length of this thread.
|
258 |
+
"""
|
259 |
+
|
260 |
+
inf_str = ''
|
261 |
+
sys_msg_str = ''
|
262 |
+
# whether to treat the first message as necessary to keep
|
263 |
+
sys_msg_flag = False
|
264 |
+
context_len_budget = self.model.context_length
|
265 |
+
|
266 |
+
# if at least 1 message is history
|
267 |
+
if len(self.messages) >= 1:
|
268 |
+
# if first message has system role
|
269 |
+
if self.messages[0]['role'] == 'system':
|
270 |
+
sys_msg_flag = True
|
271 |
+
sys_msg = self.messages[0]
|
272 |
+
sys_msg_str = sys_msg.as_string()
|
273 |
+
context_len_budget -= self.model.get_length(sys_msg_str)
|
274 |
+
|
275 |
+
if sys_msg_flag:
|
276 |
+
iterator = reversed(self.messages[1:])
|
277 |
+
else:
|
278 |
+
iterator = reversed(self.messages)
|
279 |
+
|
280 |
+
for message in iterator:
|
281 |
+
msg_str = message.as_string()
|
282 |
+
context_len_budget -= self.model.get_length(msg_str)
|
283 |
+
if context_len_budget <= 0:
|
284 |
+
break
|
285 |
+
inf_str = msg_str + inf_str
|
286 |
+
|
287 |
+
if sys_msg_flag:
|
288 |
+
inf_str = sys_msg_str + inf_str
|
289 |
+
inf_str += self.format['bot_prefix']
|
290 |
+
|
291 |
+
return inf_str
|
292 |
+
|
293 |
+
|
294 |
+
def send(self, prompt: str) -> str:
|
295 |
+
"""
|
296 |
+
Send a message in this thread. This adds your message and the bot's
|
297 |
+
response to the list of messages.
|
298 |
+
|
299 |
+
Returns a string containing the response to your message.
|
300 |
+
"""
|
301 |
+
|
302 |
+
self.add_message("user", prompt)
|
303 |
+
output = self.model.generate(
|
304 |
+
self.inference_str_from_messages(),
|
305 |
+
stops=self.format['stops'],
|
306 |
+
sampler=self.sampler
|
307 |
+
)
|
308 |
+
self.add_message("bot", output)
|
309 |
+
|
310 |
+
return output
|
311 |
+
|
312 |
+
|
313 |
+
def _interactive_update_sampler(self) -> None:
|
314 |
+
"""Interactively update the sampler settings used in this Thread"""
|
315 |
+
print()
|
316 |
+
try:
|
317 |
+
new_max_len_tokens = input(f'max_len_tokens: {self.sampler.max_len_tokens} -> ')
|
318 |
+
new_temp = input(f'temp: {self.sampler.temp} -> ')
|
319 |
+
new_top_p = input(f'top_p: {self.sampler.top_p} -> ')
|
320 |
+
new_min_p = input(f'min_p: {self.sampler.min_p} -> ')
|
321 |
+
new_frequency_penalty = input(f'frequency_penalty: {self.sampler.frequency_penalty} -> ')
|
322 |
+
new_presence_penalty = input(f'presence_penalty: {self.sampler.presence_penalty} -> ')
|
323 |
+
new_repeat_penalty = input(f'repeat_penalty: {self.sampler.repeat_penalty} -> ')
|
324 |
+
new_top_k = input(f'top_k: {self.sampler.top_k} -> ')
|
325 |
+
|
326 |
+
except KeyboardInterrupt:
|
327 |
+
print('\nwebscout.Local: sampler settings not updated\n')
|
328 |
+
return
|
329 |
+
print()
|
330 |
+
|
331 |
+
try:
|
332 |
+
self.sampler.max_len_tokens = int(new_max_len_tokens)
|
333 |
+
except ValueError:
|
334 |
+
pass
|
335 |
+
else:
|
336 |
+
print('webscout.Local: max_len_tokens updated')
|
337 |
+
|
338 |
+
try:
|
339 |
+
self.sampler.temp = float(new_temp)
|
340 |
+
except ValueError:
|
341 |
+
pass
|
342 |
+
else:
|
343 |
+
print('webscout.Local: temp updated')
|
344 |
+
|
345 |
+
try:
|
346 |
+
self.sampler.top_p = float(new_top_p)
|
347 |
+
except ValueError:
|
348 |
+
pass
|
349 |
+
else:
|
350 |
+
print('webscout.Local: top_p updated')
|
351 |
+
|
352 |
+
try:
|
353 |
+
self.sampler.min_p = float(new_min_p)
|
354 |
+
except ValueError:
|
355 |
+
pass
|
356 |
+
else:
|
357 |
+
print('webscout.Local: min_p updated')
|
358 |
+
|
359 |
+
try:
|
360 |
+
self.sampler.frequency_penalty = float(new_frequency_penalty)
|
361 |
+
except ValueError:
|
362 |
+
pass
|
363 |
+
else:
|
364 |
+
print('webscout.Local: frequency_penalty updated')
|
365 |
+
|
366 |
+
try:
|
367 |
+
self.sampler.presence_penalty = float(new_presence_penalty)
|
368 |
+
except ValueError:
|
369 |
+
pass
|
370 |
+
else:
|
371 |
+
print('webscout.Local: presence_penalty updated')
|
372 |
+
|
373 |
+
try:
|
374 |
+
self.sampler.repeat_penalty = float(new_repeat_penalty)
|
375 |
+
except ValueError:
|
376 |
+
pass
|
377 |
+
else:
|
378 |
+
print('webscout.Local: repeat_penalty updated')
|
379 |
+
|
380 |
+
try:
|
381 |
+
self.sampler.top_k = int(new_top_k)
|
382 |
+
except ValueError:
|
383 |
+
pass
|
384 |
+
else:
|
385 |
+
print('webscout.Local: top_k updated')
|
386 |
+
print()
|
387 |
+
|
388 |
+
|
389 |
+
def _interactive_input(
|
390 |
+
self,
|
391 |
+
prompt: str,
|
392 |
+
_dim_style: str,
|
393 |
+
_user_style: str,
|
394 |
+
_bot_style: str,
|
395 |
+
_special_style: str
|
396 |
+
) -> tuple:
|
397 |
+
"""
|
398 |
+
Recive input from the user, while handling multi-line input
|
399 |
+
and commands
|
400 |
+
"""
|
401 |
+
full_user_input = '' # may become multiline
|
402 |
+
|
403 |
+
while True:
|
404 |
+
user_input = input(prompt)
|
405 |
+
|
406 |
+
if user_input.endswith('\\'):
|
407 |
+
full_user_input += user_input[:-1] + '\n'
|
408 |
+
|
409 |
+
elif user_input == '!':
|
410 |
+
|
411 |
+
print()
|
412 |
+
try:
|
413 |
+
command = input(f'{RESET_ALL} ! {_dim_style}')
|
414 |
+
except KeyboardInterrupt:
|
415 |
+
print('\n')
|
416 |
+
continue
|
417 |
+
|
418 |
+
if command == '':
|
419 |
+
print(f'\n[no command]\n')
|
420 |
+
|
421 |
+
elif command.lower() in ['reset', 'restart']:
|
422 |
+
self.reset()
|
423 |
+
print(f'\n[thread reset]\n')
|
424 |
+
|
425 |
+
elif command.lower() in ['cls', 'clear']:
|
426 |
+
cls()
|
427 |
+
print()
|
428 |
+
|
429 |
+
elif command.lower() in ['ctx', 'context']:
|
430 |
+
print(f"\n{self.len_messages()}\n")
|
431 |
+
|
432 |
+
elif command.lower() in ['stats', 'print_stats']:
|
433 |
+
print()
|
434 |
+
self.print_stats()
|
435 |
+
print()
|
436 |
+
|
437 |
+
elif command.lower() in ['sampler', 'samplers', 'settings']:
|
438 |
+
self._interactive_update_sampler()
|
439 |
+
|
440 |
+
elif command.lower() in ['str', 'string', 'as_string']:
|
441 |
+
print(f"\n{self.as_string()}\n")
|
442 |
+
|
443 |
+
elif command.lower() in ['repr', 'save', 'backup']:
|
444 |
+
print(f"\n{repr(self)}\n")
|
445 |
+
|
446 |
+
elif command.lower() in ['remove', 'rem', 'delete', 'del']:
|
447 |
+
print()
|
448 |
+
old_len = len(self.messages)
|
449 |
+
del self.messages[-1]
|
450 |
+
assert len(self.messages) == (old_len - 1)
|
451 |
+
print('[removed last message]\n')
|
452 |
+
|
453 |
+
elif command.lower() in ['last', 'repeat']:
|
454 |
+
last_msg = self.messages[-1]
|
455 |
+
if last_msg['role'] == 'user':
|
456 |
+
print(f"\n{_user_style}{last_msg['content']}{RESET_ALL}\n")
|
457 |
+
elif last_msg['role'] == 'bot':
|
458 |
+
print(f"\n{_bot_style}{last_msg['content']}{RESET_ALL}\n")
|
459 |
+
|
460 |
+
elif command.lower() in ['inf', 'inference', 'inf_str']:
|
461 |
+
print(f'\n"""{self.inference_str_from_messages()}"""\n')
|
462 |
+
|
463 |
+
elif command.lower() in ['reroll', 're-roll', 're', 'swipe']:
|
464 |
+
old_len = len(self.messages)
|
465 |
+
del self.messages[-1]
|
466 |
+
assert len(self.messages) == (old_len - 1)
|
467 |
+
return '', None
|
468 |
+
|
469 |
+
elif command.lower() in ['exit', 'quit']:
|
470 |
+
print(RESET_ALL)
|
471 |
+
return None, None
|
472 |
+
|
473 |
+
elif command.lower() in ['help', '/?', '?']:
|
474 |
+
print()
|
475 |
+
print('reset | restart -- Reset the thread to its original state')
|
476 |
+
print('clear | cls -- Clear the terminal')
|
477 |
+
print('context | ctx -- Get the context usage in tokens')
|
478 |
+
print('print_stats | stats -- Get the context usage stats')
|
479 |
+
print('sampler | settings -- Update the sampler settings')
|
480 |
+
print('string | str -- Print the message history as a string')
|
481 |
+
print('repr | save -- Print the representation of the thread')
|
482 |
+
print('remove | delete -- Remove the last message')
|
483 |
+
print('last | repeat -- Repeat the last message')
|
484 |
+
print('inference | inf -- Print the inference string')
|
485 |
+
print('reroll | swipe -- Regenerate the last message')
|
486 |
+
print('exit | quit -- Exit the interactive chat (can also use ^C)')
|
487 |
+
print('help | ? -- Show this screen')
|
488 |
+
print()
|
489 |
+
print("TIP: type < at the prompt and press ENTER to prefix the bot's next message.")
|
490 |
+
print(' for example, type "Sure!" to bypass refusals')
|
491 |
+
print()
|
492 |
+
print("TIP: type !! at the prompt and press ENTER to insert a system message")
|
493 |
+
print()
|
494 |
+
|
495 |
+
else:
|
496 |
+
print(f'\n[unknown command]\n')
|
497 |
+
|
498 |
+
# prefix the bot's next message
|
499 |
+
elif user_input == '<':
|
500 |
+
|
501 |
+
print()
|
502 |
+
try:
|
503 |
+
next_message_start = input(f'{RESET_ALL} < {_dim_style}')
|
504 |
+
|
505 |
+
except KeyboardInterrupt:
|
506 |
+
print(f'{RESET_ALL}\n')
|
507 |
+
continue
|
508 |
+
|
509 |
+
else:
|
510 |
+
print()
|
511 |
+
return '', next_message_start
|
512 |
+
|
513 |
+
# insert a system message
|
514 |
+
elif user_input == '!!':
|
515 |
+
print()
|
516 |
+
|
517 |
+
try:
|
518 |
+
next_sys_msg = input(f'{RESET_ALL} !! {_special_style}')
|
519 |
+
|
520 |
+
except KeyboardInterrupt:
|
521 |
+
print(f'{RESET_ALL}\n')
|
522 |
+
continue
|
523 |
+
|
524 |
+
else:
|
525 |
+
print()
|
526 |
+
return next_sys_msg, -1
|
527 |
+
|
528 |
+
# concatenate multi-line input
|
529 |
+
else:
|
530 |
+
full_user_input += user_input
|
531 |
+
return full_user_input, None
|
532 |
+
|
533 |
+
|
534 |
+
def interact(
|
535 |
+
self,
|
536 |
+
color: bool = True,
|
537 |
+
header: Optional[str] = None,
|
538 |
+
stream: bool = True
|
539 |
+
) -> None:
|
540 |
+
"""
|
541 |
+
Start an interactive chat session using this Thread.
|
542 |
+
|
543 |
+
While text is being generated, press `^C` to interrupt the bot.
|
544 |
+
Then you have the option to press `ENTER` to re-roll, or to simply type
|
545 |
+
another message.
|
546 |
+
|
547 |
+
At the prompt, press `^C` to end the chat session.
|
548 |
+
|
549 |
+
Type `!` and press `ENTER` to enter a basic command prompt. For a list
|
550 |
+
of commands, type `help` at this prompt.
|
551 |
+
|
552 |
+
Type `<` and press `ENTER` to prefix the bot's next message, for
|
553 |
+
example with `Sure!`.
|
554 |
+
|
555 |
+
Type `!!` at the prompt and press `ENTER` to insert a system message.
|
556 |
+
|
557 |
+
The following parameters are optional:
|
558 |
+
- color: Whether to use colored text to differentiate user / bot
|
559 |
+
- header: Header text to print at the start of the interaction
|
560 |
+
- stream: Whether to stream text as it is generated
|
561 |
+
"""
|
562 |
+
print()
|
563 |
+
|
564 |
+
# fresh import of color codes in case `color` param has changed
|
565 |
+
from .utils import SPECIAL_STYLE, USER_STYLE, BOT_STYLE, DIM_STYLE
|
566 |
+
|
567 |
+
# disable color codes if explicitly disabled by `color` param
|
568 |
+
if not color:
|
569 |
+
SPECIAL_STYLE = ''
|
570 |
+
USER_STYLE = ''
|
571 |
+
BOT_STYLE = ''
|
572 |
+
DIM_STYLE = ''
|
573 |
+
|
574 |
+
if header is not None:
|
575 |
+
print(f"{SPECIAL_STYLE}{header}{RESET_ALL}\n")
|
576 |
+
|
577 |
+
while True:
|
578 |
+
|
579 |
+
prompt = f"{RESET_ALL} > {USER_STYLE}"
|
580 |
+
|
581 |
+
try:
|
582 |
+
user_prompt, next_message_start = self._interactive_input(
|
583 |
+
prompt,
|
584 |
+
DIM_STYLE,
|
585 |
+
USER_STYLE,
|
586 |
+
BOT_STYLE,
|
587 |
+
SPECIAL_STYLE
|
588 |
+
)
|
589 |
+
except KeyboardInterrupt:
|
590 |
+
print(f"{RESET_ALL}\n")
|
591 |
+
return
|
592 |
+
|
593 |
+
# got 'exit' or 'quit' command
|
594 |
+
if user_prompt is None and next_message_start is None:
|
595 |
+
break
|
596 |
+
|
597 |
+
# insert a system message via `!!` prompt
|
598 |
+
if next_message_start == -1:
|
599 |
+
self.add_message('system', user_prompt)
|
600 |
+
continue
|
601 |
+
|
602 |
+
if next_message_start is not None:
|
603 |
+
try:
|
604 |
+
if stream:
|
605 |
+
print(f"{BOT_STYLE}{next_message_start}", end='', flush=True)
|
606 |
+
output = next_message_start + self.model.stream_print(
|
607 |
+
self.inference_str_from_messages() + next_message_start,
|
608 |
+
stops=self.format['stops'],
|
609 |
+
sampler=self.sampler,
|
610 |
+
end=''
|
611 |
+
)
|
612 |
+
else:
|
613 |
+
print(f"{BOT_STYLE}", end='', flush=True)
|
614 |
+
output = next_message_start + self.model.generate(
|
615 |
+
self.inference_str_from_messages() + next_message_start,
|
616 |
+
stops=self.format['stops'],
|
617 |
+
sampler=self.sampler
|
618 |
+
)
|
619 |
+
print(output, end='', flush=True)
|
620 |
+
except KeyboardInterrupt:
|
621 |
+
print(f"{DIM_STYLE} [message not added to history; press ENTER to re-roll]\n")
|
622 |
+
continue
|
623 |
+
else:
|
624 |
+
self.add_message("bot", output)
|
625 |
+
else:
|
626 |
+
print(BOT_STYLE)
|
627 |
+
if user_prompt != "":
|
628 |
+
self.add_message("user", user_prompt)
|
629 |
+
try:
|
630 |
+
if stream:
|
631 |
+
output = self.model.stream_print(
|
632 |
+
self.inference_str_from_messages(),
|
633 |
+
stops=self.format['stops'],
|
634 |
+
sampler=self.sampler,
|
635 |
+
end=''
|
636 |
+
)
|
637 |
+
else:
|
638 |
+
output = self.model.generate(
|
639 |
+
self.inference_str_from_messages(),
|
640 |
+
stops=self.format['stops'],
|
641 |
+
sampler=self.sampler
|
642 |
+
)
|
643 |
+
print(output, end='', flush=True)
|
644 |
+
except KeyboardInterrupt:
|
645 |
+
print(f"{DIM_STYLE} [message not added to history; press ENTER to re-roll]\n")
|
646 |
+
continue
|
647 |
+
else:
|
648 |
+
self.add_message("bot", output)
|
649 |
+
|
650 |
+
if output.endswith("\n\n"):
|
651 |
+
print(RESET_ALL, end = '', flush=True)
|
652 |
+
elif output.endswith("\n"):
|
653 |
+
print(RESET_ALL)
|
654 |
+
else:
|
655 |
+
print(f"{RESET_ALL}\n")
|
656 |
+
|
657 |
+
|
658 |
+
def reset(self) -> None:
|
659 |
+
"""
|
660 |
+
Clear the list of messages, which resets the thread to its original
|
661 |
+
state
|
662 |
+
"""
|
663 |
+
self.messages: list[Message] = [
|
664 |
+
self.create_message("system", self.format['system_content'])
|
665 |
+
] if self._messages is None else self._messages
|
666 |
+
|
667 |
+
|
668 |
+
def as_string(self) -> str:
|
669 |
+
"""Return this thread's message history as a string"""
|
670 |
+
thread_string = ''
|
671 |
+
for msg in self.messages:
|
672 |
+
thread_string += msg.as_string()
|
673 |
+
return thread_string
|
674 |
+
|
675 |
+
|
676 |
+
def print_stats(
|
677 |
+
self,
|
678 |
+
end: str = '\n',
|
679 |
+
file: _SupportsWriteAndFlush = sys.stdout,
|
680 |
+
flush: bool = True
|
681 |
+
) -> None:
|
682 |
+
"""Print stats about the context usage in this thread"""
|
683 |
+
thread_len_tokens = self.len_messages()
|
684 |
+
max_ctx_len = self.model.context_length
|
685 |
+
context_used_percentage = round((thread_len_tokens/max_ctx_len)*100)
|
686 |
+
print(f"{thread_len_tokens} / {max_ctx_len} tokens", file=file, flush=flush)
|
687 |
+
print(f"{context_used_percentage}% of context used", file=file, flush=flush)
|
688 |
+
print(f"{len(self.messages)} messages", end=end, file=file, flush=flush)
|
689 |
+
if not flush:
|
690 |
+
file.flush()
|
webscout/Local/utils.py
ADDED
@@ -0,0 +1,185 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from ._version import __version__, __llama_cpp_version__
|
2 |
+
|
3 |
+
import sys
|
4 |
+
import numpy as np
|
5 |
+
|
6 |
+
from typing import Any, Iterable, TextIO
|
7 |
+
from time import strftime
|
8 |
+
from enum import IntEnum
|
9 |
+
from struct import unpack
|
10 |
+
from colorama import Fore
|
11 |
+
from huggingface_hub import hf_hub_url, cached_download
|
12 |
+
|
13 |
+
# color codes used in Thread.interact()
|
14 |
+
RESET_ALL = Fore.RESET
|
15 |
+
USER_STYLE = RESET_ALL + Fore.GREEN
|
16 |
+
BOT_STYLE = RESET_ALL + Fore.CYAN
|
17 |
+
DIM_STYLE = RESET_ALL + Fore.LIGHTBLACK_EX
|
18 |
+
SPECIAL_STYLE = RESET_ALL + Fore.YELLOW
|
19 |
+
|
20 |
+
# for typing of softmax parameter `z`
|
21 |
+
class _ArrayLike(Iterable):
|
22 |
+
pass
|
23 |
+
|
24 |
+
# for typing of Model.stream_print() parameter `file`
|
25 |
+
class _SupportsWriteAndFlush(TextIO):
|
26 |
+
pass
|
27 |
+
|
28 |
+
def download_model(repo_id: str, filename: str, cache_dir: str = ".cache") -> str:
|
29 |
+
"""
|
30 |
+
Downloads a GGUF model file from Hugging Face Hub.
|
31 |
+
|
32 |
+
repo_id: The Hugging Face repository ID (e.g., 'facebook/bart-large-cnn').
|
33 |
+
filename: The name of the GGUF file within the repository (e.g., 'model.gguf').
|
34 |
+
cache_dir: The directory where the downloaded file should be stored.
|
35 |
+
|
36 |
+
Returns: The path to the downloaded file.
|
37 |
+
"""
|
38 |
+
url = hf_hub_url(repo_id, filename)
|
39 |
+
filepath = cached_download(url, cache_dir=cache_dir, force_filename=filename)
|
40 |
+
return filepath
|
41 |
+
|
42 |
+
class GGUFReader:
|
43 |
+
"""
|
44 |
+
Peek at file header for GGUF metadata
|
45 |
+
|
46 |
+
Raise ValueError if file is not GGUF or is outdated
|
47 |
+
|
48 |
+
Credit to oobabooga for the parts of the code in this class
|
49 |
+
|
50 |
+
Format spec: https://github.com/philpax/ggml/blob/gguf-spec/docs/gguf.md
|
51 |
+
"""
|
52 |
+
|
53 |
+
class GGUFValueType(IntEnum):
|
54 |
+
UINT8 = 0
|
55 |
+
INT8 = 1
|
56 |
+
UINT16 = 2
|
57 |
+
INT16 = 3
|
58 |
+
UINT32 = 4
|
59 |
+
INT32 = 5
|
60 |
+
FLOAT32 = 6
|
61 |
+
BOOL = 7
|
62 |
+
STRING = 8
|
63 |
+
ARRAY = 9
|
64 |
+
UINT64 = 10
|
65 |
+
INT64 = 11
|
66 |
+
FLOAT64 = 12
|
67 |
+
|
68 |
+
_simple_value_packing = {
|
69 |
+
GGUFValueType.UINT8: "<B",
|
70 |
+
GGUFValueType.INT8: "<b",
|
71 |
+
GGUFValueType.UINT16: "<H",
|
72 |
+
GGUFValueType.INT16: "<h",
|
73 |
+
GGUFValueType.UINT32: "<I",
|
74 |
+
GGUFValueType.INT32: "<i",
|
75 |
+
GGUFValueType.FLOAT32: "<f",
|
76 |
+
GGUFValueType.UINT64: "<Q",
|
77 |
+
GGUFValueType.INT64: "<q",
|
78 |
+
GGUFValueType.FLOAT64: "<d",
|
79 |
+
GGUFValueType.BOOL: "?",
|
80 |
+
}
|
81 |
+
|
82 |
+
value_type_info = {
|
83 |
+
GGUFValueType.UINT8: 1,
|
84 |
+
GGUFValueType.INT8: 1,
|
85 |
+
GGUFValueType.UINT16: 2,
|
86 |
+
GGUFValueType.INT16: 2,
|
87 |
+
GGUFValueType.UINT32: 4,
|
88 |
+
GGUFValueType.INT32: 4,
|
89 |
+
GGUFValueType.FLOAT32: 4,
|
90 |
+
GGUFValueType.UINT64: 8,
|
91 |
+
GGUFValueType.INT64: 8,
|
92 |
+
GGUFValueType.FLOAT64: 8,
|
93 |
+
GGUFValueType.BOOL: 1,
|
94 |
+
}
|
95 |
+
|
96 |
+
def get_single(self, value_type, file) -> Any:
|
97 |
+
if value_type == GGUFReader.GGUFValueType.STRING:
|
98 |
+
value_length = unpack("<Q", file.read(8))[0]
|
99 |
+
value = file.read(value_length)
|
100 |
+
value = value.decode("utf-8")
|
101 |
+
else:
|
102 |
+
type_str = GGUFReader._simple_value_packing.get(value_type)
|
103 |
+
bytes_length = GGUFReader.value_type_info.get(value_type)
|
104 |
+
value = unpack(type_str, file.read(bytes_length))[0]
|
105 |
+
|
106 |
+
return value
|
107 |
+
|
108 |
+
def load_metadata(self, fname) -> dict:
|
109 |
+
metadata = {}
|
110 |
+
with open(fname, "rb") as file:
|
111 |
+
GGUF_MAGIC = file.read(4)
|
112 |
+
|
113 |
+
if GGUF_MAGIC != b"GGUF":
|
114 |
+
raise ValueError(
|
115 |
+
"your model file is not a valid GGUF file "
|
116 |
+
f"(magic number mismatch, got {GGUF_MAGIC}, "
|
117 |
+
"expected b'GGUF')"
|
118 |
+
)
|
119 |
+
|
120 |
+
GGUF_VERSION = unpack("<I", file.read(4))[0]
|
121 |
+
|
122 |
+
if GGUF_VERSION == 1:
|
123 |
+
raise ValueError(
|
124 |
+
"your model file reports GGUF version 1, "
|
125 |
+
"but only versions 2 and above are supported. "
|
126 |
+
"re-convert your model or download a newer version"
|
127 |
+
)
|
128 |
+
|
129 |
+
# ti_data_count = struct.unpack("<Q", file.read(8))[0]
|
130 |
+
file.read(8)
|
131 |
+
kv_data_count = unpack("<Q", file.read(8))[0]
|
132 |
+
|
133 |
+
for _ in range(kv_data_count):
|
134 |
+
key_length = unpack("<Q", file.read(8))[0]
|
135 |
+
key = file.read(key_length)
|
136 |
+
|
137 |
+
value_type = GGUFReader.GGUFValueType(
|
138 |
+
unpack("<I", file.read(4))[0]
|
139 |
+
)
|
140 |
+
if value_type == GGUFReader.GGUFValueType.ARRAY:
|
141 |
+
ltype = GGUFReader.GGUFValueType(
|
142 |
+
unpack("<I", file.read(4))[0]
|
143 |
+
)
|
144 |
+
length = unpack("<Q", file.read(8))[0]
|
145 |
+
arr = [
|
146 |
+
GGUFReader.get_single(
|
147 |
+
self,
|
148 |
+
ltype,
|
149 |
+
file
|
150 |
+
) for _ in range(length)
|
151 |
+
]
|
152 |
+
metadata[key.decode()] = arr
|
153 |
+
else:
|
154 |
+
value = GGUFReader.get_single(self, value_type, file)
|
155 |
+
metadata[key.decode()] = value
|
156 |
+
|
157 |
+
return metadata
|
158 |
+
|
159 |
+
def softmax(z: _ArrayLike) -> np.ndarray:
|
160 |
+
"""
|
161 |
+
Compute softmax over values in z, where z is array-like
|
162 |
+
"""
|
163 |
+
e_z = np.exp(z - np.max(z))
|
164 |
+
return e_z / e_z.sum()
|
165 |
+
|
166 |
+
def cls() -> None:
|
167 |
+
"""Clear the terminal"""
|
168 |
+
print("\033c\033[3J", end='', flush=True)
|
169 |
+
|
170 |
+
# no longer used in this module, but left for others to use
|
171 |
+
def get_timestamp_prefix_str() -> str:
|
172 |
+
# helpful: https://strftime.net
|
173 |
+
return strftime("[%Y, %b %e, %a %l:%M %p] ")
|
174 |
+
|
175 |
+
def truncate(text: str) -> str:
|
176 |
+
return text if len(text) < 63 else f"{text[:60]}..."
|
177 |
+
|
178 |
+
def print_verbose(text: str) -> None:
|
179 |
+
print("webscout.Local: verbose:", text, file=sys.stderr, flush=True)
|
180 |
+
|
181 |
+
def print_info(text: str) -> None:
|
182 |
+
print("webscout.Local: info:", text, file=sys.stderr, flush=True)
|
183 |
+
|
184 |
+
def print_warning(text: str) -> None:
|
185 |
+
print("webscout.Local: warning:", text, file=sys.stderr, flush=True)
|
webscout/Provider/BasedGPT.py
ADDED
@@ -0,0 +1,226 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
import uuid
|
3 |
+
from selenium import webdriver
|
4 |
+
from selenium.webdriver.chrome.options import Options
|
5 |
+
from selenium.webdriver.common.by import By
|
6 |
+
from selenium.webdriver.support import expected_conditions as EC
|
7 |
+
from selenium.webdriver.support.ui import WebDriverWait
|
8 |
+
import click
|
9 |
+
import requests
|
10 |
+
from requests import get
|
11 |
+
from uuid import uuid4
|
12 |
+
from re import findall
|
13 |
+
from requests.exceptions import RequestException
|
14 |
+
from curl_cffi.requests import get, RequestsError
|
15 |
+
import g4f
|
16 |
+
from random import randint
|
17 |
+
from PIL import Image
|
18 |
+
import io
|
19 |
+
import re
|
20 |
+
import json
|
21 |
+
import yaml
|
22 |
+
from ..AIutel import Optimizers
|
23 |
+
from ..AIutel import Conversation
|
24 |
+
from ..AIutel import AwesomePrompts, sanitize_stream
|
25 |
+
from ..AIbase import Provider, AsyncProvider
|
26 |
+
from webscout import exceptions
|
27 |
+
from typing import Any, AsyncGenerator, Dict
|
28 |
+
import logging
|
29 |
+
import httpx
|
30 |
+
|
31 |
+
class BasedGPT(Provider):
|
32 |
+
def __init__(
|
33 |
+
self,
|
34 |
+
is_conversation: bool = True,
|
35 |
+
max_tokens: int = 600,
|
36 |
+
timeout: int = 30,
|
37 |
+
intro: str = None,
|
38 |
+
filepath: str = None,
|
39 |
+
update_file: bool = True,
|
40 |
+
proxies: dict = {},
|
41 |
+
history_offset: int = 10250,
|
42 |
+
act: str = None,
|
43 |
+
system_prompt: str = "Be Helpful and Friendly",
|
44 |
+
):
|
45 |
+
"""Instantiates BasedGPT
|
46 |
+
|
47 |
+
Args:
|
48 |
+
is_conversation (bool, optional): Flag for chatting conversationally. Defaults to True.
|
49 |
+
max_tokens (int, optional): Maximum number of tokens to be generated upon completion. Defaults to 600.
|
50 |
+
timeout (int, optional): Http request timeout. Defaults to 30.
|
51 |
+
intro (str, optional): Conversation introductory prompt. Defaults to None.
|
52 |
+
filepath (str, optional): Path to file containing conversation history. Defaults to None.
|
53 |
+
update_file (bool, optional): Add new prompts and responses to the file. Defaults to True.
|
54 |
+
proxies (dict, optional): Http request proxies. Defaults to {}.
|
55 |
+
history_offset (int, optional): Limit conversation history to this number of last texts. Defaults to 10250.
|
56 |
+
act (str|int, optional): Awesome prompt key or index. (Used as intro). Defaults to None.
|
57 |
+
system_prompt (str, optional): System prompt for BasedGPT. Defaults to "Be Helpful and Friendly".
|
58 |
+
"""
|
59 |
+
self.session = requests.Session()
|
60 |
+
self.is_conversation = is_conversation
|
61 |
+
self.max_tokens_to_sample = max_tokens
|
62 |
+
self.chat_endpoint = "https://www.basedgpt.chat/api/chat"
|
63 |
+
self.stream_chunk_size = 64
|
64 |
+
self.timeout = timeout
|
65 |
+
self.last_response = {}
|
66 |
+
self.system_prompt = system_prompt
|
67 |
+
|
68 |
+
self.__available_optimizers = (
|
69 |
+
method
|
70 |
+
for method in dir(Optimizers)
|
71 |
+
if callable(getattr(Optimizers, method)) and not method.startswith("__")
|
72 |
+
)
|
73 |
+
self.session.headers.update(
|
74 |
+
{"Content-Type": "application/json"}
|
75 |
+
)
|
76 |
+
Conversation.intro = (
|
77 |
+
AwesomePrompts().get_act(
|
78 |
+
act, raise_not_found=True, default=None, case_insensitive=True
|
79 |
+
)
|
80 |
+
if act
|
81 |
+
else intro or Conversation.intro
|
82 |
+
)
|
83 |
+
self.conversation = Conversation(
|
84 |
+
is_conversation, self.max_tokens_to_sample, filepath, update_file
|
85 |
+
)
|
86 |
+
self.conversation.history_offset = history_offset
|
87 |
+
self.session.proxies = proxies
|
88 |
+
|
89 |
+
def ask(
|
90 |
+
self,
|
91 |
+
prompt: str,
|
92 |
+
stream: bool = False,
|
93 |
+
raw: bool = False,
|
94 |
+
optimizer: str = None,
|
95 |
+
conversationally: bool = False,
|
96 |
+
) -> dict:
|
97 |
+
"""Chat with AI
|
98 |
+
|
99 |
+
Args:
|
100 |
+
prompt (str): Prompt to be send.
|
101 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
102 |
+
raw (bool, optional): Stream back raw response as received. Defaults to False.
|
103 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
104 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
105 |
+
Returns:
|
106 |
+
dict : {}
|
107 |
+
```json
|
108 |
+
{
|
109 |
+
"id": "chatcmpl-TaREJpBZsRVQFRFic1wIA7Q7XfnaD",
|
110 |
+
"object": "chat.completion",
|
111 |
+
"created": 1704623244,
|
112 |
+
"model": "gpt-3.5-turbo",
|
113 |
+
"usage": {
|
114 |
+
"prompt_tokens": 0,
|
115 |
+
"completion_tokens": 0,
|
116 |
+
"total_tokens": 0
|
117 |
+
},
|
118 |
+
"choices": [
|
119 |
+
{
|
120 |
+
"message": {
|
121 |
+
"role": "assistant",
|
122 |
+
"content": "Hello! How can I assist you today?"
|
123 |
+
},
|
124 |
+
"finish_reason": "stop",
|
125 |
+
"index": 0
|
126 |
+
}
|
127 |
+
]
|
128 |
+
}
|
129 |
+
```
|
130 |
+
"""
|
131 |
+
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
132 |
+
if optimizer:
|
133 |
+
if optimizer in self.__available_optimizers:
|
134 |
+
conversation_prompt = getattr(Optimizers, optimizer)(
|
135 |
+
conversation_prompt if conversationally else prompt
|
136 |
+
)
|
137 |
+
else:
|
138 |
+
raise Exception(
|
139 |
+
f"Optimizer is not one of {self.__available_optimizers}"
|
140 |
+
)
|
141 |
+
|
142 |
+
payload = {
|
143 |
+
"messages": [
|
144 |
+
{"role": "system", "content": self.system_prompt},
|
145 |
+
{"role": "user", "content": conversation_prompt},
|
146 |
+
],
|
147 |
+
}
|
148 |
+
|
149 |
+
def for_stream():
|
150 |
+
response = self.session.post(
|
151 |
+
self.chat_endpoint, json=payload, stream=True, timeout=self.timeout
|
152 |
+
)
|
153 |
+
if not response.ok:
|
154 |
+
raise exceptions.FailedToGenerateResponseError(
|
155 |
+
f"Failed to generate response - ({response.status_code}, {response.reason}) - {response.text}"
|
156 |
+
)
|
157 |
+
|
158 |
+
message_load = ""
|
159 |
+
for value in response.iter_lines(
|
160 |
+
decode_unicode=True,
|
161 |
+
delimiter="",
|
162 |
+
chunk_size=self.stream_chunk_size,
|
163 |
+
):
|
164 |
+
try:
|
165 |
+
message_load += value
|
166 |
+
yield value if raw else dict(text=message_load)
|
167 |
+
except json.decoder.JSONDecodeError:
|
168 |
+
pass
|
169 |
+
self.last_response.update(dict(text=message_load))
|
170 |
+
self.conversation.update_chat_history(
|
171 |
+
prompt, self.get_message(self.last_response)
|
172 |
+
)
|
173 |
+
|
174 |
+
def for_non_stream():
|
175 |
+
for _ in for_stream():
|
176 |
+
pass
|
177 |
+
return self.last_response
|
178 |
+
|
179 |
+
return for_stream() if stream else for_non_stream()
|
180 |
+
|
181 |
+
def chat(
|
182 |
+
self,
|
183 |
+
prompt: str,
|
184 |
+
stream: bool = False,
|
185 |
+
optimizer: str = None,
|
186 |
+
conversationally: bool = False,
|
187 |
+
) -> str:
|
188 |
+
"""Generate response `str`
|
189 |
+
Args:
|
190 |
+
prompt (str): Prompt to be send.
|
191 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
192 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
193 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
194 |
+
Returns:
|
195 |
+
str: Response generated
|
196 |
+
"""
|
197 |
+
|
198 |
+
def for_stream():
|
199 |
+
for response in self.ask(
|
200 |
+
prompt, True, optimizer=optimizer, conversationally=conversationally
|
201 |
+
):
|
202 |
+
yield self.get_message(response)
|
203 |
+
|
204 |
+
def for_non_stream():
|
205 |
+
return self.get_message(
|
206 |
+
self.ask(
|
207 |
+
prompt,
|
208 |
+
False,
|
209 |
+
optimizer=optimizer,
|
210 |
+
conversationally=conversationally,
|
211 |
+
)
|
212 |
+
)
|
213 |
+
|
214 |
+
return for_stream() if stream else for_non_stream()
|
215 |
+
|
216 |
+
def get_message(self, response: dict) -> str:
|
217 |
+
"""Retrieves message only from response
|
218 |
+
|
219 |
+
Args:
|
220 |
+
response (dict): Response generated by `self.ask`
|
221 |
+
|
222 |
+
Returns:
|
223 |
+
str: Message extracted
|
224 |
+
"""
|
225 |
+
assert isinstance(response, dict), "Response should be of dict data-type only"
|
226 |
+
return response["text"]
|
webscout/Provider/Berlin4h.py
ADDED
@@ -0,0 +1,211 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import requests
|
2 |
+
import json
|
3 |
+
import uuid
|
4 |
+
from typing import Any, Dict, Optional
|
5 |
+
from ..AIutel import Optimizers
|
6 |
+
from ..AIutel import Conversation
|
7 |
+
from ..AIutel import AwesomePrompts, sanitize_stream
|
8 |
+
from ..AIbase import Provider, AsyncProvider
|
9 |
+
from webscout import exceptions
|
10 |
+
|
11 |
+
class Berlin4h(Provider):
|
12 |
+
"""
|
13 |
+
A class to interact with the Berlin4h AI API.
|
14 |
+
"""
|
15 |
+
|
16 |
+
def __init__(
|
17 |
+
self,
|
18 |
+
api_token: str = "3bf369cd84339603f8a5361e964f9ebe",
|
19 |
+
api_endpoint: str = "https://ai.berlin4h.top/api/chat/completions",
|
20 |
+
model: str = "gpt-3.5-turbo",
|
21 |
+
temperature: float = 0.9,
|
22 |
+
presence_penalty: float = 0,
|
23 |
+
frequency_penalty: float = 0,
|
24 |
+
max_tokens: int = 4000,
|
25 |
+
is_conversation: bool = True,
|
26 |
+
timeout: int = 30,
|
27 |
+
intro: str = None,
|
28 |
+
filepath: str = None,
|
29 |
+
update_file: bool = True,
|
30 |
+
proxies: dict = {},
|
31 |
+
history_offset: int = 10250,
|
32 |
+
act: str = None,
|
33 |
+
) -> None:
|
34 |
+
"""
|
35 |
+
Initializes the Berlin4h API with given parameters.
|
36 |
+
|
37 |
+
Args:
|
38 |
+
api_token (str): The API token for authentication.
|
39 |
+
api_endpoint (str): The API endpoint to use for requests.
|
40 |
+
model (str): The AI model to use for text generation.
|
41 |
+
temperature (float): The temperature parameter for the model.
|
42 |
+
presence_penalty (float): The presence penalty parameter for the model.
|
43 |
+
frequency_penalty (float): The frequency penalty parameter for the model.
|
44 |
+
max_tokens (int): The maximum number of tokens to generate.
|
45 |
+
is_conversation (bool, optional): Flag for chatting conversationally. Defaults to True.
|
46 |
+
timeout (int, optional): Http request timeout. Defaults to 30.
|
47 |
+
intro (str, optional): Conversation introductory prompt. Defaults to None.
|
48 |
+
filepath (str, optional): Path to file containing conversation history. Defaults to None.
|
49 |
+
update_file (bool, optional): Add new prompts and responses to the file. Defaults to True.
|
50 |
+
proxies (dict, optional): Http request proxies. Defaults to {}.
|
51 |
+
history_offset (int, optional): Limit conversation history to this number of last texts. Defaults to 10250.
|
52 |
+
act (str|int, optional): Awesome prompt key or index. (Used as intro). Defaults to None.
|
53 |
+
"""
|
54 |
+
self.api_token = api_token
|
55 |
+
self.api_endpoint = api_endpoint
|
56 |
+
self.model = model
|
57 |
+
self.temperature = temperature
|
58 |
+
self.presence_penalty = presence_penalty
|
59 |
+
self.frequency_penalty = frequency_penalty
|
60 |
+
self.max_tokens = max_tokens
|
61 |
+
self.parent_message_id: Optional[str] = None
|
62 |
+
self.session = requests.Session()
|
63 |
+
self.is_conversation = is_conversation
|
64 |
+
self.max_tokens_to_sample = max_tokens
|
65 |
+
self.stream_chunk_size = 1
|
66 |
+
self.timeout = timeout
|
67 |
+
self.last_response = {}
|
68 |
+
self.headers = {"Content-Type": "application/json", "Token": self.api_token}
|
69 |
+
self.__available_optimizers = (
|
70 |
+
method
|
71 |
+
for method in dir(Optimizers)
|
72 |
+
if callable(getattr(Optimizers, method)) and not method.startswith("__")
|
73 |
+
)
|
74 |
+
self.session.headers.update(self.headers)
|
75 |
+
Conversation.intro = (
|
76 |
+
AwesomePrompts().get_act(
|
77 |
+
act, raise_not_found=True, default=None, case_insensitive=True
|
78 |
+
)
|
79 |
+
if act
|
80 |
+
else intro or Conversation.intro
|
81 |
+
)
|
82 |
+
self.conversation = Conversation(
|
83 |
+
is_conversation, self.max_tokens_to_sample, filepath, update_file
|
84 |
+
)
|
85 |
+
self.conversation.history_offset = history_offset
|
86 |
+
self.session.proxies = proxies
|
87 |
+
|
88 |
+
def ask(
|
89 |
+
self,
|
90 |
+
prompt: str,
|
91 |
+
stream: bool = False,
|
92 |
+
raw: bool = False,
|
93 |
+
optimizer: str = None,
|
94 |
+
conversationally: bool = False,
|
95 |
+
) -> Dict[str, Any]:
|
96 |
+
"""
|
97 |
+
Sends a prompt to the Berlin4h AI API and returns the response.
|
98 |
+
|
99 |
+
Args:
|
100 |
+
prompt: The text prompt to generate text from.
|
101 |
+
stream (bool, optional): Whether to stream the response. Defaults to False.
|
102 |
+
raw (bool, optional): Whether to return the raw response. Defaults to False.
|
103 |
+
optimizer (str, optional): The name of the optimizer to use. Defaults to None.
|
104 |
+
conversationally (bool, optional): Whether to chat conversationally. Defaults to False.
|
105 |
+
|
106 |
+
Returns:
|
107 |
+
The response from the API.
|
108 |
+
"""
|
109 |
+
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
110 |
+
if optimizer:
|
111 |
+
if optimizer in self.__available_optimizers:
|
112 |
+
conversation_prompt = getattr(Optimizers, optimizer)(
|
113 |
+
conversation_prompt if conversationally else prompt
|
114 |
+
)
|
115 |
+
else:
|
116 |
+
raise Exception(
|
117 |
+
f"Optimizer is not one of {self.__available_optimizers}"
|
118 |
+
)
|
119 |
+
|
120 |
+
payload: Dict[str, any] = {
|
121 |
+
"prompt": conversation_prompt,
|
122 |
+
"parentMessageId": self.parent_message_id or str(uuid.uuid4()),
|
123 |
+
"options": {
|
124 |
+
"model": self.model,
|
125 |
+
"temperature": self.temperature,
|
126 |
+
"presence_penalty": self.presence_penalty,
|
127 |
+
"frequency_penalty": self.frequency_penalty,
|
128 |
+
"max_tokens": self.max_tokens,
|
129 |
+
},
|
130 |
+
}
|
131 |
+
|
132 |
+
def for_stream():
|
133 |
+
response = self.session.post(
|
134 |
+
self.api_endpoint, json=payload, headers=self.headers, stream=True, timeout=self.timeout
|
135 |
+
)
|
136 |
+
|
137 |
+
if not response.ok:
|
138 |
+
raise exceptions.FailedToGenerateResponseError(
|
139 |
+
f"Failed to generate response - ({response.status_code}, {response.reason})"
|
140 |
+
)
|
141 |
+
|
142 |
+
streaming_response = ""
|
143 |
+
# Collect the entire line before processing
|
144 |
+
for line in response.iter_lines(decode_unicode=True):
|
145 |
+
if line:
|
146 |
+
try:
|
147 |
+
json_data = json.loads(line)
|
148 |
+
content = json_data['content']
|
149 |
+
if ">" in content: break
|
150 |
+
streaming_response += content
|
151 |
+
yield content if raw else dict(text=streaming_response) # Yield accumulated response
|
152 |
+
except:
|
153 |
+
continue
|
154 |
+
self.last_response.update(dict(text=streaming_response))
|
155 |
+
self.conversation.update_chat_history(
|
156 |
+
prompt, self.get_message(self.last_response)
|
157 |
+
)
|
158 |
+
|
159 |
+
def for_non_stream():
|
160 |
+
for _ in for_stream():
|
161 |
+
pass
|
162 |
+
return self.last_response
|
163 |
+
|
164 |
+
return for_stream() if stream else for_non_stream()
|
165 |
+
|
166 |
+
def chat(
|
167 |
+
self,
|
168 |
+
prompt: str,
|
169 |
+
stream: bool = False,
|
170 |
+
optimizer: str = None,
|
171 |
+
conversationally: bool = False,
|
172 |
+
) -> str:
|
173 |
+
"""Generate response `str`
|
174 |
+
Args:
|
175 |
+
prompt (str): Prompt to be send.
|
176 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
177 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
178 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
179 |
+
Returns:
|
180 |
+
str: Response generated
|
181 |
+
"""
|
182 |
+
|
183 |
+
def for_stream():
|
184 |
+
for response in self.ask(
|
185 |
+
prompt, True, optimizer=optimizer, conversationally=conversationally
|
186 |
+
):
|
187 |
+
yield self.get_message(response)
|
188 |
+
|
189 |
+
def for_non_stream():
|
190 |
+
return self.get_message(
|
191 |
+
self.ask(
|
192 |
+
prompt,
|
193 |
+
False,
|
194 |
+
optimizer=optimizer,
|
195 |
+
conversationally=conversationally,
|
196 |
+
)
|
197 |
+
)
|
198 |
+
|
199 |
+
return for_stream() if stream else for_non_stream()
|
200 |
+
|
201 |
+
def get_message(self, response: dict) -> str:
|
202 |
+
"""Retrieves message only from response
|
203 |
+
|
204 |
+
Args:
|
205 |
+
response (dict): Response generated by `self.ask`
|
206 |
+
|
207 |
+
Returns:
|
208 |
+
str: Message extracted
|
209 |
+
"""
|
210 |
+
assert isinstance(response, dict), "Response should be of dict data-type only"
|
211 |
+
return response["text"]
|
webscout/Provider/Blackboxai.py
ADDED
@@ -0,0 +1,440 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
import uuid
|
3 |
+
from selenium import webdriver
|
4 |
+
from selenium.webdriver.chrome.options import Options
|
5 |
+
from selenium.webdriver.common.by import By
|
6 |
+
from selenium.webdriver.support import expected_conditions as EC
|
7 |
+
from selenium.webdriver.support.ui import WebDriverWait
|
8 |
+
import click
|
9 |
+
import requests
|
10 |
+
from requests import get
|
11 |
+
from uuid import uuid4
|
12 |
+
from re import findall
|
13 |
+
from requests.exceptions import RequestException
|
14 |
+
from curl_cffi.requests import get, RequestsError
|
15 |
+
import g4f
|
16 |
+
from random import randint
|
17 |
+
from PIL import Image
|
18 |
+
import io
|
19 |
+
import re
|
20 |
+
import json
|
21 |
+
import yaml
|
22 |
+
from ..AIutel import Optimizers
|
23 |
+
from ..AIutel import Conversation
|
24 |
+
from ..AIutel import AwesomePrompts, sanitize_stream
|
25 |
+
from ..AIbase import Provider, AsyncProvider
|
26 |
+
from Helpingai_T2 import Perplexity
|
27 |
+
from webscout import exceptions
|
28 |
+
from typing import Any, AsyncGenerator, Dict
|
29 |
+
import logging
|
30 |
+
import httpx
|
31 |
+
|
32 |
+
#------------------------------------------------------BLACKBOXAI--------------------------------------------------------
|
33 |
+
class BLACKBOXAI:
|
34 |
+
def __init__(
|
35 |
+
self,
|
36 |
+
is_conversation: bool = True,
|
37 |
+
max_tokens: int = 8000,
|
38 |
+
timeout: int = 30,
|
39 |
+
intro: str = None,
|
40 |
+
filepath: str = None,
|
41 |
+
update_file: bool = True,
|
42 |
+
proxies: dict = {},
|
43 |
+
history_offset: int = 10250,
|
44 |
+
act: str = None,
|
45 |
+
model: str = None,
|
46 |
+
):
|
47 |
+
"""Instantiates BLACKBOXAI
|
48 |
+
|
49 |
+
Args:
|
50 |
+
is_conversation (bool, optional): Flag for chatting conversationally. Defaults to True
|
51 |
+
max_tokens (int, optional): Maximum number of tokens to be generated upon completion. Defaults to 600.
|
52 |
+
timeout (int, optional): Http request timeout. Defaults to 30.
|
53 |
+
intro (str, optional): Conversation introductory prompt. Defaults to None.
|
54 |
+
filepath (str, optional): Path to file containing conversation history. Defaults to None.
|
55 |
+
update_file (bool, optional): Add new prompts and responses to the file. Defaults to True.
|
56 |
+
proxies (dict, optional): Http request proxies. Defaults to {}.
|
57 |
+
history_offset (int, optional): Limit conversation history to this number of last texts. Defaults to 10250.
|
58 |
+
act (str|int, optional): Awesome prompt key or index. (Used as intro). Defaults to None.
|
59 |
+
model (str, optional): Model name. Defaults to "Phind Model".
|
60 |
+
"""
|
61 |
+
self.session = requests.Session()
|
62 |
+
self.max_tokens_to_sample = max_tokens
|
63 |
+
self.is_conversation = is_conversation
|
64 |
+
self.chat_endpoint = "https://www.blackbox.ai/api/chat"
|
65 |
+
self.stream_chunk_size = 64
|
66 |
+
self.timeout = timeout
|
67 |
+
self.last_response = {}
|
68 |
+
self.model = model
|
69 |
+
self.previewToken: str = None
|
70 |
+
self.userId: str = ""
|
71 |
+
self.codeModelMode: bool = True
|
72 |
+
self.id: str = ""
|
73 |
+
self.agentMode: dict = {}
|
74 |
+
self.trendingAgentMode: dict = {}
|
75 |
+
self.isMicMode: bool = False
|
76 |
+
|
77 |
+
self.headers = {
|
78 |
+
"Content-Type": "application/json",
|
79 |
+
"User-Agent": "",
|
80 |
+
"Accept": "*/*",
|
81 |
+
"Accept-Encoding": "Identity",
|
82 |
+
}
|
83 |
+
|
84 |
+
self.__available_optimizers = (
|
85 |
+
method
|
86 |
+
for method in dir(Optimizers)
|
87 |
+
if callable(getattr(Optimizers, method)) and not method.startswith("__")
|
88 |
+
)
|
89 |
+
self.session.headers.update(self.headers)
|
90 |
+
Conversation.intro = (
|
91 |
+
AwesomePrompts().get_act(
|
92 |
+
act, raise_not_found=True, default=None, case_insensitive=True
|
93 |
+
)
|
94 |
+
if act
|
95 |
+
else intro or Conversation.intro
|
96 |
+
)
|
97 |
+
self.conversation = Conversation(
|
98 |
+
is_conversation, self.max_tokens_to_sample, filepath, update_file
|
99 |
+
)
|
100 |
+
self.conversation.history_offset = history_offset
|
101 |
+
self.session.proxies = proxies
|
102 |
+
|
103 |
+
def ask(
|
104 |
+
self,
|
105 |
+
prompt: str,
|
106 |
+
stream: bool = False,
|
107 |
+
raw: bool = False,
|
108 |
+
optimizer: str = None,
|
109 |
+
conversationally: bool = False,
|
110 |
+
) -> dict:
|
111 |
+
"""Chat with AI
|
112 |
+
|
113 |
+
Args:
|
114 |
+
prompt (str): Prompt to be send.
|
115 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
116 |
+
raw (bool, optional): Stream back raw response as received. Defaults to False.
|
117 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
118 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
119 |
+
Returns:
|
120 |
+
dict : {}
|
121 |
+
```json
|
122 |
+
{
|
123 |
+
"text" : "print('How may I help you today?')"
|
124 |
+
}
|
125 |
+
```
|
126 |
+
"""
|
127 |
+
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
128 |
+
if optimizer:
|
129 |
+
if optimizer in self.__available_optimizers:
|
130 |
+
conversation_prompt = getattr(Optimizers, optimizer)(
|
131 |
+
conversation_prompt if conversationally else prompt
|
132 |
+
)
|
133 |
+
else:
|
134 |
+
raise Exception(
|
135 |
+
f"Optimizer is not one of {self.__available_optimizers}"
|
136 |
+
)
|
137 |
+
|
138 |
+
self.session.headers.update(self.headers)
|
139 |
+
payload = {
|
140 |
+
"messages": [
|
141 |
+
# json.loads(prev_messages),
|
142 |
+
{"content": conversation_prompt, "role": "user"}
|
143 |
+
],
|
144 |
+
"id": self.id,
|
145 |
+
"previewToken": self.previewToken,
|
146 |
+
"userId": self.userId,
|
147 |
+
"codeModelMode": self.codeModelMode,
|
148 |
+
"agentMode": self.agentMode,
|
149 |
+
"trendingAgentMode": self.trendingAgentMode,
|
150 |
+
"isMicMode": self.isMicMode,
|
151 |
+
}
|
152 |
+
|
153 |
+
def for_stream():
|
154 |
+
response = self.session.post(
|
155 |
+
self.chat_endpoint, json=payload, stream=True, timeout=self.timeout
|
156 |
+
)
|
157 |
+
if (
|
158 |
+
not response.ok
|
159 |
+
or not response.headers.get("Content-Type")
|
160 |
+
== "text/plain; charset=utf-8"
|
161 |
+
):
|
162 |
+
raise Exception(
|
163 |
+
f"Failed to generate response - ({response.status_code}, {response.reason}) - {response.text}"
|
164 |
+
)
|
165 |
+
streaming_text = ""
|
166 |
+
for value in response.iter_lines(
|
167 |
+
decode_unicode=True,
|
168 |
+
chunk_size=self.stream_chunk_size,
|
169 |
+
delimiter="\n",
|
170 |
+
):
|
171 |
+
try:
|
172 |
+
if bool(value):
|
173 |
+
streaming_text += value + ("\n" if stream else "")
|
174 |
+
|
175 |
+
resp = dict(text=streaming_text)
|
176 |
+
self.last_response.update(resp)
|
177 |
+
yield value if raw else resp
|
178 |
+
except json.decoder.JSONDecodeError:
|
179 |
+
pass
|
180 |
+
self.conversation.update_chat_history(
|
181 |
+
prompt, self.get_message(self.last_response)
|
182 |
+
)
|
183 |
+
|
184 |
+
def for_non_stream():
|
185 |
+
for _ in for_stream():
|
186 |
+
pass
|
187 |
+
return self.last_response
|
188 |
+
|
189 |
+
return for_stream() if stream else for_non_stream()
|
190 |
+
|
191 |
+
def chat(
|
192 |
+
self,
|
193 |
+
prompt: str,
|
194 |
+
stream: bool = False,
|
195 |
+
optimizer: str = None,
|
196 |
+
conversationally: bool = False,
|
197 |
+
) -> str:
|
198 |
+
"""Generate response `str`
|
199 |
+
Args:
|
200 |
+
prompt (str): Prompt to be send.
|
201 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
202 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
203 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
204 |
+
Returns:
|
205 |
+
str: Response generated
|
206 |
+
"""
|
207 |
+
|
208 |
+
def for_stream():
|
209 |
+
for response in self.ask(
|
210 |
+
prompt, True, optimizer=optimizer, conversationally=conversationally
|
211 |
+
):
|
212 |
+
yield self.get_message(response)
|
213 |
+
|
214 |
+
def for_non_stream():
|
215 |
+
return self.get_message(
|
216 |
+
self.ask(
|
217 |
+
prompt,
|
218 |
+
False,
|
219 |
+
optimizer=optimizer,
|
220 |
+
conversationally=conversationally,
|
221 |
+
)
|
222 |
+
)
|
223 |
+
|
224 |
+
return for_stream() if stream else for_non_stream()
|
225 |
+
|
226 |
+
def get_message(self, response: dict) -> str:
|
227 |
+
"""Retrieves message only from response
|
228 |
+
|
229 |
+
Args:
|
230 |
+
response (dict): Response generated by `self.ask`
|
231 |
+
|
232 |
+
Returns:
|
233 |
+
str: Message extracted
|
234 |
+
"""
|
235 |
+
assert isinstance(response, dict), "Response should be of dict data-type only"
|
236 |
+
return response["text"]
|
237 |
+
@staticmethod
|
238 |
+
def chat_cli(prompt):
|
239 |
+
"""Sends a request to the BLACKBOXAI API and processes the response."""
|
240 |
+
blackbox_ai = BLACKBOXAI() # Initialize a BLACKBOXAI instance
|
241 |
+
response = blackbox_ai.ask(prompt) # Perform a chat with the given prompt
|
242 |
+
processed_response = blackbox_ai.get_message(response) # Process the response
|
243 |
+
print(processed_response)
|
244 |
+
class AsyncBLACKBOXAI(AsyncProvider):
|
245 |
+
def __init__(
|
246 |
+
self,
|
247 |
+
is_conversation: bool = True,
|
248 |
+
max_tokens: int = 600,
|
249 |
+
timeout: int = 30,
|
250 |
+
intro: str = None,
|
251 |
+
filepath: str = None,
|
252 |
+
update_file: bool = True,
|
253 |
+
proxies: dict = {},
|
254 |
+
history_offset: int = 10250,
|
255 |
+
act: str = None,
|
256 |
+
model: str = None,
|
257 |
+
):
|
258 |
+
"""Instantiates BLACKBOXAI
|
259 |
+
|
260 |
+
Args:
|
261 |
+
is_conversation (bool, optional): Flag for chatting conversationally. Defaults to True
|
262 |
+
max_tokens (int, optional): Maximum number of tokens to be generated upon completion. Defaults to 600.
|
263 |
+
timeout (int, optional): Http request timeout. Defaults to 30.
|
264 |
+
intro (str, optional): Conversation introductory prompt. Defaults to None.
|
265 |
+
filepath (str, optional): Path to file containing conversation history. Defaults to None.
|
266 |
+
update_file (bool, optional): Add new prompts and responses to the file. Defaults to True.
|
267 |
+
proxies (dict, optional): Http request proxies. Defaults to {}.
|
268 |
+
history_offset (int, optional): Limit conversation history to this number of last texts. Defaults to 10250.
|
269 |
+
act (str|int, optional): Awesome prompt key or index. (Used as intro). Defaults to None.
|
270 |
+
model (str, optional): Model name. Defaults to "Phind Model".
|
271 |
+
"""
|
272 |
+
self.max_tokens_to_sample = max_tokens
|
273 |
+
self.is_conversation = is_conversation
|
274 |
+
self.chat_endpoint = "https://www.blackbox.ai/api/chat"
|
275 |
+
self.stream_chunk_size = 64
|
276 |
+
self.timeout = timeout
|
277 |
+
self.last_response = {}
|
278 |
+
self.model = model
|
279 |
+
self.previewToken: str = None
|
280 |
+
self.userId: str = ""
|
281 |
+
self.codeModelMode: bool = True
|
282 |
+
self.id: str = ""
|
283 |
+
self.agentMode: dict = {}
|
284 |
+
self.trendingAgentMode: dict = {}
|
285 |
+
self.isMicMode: bool = False
|
286 |
+
|
287 |
+
self.headers = {
|
288 |
+
"Content-Type": "application/json",
|
289 |
+
"User-Agent": "",
|
290 |
+
"Accept": "*/*",
|
291 |
+
"Accept-Encoding": "Identity",
|
292 |
+
}
|
293 |
+
|
294 |
+
self.__available_optimizers = (
|
295 |
+
method
|
296 |
+
for method in dir(Optimizers)
|
297 |
+
if callable(getattr(Optimizers, method)) and not method.startswith("__")
|
298 |
+
)
|
299 |
+
Conversation.intro = (
|
300 |
+
AwesomePrompts().get_act(
|
301 |
+
act, raise_not_found=True, default=None, case_insensitive=True
|
302 |
+
)
|
303 |
+
if act
|
304 |
+
else intro or Conversation.intro
|
305 |
+
)
|
306 |
+
self.conversation = Conversation(
|
307 |
+
is_conversation, self.max_tokens_to_sample, filepath, update_file
|
308 |
+
)
|
309 |
+
self.conversation.history_offset = history_offset
|
310 |
+
self.session = httpx.AsyncClient(headers=self.headers, proxies=proxies)
|
311 |
+
|
312 |
+
async def ask(
|
313 |
+
self,
|
314 |
+
prompt: str,
|
315 |
+
stream: bool = False,
|
316 |
+
raw: bool = False,
|
317 |
+
optimizer: str = None,
|
318 |
+
conversationally: bool = False,
|
319 |
+
) -> dict | AsyncGenerator:
|
320 |
+
"""Chat with AI asynchronously.
|
321 |
+
|
322 |
+
Args:
|
323 |
+
prompt (str): Prompt to be send.
|
324 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
325 |
+
raw (bool, optional): Stream back raw response as received. Defaults to False.
|
326 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
327 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
328 |
+
Returns:
|
329 |
+
dict|AsyncGenerator : ai content
|
330 |
+
```json
|
331 |
+
{
|
332 |
+
"text" : "print('How may I help you today?')"
|
333 |
+
}
|
334 |
+
```
|
335 |
+
"""
|
336 |
+
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
337 |
+
if optimizer:
|
338 |
+
if optimizer in self.__available_optimizers:
|
339 |
+
conversation_prompt = getattr(Optimizers, optimizer)(
|
340 |
+
conversation_prompt if conversationally else prompt
|
341 |
+
)
|
342 |
+
else:
|
343 |
+
raise Exception(
|
344 |
+
f"Optimizer is not one of {self.__available_optimizers}"
|
345 |
+
)
|
346 |
+
|
347 |
+
payload = {
|
348 |
+
"messages": [
|
349 |
+
# json.loads(prev_messages),
|
350 |
+
{"content": conversation_prompt, "role": "user"}
|
351 |
+
],
|
352 |
+
"id": self.id,
|
353 |
+
"previewToken": self.previewToken,
|
354 |
+
"userId": self.userId,
|
355 |
+
"codeModelMode": self.codeModelMode,
|
356 |
+
"agentMode": self.agentMode,
|
357 |
+
"trendingAgentMode": self.trendingAgentMode,
|
358 |
+
"isMicMode": self.isMicMode,
|
359 |
+
}
|
360 |
+
|
361 |
+
async def for_stream():
|
362 |
+
async with self.session.stream(
|
363 |
+
"POST", self.chat_endpoint, json=payload, timeout=self.timeout
|
364 |
+
) as response:
|
365 |
+
if (
|
366 |
+
not response.is_success
|
367 |
+
or not response.headers.get("Content-Type")
|
368 |
+
== "text/plain; charset=utf-8"
|
369 |
+
):
|
370 |
+
raise exceptions.FailedToGenerateResponseError(
|
371 |
+
f"Failed to generate response - ({response.status_code}, {response.reason_phrase})"
|
372 |
+
)
|
373 |
+
streaming_text = ""
|
374 |
+
async for value in response.aiter_lines():
|
375 |
+
try:
|
376 |
+
if bool(value):
|
377 |
+
streaming_text += value + ("\n" if stream else "")
|
378 |
+
resp = dict(text=streaming_text)
|
379 |
+
self.last_response.update(resp)
|
380 |
+
yield value if raw else resp
|
381 |
+
except json.decoder.JSONDecodeError:
|
382 |
+
pass
|
383 |
+
self.conversation.update_chat_history(
|
384 |
+
prompt, await self.get_message(self.last_response)
|
385 |
+
)
|
386 |
+
|
387 |
+
async def for_non_stream():
|
388 |
+
async for _ in for_stream():
|
389 |
+
pass
|
390 |
+
return self.last_response
|
391 |
+
|
392 |
+
return for_stream() if stream else await for_non_stream()
|
393 |
+
|
394 |
+
async def chat(
|
395 |
+
self,
|
396 |
+
prompt: str,
|
397 |
+
stream: bool = False,
|
398 |
+
optimizer: str = None,
|
399 |
+
conversationally: bool = False,
|
400 |
+
) -> str | AsyncGenerator:
|
401 |
+
"""Generate response `str` asynchronously.
|
402 |
+
Args:
|
403 |
+
prompt (str): Prompt to be send.
|
404 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
405 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
406 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
407 |
+
Returns:
|
408 |
+
str|AsyncGenerator: Response generated
|
409 |
+
"""
|
410 |
+
|
411 |
+
async def for_stream():
|
412 |
+
async_ask = await self.ask(
|
413 |
+
prompt, True, optimizer=optimizer, conversationally=conversationally
|
414 |
+
)
|
415 |
+
async for response in async_ask:
|
416 |
+
yield await self.get_message(response)
|
417 |
+
|
418 |
+
async def for_non_stream():
|
419 |
+
return await self.get_message(
|
420 |
+
await self.ask(
|
421 |
+
prompt,
|
422 |
+
False,
|
423 |
+
optimizer=optimizer,
|
424 |
+
conversationally=conversationally,
|
425 |
+
)
|
426 |
+
)
|
427 |
+
|
428 |
+
return for_stream() if stream else await for_non_stream()
|
429 |
+
|
430 |
+
async def get_message(self, response: dict) -> str:
|
431 |
+
"""Retrieves message only from response
|
432 |
+
|
433 |
+
Args:
|
434 |
+
response (dict): Response generated by `self.ask`
|
435 |
+
|
436 |
+
Returns:
|
437 |
+
str: Message extracted
|
438 |
+
"""
|
439 |
+
assert isinstance(response, dict), "Response should be of dict data-type only"
|
440 |
+
return response["text"]
|
webscout/Provider/ChatGPTUK.py
ADDED
@@ -0,0 +1,214 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import requests
|
2 |
+
from typing import Any, AsyncGenerator, Dict, Optional
|
3 |
+
import json
|
4 |
+
import re
|
5 |
+
|
6 |
+
from ..AIutel import Optimizers
|
7 |
+
from ..AIutel import Conversation
|
8 |
+
from ..AIutel import AwesomePrompts, sanitize_stream
|
9 |
+
from ..AIbase import Provider, AsyncProvider
|
10 |
+
from webscout import exceptions
|
11 |
+
|
12 |
+
|
13 |
+
class ChatGPTUK(Provider):
|
14 |
+
"""
|
15 |
+
A class to interact with the ChatGPT UK API.
|
16 |
+
"""
|
17 |
+
|
18 |
+
def __init__(
|
19 |
+
self,
|
20 |
+
is_conversation: bool = True,
|
21 |
+
max_tokens: int = 600,
|
22 |
+
temperature: float = 0.9,
|
23 |
+
presence_penalty: float = 0,
|
24 |
+
frequency_penalty: float = 0,
|
25 |
+
top_p: float = 1,
|
26 |
+
model: str = "google-gemini-pro",
|
27 |
+
timeout: int = 30,
|
28 |
+
intro: str = None,
|
29 |
+
filepath: str = None,
|
30 |
+
update_file: bool = True,
|
31 |
+
proxies: dict = {},
|
32 |
+
history_offset: int = 10250,
|
33 |
+
act: str = None,
|
34 |
+
) -> None:
|
35 |
+
"""
|
36 |
+
Initializes the ChatGPTUK API with given parameters.
|
37 |
+
|
38 |
+
Args:
|
39 |
+
is_conversation (bool, optional): Flag for chatting conversationally. Defaults to True.
|
40 |
+
max_tokens (int, optional): Maximum number of tokens to be generated upon completion. Defaults to 600.
|
41 |
+
temperature (float, optional): Charge of the generated text's randomness. Defaults to 0.9.
|
42 |
+
presence_penalty (float, optional): Chances of topic being repeated. Defaults to 0.
|
43 |
+
frequency_penalty (float, optional): Chances of word being repeated. Defaults to 0.
|
44 |
+
top_p (float, optional): Sampling threshold during inference time. Defaults to 1.
|
45 |
+
model (str, optional): LLM model name. Defaults to "google-gemini-pro".
|
46 |
+
timeout (int, optional): Http request timeout. Defaults to 30.
|
47 |
+
intro (str, optional): Conversation introductory prompt. Defaults to None.
|
48 |
+
filepath (str, optional): Path to file containing conversation history. Defaults to None.
|
49 |
+
update_file (bool, optional): Add new prompts and responses to the file. Defaults to True.
|
50 |
+
proxies (dict, optional): Http request proxies. Defaults to {}.
|
51 |
+
history_offset (int, optional): Limit conversation history to this number of last texts. Defaults to 10250.
|
52 |
+
act (str|int, optional): Awesome prompt key or index. (Used as intro). Defaults to None.
|
53 |
+
"""
|
54 |
+
self.session = requests.Session()
|
55 |
+
self.is_conversation = is_conversation
|
56 |
+
self.max_tokens_to_sample = max_tokens
|
57 |
+
self.api_endpoint = "https://free.chatgpt.org.uk/api/openai/v1/chat/completions"
|
58 |
+
self.stream_chunk_size = 64
|
59 |
+
self.timeout = timeout
|
60 |
+
self.last_response = {}
|
61 |
+
self.model = model
|
62 |
+
self.temperature = temperature
|
63 |
+
self.presence_penalty = presence_penalty
|
64 |
+
self.frequency_penalty = frequency_penalty
|
65 |
+
self.top_p = top_p
|
66 |
+
self.headers = {"Content-Type": "application/json"}
|
67 |
+
|
68 |
+
self.__available_optimizers = (
|
69 |
+
method
|
70 |
+
for method in dir(Optimizers)
|
71 |
+
if callable(getattr(Optimizers, method)) and not method.startswith("__")
|
72 |
+
)
|
73 |
+
self.session.headers.update(self.headers)
|
74 |
+
Conversation.intro = (
|
75 |
+
AwesomePrompts().get_act(
|
76 |
+
act, raise_not_found=True, default=None, case_insensitive=True
|
77 |
+
)
|
78 |
+
if act
|
79 |
+
else intro or Conversation.intro
|
80 |
+
)
|
81 |
+
self.conversation = Conversation(
|
82 |
+
is_conversation, self.max_tokens_to_sample, filepath, update_file
|
83 |
+
)
|
84 |
+
self.conversation.history_offset = history_offset
|
85 |
+
self.session.proxies = proxies
|
86 |
+
|
87 |
+
def ask(
|
88 |
+
self,
|
89 |
+
prompt: str,
|
90 |
+
stream: bool = False,
|
91 |
+
raw: bool = False,
|
92 |
+
optimizer: str = None,
|
93 |
+
conversationally: bool = False,
|
94 |
+
) -> dict:
|
95 |
+
"""Chat with AI
|
96 |
+
|
97 |
+
Args:
|
98 |
+
prompt (str): Prompt to be send.
|
99 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
100 |
+
raw (bool, optional): Stream back raw response as received. Defaults to False.
|
101 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
102 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
103 |
+
Returns:
|
104 |
+
dict : {}
|
105 |
+
```json
|
106 |
+
{
|
107 |
+
"text" : "How may I assist you today?"
|
108 |
+
}
|
109 |
+
```
|
110 |
+
"""
|
111 |
+
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
112 |
+
if optimizer:
|
113 |
+
if optimizer in self.__available_optimizers:
|
114 |
+
conversation_prompt = getattr(Optimizers, optimizer)(
|
115 |
+
conversation_prompt if conversationally else prompt
|
116 |
+
)
|
117 |
+
else:
|
118 |
+
raise Exception(
|
119 |
+
f"Optimizer is not one of {self.__available_optimizers}"
|
120 |
+
)
|
121 |
+
|
122 |
+
self.session.headers.update(self.headers)
|
123 |
+
payload = {
|
124 |
+
"messages": [
|
125 |
+
{"role": "system", "content": "Keep your responses long and detailed"},
|
126 |
+
{"role": "user", "content": conversation_prompt}
|
127 |
+
],
|
128 |
+
"stream": True,
|
129 |
+
"model": self.model,
|
130 |
+
"temperature": self.temperature,
|
131 |
+
"presence_penalty": self.presence_penalty,
|
132 |
+
"frequency_penalty": self.frequency_penalty,
|
133 |
+
"top_p": self.top_p,
|
134 |
+
"max_tokens": self.max_tokens_to_sample
|
135 |
+
}
|
136 |
+
|
137 |
+
def for_stream():
|
138 |
+
response = self.session.post(
|
139 |
+
self.api_endpoint, json=payload, stream=True, timeout=self.timeout
|
140 |
+
)
|
141 |
+
if not response.ok:
|
142 |
+
raise exceptions.FailedToGenerateResponseError(
|
143 |
+
f"Failed to generate response - ({response.status_code}, {response.reason}) - {response.text}"
|
144 |
+
)
|
145 |
+
|
146 |
+
streaming_response = ""
|
147 |
+
for line in response.iter_lines(decode_unicode=True, chunk_size=1):
|
148 |
+
if line:
|
149 |
+
modified_line = re.sub("data:", "", line)
|
150 |
+
try:
|
151 |
+
json_data = json.loads(modified_line)
|
152 |
+
content = json_data['choices'][0]['delta']['content']
|
153 |
+
streaming_response += content
|
154 |
+
yield content if raw else dict(text=streaming_response)
|
155 |
+
except:
|
156 |
+
continue
|
157 |
+
self.last_response.update(dict(text=streaming_response))
|
158 |
+
self.conversation.update_chat_history(
|
159 |
+
prompt, self.get_message(self.last_response)
|
160 |
+
)
|
161 |
+
|
162 |
+
def for_non_stream():
|
163 |
+
for _ in for_stream():
|
164 |
+
pass
|
165 |
+
return self.last_response
|
166 |
+
|
167 |
+
return for_stream() if stream else for_non_stream()
|
168 |
+
|
169 |
+
def chat(
|
170 |
+
self,
|
171 |
+
prompt: str,
|
172 |
+
stream: bool = False,
|
173 |
+
optimizer: str = None,
|
174 |
+
conversationally: bool = False,
|
175 |
+
) -> str:
|
176 |
+
"""Generate response `str`
|
177 |
+
Args:
|
178 |
+
prompt (str): Prompt to be send.
|
179 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
180 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
181 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
182 |
+
Returns:
|
183 |
+
str: Response generated
|
184 |
+
"""
|
185 |
+
|
186 |
+
def for_stream():
|
187 |
+
for response in self.ask(
|
188 |
+
prompt, True, optimizer=optimizer, conversationally=conversationally
|
189 |
+
):
|
190 |
+
yield self.get_message(response)
|
191 |
+
|
192 |
+
def for_non_stream():
|
193 |
+
return self.get_message(
|
194 |
+
self.ask(
|
195 |
+
prompt,
|
196 |
+
False,
|
197 |
+
optimizer=optimizer,
|
198 |
+
conversationally=conversationally,
|
199 |
+
)
|
200 |
+
)
|
201 |
+
|
202 |
+
return for_stream() if stream else for_non_stream()
|
203 |
+
|
204 |
+
def get_message(self, response: dict) -> str:
|
205 |
+
"""Retrieves message only from response
|
206 |
+
|
207 |
+
Args:
|
208 |
+
response (dict): Response generated by `self.ask`
|
209 |
+
|
210 |
+
Returns:
|
211 |
+
str: Message extracted
|
212 |
+
"""
|
213 |
+
assert isinstance(response, dict), "Response should be of dict data-type only"
|
214 |
+
return response["text"]
|
webscout/Provider/Cohere.py
ADDED
@@ -0,0 +1,223 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
import uuid
|
3 |
+
from selenium import webdriver
|
4 |
+
from selenium.webdriver.chrome.options import Options
|
5 |
+
from selenium.webdriver.common.by import By
|
6 |
+
from selenium.webdriver.support import expected_conditions as EC
|
7 |
+
from selenium.webdriver.support.ui import WebDriverWait
|
8 |
+
import click
|
9 |
+
import requests
|
10 |
+
from requests import get
|
11 |
+
from uuid import uuid4
|
12 |
+
from re import findall
|
13 |
+
from requests.exceptions import RequestException
|
14 |
+
from curl_cffi.requests import get, RequestsError
|
15 |
+
import g4f
|
16 |
+
from random import randint
|
17 |
+
from PIL import Image
|
18 |
+
import io
|
19 |
+
import re
|
20 |
+
import json
|
21 |
+
import yaml
|
22 |
+
from ..AIutel import Optimizers
|
23 |
+
from ..AIutel import Conversation
|
24 |
+
from ..AIutel import AwesomePrompts, sanitize_stream
|
25 |
+
from ..AIbase import Provider, AsyncProvider
|
26 |
+
from Helpingai_T2 import Perplexity
|
27 |
+
from webscout import exceptions
|
28 |
+
from typing import Any, AsyncGenerator, Dict
|
29 |
+
import logging
|
30 |
+
import httpx
|
31 |
+
#-----------------------------------------------Cohere--------------------------------------------
|
32 |
+
class Cohere(Provider):
|
33 |
+
def __init__(
|
34 |
+
self,
|
35 |
+
api_key: str,
|
36 |
+
is_conversation: bool = True,
|
37 |
+
max_tokens: int = 600,
|
38 |
+
model: str = "command-r-plus",
|
39 |
+
temperature: float = 0.7,
|
40 |
+
system_prompt: str = "You are helpful AI",
|
41 |
+
timeout: int = 30,
|
42 |
+
intro: str = None,
|
43 |
+
filepath: str = None,
|
44 |
+
update_file: bool = True,
|
45 |
+
proxies: dict = {},
|
46 |
+
history_offset: int = 10250,
|
47 |
+
act: str = None,
|
48 |
+
top_k: int = -1,
|
49 |
+
top_p: float = 0.999,
|
50 |
+
):
|
51 |
+
"""Initializes Cohere
|
52 |
+
|
53 |
+
Args:
|
54 |
+
api_key (str): Cohere API key.
|
55 |
+
is_conversation (bool, optional): Flag for chatting conversationally. Defaults to True.
|
56 |
+
max_tokens (int, optional): Maximum number of tokens to be generated upon completion. Defaults to 600.
|
57 |
+
model (str, optional): Model to use for generating text. Defaults to "command-r-plus".
|
58 |
+
temperature (float, optional): Diversity of the generated text. Higher values produce more diverse outputs.
|
59 |
+
Defaults to 0.7.
|
60 |
+
system_prompt (str, optional): A system_prompt or context to set the style or tone of the generated text.
|
61 |
+
Defaults to "You are helpful AI".
|
62 |
+
timeout (int, optional): Http request timeout. Defaults to 30.
|
63 |
+
intro (str, optional): Conversation introductory prompt. Defaults to None.
|
64 |
+
filepath (str, optional): Path to file containing conversation history. Defaults to None.
|
65 |
+
update_file (bool, optional): Add new prompts and responses to the file. Defaults to True.
|
66 |
+
proxies (dict, optional): Http request proxies. Defaults to {}.
|
67 |
+
history_offset (int, optional): Limit conversation history to this number of last texts. Defaults to 10250.
|
68 |
+
act (str|int, optional): Awesome prompt key or index. (Used as intro). Defaults to None.
|
69 |
+
"""
|
70 |
+
self.session = requests.Session()
|
71 |
+
self.is_conversation = is_conversation
|
72 |
+
self.max_tokens_to_sample = max_tokens
|
73 |
+
self.api_key = api_key
|
74 |
+
self.model = model
|
75 |
+
self.temperature = temperature
|
76 |
+
self.system_prompt = system_prompt
|
77 |
+
self.chat_endpoint = "https://production.api.os.cohere.ai/coral/v1/chat"
|
78 |
+
self.stream_chunk_size = 64
|
79 |
+
self.timeout = timeout
|
80 |
+
self.last_response = {}
|
81 |
+
self.headers = {
|
82 |
+
"Content-Type": "application/json",
|
83 |
+
"Authorization": f"Bearer {self.api_key}",
|
84 |
+
}
|
85 |
+
|
86 |
+
self.__available_optimizers = (
|
87 |
+
method
|
88 |
+
for method in dir(Optimizers)
|
89 |
+
if callable(getattr(Optimizers, method)) and not method.startswith("__")
|
90 |
+
)
|
91 |
+
self.session.headers.update(self.headers)
|
92 |
+
Conversation.intro = (
|
93 |
+
AwesomePrompts().get_act(
|
94 |
+
act, raise_not_found=True, default=None, case_insensitive=True
|
95 |
+
)
|
96 |
+
if act
|
97 |
+
else intro or Conversation.intro
|
98 |
+
)
|
99 |
+
self.conversation = Conversation(
|
100 |
+
is_conversation, self.max_tokens_to_sample, filepath, update_file
|
101 |
+
)
|
102 |
+
self.conversation.history_offset = history_offset
|
103 |
+
self.session.proxies = proxies
|
104 |
+
|
105 |
+
def ask(
|
106 |
+
self,
|
107 |
+
prompt: str,
|
108 |
+
stream: bool = False,
|
109 |
+
raw: bool = False,
|
110 |
+
optimizer: str = None,
|
111 |
+
conversationally: bool = False,
|
112 |
+
) -> dict:
|
113 |
+
"""Chat with AI
|
114 |
+
|
115 |
+
Args:
|
116 |
+
prompt (str): Prompt to be send.
|
117 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
118 |
+
raw (bool, optional): Stream back raw response as received. Defaults to False.
|
119 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
120 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
121 |
+
Returns:
|
122 |
+
dict : {}
|
123 |
+
```json
|
124 |
+
{
|
125 |
+
"text" : "How may I assist you today?"
|
126 |
+
}
|
127 |
+
```
|
128 |
+
"""
|
129 |
+
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
130 |
+
if optimizer:
|
131 |
+
if optimizer in self.__available_optimizers:
|
132 |
+
conversation_prompt = getattr(Optimizers, optimizer)(
|
133 |
+
conversation_prompt if conversationally else prompt
|
134 |
+
)
|
135 |
+
else:
|
136 |
+
raise Exception(
|
137 |
+
f"Optimizer is not one of {self.__available_optimizers}"
|
138 |
+
)
|
139 |
+
self.session.headers.update(self.headers)
|
140 |
+
payload = {
|
141 |
+
"message": conversation_prompt,
|
142 |
+
"model": self.model,
|
143 |
+
"temperature": self.temperature,
|
144 |
+
"preamble": self.system_prompt,
|
145 |
+
}
|
146 |
+
|
147 |
+
def for_stream():
|
148 |
+
response = self.session.post(
|
149 |
+
self.chat_endpoint, json=payload, stream=True, timeout=self.timeout
|
150 |
+
)
|
151 |
+
if not response.ok:
|
152 |
+
raise Exception(
|
153 |
+
f"Failed to generate response - ({response.status_code}, {response.reason}) - {response.text}"
|
154 |
+
)
|
155 |
+
|
156 |
+
for value in response.iter_lines(
|
157 |
+
decode_unicode=True,
|
158 |
+
chunk_size=self.stream_chunk_size,
|
159 |
+
):
|
160 |
+
try:
|
161 |
+
resp = json.loads(value.strip().split("\n")[-1])
|
162 |
+
self.last_response.update(resp)
|
163 |
+
yield value if raw else resp
|
164 |
+
except json.decoder.JSONDecodeError:
|
165 |
+
pass
|
166 |
+
self.conversation.update_chat_history(
|
167 |
+
prompt, self.get_message(self.last_response)
|
168 |
+
)
|
169 |
+
|
170 |
+
def for_non_stream():
|
171 |
+
# let's make use of stream
|
172 |
+
for _ in for_stream():
|
173 |
+
pass
|
174 |
+
return self.last_response
|
175 |
+
|
176 |
+
return for_stream() if stream else for_non_stream()
|
177 |
+
|
178 |
+
def chat(
|
179 |
+
self,
|
180 |
+
prompt: str,
|
181 |
+
stream: bool = False,
|
182 |
+
optimizer: str = None,
|
183 |
+
conversationally: bool = False,
|
184 |
+
) -> str:
|
185 |
+
"""Generate response `str`
|
186 |
+
Args:
|
187 |
+
prompt (str): Prompt to be send.
|
188 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
189 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
190 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
191 |
+
Returns:
|
192 |
+
str: Response generated
|
193 |
+
"""
|
194 |
+
|
195 |
+
def for_stream():
|
196 |
+
for response in self.ask(
|
197 |
+
prompt, True, optimizer=optimizer, conversationally=conversationally
|
198 |
+
):
|
199 |
+
yield self.get_message(response)
|
200 |
+
|
201 |
+
def for_non_stream():
|
202 |
+
return self.get_message(
|
203 |
+
self.ask(
|
204 |
+
prompt,
|
205 |
+
False,
|
206 |
+
optimizer=optimizer,
|
207 |
+
conversationally=conversationally,
|
208 |
+
)
|
209 |
+
)
|
210 |
+
|
211 |
+
return for_stream() if stream else for_non_stream()
|
212 |
+
|
213 |
+
def get_message(self, response: dict) -> str:
|
214 |
+
"""Retrieves message only from response
|
215 |
+
|
216 |
+
Args:
|
217 |
+
response (dict): Response generated by `self.ask`
|
218 |
+
|
219 |
+
Returns:
|
220 |
+
str: Message extracted
|
221 |
+
"""
|
222 |
+
assert isinstance(response, dict), "Response should be of dict data-type only"
|
223 |
+
return response["result"]["chatStreamEndEvent"]["response"]["text"]
|
webscout/Provider/Gemini.py
ADDED
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
import uuid
|
3 |
+
from selenium import webdriver
|
4 |
+
from selenium.webdriver.chrome.options import Options
|
5 |
+
from selenium.webdriver.common.by import By
|
6 |
+
from selenium.webdriver.support import expected_conditions as EC
|
7 |
+
from selenium.webdriver.support.ui import WebDriverWait
|
8 |
+
import click
|
9 |
+
import requests
|
10 |
+
from requests import get
|
11 |
+
from uuid import uuid4
|
12 |
+
from re import findall
|
13 |
+
from requests.exceptions import RequestException
|
14 |
+
from curl_cffi.requests import get, RequestsError
|
15 |
+
import g4f
|
16 |
+
from random import randint
|
17 |
+
from PIL import Image
|
18 |
+
import io
|
19 |
+
import re
|
20 |
+
import json
|
21 |
+
import yaml
|
22 |
+
from ..AIutel import Optimizers
|
23 |
+
from ..AIutel import Conversation
|
24 |
+
from ..AIutel import AwesomePrompts, sanitize_stream
|
25 |
+
from ..AIbase import Provider, AsyncProvider
|
26 |
+
from Helpingai_T2 import Perplexity
|
27 |
+
from webscout import exceptions
|
28 |
+
from typing import Any, AsyncGenerator, Dict
|
29 |
+
import logging
|
30 |
+
import httpx
|
31 |
+
from Bard import Chatbot
|
32 |
+
import logging
|
33 |
+
from os import path
|
34 |
+
from json import load
|
35 |
+
from json import dumps
|
36 |
+
import warnings
|
37 |
+
logging.getLogger("httpx").setLevel(logging.ERROR)
|
38 |
+
warnings.simplefilter("ignore", category=UserWarning)
|
39 |
+
class GEMINI(Provider):
|
40 |
+
def __init__(
|
41 |
+
self,
|
42 |
+
cookie_file: str,
|
43 |
+
proxy: dict = {},
|
44 |
+
timeout: int = 30,
|
45 |
+
):
|
46 |
+
"""Initializes GEMINI
|
47 |
+
|
48 |
+
Args:
|
49 |
+
cookie_file (str): Path to `bard.google.com.cookies.json` file
|
50 |
+
proxy (dict, optional): Http request proxy. Defaults to {}.
|
51 |
+
timeout (int, optional): Http request timeout. Defaults to 30.
|
52 |
+
"""
|
53 |
+
self.conversation = Conversation(False)
|
54 |
+
self.session_auth1 = None
|
55 |
+
self.session_auth2 = None
|
56 |
+
assert isinstance(
|
57 |
+
cookie_file, str
|
58 |
+
), f"cookie_file should be of {str} only not '{type(cookie_file)}'"
|
59 |
+
if path.isfile(cookie_file):
|
60 |
+
# let's assume auth is a path to exported .json cookie-file
|
61 |
+
with open(cookie_file) as fh:
|
62 |
+
entries = load(fh)
|
63 |
+
for entry in entries:
|
64 |
+
if entry["name"] == "__Secure-1PSID":
|
65 |
+
self.session_auth1 = entry["value"]
|
66 |
+
elif entry["name"] == "__Secure-1PSIDTS":
|
67 |
+
self.session_auth2 = entry["value"]
|
68 |
+
|
69 |
+
assert all(
|
70 |
+
[self.session_auth1, self.session_auth2]
|
71 |
+
), f"Failed to extract the required cookie value from file '{cookie_file}'"
|
72 |
+
else:
|
73 |
+
raise Exception(f"{cookie_file} is not a valid file path")
|
74 |
+
|
75 |
+
self.session = Chatbot(self.session_auth1, self.session_auth2, proxy, timeout)
|
76 |
+
self.last_response = {}
|
77 |
+
self.__available_optimizers = (
|
78 |
+
method
|
79 |
+
for method in dir(Optimizers)
|
80 |
+
if callable(getattr(Optimizers, method)) and not method.startswith("__")
|
81 |
+
)
|
82 |
+
|
83 |
+
def ask(
|
84 |
+
self,
|
85 |
+
prompt: str,
|
86 |
+
stream: bool = False,
|
87 |
+
raw: bool = False,
|
88 |
+
optimizer: str = None,
|
89 |
+
conversationally: bool = False,
|
90 |
+
) -> dict:
|
91 |
+
"""Chat with AI
|
92 |
+
|
93 |
+
Args:
|
94 |
+
prompt (str): Prompt to be send.
|
95 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
96 |
+
raw (bool, optional): Stream back raw response as received. Defaults to False.
|
97 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defeaults to None
|
98 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
99 |
+
Returns:
|
100 |
+
dict : {}
|
101 |
+
```json
|
102 |
+
{
|
103 |
+
"content": "General Kenobi! \n\n(I couldn't help but respond with the iconic Star Wars greeting since you used it first. )\n\nIs there anything I can help you with today?\n[Image of Hello there General Kenobi]",
|
104 |
+
"conversation_id": "c_f13f6217f9a997aa",
|
105 |
+
"response_id": "r_d3665f95975c368f",
|
106 |
+
"factualityQueries": null,
|
107 |
+
"textQuery": [
|
108 |
+
"hello there",
|
109 |
+
1
|
110 |
+
],
|
111 |
+
"choices": [
|
112 |
+
{
|
113 |
+
"id": "rc_ea075c9671bfd8cb",
|
114 |
+
"content": [
|
115 |
+
"General Kenobi! \n\n(I couldn't help but respond with the iconic Star Wars greeting since you used it first. )\n\nIs there anything I can help you with today?\n[Image of Hello there General Kenobi]"
|
116 |
+
]
|
117 |
+
},
|
118 |
+
{
|
119 |
+
"id": "rc_de6dd3fb793a5402",
|
120 |
+
"content": [
|
121 |
+
"General Kenobi! (or just a friendly hello, whichever you prefer!). \n\nI see you're a person of culture as well. *Star Wars* references are always appreciated. \n\nHow can I help you today?\n"
|
122 |
+
]
|
123 |
+
},
|
124 |
+
{
|
125 |
+
"id": "rc_a672ac089caf32db",
|
126 |
+
"content": [
|
127 |
+
"General Kenobi! (or just a friendly hello if you're not a Star Wars fan!). \n\nHow can I help you today? Feel free to ask me anything, or tell me what you'd like to chat about. I'm here to assist in any way I can.\n[Image of Obi-Wan Kenobi saying hello there]"
|
128 |
+
]
|
129 |
+
}
|
130 |
+
],
|
131 |
+
|
132 |
+
"images": [
|
133 |
+
"https://i.pinimg.com/originals/40/74/60/407460925c9e419d82b93313f0b42f71.jpg"
|
134 |
+
]
|
135 |
+
}
|
136 |
+
|
137 |
+
```
|
138 |
+
"""
|
139 |
+
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
140 |
+
if optimizer:
|
141 |
+
if optimizer in self.__available_optimizers:
|
142 |
+
conversation_prompt = getattr(Optimizers, optimizer)(
|
143 |
+
conversation_prompt if conversationally else prompt
|
144 |
+
)
|
145 |
+
else:
|
146 |
+
raise Exception(
|
147 |
+
f"Optimizer is not one of {self.__available_optimizers}"
|
148 |
+
)
|
149 |
+
|
150 |
+
def for_stream():
|
151 |
+
response = self.session.ask(prompt)
|
152 |
+
self.last_response.update(response)
|
153 |
+
self.conversation.update_chat_history(
|
154 |
+
prompt, self.get_message(self.last_response)
|
155 |
+
)
|
156 |
+
yield dumps(response) if raw else response
|
157 |
+
|
158 |
+
def for_non_stream():
|
159 |
+
# let's make use of stream
|
160 |
+
for _ in for_stream():
|
161 |
+
pass
|
162 |
+
return self.last_response
|
163 |
+
|
164 |
+
return for_stream() if stream else for_non_stream()
|
165 |
+
|
166 |
+
def chat(
|
167 |
+
self,
|
168 |
+
prompt: str,
|
169 |
+
stream: bool = False,
|
170 |
+
optimizer: str = None,
|
171 |
+
conversationally: bool = False,
|
172 |
+
) -> str:
|
173 |
+
"""Generate response `str`
|
174 |
+
Args:
|
175 |
+
prompt (str): Prompt to be send.
|
176 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
177 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
178 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
179 |
+
Returns:
|
180 |
+
str: Response generated
|
181 |
+
"""
|
182 |
+
|
183 |
+
def for_stream():
|
184 |
+
for response in self.ask(
|
185 |
+
prompt, True, optimizer=optimizer, conversationally=conversationally
|
186 |
+
):
|
187 |
+
yield self.get_message(response)
|
188 |
+
|
189 |
+
def for_non_stream():
|
190 |
+
return self.get_message(
|
191 |
+
self.ask(
|
192 |
+
prompt,
|
193 |
+
False,
|
194 |
+
optimizer=optimizer,
|
195 |
+
conversationally=conversationally,
|
196 |
+
)
|
197 |
+
)
|
198 |
+
|
199 |
+
return for_stream() if stream else for_non_stream()
|
200 |
+
|
201 |
+
def get_message(self, response: dict) -> str:
|
202 |
+
"""Retrieves message only from response
|
203 |
+
|
204 |
+
Args:
|
205 |
+
response (dict): Response generated by `self.ask`
|
206 |
+
|
207 |
+
Returns:
|
208 |
+
str: Message extracted
|
209 |
+
"""
|
210 |
+
assert isinstance(response, dict), "Response should be of dict data-type only"
|
211 |
+
return response["content"]
|
212 |
+
|
213 |
+
def reset(self):
|
214 |
+
"""Reset the current conversation"""
|
215 |
+
self.session.async_chatbot.conversation_id = ""
|
216 |
+
self.session.async_chatbot.response_id = ""
|
217 |
+
self.session.async_chatbot.choice_id = ""
|
webscout/Provider/Groq.py
ADDED
@@ -0,0 +1,512 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
import uuid
|
3 |
+
from selenium import webdriver
|
4 |
+
from selenium.webdriver.chrome.options import Options
|
5 |
+
from selenium.webdriver.common.by import By
|
6 |
+
from selenium.webdriver.support import expected_conditions as EC
|
7 |
+
from selenium.webdriver.support.ui import WebDriverWait
|
8 |
+
import click
|
9 |
+
import requests
|
10 |
+
from requests import get
|
11 |
+
from uuid import uuid4
|
12 |
+
from re import findall
|
13 |
+
from requests.exceptions import RequestException
|
14 |
+
from curl_cffi.requests import get, RequestsError
|
15 |
+
import g4f
|
16 |
+
from random import randint
|
17 |
+
from PIL import Image
|
18 |
+
import io
|
19 |
+
import re
|
20 |
+
import json
|
21 |
+
import yaml
|
22 |
+
from ..AIutel import Optimizers
|
23 |
+
from ..AIutel import Conversation
|
24 |
+
from ..AIutel import AwesomePrompts, sanitize_stream
|
25 |
+
from ..AIbase import Provider, AsyncProvider
|
26 |
+
from Helpingai_T2 import Perplexity
|
27 |
+
from webscout import exceptions
|
28 |
+
from typing import Any, AsyncGenerator, Dict
|
29 |
+
import logging
|
30 |
+
import httpx
|
31 |
+
|
32 |
+
class GROQ(Provider):
|
33 |
+
def __init__(
|
34 |
+
self,
|
35 |
+
api_key: str,
|
36 |
+
is_conversation: bool = True,
|
37 |
+
max_tokens: int = 600,
|
38 |
+
temperature: float = 1,
|
39 |
+
presence_penalty: int = 0,
|
40 |
+
frequency_penalty: int = 0,
|
41 |
+
top_p: float = 1,
|
42 |
+
model: str = "mixtral-8x7b-32768",
|
43 |
+
timeout: int = 30,
|
44 |
+
intro: str = None,
|
45 |
+
filepath: str = None,
|
46 |
+
update_file: bool = True,
|
47 |
+
proxies: dict = {},
|
48 |
+
history_offset: int = 10250,
|
49 |
+
act: str = None,
|
50 |
+
):
|
51 |
+
"""Instantiates GROQ
|
52 |
+
|
53 |
+
Args:
|
54 |
+
api_key (key): GROQ's API key.
|
55 |
+
is_conversation (bool, optional): Flag for chatting conversationally. Defaults to True.
|
56 |
+
max_tokens (int, optional): Maximum number of tokens to be generated upon completion. Defaults to 600.
|
57 |
+
temperature (float, optional): Charge of the generated text's randomness. Defaults to 1.
|
58 |
+
presence_penalty (int, optional): Chances of topic being repeated. Defaults to 0.
|
59 |
+
frequency_penalty (int, optional): Chances of word being repeated. Defaults to 0.
|
60 |
+
top_p (float, optional): Sampling threshold during inference time. Defaults to 0.999.
|
61 |
+
model (str, optional): LLM model name. Defaults to "gpt-3.5-turbo".
|
62 |
+
timeout (int, optional): Http request timeout. Defaults to 30.
|
63 |
+
intro (str, optional): Conversation introductory prompt. Defaults to None.
|
64 |
+
filepath (str, optional): Path to file containing conversation history. Defaults to None.
|
65 |
+
update_file (bool, optional): Add new prompts and responses to the file. Defaults to True.
|
66 |
+
proxies (dict, optional): Http request proxies. Defaults to {}.
|
67 |
+
history_offset (int, optional): Limit conversation history to this number of last texts. Defaults to 10250.
|
68 |
+
act (str|int, optional): Awesome prompt key or index. (Used as intro). Defaults to None.
|
69 |
+
"""
|
70 |
+
self.session = requests.Session()
|
71 |
+
self.is_conversation = is_conversation
|
72 |
+
self.max_tokens_to_sample = max_tokens
|
73 |
+
self.api_key = api_key
|
74 |
+
self.model = model
|
75 |
+
self.temperature = temperature
|
76 |
+
self.presence_penalty = presence_penalty
|
77 |
+
self.frequency_penalty = frequency_penalty
|
78 |
+
self.top_p = top_p
|
79 |
+
self.chat_endpoint = "https://api.groq.com/openai/v1/chat/completions"
|
80 |
+
self.stream_chunk_size = 64
|
81 |
+
self.timeout = timeout
|
82 |
+
self.last_response = {}
|
83 |
+
self.headers = {
|
84 |
+
"Content-Type": "application/json",
|
85 |
+
"Authorization": f"Bearer {self.api_key}",
|
86 |
+
}
|
87 |
+
|
88 |
+
self.__available_optimizers = (
|
89 |
+
method
|
90 |
+
for method in dir(Optimizers)
|
91 |
+
if callable(getattr(Optimizers, method)) and not method.startswith("__")
|
92 |
+
)
|
93 |
+
self.session.headers.update(self.headers)
|
94 |
+
Conversation.intro = (
|
95 |
+
AwesomePrompts().get_act(
|
96 |
+
act, raise_not_found=True, default=None, case_insensitive=True
|
97 |
+
)
|
98 |
+
if act
|
99 |
+
else intro or Conversation.intro
|
100 |
+
)
|
101 |
+
self.conversation = Conversation(
|
102 |
+
is_conversation, self.max_tokens_to_sample, filepath, update_file
|
103 |
+
)
|
104 |
+
self.conversation.history_offset = history_offset
|
105 |
+
self.session.proxies = proxies
|
106 |
+
|
107 |
+
def ask(
|
108 |
+
self,
|
109 |
+
prompt: str,
|
110 |
+
stream: bool = False,
|
111 |
+
raw: bool = False,
|
112 |
+
optimizer: str = None,
|
113 |
+
conversationally: bool = False,
|
114 |
+
) -> dict:
|
115 |
+
"""Chat with AI
|
116 |
+
|
117 |
+
Args:
|
118 |
+
prompt (str): Prompt to be send.
|
119 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
120 |
+
raw (bool, optional): Stream back raw response as received. Defaults to False.
|
121 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
122 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
123 |
+
Returns:
|
124 |
+
dict : {}
|
125 |
+
```json
|
126 |
+
{
|
127 |
+
"id": "c0c8d139-d2b9-9909-8aa1-14948bc28404",
|
128 |
+
"object": "chat.completion",
|
129 |
+
"created": 1710852779,
|
130 |
+
"model": "mixtral-8x7b-32768",
|
131 |
+
"choices": [
|
132 |
+
{
|
133 |
+
"index": 0,
|
134 |
+
"message": {
|
135 |
+
"role": "assistant",
|
136 |
+
"content": "Hello! How can I assist you today? I'm here to help answer your questions and engage in conversation on a wide variety of topics. Feel free to ask me anything!"
|
137 |
+
},
|
138 |
+
"logprobs": null,
|
139 |
+
"finish_reason": "stop"
|
140 |
+
}
|
141 |
+
],
|
142 |
+
"usage": {
|
143 |
+
"prompt_tokens": 47,
|
144 |
+
"prompt_time": 0.03,
|
145 |
+
"completion_tokens": 37,
|
146 |
+
"completion_time": 0.069,
|
147 |
+
"total_tokens": 84,
|
148 |
+
"total_time": 0.099
|
149 |
+
},
|
150 |
+
"system_fingerprint": null
|
151 |
+
}
|
152 |
+
```
|
153 |
+
"""
|
154 |
+
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
155 |
+
if optimizer:
|
156 |
+
if optimizer in self.__available_optimizers:
|
157 |
+
conversation_prompt = getattr(Optimizers, optimizer)(
|
158 |
+
conversation_prompt if conversationally else prompt
|
159 |
+
)
|
160 |
+
else:
|
161 |
+
raise Exception(
|
162 |
+
f"Optimizer is not one of {self.__available_optimizers}"
|
163 |
+
)
|
164 |
+
self.session.headers.update(self.headers)
|
165 |
+
payload = {
|
166 |
+
"frequency_penalty": self.frequency_penalty,
|
167 |
+
"messages": [{"content": conversation_prompt, "role": "user"}],
|
168 |
+
"model": self.model,
|
169 |
+
"presence_penalty": self.presence_penalty,
|
170 |
+
"stream": stream,
|
171 |
+
"temperature": self.temperature,
|
172 |
+
"top_p": self.top_p,
|
173 |
+
}
|
174 |
+
|
175 |
+
def for_stream():
|
176 |
+
response = self.session.post(
|
177 |
+
self.chat_endpoint, json=payload, stream=True, timeout=self.timeout
|
178 |
+
)
|
179 |
+
if not response.ok:
|
180 |
+
raise Exception(
|
181 |
+
f"Failed to generate response - ({response.status_code}, {response.reason}) - {response.text}"
|
182 |
+
)
|
183 |
+
|
184 |
+
message_load = ""
|
185 |
+
for value in response.iter_lines(
|
186 |
+
decode_unicode=True,
|
187 |
+
delimiter="" if raw else "data:",
|
188 |
+
chunk_size=self.stream_chunk_size,
|
189 |
+
):
|
190 |
+
try:
|
191 |
+
resp = json.loads(value)
|
192 |
+
incomplete_message = self.get_message(resp)
|
193 |
+
if incomplete_message:
|
194 |
+
message_load += incomplete_message
|
195 |
+
resp["choices"][0]["delta"]["content"] = message_load
|
196 |
+
self.last_response.update(resp)
|
197 |
+
yield value if raw else resp
|
198 |
+
elif raw:
|
199 |
+
yield value
|
200 |
+
except json.decoder.JSONDecodeError:
|
201 |
+
pass
|
202 |
+
self.conversation.update_chat_history(
|
203 |
+
prompt, self.get_message(self.last_response)
|
204 |
+
)
|
205 |
+
|
206 |
+
def for_non_stream():
|
207 |
+
response = self.session.post(
|
208 |
+
self.chat_endpoint, json=payload, stream=False, timeout=self.timeout
|
209 |
+
)
|
210 |
+
if not response.ok:
|
211 |
+
raise Exception(
|
212 |
+
f"Failed to generate response - ({response.status_code}, {response.reason}) - {response.text}"
|
213 |
+
)
|
214 |
+
resp = response.json()
|
215 |
+
self.last_response.update(resp)
|
216 |
+
self.conversation.update_chat_history(
|
217 |
+
prompt, self.get_message(self.last_response)
|
218 |
+
)
|
219 |
+
return resp
|
220 |
+
|
221 |
+
return for_stream() if stream else for_non_stream()
|
222 |
+
|
223 |
+
def chat(
|
224 |
+
self,
|
225 |
+
prompt: str,
|
226 |
+
stream: bool = False,
|
227 |
+
optimizer: str = None,
|
228 |
+
conversationally: bool = False,
|
229 |
+
) -> str:
|
230 |
+
"""Generate response `str`
|
231 |
+
Args:
|
232 |
+
prompt (str): Prompt to be send.
|
233 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
234 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
235 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
236 |
+
Returns:
|
237 |
+
str: Response generated
|
238 |
+
"""
|
239 |
+
|
240 |
+
def for_stream():
|
241 |
+
for response in self.ask(
|
242 |
+
prompt, True, optimizer=optimizer, conversationally=conversationally
|
243 |
+
):
|
244 |
+
yield self.get_message(response)
|
245 |
+
|
246 |
+
def for_non_stream():
|
247 |
+
return self.get_message(
|
248 |
+
self.ask(
|
249 |
+
prompt,
|
250 |
+
False,
|
251 |
+
optimizer=optimizer,
|
252 |
+
conversationally=conversationally,
|
253 |
+
)
|
254 |
+
)
|
255 |
+
|
256 |
+
return for_stream() if stream else for_non_stream()
|
257 |
+
|
258 |
+
def get_message(self, response: dict) -> str:
|
259 |
+
"""Retrieves message only from response
|
260 |
+
|
261 |
+
Args:
|
262 |
+
response (dict): Response generated by `self.ask`
|
263 |
+
|
264 |
+
Returns:
|
265 |
+
str: Message extracted
|
266 |
+
"""
|
267 |
+
assert isinstance(response, dict), "Response should be of dict data-type only"
|
268 |
+
try:
|
269 |
+
if response["choices"][0].get("delta"):
|
270 |
+
return response["choices"][0]["delta"]["content"]
|
271 |
+
return response["choices"][0]["message"]["content"]
|
272 |
+
except KeyError:
|
273 |
+
return ""
|
274 |
+
class AsyncGROQ(AsyncProvider):
|
275 |
+
def __init__(
|
276 |
+
self,
|
277 |
+
api_key: str,
|
278 |
+
is_conversation: bool = True,
|
279 |
+
max_tokens: int = 600,
|
280 |
+
temperature: float = 1,
|
281 |
+
presence_penalty: int = 0,
|
282 |
+
frequency_penalty: int = 0,
|
283 |
+
top_p: float = 1,
|
284 |
+
model: str = "mixtral-8x7b-32768",
|
285 |
+
timeout: int = 30,
|
286 |
+
intro: str = None,
|
287 |
+
filepath: str = None,
|
288 |
+
update_file: bool = True,
|
289 |
+
proxies: dict = {},
|
290 |
+
history_offset: int = 10250,
|
291 |
+
act: str = None,
|
292 |
+
):
|
293 |
+
"""Instantiates GROQ
|
294 |
+
|
295 |
+
Args:
|
296 |
+
api_key (key): GROQ's API key.
|
297 |
+
is_conversation (bool, optional): Flag for chatting conversationally. Defaults to True.
|
298 |
+
max_tokens (int, optional): Maximum number of tokens to be generated upon completion. Defaults to 600.
|
299 |
+
temperature (float, optional): Charge of the generated text's randomness. Defaults to 1.
|
300 |
+
presence_penalty (int, optional): Chances of topic being repeated. Defaults to 0.
|
301 |
+
frequency_penalty (int, optional): Chances of word being repeated. Defaults to 0.
|
302 |
+
top_p (float, optional): Sampling threshold during inference time. Defaults to 0.999.
|
303 |
+
model (str, optional): LLM model name. Defaults to "gpt-3.5-turbo".
|
304 |
+
timeout (int, optional): Http request timeout. Defaults to 30.
|
305 |
+
intro (str, optional): Conversation introductory prompt. Defaults to None.
|
306 |
+
filepath (str, optional): Path to file containing conversation history. Defaults to None.
|
307 |
+
update_file (bool, optional): Add new prompts and responses to the file. Defaults to True.
|
308 |
+
proxies (dict, optional): Http request proxies. Defaults to {}.
|
309 |
+
history_offset (int, optional): Limit conversation history to this number of last texts. Defaults to 10250.
|
310 |
+
act (str|int, optional): Awesome prompt key or index. (Used as intro). Defaults to None.
|
311 |
+
"""
|
312 |
+
self.is_conversation = is_conversation
|
313 |
+
self.max_tokens_to_sample = max_tokens
|
314 |
+
self.api_key = api_key
|
315 |
+
self.model = model
|
316 |
+
self.temperature = temperature
|
317 |
+
self.presence_penalty = presence_penalty
|
318 |
+
self.frequency_penalty = frequency_penalty
|
319 |
+
self.top_p = top_p
|
320 |
+
self.chat_endpoint = "https://api.groq.com/openai/v1/chat/completions"
|
321 |
+
self.stream_chunk_size = 64
|
322 |
+
self.timeout = timeout
|
323 |
+
self.last_response = {}
|
324 |
+
self.headers = {
|
325 |
+
"Content-Type": "application/json",
|
326 |
+
"Authorization": f"Bearer {self.api_key}",
|
327 |
+
}
|
328 |
+
|
329 |
+
self.__available_optimizers = (
|
330 |
+
method
|
331 |
+
for method in dir(Optimizers)
|
332 |
+
if callable(getattr(Optimizers, method)) and not method.startswith("__")
|
333 |
+
)
|
334 |
+
Conversation.intro = (
|
335 |
+
AwesomePrompts().get_act(
|
336 |
+
act, raise_not_found=True, default=None, case_insensitive=True
|
337 |
+
)
|
338 |
+
if act
|
339 |
+
else intro or Conversation.intro
|
340 |
+
)
|
341 |
+
self.conversation = Conversation(
|
342 |
+
is_conversation, self.max_tokens_to_sample, filepath, update_file
|
343 |
+
)
|
344 |
+
self.conversation.history_offset = history_offset
|
345 |
+
self.session = httpx.AsyncClient(headers=self.headers, proxies=proxies)
|
346 |
+
|
347 |
+
async def ask(
|
348 |
+
self,
|
349 |
+
prompt: str,
|
350 |
+
stream: bool = False,
|
351 |
+
raw: bool = False,
|
352 |
+
optimizer: str = None,
|
353 |
+
conversationally: bool = False,
|
354 |
+
) -> dict | AsyncGenerator:
|
355 |
+
"""Chat with AI asynchronously.
|
356 |
+
|
357 |
+
Args:
|
358 |
+
prompt (str): Prompt to be send.
|
359 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
360 |
+
raw (bool, optional): Stream back raw response as received. Defaults to False.
|
361 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
362 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
363 |
+
Returns:
|
364 |
+
dict|AsyncGenerator : ai content
|
365 |
+
```json
|
366 |
+
{
|
367 |
+
"id": "c0c8d139-d2b9-9909-8aa1-14948bc28404",
|
368 |
+
"object": "chat.completion",
|
369 |
+
"created": 1710852779,
|
370 |
+
"model": "mixtral-8x7b-32768",
|
371 |
+
"choices": [
|
372 |
+
{
|
373 |
+
"index": 0,
|
374 |
+
"message": {
|
375 |
+
"role": "assistant",
|
376 |
+
"content": "Hello! How can I assist you today? I'm here to help answer your questions and engage in conversation on a wide variety of topics. Feel free to ask me anything!"
|
377 |
+
},
|
378 |
+
"logprobs": null,
|
379 |
+
"finish_reason": "stop"
|
380 |
+
}
|
381 |
+
],
|
382 |
+
"usage": {
|
383 |
+
"prompt_tokens": 47,
|
384 |
+
"prompt_time": 0.03,
|
385 |
+
"completion_tokens": 37,
|
386 |
+
"completion_time": 0.069,
|
387 |
+
"total_tokens": 84,
|
388 |
+
"total_time": 0.099
|
389 |
+
},
|
390 |
+
"system_fingerprint": null
|
391 |
+
}
|
392 |
+
```
|
393 |
+
"""
|
394 |
+
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
395 |
+
if optimizer:
|
396 |
+
if optimizer in self.__available_optimizers:
|
397 |
+
conversation_prompt = getattr(Optimizers, optimizer)(
|
398 |
+
conversation_prompt if conversationally else prompt
|
399 |
+
)
|
400 |
+
else:
|
401 |
+
raise Exception(
|
402 |
+
f"Optimizer is not one of {self.__available_optimizers}"
|
403 |
+
)
|
404 |
+
payload = {
|
405 |
+
"frequency_penalty": self.frequency_penalty,
|
406 |
+
"messages": [{"content": conversation_prompt, "role": "user"}],
|
407 |
+
"model": self.model,
|
408 |
+
"presence_penalty": self.presence_penalty,
|
409 |
+
"stream": stream,
|
410 |
+
"temperature": self.temperature,
|
411 |
+
"top_p": self.top_p,
|
412 |
+
}
|
413 |
+
|
414 |
+
async def for_stream():
|
415 |
+
async with self.session.stream(
|
416 |
+
"POST", self.chat_endpoint, json=payload, timeout=self.timeout
|
417 |
+
) as response:
|
418 |
+
if not response.is_success:
|
419 |
+
raise Exception(
|
420 |
+
f"Failed to generate response - ({response.status_code}, {response.reason_phrase})"
|
421 |
+
)
|
422 |
+
|
423 |
+
message_load = ""
|
424 |
+
intro_value = "data:"
|
425 |
+
async for value in response.aiter_lines():
|
426 |
+
try:
|
427 |
+
if value.startswith(intro_value):
|
428 |
+
value = value[len(intro_value) :]
|
429 |
+
resp = json.loads(value)
|
430 |
+
incomplete_message = await self.get_message(resp)
|
431 |
+
if incomplete_message:
|
432 |
+
message_load += incomplete_message
|
433 |
+
resp["choices"][0]["delta"]["content"] = message_load
|
434 |
+
self.last_response.update(resp)
|
435 |
+
yield value if raw else resp
|
436 |
+
elif raw:
|
437 |
+
yield value
|
438 |
+
except json.decoder.JSONDecodeError:
|
439 |
+
pass
|
440 |
+
self.conversation.update_chat_history(
|
441 |
+
prompt, await self.get_message(self.last_response)
|
442 |
+
)
|
443 |
+
|
444 |
+
async def for_non_stream():
|
445 |
+
response = httpx.post(
|
446 |
+
self.chat_endpoint, json=payload, timeout=self.timeout
|
447 |
+
)
|
448 |
+
if not response.is_success:
|
449 |
+
raise Exception(
|
450 |
+
f"Failed to generate response - ({response.status_code}, {response.reason_phrase})"
|
451 |
+
)
|
452 |
+
resp = response.json()
|
453 |
+
self.last_response.update(resp)
|
454 |
+
self.conversation.update_chat_history(
|
455 |
+
prompt, await self.get_message(self.last_response)
|
456 |
+
)
|
457 |
+
return resp
|
458 |
+
|
459 |
+
return for_stream() if stream else await for_non_stream()
|
460 |
+
|
461 |
+
async def chat(
|
462 |
+
self,
|
463 |
+
prompt: str,
|
464 |
+
stream: bool = False,
|
465 |
+
optimizer: str = None,
|
466 |
+
conversationally: bool = False,
|
467 |
+
) -> str | AsyncGenerator:
|
468 |
+
"""Generate response `str` asynchronously.
|
469 |
+
Args:
|
470 |
+
prompt (str): Prompt to be send.
|
471 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
472 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
473 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
474 |
+
Returns:
|
475 |
+
str|AsyncGenerator: Response generated
|
476 |
+
"""
|
477 |
+
|
478 |
+
async def for_stream():
|
479 |
+
async_ask = await self.ask(
|
480 |
+
prompt, True, optimizer=optimizer, conversationally=conversationally
|
481 |
+
)
|
482 |
+
async for response in async_ask:
|
483 |
+
yield await self.get_message(response)
|
484 |
+
|
485 |
+
async def for_non_stream():
|
486 |
+
return await self.get_message(
|
487 |
+
await self.ask(
|
488 |
+
prompt,
|
489 |
+
False,
|
490 |
+
optimizer=optimizer,
|
491 |
+
conversationally=conversationally,
|
492 |
+
)
|
493 |
+
)
|
494 |
+
|
495 |
+
return for_stream() if stream else await for_non_stream()
|
496 |
+
|
497 |
+
async def get_message(self, response: dict) -> str:
|
498 |
+
"""Retrieves message only from response
|
499 |
+
|
500 |
+
Args:
|
501 |
+
response (dict): Response generated by `self.ask`
|
502 |
+
|
503 |
+
Returns:
|
504 |
+
str: Message extracted
|
505 |
+
"""
|
506 |
+
assert isinstance(response, dict), "Response should be of dict data-type only"
|
507 |
+
try:
|
508 |
+
if response["choices"][0].get("delta"):
|
509 |
+
return response["choices"][0]["delta"]["content"]
|
510 |
+
return response["choices"][0]["message"]["content"]
|
511 |
+
except KeyError:
|
512 |
+
return ""
|
webscout/Provider/Koboldai.py
ADDED
@@ -0,0 +1,402 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
import uuid
|
3 |
+
from selenium import webdriver
|
4 |
+
from selenium.webdriver.chrome.options import Options
|
5 |
+
from selenium.webdriver.common.by import By
|
6 |
+
from selenium.webdriver.support import expected_conditions as EC
|
7 |
+
from selenium.webdriver.support.ui import WebDriverWait
|
8 |
+
import click
|
9 |
+
import requests
|
10 |
+
from requests import get
|
11 |
+
from uuid import uuid4
|
12 |
+
from re import findall
|
13 |
+
from requests.exceptions import RequestException
|
14 |
+
from curl_cffi.requests import get, RequestsError
|
15 |
+
import g4f
|
16 |
+
from random import randint
|
17 |
+
from PIL import Image
|
18 |
+
import io
|
19 |
+
import re
|
20 |
+
import json
|
21 |
+
import yaml
|
22 |
+
from ..AIutel import Optimizers
|
23 |
+
from ..AIutel import Conversation
|
24 |
+
from ..AIutel import AwesomePrompts, sanitize_stream
|
25 |
+
from ..AIbase import Provider, AsyncProvider
|
26 |
+
from Helpingai_T2 import Perplexity
|
27 |
+
from webscout import exceptions
|
28 |
+
from typing import Any, AsyncGenerator, Dict
|
29 |
+
import logging
|
30 |
+
import httpx
|
31 |
+
#------------------------------------------------------KOBOLDAI-----------------------------------------------------------
|
32 |
+
class KOBOLDAI(Provider):
|
33 |
+
def __init__(
|
34 |
+
self,
|
35 |
+
is_conversation: bool = True,
|
36 |
+
max_tokens: int = 600,
|
37 |
+
temperature: float = 1,
|
38 |
+
top_p: float = 1,
|
39 |
+
timeout: int = 30,
|
40 |
+
intro: str = None,
|
41 |
+
filepath: str = None,
|
42 |
+
update_file: bool = True,
|
43 |
+
proxies: dict = {},
|
44 |
+
history_offset: int = 10250,
|
45 |
+
act: str = None,
|
46 |
+
):
|
47 |
+
"""Instantiate TGPT
|
48 |
+
|
49 |
+
Args:
|
50 |
+
is_conversation (str, optional): Flag for chatting conversationally. Defaults to True.
|
51 |
+
max_tokens (int, optional): Maximum number of tokens to be generated upon completion. Defaults to 600.
|
52 |
+
temperature (float, optional): Charge of the generated text's randomness. Defaults to 0.2.
|
53 |
+
top_p (float, optional): Sampling threshold during inference time. Defaults to 0.999.
|
54 |
+
timeout (int, optional): Http requesting timeout. Defaults to 30
|
55 |
+
intro (str, optional): Conversation introductory prompt. Defaults to `Conversation.intro`.
|
56 |
+
filepath (str, optional): Path to file containing conversation history. Defaults to None.
|
57 |
+
update_file (bool, optional): Add new prompts and responses to the file. Defaults to True.
|
58 |
+
proxies (dict, optional) : Http reqiuest proxies (socks). Defaults to {}.
|
59 |
+
history_offset (int, optional): Limit conversation history to this number of last texts. Defaults to 10250.
|
60 |
+
act (str|int, optional): Awesome prompt key or index. (Used as intro). Defaults to None.
|
61 |
+
"""
|
62 |
+
self.session = requests.Session()
|
63 |
+
self.is_conversation = is_conversation
|
64 |
+
self.max_tokens_to_sample = max_tokens
|
65 |
+
self.temperature = temperature
|
66 |
+
self.top_p = top_p
|
67 |
+
self.chat_endpoint = (
|
68 |
+
"https://koboldai-koboldcpp-tiefighter.hf.space/api/extra/generate/stream"
|
69 |
+
)
|
70 |
+
self.stream_chunk_size = 64
|
71 |
+
self.timeout = timeout
|
72 |
+
self.last_response = {}
|
73 |
+
self.headers = {
|
74 |
+
"Content-Type": "application/json",
|
75 |
+
"Accept": "application/json",
|
76 |
+
}
|
77 |
+
|
78 |
+
self.__available_optimizers = (
|
79 |
+
method
|
80 |
+
for method in dir(Optimizers)
|
81 |
+
if callable(getattr(Optimizers, method)) and not method.startswith("__")
|
82 |
+
)
|
83 |
+
self.session.headers.update(self.headers)
|
84 |
+
Conversation.intro = (
|
85 |
+
AwesomePrompts().get_act(
|
86 |
+
act, raise_not_found=True, default=None, case_insensitive=True
|
87 |
+
)
|
88 |
+
if act
|
89 |
+
else intro or Conversation.intro
|
90 |
+
)
|
91 |
+
self.conversation = Conversation(
|
92 |
+
is_conversation, self.max_tokens_to_sample, filepath, update_file
|
93 |
+
)
|
94 |
+
self.conversation.history_offset = history_offset
|
95 |
+
self.session.proxies = proxies
|
96 |
+
|
97 |
+
def ask(
|
98 |
+
self,
|
99 |
+
prompt: str,
|
100 |
+
stream: bool = False,
|
101 |
+
raw: bool = False,
|
102 |
+
optimizer: str = None,
|
103 |
+
conversationally: bool = False,
|
104 |
+
) -> dict:
|
105 |
+
"""Chat with AI
|
106 |
+
|
107 |
+
Args:
|
108 |
+
prompt (str): Prompt to be send.
|
109 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
110 |
+
raw (bool, optional): Stream back raw response as received. Defaults to False.
|
111 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
112 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
113 |
+
Returns:
|
114 |
+
dict : {}
|
115 |
+
```json
|
116 |
+
{
|
117 |
+
"token" : "How may I assist you today?"
|
118 |
+
}
|
119 |
+
```
|
120 |
+
"""
|
121 |
+
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
122 |
+
if optimizer:
|
123 |
+
if optimizer in self.__available_optimizers:
|
124 |
+
conversation_prompt = getattr(Optimizers, optimizer)(
|
125 |
+
conversation_prompt if conversationally else prompt
|
126 |
+
)
|
127 |
+
else:
|
128 |
+
raise Exception(
|
129 |
+
f"Optimizer is not one of {self.__available_optimizers}"
|
130 |
+
)
|
131 |
+
|
132 |
+
self.session.headers.update(self.headers)
|
133 |
+
payload = {
|
134 |
+
"prompt": conversation_prompt,
|
135 |
+
"temperature": self.temperature,
|
136 |
+
"top_p": self.top_p,
|
137 |
+
}
|
138 |
+
|
139 |
+
def for_stream():
|
140 |
+
response = self.session.post(
|
141 |
+
self.chat_endpoint, json=payload, stream=True, timeout=self.timeout
|
142 |
+
)
|
143 |
+
if not response.ok:
|
144 |
+
raise Exception(
|
145 |
+
f"Failed to generate response - ({response.status_code}, {response.reason}) - {response.text}"
|
146 |
+
)
|
147 |
+
|
148 |
+
message_load = ""
|
149 |
+
for value in response.iter_lines(
|
150 |
+
decode_unicode=True,
|
151 |
+
delimiter="" if raw else "event: message\ndata:",
|
152 |
+
chunk_size=self.stream_chunk_size,
|
153 |
+
):
|
154 |
+
try:
|
155 |
+
resp = json.loads(value)
|
156 |
+
message_load += self.get_message(resp)
|
157 |
+
resp["token"] = message_load
|
158 |
+
self.last_response.update(resp)
|
159 |
+
yield value if raw else resp
|
160 |
+
except json.decoder.JSONDecodeError:
|
161 |
+
pass
|
162 |
+
self.conversation.update_chat_history(
|
163 |
+
prompt, self.get_message(self.last_response)
|
164 |
+
)
|
165 |
+
|
166 |
+
def for_non_stream():
|
167 |
+
# let's make use of stream
|
168 |
+
for _ in for_stream():
|
169 |
+
pass
|
170 |
+
return self.last_response
|
171 |
+
|
172 |
+
return for_stream() if stream else for_non_stream()
|
173 |
+
|
174 |
+
def chat(
|
175 |
+
self,
|
176 |
+
prompt: str,
|
177 |
+
stream: bool = False,
|
178 |
+
optimizer: str = None,
|
179 |
+
conversationally: bool = False,
|
180 |
+
) -> str:
|
181 |
+
"""Generate response `str`
|
182 |
+
Args:
|
183 |
+
prompt (str): Prompt to be send.
|
184 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
185 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
186 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
187 |
+
Returns:
|
188 |
+
str: Response generated
|
189 |
+
"""
|
190 |
+
|
191 |
+
def for_stream():
|
192 |
+
for response in self.ask(
|
193 |
+
prompt, True, optimizer=optimizer, conversationally=conversationally
|
194 |
+
):
|
195 |
+
yield self.get_message(response)
|
196 |
+
|
197 |
+
def for_non_stream():
|
198 |
+
return self.get_message(
|
199 |
+
self.ask(
|
200 |
+
prompt,
|
201 |
+
False,
|
202 |
+
optimizer=optimizer,
|
203 |
+
conversationally=conversationally,
|
204 |
+
)
|
205 |
+
)
|
206 |
+
|
207 |
+
return for_stream() if stream else for_non_stream()
|
208 |
+
|
209 |
+
def get_message(self, response: dict) -> str:
|
210 |
+
"""Retrieves message only from response
|
211 |
+
|
212 |
+
Args:
|
213 |
+
response (dict): Response generated by `self.ask`
|
214 |
+
|
215 |
+
Returns:
|
216 |
+
str: Message extracted
|
217 |
+
"""
|
218 |
+
assert isinstance(response, dict), "Response should be of dict data-type only"
|
219 |
+
return response.get("token")
|
220 |
+
class AsyncKOBOLDAI(AsyncProvider):
|
221 |
+
def __init__(
|
222 |
+
self,
|
223 |
+
is_conversation: bool = True,
|
224 |
+
max_tokens: int = 600,
|
225 |
+
temperature: float = 1,
|
226 |
+
top_p: float = 1,
|
227 |
+
timeout: int = 30,
|
228 |
+
intro: str = None,
|
229 |
+
filepath: str = None,
|
230 |
+
update_file: bool = True,
|
231 |
+
proxies: dict = {},
|
232 |
+
history_offset: int = 10250,
|
233 |
+
act: str = None,
|
234 |
+
):
|
235 |
+
"""Instantiate TGPT
|
236 |
+
|
237 |
+
Args:
|
238 |
+
is_conversation (str, optional): Flag for chatting conversationally. Defaults to True.
|
239 |
+
max_tokens (int, optional): Maximum number of tokens to be generated upon completion. Defaults to 600.
|
240 |
+
temperature (float, optional): Charge of the generated text's randomness. Defaults to 0.2.
|
241 |
+
top_p (float, optional): Sampling threshold during inference time. Defaults to 0.999.
|
242 |
+
timeout (int, optional): Http requesting timeout. Defaults to 30
|
243 |
+
intro (str, optional): Conversation introductory prompt. Defaults to `Conversation.intro`.
|
244 |
+
filepath (str, optional): Path to file containing conversation history. Defaults to None.
|
245 |
+
update_file (bool, optional): Add new prompts and responses to the file. Defaults to True.
|
246 |
+
proxies (dict, optional) : Http reqiuest proxies (socks). Defaults to {}.
|
247 |
+
history_offset (int, optional): Limit conversation history to this number of last texts. Defaults to 10250.
|
248 |
+
act (str|int, optional): Awesome prompt key or index. (Used as intro). Defaults to None.
|
249 |
+
"""
|
250 |
+
self.is_conversation = is_conversation
|
251 |
+
self.max_tokens_to_sample = max_tokens
|
252 |
+
self.temperature = temperature
|
253 |
+
self.top_p = top_p
|
254 |
+
self.chat_endpoint = (
|
255 |
+
"https://koboldai-koboldcpp-tiefighter.hf.space/api/extra/generate/stream"
|
256 |
+
)
|
257 |
+
self.stream_chunk_size = 64
|
258 |
+
self.timeout = timeout
|
259 |
+
self.last_response = {}
|
260 |
+
self.headers = {
|
261 |
+
"Content-Type": "application/json",
|
262 |
+
"Accept": "application/json",
|
263 |
+
}
|
264 |
+
|
265 |
+
self.__available_optimizers = (
|
266 |
+
method
|
267 |
+
for method in dir(Optimizers)
|
268 |
+
if callable(getattr(Optimizers, method)) and not method.startswith("__")
|
269 |
+
)
|
270 |
+
Conversation.intro = (
|
271 |
+
AwesomePrompts().get_act(
|
272 |
+
act, raise_not_found=True, default=None, case_insensitive=True
|
273 |
+
)
|
274 |
+
if act
|
275 |
+
else intro or Conversation.intro
|
276 |
+
)
|
277 |
+
self.conversation = Conversation(
|
278 |
+
is_conversation, self.max_tokens_to_sample, filepath, update_file
|
279 |
+
)
|
280 |
+
self.conversation.history_offset = history_offset
|
281 |
+
self.session = httpx.AsyncClient(headers=self.headers, proxies=proxies)
|
282 |
+
|
283 |
+
async def ask(
|
284 |
+
self,
|
285 |
+
prompt: str,
|
286 |
+
stream: bool = False,
|
287 |
+
raw: bool = False,
|
288 |
+
optimizer: str = None,
|
289 |
+
conversationally: bool = False,
|
290 |
+
) -> dict | AsyncGenerator:
|
291 |
+
"""Chat with AI asynchronously.
|
292 |
+
|
293 |
+
Args:
|
294 |
+
prompt (str): Prompt to be send.
|
295 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
296 |
+
raw (bool, optional): Stream back raw response as received. Defaults to False.
|
297 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
298 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
299 |
+
Returns:
|
300 |
+
dict|AsyncGenerator : ai content
|
301 |
+
```json
|
302 |
+
{
|
303 |
+
"token" : "How may I assist you today?"
|
304 |
+
}
|
305 |
+
```
|
306 |
+
"""
|
307 |
+
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
308 |
+
if optimizer:
|
309 |
+
if optimizer in self.__available_optimizers:
|
310 |
+
conversation_prompt = getattr(Optimizers, optimizer)(
|
311 |
+
conversation_prompt if conversationally else prompt
|
312 |
+
)
|
313 |
+
else:
|
314 |
+
raise Exception(
|
315 |
+
f"Optimizer is not one of {self.__available_optimizers}"
|
316 |
+
)
|
317 |
+
|
318 |
+
payload = {
|
319 |
+
"prompt": conversation_prompt,
|
320 |
+
"temperature": self.temperature,
|
321 |
+
"top_p": self.top_p,
|
322 |
+
}
|
323 |
+
|
324 |
+
async def for_stream():
|
325 |
+
async with self.session.stream(
|
326 |
+
"POST", self.chat_endpoint, json=payload, timeout=self.timeout
|
327 |
+
) as response:
|
328 |
+
if not response.is_success:
|
329 |
+
raise exceptions.FailedToGenerateResponseError(
|
330 |
+
f"Failed to generate response - ({response.status_code}, {response.reason_phrase})"
|
331 |
+
)
|
332 |
+
|
333 |
+
message_load = ""
|
334 |
+
async for value in response.aiter_lines():
|
335 |
+
try:
|
336 |
+
resp = sanitize_stream(value)
|
337 |
+
message_load += await self.get_message(resp)
|
338 |
+
resp["token"] = message_load
|
339 |
+
self.last_response.update(resp)
|
340 |
+
yield value if raw else resp
|
341 |
+
except json.decoder.JSONDecodeError:
|
342 |
+
pass
|
343 |
+
|
344 |
+
self.conversation.update_chat_history(
|
345 |
+
prompt, await self.get_message(self.last_response)
|
346 |
+
)
|
347 |
+
|
348 |
+
async def for_non_stream():
|
349 |
+
# let's make use of stream
|
350 |
+
async for _ in for_stream():
|
351 |
+
pass
|
352 |
+
return self.last_response
|
353 |
+
|
354 |
+
return for_stream() if stream else await for_non_stream()
|
355 |
+
|
356 |
+
async def chat(
|
357 |
+
self,
|
358 |
+
prompt: str,
|
359 |
+
stream: bool = False,
|
360 |
+
optimizer: str = None,
|
361 |
+
conversationally: bool = False,
|
362 |
+
) -> str | AsyncGenerator:
|
363 |
+
"""Generate response `str` asynchronously.
|
364 |
+
Args:
|
365 |
+
prompt (str): Prompt to be send.
|
366 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
367 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
368 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
369 |
+
Returns:
|
370 |
+
str: Response generated
|
371 |
+
"""
|
372 |
+
|
373 |
+
async def for_stream():
|
374 |
+
async_ask = await self.ask(
|
375 |
+
prompt, True, optimizer=optimizer, conversationally=conversationally
|
376 |
+
)
|
377 |
+
async for response in async_ask:
|
378 |
+
yield await self.get_message(response)
|
379 |
+
|
380 |
+
async def for_non_stream():
|
381 |
+
return await self.get_message(
|
382 |
+
await self.ask(
|
383 |
+
prompt,
|
384 |
+
False,
|
385 |
+
optimizer=optimizer,
|
386 |
+
conversationally=conversationally,
|
387 |
+
)
|
388 |
+
)
|
389 |
+
|
390 |
+
return for_stream() if stream else await for_non_stream()
|
391 |
+
|
392 |
+
async def get_message(self, response: dict) -> str:
|
393 |
+
"""Retrieves message only from response
|
394 |
+
|
395 |
+
Args:
|
396 |
+
response (dict): Response generated by `self.ask`
|
397 |
+
|
398 |
+
Returns:
|
399 |
+
str: Message extracted
|
400 |
+
"""
|
401 |
+
assert isinstance(response, dict), "Response should be of dict data-type only"
|
402 |
+
return response.get("token")
|
webscout/Provider/Leo.py
ADDED
@@ -0,0 +1,469 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
import uuid
|
3 |
+
from selenium import webdriver
|
4 |
+
from selenium.webdriver.chrome.options import Options
|
5 |
+
from selenium.webdriver.common.by import By
|
6 |
+
from selenium.webdriver.support import expected_conditions as EC
|
7 |
+
from selenium.webdriver.support.ui import WebDriverWait
|
8 |
+
import click
|
9 |
+
import requests
|
10 |
+
from requests import get
|
11 |
+
from uuid import uuid4
|
12 |
+
from re import findall
|
13 |
+
from requests.exceptions import RequestException
|
14 |
+
from curl_cffi.requests import get, RequestsError
|
15 |
+
import g4f
|
16 |
+
from random import randint
|
17 |
+
from PIL import Image
|
18 |
+
import io
|
19 |
+
import re
|
20 |
+
import json
|
21 |
+
import yaml
|
22 |
+
from ..AIutel import Optimizers
|
23 |
+
from ..AIutel import Conversation
|
24 |
+
from ..AIutel import AwesomePrompts, sanitize_stream
|
25 |
+
from ..AIbase import Provider, AsyncProvider
|
26 |
+
from Helpingai_T2 import Perplexity
|
27 |
+
from webscout import exceptions
|
28 |
+
from typing import Any, AsyncGenerator, Dict
|
29 |
+
import logging
|
30 |
+
import httpx
|
31 |
+
#--------------------------------------LEO-----------------------------------------
|
32 |
+
class LEO(Provider):
|
33 |
+
|
34 |
+
def __init__(
|
35 |
+
self,
|
36 |
+
is_conversation: bool = True,
|
37 |
+
max_tokens: int = 600,
|
38 |
+
temperature: float = 0.2,
|
39 |
+
top_k: int = -1,
|
40 |
+
top_p: float = 0.999,
|
41 |
+
model: str = "llama-2-13b-chat",
|
42 |
+
brave_key: str = "qztbjzBqJueQZLFkwTTJrieu8Vw3789u",
|
43 |
+
timeout: int = 30,
|
44 |
+
intro: str = None,
|
45 |
+
filepath: str = None,
|
46 |
+
update_file: bool = True,
|
47 |
+
proxies: dict = {},
|
48 |
+
history_offset: int = 10250,
|
49 |
+
act: str = None,
|
50 |
+
):
|
51 |
+
"""Instantiate TGPT
|
52 |
+
|
53 |
+
Args:
|
54 |
+
is_conversation (str, optional): Flag for chatting conversationally. Defaults to True.
|
55 |
+
brave_key (str, optional): Brave API access key. Defaults to "qztbjzBqJueQZLFkwTTJrieu8Vw3789u".
|
56 |
+
model (str, optional): Text generation model name. Defaults to "llama-2-13b-chat".
|
57 |
+
max_tokens (int, optional): Maximum number of tokens to be generated upon completion. Defaults to 600.
|
58 |
+
temperature (float, optional): Charge of the generated text's randomness. Defaults to 0.2.
|
59 |
+
top_k (int, optional): Chance of topic being repeated. Defaults to -1.
|
60 |
+
top_p (float, optional): Sampling threshold during inference time. Defaults to 0.999.
|
61 |
+
timeput (int, optional): Http requesting timeout. Defaults to 30
|
62 |
+
intro (str, optional): Conversation introductory prompt. Defaults to `Conversation.intro`.
|
63 |
+
filepath (str, optional): Path to file containing conversation history. Defaults to None.
|
64 |
+
update_file (bool, optional): Add new prompts and responses to the file. Defaults to True.
|
65 |
+
proxies (dict, optional) : Http reqiuest proxies (socks). Defaults to {}.
|
66 |
+
history_offset (int, optional): Limit conversation history to this number of last texts. Defaults to 10250.
|
67 |
+
act (str|int, optional): Awesome prompt key or index. (Used as intro). Defaults to None.
|
68 |
+
"""
|
69 |
+
self.session = requests.Session()
|
70 |
+
self.is_conversation = is_conversation
|
71 |
+
self.max_tokens_to_sample = max_tokens
|
72 |
+
self.model = model
|
73 |
+
self.stop_sequences = ["</response>", "</s>"]
|
74 |
+
self.temperature = temperature
|
75 |
+
self.top_k = top_k
|
76 |
+
self.top_p = top_p
|
77 |
+
self.chat_endpoint = "https://ai-chat.bsg.brave.com/v1/complete"
|
78 |
+
self.stream_chunk_size = 64
|
79 |
+
self.timeout = timeout
|
80 |
+
self.last_response = {}
|
81 |
+
self.headers = {
|
82 |
+
"Content-Type": "application/json",
|
83 |
+
"accept": "text/event-stream",
|
84 |
+
"x-brave-key": brave_key,
|
85 |
+
"accept-language": "en-US,en;q=0.9",
|
86 |
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:99.0) Gecko/20100101 Firefox/110.0",
|
87 |
+
}
|
88 |
+
self.__available_optimizers = (
|
89 |
+
method
|
90 |
+
for method in dir(Optimizers)
|
91 |
+
if callable(getattr(Optimizers, method)) and not method.startswith("__")
|
92 |
+
)
|
93 |
+
self.session.headers.update(self.headers)
|
94 |
+
Conversation.intro = (
|
95 |
+
AwesomePrompts().get_act(
|
96 |
+
act, raise_not_found=True, default=None, case_insensitive=True
|
97 |
+
)
|
98 |
+
if act
|
99 |
+
else intro or Conversation.intro
|
100 |
+
)
|
101 |
+
self.conversation = Conversation(
|
102 |
+
is_conversation, self.max_tokens_to_sample, filepath, update_file
|
103 |
+
)
|
104 |
+
self.conversation.history_offset = history_offset
|
105 |
+
self.session.proxies = proxies
|
106 |
+
self.system_prompt = (
|
107 |
+
"\n\nYour name is Leo, a helpful"
|
108 |
+
"respectful and honest AI assistant created by the company Brave. You will be replying to a user of the Brave browser. "
|
109 |
+
"Always respond in a neutral tone. Be polite and courteous. Answer concisely in no more than 50-80 words."
|
110 |
+
"\n\nPlease ensure that your responses are socially unbiased and positive in nature."
|
111 |
+
"If a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. "
|
112 |
+
"If you don't know the answer to a question, please don't share false information.\n"
|
113 |
+
)
|
114 |
+
|
115 |
+
def ask(
|
116 |
+
self,
|
117 |
+
prompt: str,
|
118 |
+
stream: bool = False,
|
119 |
+
raw: bool = False,
|
120 |
+
optimizer: str = None,
|
121 |
+
conversationally: bool = False,
|
122 |
+
) -> dict:
|
123 |
+
"""Chat with AI
|
124 |
+
|
125 |
+
Args:
|
126 |
+
prompt (str): Prompt to be send.
|
127 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
128 |
+
raw (bool, optional): Stream back raw response as received. Defaults to False.
|
129 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
130 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
131 |
+
Returns:
|
132 |
+
dict : {}
|
133 |
+
```json
|
134 |
+
{
|
135 |
+
"completion": "\nNext: domestic cat breeds with short hair >>",
|
136 |
+
"stop_reason": null,
|
137 |
+
"truncated": false,
|
138 |
+
"stop": null,
|
139 |
+
"model": "llama-2-13b-chat",
|
140 |
+
"log_id": "cmpl-3kYiYxSNDvgMShSzFooz6t",
|
141 |
+
"exception": null
|
142 |
+
}
|
143 |
+
```
|
144 |
+
"""
|
145 |
+
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
146 |
+
if optimizer:
|
147 |
+
if optimizer in self.__available_optimizers:
|
148 |
+
conversation_prompt = getattr(Optimizers, optimizer)(
|
149 |
+
conversation_prompt if conversationally else prompt
|
150 |
+
)
|
151 |
+
else:
|
152 |
+
raise Exception(
|
153 |
+
f"Optimizer is not one of {self.__available_optimizers}"
|
154 |
+
)
|
155 |
+
|
156 |
+
self.session.headers.update(self.headers)
|
157 |
+
payload = {
|
158 |
+
"max_tokens_to_sample": self.max_tokens_to_sample,
|
159 |
+
"model": self.model,
|
160 |
+
"prompt": f"<s>[INST] <<SYS>>{self.system_prompt}<</SYS>>{conversation_prompt} [/INST]",
|
161 |
+
"self.stop_sequence": self.stop_sequences,
|
162 |
+
"stream": stream,
|
163 |
+
"top_k": self.top_k,
|
164 |
+
"top_p": self.top_p,
|
165 |
+
}
|
166 |
+
|
167 |
+
def for_stream():
|
168 |
+
response = self.session.post(
|
169 |
+
self.chat_endpoint, json=payload, stream=True, timeout=self.timeout
|
170 |
+
)
|
171 |
+
if (
|
172 |
+
not response.ok
|
173 |
+
or not response.headers.get("Content-Type")
|
174 |
+
== "text/event-stream; charset=utf-8"
|
175 |
+
):
|
176 |
+
raise Exception(
|
177 |
+
f"Failed to generate response - ({response.status_code}, {response.reason}) - {response.text}"
|
178 |
+
)
|
179 |
+
|
180 |
+
for value in response.iter_lines(
|
181 |
+
decode_unicode=True,
|
182 |
+
delimiter="" if raw else "data:",
|
183 |
+
chunk_size=self.stream_chunk_size,
|
184 |
+
):
|
185 |
+
try:
|
186 |
+
resp = json.loads(value)
|
187 |
+
self.last_response.update(resp)
|
188 |
+
yield value if raw else resp
|
189 |
+
except json.decoder.JSONDecodeError:
|
190 |
+
pass
|
191 |
+
self.conversation.update_chat_history(
|
192 |
+
prompt, self.get_message(self.last_response)
|
193 |
+
)
|
194 |
+
|
195 |
+
def for_non_stream():
|
196 |
+
response = self.session.post(
|
197 |
+
self.chat_endpoint, json=payload, stream=False, timeout=self.timeout
|
198 |
+
)
|
199 |
+
if (
|
200 |
+
not response.ok
|
201 |
+
or not response.headers.get("Content-Type", "") == "application/json"
|
202 |
+
):
|
203 |
+
raise Exception(
|
204 |
+
f"Failed to generate response - ({response.status_code}, {response.reason}) - {response.text}"
|
205 |
+
)
|
206 |
+
resp = response.json()
|
207 |
+
self.last_response.update(resp)
|
208 |
+
self.conversation.update_chat_history(
|
209 |
+
prompt, self.get_message(self.last_response)
|
210 |
+
)
|
211 |
+
return resp
|
212 |
+
|
213 |
+
return for_stream() if stream else for_non_stream()
|
214 |
+
|
215 |
+
def chat(
|
216 |
+
self,
|
217 |
+
prompt: str,
|
218 |
+
stream: bool = False,
|
219 |
+
optimizer: str = None,
|
220 |
+
conversationally: bool = False,
|
221 |
+
) -> str:
|
222 |
+
"""Generate response `str`
|
223 |
+
Args:
|
224 |
+
prompt (str): Prompt to be send.
|
225 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
226 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
227 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
228 |
+
Returns:
|
229 |
+
str: Response generated
|
230 |
+
"""
|
231 |
+
|
232 |
+
def for_stream():
|
233 |
+
for response in self.ask(
|
234 |
+
prompt, True, optimizer=optimizer, conversationally=conversationally
|
235 |
+
):
|
236 |
+
yield self.get_message(response)
|
237 |
+
|
238 |
+
def for_non_stream():
|
239 |
+
return self.get_message(
|
240 |
+
self.ask(
|
241 |
+
prompt,
|
242 |
+
False,
|
243 |
+
optimizer=optimizer,
|
244 |
+
conversationally=conversationally,
|
245 |
+
)
|
246 |
+
)
|
247 |
+
|
248 |
+
return for_stream() if stream else for_non_stream()
|
249 |
+
|
250 |
+
def get_message(self, response: dict) -> str:
|
251 |
+
"""Retrieves message only from response
|
252 |
+
|
253 |
+
Args:
|
254 |
+
response (dict): Response generated by `self.ask`
|
255 |
+
|
256 |
+
Returns:
|
257 |
+
str: Message extracted
|
258 |
+
"""
|
259 |
+
assert isinstance(response, dict), "Response should be of dict data-type only"
|
260 |
+
return response.get("completion")
|
261 |
+
class AsyncLEO(AsyncProvider):
|
262 |
+
def __init__(
|
263 |
+
self,
|
264 |
+
is_conversation: bool = True,
|
265 |
+
max_tokens: int = 600,
|
266 |
+
temperature: float = 0.2,
|
267 |
+
top_k: int = -1,
|
268 |
+
top_p: float = 0.999,
|
269 |
+
model: str = "llama-2-13b-chat",
|
270 |
+
brave_key: str = "qztbjzBqJueQZLFkwTTJrieu8Vw3789u",
|
271 |
+
timeout: int = 30,
|
272 |
+
intro: str = None,
|
273 |
+
filepath: str = None,
|
274 |
+
update_file: bool = True,
|
275 |
+
proxies: dict = {},
|
276 |
+
history_offset: int = 10250,
|
277 |
+
act: str = None,
|
278 |
+
):
|
279 |
+
"""Instantiate TGPT
|
280 |
+
|
281 |
+
Args:
|
282 |
+
is_conversation (str, optional): Flag for chatting conversationally. Defaults to True.
|
283 |
+
brave_key (str, optional): Brave API access key. Defaults to "qztbjzBqJueQZLFkwTTJrieu8Vw3789u".
|
284 |
+
model (str, optional): Text generation model name. Defaults to "llama-2-13b-chat".
|
285 |
+
max_tokens (int, optional): Maximum number of tokens to be generated upon completion. Defaults to 600.
|
286 |
+
temperature (float, optional): Charge of the generated text's randomness. Defaults to 0.2.
|
287 |
+
top_k (int, optional): Chance of topic being repeated. Defaults to -1.
|
288 |
+
top_p (float, optional): Sampling threshold during inference time. Defaults to 0.999.
|
289 |
+
timeput (int, optional): Http requesting timeout. Defaults to 30
|
290 |
+
intro (str, optional): Conversation introductory prompt. Defaults to `Conversation.intro`.
|
291 |
+
filepath (str, optional): Path to file containing conversation history. Defaults to None.
|
292 |
+
update_file (bool, optional): Add new prompts and responses to the file. Defaults to True.
|
293 |
+
proxies (dict, optional) : Http reqiuest proxies (socks). Defaults to {}.
|
294 |
+
history_offset (int, optional): Limit conversation history to this number of last texts. Defaults to 10250.
|
295 |
+
act (str|int, optional): Awesome prompt key or index. (Used as intro). Defaults to None.
|
296 |
+
"""
|
297 |
+
self.is_conversation = is_conversation
|
298 |
+
self.max_tokens_to_sample = max_tokens
|
299 |
+
self.model = model
|
300 |
+
self.stop_sequences = ["</response>", "</s>"]
|
301 |
+
self.temperature = temperature
|
302 |
+
self.top_k = top_k
|
303 |
+
self.top_p = top_p
|
304 |
+
self.chat_endpoint = "https://ai-chat.bsg.brave.com/v1/complete"
|
305 |
+
self.stream_chunk_size = 64
|
306 |
+
self.timeout = timeout
|
307 |
+
self.last_response = {}
|
308 |
+
self.headers = {
|
309 |
+
"Content-Type": "application/json",
|
310 |
+
"accept": "text/event-stream",
|
311 |
+
"x-brave-key": brave_key,
|
312 |
+
"accept-language": "en-US,en;q=0.9",
|
313 |
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:99.0) Gecko/20100101 Firefox/110.0",
|
314 |
+
}
|
315 |
+
self.__available_optimizers = (
|
316 |
+
method
|
317 |
+
for method in dir(Optimizers)
|
318 |
+
if callable(getattr(Optimizers, method)) and not method.startswith("__")
|
319 |
+
)
|
320 |
+
Conversation.intro = (
|
321 |
+
AwesomePrompts().get_act(
|
322 |
+
act, raise_not_found=True, default=None, case_insensitive=True
|
323 |
+
)
|
324 |
+
if act
|
325 |
+
else intro or Conversation.intro
|
326 |
+
)
|
327 |
+
self.conversation = Conversation(
|
328 |
+
is_conversation, self.max_tokens_to_sample, filepath, update_file
|
329 |
+
)
|
330 |
+
self.conversation.history_offset = history_offset
|
331 |
+
self.system_prompt = (
|
332 |
+
"\n\nYour name is Leo, a helpful"
|
333 |
+
"respectful and honest AI assistant created by the company Brave. You will be replying to a user of the Brave browser. "
|
334 |
+
"Always respond in a neutral tone. Be polite and courteous. Answer concisely in no more than 50-80 words."
|
335 |
+
"\n\nPlease ensure that your responses are socially unbiased and positive in nature."
|
336 |
+
"If a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. "
|
337 |
+
"If you don't know the answer to a question, please don't share false information.\n"
|
338 |
+
)
|
339 |
+
self.session = httpx.AsyncClient(headers=self.headers, proxies=proxies)
|
340 |
+
|
341 |
+
async def ask(
|
342 |
+
self,
|
343 |
+
prompt: str,
|
344 |
+
stream: bool = False,
|
345 |
+
raw: bool = False,
|
346 |
+
optimizer: str = None,
|
347 |
+
conversationally: bool = False,
|
348 |
+
) -> dict | AsyncGenerator:
|
349 |
+
"""Chat with AI asynchronously.
|
350 |
+
|
351 |
+
Args:
|
352 |
+
prompt (str): Prompt to be send.
|
353 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
354 |
+
raw (bool, optional): Stream back raw response as received. Defaults to False.
|
355 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
356 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
357 |
+
Returns:
|
358 |
+
dict|AsyncGenerator : ai content
|
359 |
+
```json
|
360 |
+
{
|
361 |
+
"completion": "\nNext: domestic cat breeds with short hair >>",
|
362 |
+
"stop_reason": null,
|
363 |
+
"truncated": false,
|
364 |
+
"stop": null,
|
365 |
+
"model": "llama-2-13b-chat",
|
366 |
+
"log_id": "cmpl-3kYiYxSNDvgMShSzFooz6t",
|
367 |
+
"exception": null
|
368 |
+
}
|
369 |
+
```
|
370 |
+
"""
|
371 |
+
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
372 |
+
if optimizer:
|
373 |
+
if optimizer in self.__available_optimizers:
|
374 |
+
conversation_prompt = getattr(Optimizers, optimizer)(
|
375 |
+
conversation_prompt if conversationally else prompt
|
376 |
+
)
|
377 |
+
else:
|
378 |
+
raise Exception(
|
379 |
+
f"Optimizer is not one of {self.__available_optimizers}"
|
380 |
+
)
|
381 |
+
|
382 |
+
payload = {
|
383 |
+
"max_tokens_to_sample": self.max_tokens_to_sample,
|
384 |
+
"model": self.model,
|
385 |
+
"prompt": f"<s>[INST] <<SYS>>{self.system_prompt}<</SYS>>{conversation_prompt} [/INST]",
|
386 |
+
"self.stop_sequence": self.stop_sequences,
|
387 |
+
"stream": stream,
|
388 |
+
"top_k": self.top_k,
|
389 |
+
"top_p": self.top_p,
|
390 |
+
}
|
391 |
+
|
392 |
+
async def for_stream():
|
393 |
+
async with self.session.stream(
|
394 |
+
"POST", self.chat_endpoint, json=payload, timeout=self.timeout
|
395 |
+
) as response:
|
396 |
+
if (
|
397 |
+
not response.is_success
|
398 |
+
or not response.headers.get("Content-Type")
|
399 |
+
== "text/event-stream; charset=utf-8"
|
400 |
+
):
|
401 |
+
raise exceptions.FailedToGenerateResponseError(
|
402 |
+
f"Failed to generate response - ({response.status_code}, {response.reason_phrase})"
|
403 |
+
)
|
404 |
+
async for value in response.aiter_lines():
|
405 |
+
try:
|
406 |
+
resp = sanitize_stream(value)
|
407 |
+
self.last_response.update(resp)
|
408 |
+
yield value if raw else resp
|
409 |
+
except json.decoder.JSONDecodeError:
|
410 |
+
pass
|
411 |
+
|
412 |
+
self.conversation.update_chat_history(
|
413 |
+
prompt, await self.get_message(self.last_response)
|
414 |
+
)
|
415 |
+
|
416 |
+
async def for_non_stream():
|
417 |
+
async for _ in for_stream():
|
418 |
+
pass
|
419 |
+
return self.last_response
|
420 |
+
|
421 |
+
return for_stream() if stream else await for_non_stream()
|
422 |
+
|
423 |
+
async def chat(
|
424 |
+
self,
|
425 |
+
prompt: str,
|
426 |
+
stream: bool = False,
|
427 |
+
optimizer: str = None,
|
428 |
+
conversationally: bool = False,
|
429 |
+
) -> str | AsyncGenerator:
|
430 |
+
"""Generate response `str` asynchronously.
|
431 |
+
Args:
|
432 |
+
prompt (str): Prompt to be send.
|
433 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
434 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
435 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
436 |
+
Returns:
|
437 |
+
str|AsyncGenerator: Response generated
|
438 |
+
"""
|
439 |
+
|
440 |
+
async def for_stream():
|
441 |
+
async_ask = await self.ask(
|
442 |
+
prompt, True, optimizer=optimizer, conversationally=conversationally
|
443 |
+
)
|
444 |
+
async for response in async_ask:
|
445 |
+
yield await self.get_message(response)
|
446 |
+
|
447 |
+
async def for_non_stream():
|
448 |
+
return await self.get_message(
|
449 |
+
await self.ask(
|
450 |
+
prompt,
|
451 |
+
False,
|
452 |
+
optimizer=optimizer,
|
453 |
+
conversationally=conversationally,
|
454 |
+
)
|
455 |
+
)
|
456 |
+
|
457 |
+
return for_stream() if stream else await for_non_stream()
|
458 |
+
|
459 |
+
async def get_message(self, response: dict) -> str:
|
460 |
+
"""Retrieves message only from response
|
461 |
+
|
462 |
+
Args:
|
463 |
+
response (dict): Response generated by `self.ask`
|
464 |
+
|
465 |
+
Returns:
|
466 |
+
str: Message extracted
|
467 |
+
"""
|
468 |
+
assert isinstance(response, dict), "Response should be of dict data-type only"
|
469 |
+
return response.get("completion")
|
webscout/Provider/Llama2.py
ADDED
@@ -0,0 +1,437 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
import uuid
|
3 |
+
from selenium import webdriver
|
4 |
+
from selenium.webdriver.chrome.options import Options
|
5 |
+
from selenium.webdriver.common.by import By
|
6 |
+
from selenium.webdriver.support import expected_conditions as EC
|
7 |
+
from selenium.webdriver.support.ui import WebDriverWait
|
8 |
+
import click
|
9 |
+
import requests
|
10 |
+
from requests import get
|
11 |
+
from uuid import uuid4
|
12 |
+
from re import findall
|
13 |
+
from requests.exceptions import RequestException
|
14 |
+
from curl_cffi.requests import get, RequestsError
|
15 |
+
import g4f
|
16 |
+
from random import randint
|
17 |
+
from PIL import Image
|
18 |
+
import io
|
19 |
+
import re
|
20 |
+
import json
|
21 |
+
import yaml
|
22 |
+
from ..AIutel import Optimizers
|
23 |
+
from ..AIutel import Conversation
|
24 |
+
from ..AIutel import AwesomePrompts, sanitize_stream
|
25 |
+
from ..AIbase import Provider, AsyncProvider
|
26 |
+
from Helpingai_T2 import Perplexity
|
27 |
+
from webscout import exceptions
|
28 |
+
from typing import Any, AsyncGenerator, Dict
|
29 |
+
import logging
|
30 |
+
import httpx
|
31 |
+
|
32 |
+
class AsyncLLAMA2(AsyncProvider):
|
33 |
+
def __init__(
|
34 |
+
self,
|
35 |
+
is_conversation: bool = True,
|
36 |
+
max_tokens: int = 800,
|
37 |
+
temperature: float = 0.75,
|
38 |
+
presence_penalty: int = 0,
|
39 |
+
frequency_penalty: int = 0,
|
40 |
+
top_p: float = 0.9,
|
41 |
+
model: str = "meta/meta-llama-3-70b-instruct",
|
42 |
+
timeout: int = 30,
|
43 |
+
intro: str = None,
|
44 |
+
filepath: str = None,
|
45 |
+
update_file: bool = True,
|
46 |
+
proxies: dict = {},
|
47 |
+
history_offset: int = 10250,
|
48 |
+
act: str = None,
|
49 |
+
):
|
50 |
+
"""Instantiates LLAMA2
|
51 |
+
|
52 |
+
Args:
|
53 |
+
is_conversation (bool, optional): Flag for chatting conversationally. Defaults to True.
|
54 |
+
max_tokens (int, optional): Maximum number of tokens to be generated upon completion. Defaults to 800.
|
55 |
+
temperature (float, optional): Charge of the generated text's randomness. Defaults to 0.75.
|
56 |
+
presence_penalty (int, optional): Chances of topic being repeated. Defaults to 0.
|
57 |
+
frequency_penalty (int, optional): Chances of word being repeated. Defaults to 0.
|
58 |
+
top_p (float, optional): Sampling threshold during inference time. Defaults to 0.9.
|
59 |
+
model (str, optional): LLM model name. Defaults to "meta/llama-2-70b-chat".
|
60 |
+
timeout (int, optional): Http request timeout. Defaults to 30.
|
61 |
+
intro (str, optional): Conversation introductory prompt. Defaults to None.
|
62 |
+
filepath (str, optional): Path to file containing conversation history. Defaults to None.
|
63 |
+
update_file (bool, optional): Add new prompts and responses to the file. Defaults to True.
|
64 |
+
proxies (dict, optional): Http request proxies. Defaults to {}.
|
65 |
+
history_offset (int, optional): Limit conversation history to this number of last texts. Defaults to 10250.
|
66 |
+
act (str|int, optional): Awesome prompt key or index. (Used as intro). Defaults to None.
|
67 |
+
"""
|
68 |
+
self.is_conversation = is_conversation
|
69 |
+
self.max_tokens_to_sample = max_tokens
|
70 |
+
self.model = model
|
71 |
+
self.temperature = temperature
|
72 |
+
self.presence_penalty = presence_penalty
|
73 |
+
self.frequency_penalty = frequency_penalty
|
74 |
+
self.top_p = top_p
|
75 |
+
self.chat_endpoint = "https://www.llama2.ai/api"
|
76 |
+
self.stream_chunk_size = 64
|
77 |
+
self.timeout = timeout
|
78 |
+
self.last_response = {}
|
79 |
+
self.headers = {
|
80 |
+
"Content-Type": "application/json",
|
81 |
+
"Referer": "https://www.llama2.ai/",
|
82 |
+
"Content-Type": "text/plain;charset=UTF-8",
|
83 |
+
"Origin": "https://www.llama2.ai",
|
84 |
+
}
|
85 |
+
|
86 |
+
self.__available_optimizers = (
|
87 |
+
method
|
88 |
+
for method in dir(Optimizers)
|
89 |
+
if callable(getattr(Optimizers, method)) and not method.startswith("__")
|
90 |
+
)
|
91 |
+
Conversation.intro = (
|
92 |
+
AwesomePrompts().get_act(
|
93 |
+
act, raise_not_found=True, default=None, case_insensitive=True
|
94 |
+
)
|
95 |
+
if act
|
96 |
+
else intro or Conversation.intro
|
97 |
+
)
|
98 |
+
self.conversation = Conversation(
|
99 |
+
is_conversation, self.max_tokens_to_sample, filepath, update_file
|
100 |
+
)
|
101 |
+
self.conversation.history_offset = history_offset
|
102 |
+
self.session = httpx.AsyncClient(
|
103 |
+
headers=self.headers,
|
104 |
+
proxies=proxies,
|
105 |
+
)
|
106 |
+
|
107 |
+
async def ask(
|
108 |
+
self,
|
109 |
+
prompt: str,
|
110 |
+
stream: bool = False,
|
111 |
+
raw: bool = False,
|
112 |
+
optimizer: str = None,
|
113 |
+
conversationally: bool = False,
|
114 |
+
) -> dict | AsyncGenerator:
|
115 |
+
"""Chat with AI asynchronously.
|
116 |
+
|
117 |
+
Args:
|
118 |
+
prompt (str): Prompt to be send.
|
119 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
120 |
+
raw (bool, optional): Stream back raw response as received. Defaults to False.
|
121 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
122 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
123 |
+
Returns:
|
124 |
+
dict|AsyncGeneraror[dict] : ai content
|
125 |
+
```json
|
126 |
+
{
|
127 |
+
"text" : "How may I help you today?"
|
128 |
+
}
|
129 |
+
```
|
130 |
+
"""
|
131 |
+
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
132 |
+
if optimizer:
|
133 |
+
if optimizer in self.__available_optimizers:
|
134 |
+
conversation_prompt = getattr(Optimizers, optimizer)(
|
135 |
+
conversation_prompt if conversationally else prompt
|
136 |
+
)
|
137 |
+
else:
|
138 |
+
raise Exception(
|
139 |
+
f"Optimizer is not one of {self.__available_optimizers}"
|
140 |
+
)
|
141 |
+
|
142 |
+
payload = {
|
143 |
+
"prompt": f"{conversation_prompt}<s>[INST] {prompt} [/INST]",
|
144 |
+
"model": self.model,
|
145 |
+
"systemPrompt": "You are a helpful assistant.",
|
146 |
+
"temperature": self.temperature,
|
147 |
+
"topP": self.top_p,
|
148 |
+
"maxTokens": self.max_tokens_to_sample,
|
149 |
+
"image": None,
|
150 |
+
"audio": None,
|
151 |
+
}
|
152 |
+
|
153 |
+
async def for_stream():
|
154 |
+
async with self.session.stream(
|
155 |
+
"POST", self.chat_endpoint, json=payload, timeout=self.timeout
|
156 |
+
) as response:
|
157 |
+
if (
|
158 |
+
not response.is_success
|
159 |
+
or not response.headers.get("Content-Type")
|
160 |
+
== "text/plain; charset=utf-8"
|
161 |
+
):
|
162 |
+
raise exceptions.FailedToGenerateResponseError(
|
163 |
+
f"Failed to generate response - ({response.status_code}, {response.reason_phrase})"
|
164 |
+
)
|
165 |
+
message_load: str = ""
|
166 |
+
async for value in response.aiter_lines():
|
167 |
+
try:
|
168 |
+
if bool(value.strip()):
|
169 |
+
message_load += value + "\n"
|
170 |
+
resp: dict = dict(text=message_load)
|
171 |
+
yield value if raw else resp
|
172 |
+
self.last_response.update(resp)
|
173 |
+
except json.decoder.JSONDecodeError:
|
174 |
+
pass
|
175 |
+
self.conversation.update_chat_history(
|
176 |
+
prompt, await self.get_message(self.last_response)
|
177 |
+
)
|
178 |
+
|
179 |
+
async def for_non_stream():
|
180 |
+
async for _ in for_stream():
|
181 |
+
pass
|
182 |
+
return self.last_response
|
183 |
+
|
184 |
+
return for_stream() if stream else await for_non_stream()
|
185 |
+
|
186 |
+
async def chat(
|
187 |
+
self,
|
188 |
+
prompt: str,
|
189 |
+
stream: bool = False,
|
190 |
+
optimizer: str = None,
|
191 |
+
conversationally: bool = False,
|
192 |
+
) -> str | AsyncGenerator:
|
193 |
+
"""Generate response `str` asynchronously.
|
194 |
+
Args:
|
195 |
+
prompt (str): Prompt to be send.
|
196 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
197 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
198 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
199 |
+
Returns:
|
200 |
+
str|AsyncGenerator: Response generated
|
201 |
+
"""
|
202 |
+
|
203 |
+
async def for_stream():
|
204 |
+
async_ask = await self.ask(
|
205 |
+
prompt, True, optimizer=optimizer, conversationally=conversationally
|
206 |
+
)
|
207 |
+
async for response in async_ask:
|
208 |
+
yield await self.get_message(response)
|
209 |
+
|
210 |
+
async def for_non_stream():
|
211 |
+
return await self.get_message(
|
212 |
+
await self.ask(
|
213 |
+
prompt,
|
214 |
+
False,
|
215 |
+
optimizer=optimizer,
|
216 |
+
conversationally=conversationally,
|
217 |
+
)
|
218 |
+
)
|
219 |
+
|
220 |
+
return for_stream() if stream else await for_non_stream()
|
221 |
+
|
222 |
+
async def get_message(self, response: dict) -> str:
|
223 |
+
"""Retrieves message only from response
|
224 |
+
|
225 |
+
Args:
|
226 |
+
response (str): Response generated by `self.ask`
|
227 |
+
|
228 |
+
Returns:
|
229 |
+
str: Message extracted
|
230 |
+
"""
|
231 |
+
assert isinstance(response, dict), "Response should be of dict data-type only"
|
232 |
+
return response["text"]
|
233 |
+
class LLAMA2(Provider):
|
234 |
+
def __init__(
|
235 |
+
self,
|
236 |
+
is_conversation: bool = True,
|
237 |
+
max_tokens: int = 800,
|
238 |
+
temperature: float = 0.75,
|
239 |
+
presence_penalty: int = 0,
|
240 |
+
frequency_penalty: int = 0,
|
241 |
+
top_p: float = 0.9,
|
242 |
+
model: str = "meta/meta-llama-3-70b-instruct",
|
243 |
+
timeout: int = 30,
|
244 |
+
intro: str = None,
|
245 |
+
filepath: str = None,
|
246 |
+
update_file: bool = True,
|
247 |
+
proxies: dict = {},
|
248 |
+
history_offset: int = 10250,
|
249 |
+
act: str = None,
|
250 |
+
):
|
251 |
+
"""Instantiates LLAMA2
|
252 |
+
|
253 |
+
Args:
|
254 |
+
is_conversation (bool, optional): Flag for chatting conversationally. Defaults to True.
|
255 |
+
max_tokens (int, optional): Maximum number of tokens to be generated upon completion. Defaults to 800.
|
256 |
+
temperature (float, optional): Charge of the generated text's randomness. Defaults to 0.75.
|
257 |
+
presence_penalty (int, optional): Chances of topic being repeated. Defaults to 0.
|
258 |
+
frequency_penalty (int, optional): Chances of word being repeated. Defaults to 0.
|
259 |
+
top_p (float, optional): Sampling threshold during inference time. Defaults to 0.9.
|
260 |
+
model (str, optional): LLM model name. Defaults to "meta/llama-2-70b-chat".
|
261 |
+
timeout (int, optional): Http request timeout. Defaults to 30.
|
262 |
+
intro (str, optional): Conversation introductory prompt. Defaults to None.
|
263 |
+
filepath (str, optional): Path to file containing conversation history. Defaults to None.
|
264 |
+
update_file (bool, optional): Add new prompts and responses to the file. Defaults to True.
|
265 |
+
proxies (dict, optional): Http request proxies. Defaults to {}.
|
266 |
+
history_offset (int, optional): Limit conversation history to this number of last texts. Defaults to 10250.
|
267 |
+
act (str|int, optional): Awesome prompt key or index. (Used as intro). Defaults to None.
|
268 |
+
"""
|
269 |
+
self.session = requests.Session()
|
270 |
+
self.is_conversation = is_conversation
|
271 |
+
self.max_tokens_to_sample = max_tokens
|
272 |
+
self.model = model
|
273 |
+
self.temperature = temperature
|
274 |
+
self.presence_penalty = presence_penalty
|
275 |
+
self.frequency_penalty = frequency_penalty
|
276 |
+
self.top_p = top_p
|
277 |
+
self.chat_endpoint = "https://www.llama2.ai/api"
|
278 |
+
self.stream_chunk_size = 64
|
279 |
+
self.timeout = timeout
|
280 |
+
self.last_response = {}
|
281 |
+
self.headers = {
|
282 |
+
"Content-Type": "application/json",
|
283 |
+
"Referer": "https://www.llama2.ai/",
|
284 |
+
"Content-Type": "text/plain;charset=UTF-8",
|
285 |
+
"Origin": "https://www.llama2.ai",
|
286 |
+
}
|
287 |
+
|
288 |
+
self.__available_optimizers = (
|
289 |
+
method
|
290 |
+
for method in dir(Optimizers)
|
291 |
+
if callable(getattr(Optimizers, method)) and not method.startswith("__")
|
292 |
+
)
|
293 |
+
self.session.headers.update(self.headers)
|
294 |
+
Conversation.intro = (
|
295 |
+
AwesomePrompts().get_act(
|
296 |
+
act, raise_not_found=True, default=None, case_insensitive=True
|
297 |
+
)
|
298 |
+
if act
|
299 |
+
else intro or Conversation.intro
|
300 |
+
)
|
301 |
+
self.conversation = Conversation(
|
302 |
+
is_conversation, self.max_tokens_to_sample, filepath, update_file
|
303 |
+
)
|
304 |
+
self.conversation.history_offset = history_offset
|
305 |
+
self.session.proxies = proxies
|
306 |
+
|
307 |
+
def ask(
|
308 |
+
self,
|
309 |
+
prompt: str,
|
310 |
+
stream: bool = False,
|
311 |
+
raw: bool = False,
|
312 |
+
optimizer: str = None,
|
313 |
+
conversationally: bool = False,
|
314 |
+
) -> dict:
|
315 |
+
"""Chat with AI
|
316 |
+
|
317 |
+
Args:
|
318 |
+
prompt (str): Prompt to be send.
|
319 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
320 |
+
raw (bool, optional): Stream back raw response as received. Defaults to False.
|
321 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
322 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
323 |
+
Returns:
|
324 |
+
dict : {}
|
325 |
+
```json
|
326 |
+
{
|
327 |
+
"text" : "How may I help you today?"
|
328 |
+
}
|
329 |
+
```
|
330 |
+
"""
|
331 |
+
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
332 |
+
if optimizer:
|
333 |
+
if optimizer in self.__available_optimizers:
|
334 |
+
conversation_prompt = getattr(Optimizers, optimizer)(
|
335 |
+
conversation_prompt if conversationally else prompt
|
336 |
+
)
|
337 |
+
else:
|
338 |
+
raise Exception(
|
339 |
+
f"Optimizer is not one of {self.__available_optimizers}"
|
340 |
+
)
|
341 |
+
self.session.headers.update(self.headers)
|
342 |
+
|
343 |
+
payload = {
|
344 |
+
"prompt": f"{conversation_prompt}<s>[INST] {prompt} [/INST]",
|
345 |
+
"model": self.model,
|
346 |
+
"systemPrompt": "You are a helpful assistant.",
|
347 |
+
"temperature": self.temperature,
|
348 |
+
"topP": self.top_p,
|
349 |
+
"maxTokens": self.max_tokens_to_sample,
|
350 |
+
"image": None,
|
351 |
+
"audio": None,
|
352 |
+
}
|
353 |
+
|
354 |
+
def for_stream():
|
355 |
+
response = self.session.post(
|
356 |
+
self.chat_endpoint, json=payload, stream=True, timeout=self.timeout
|
357 |
+
)
|
358 |
+
if (
|
359 |
+
not response.ok
|
360 |
+
or not response.headers.get("Content-Type")
|
361 |
+
== "text/plain; charset=utf-8"
|
362 |
+
):
|
363 |
+
raise exceptions.FailedToGenerateResponseError(
|
364 |
+
f"Failed to generate response - ({response.status_code}, {response.reason})"
|
365 |
+
)
|
366 |
+
|
367 |
+
message_load: str = ""
|
368 |
+
for value in response.iter_lines(
|
369 |
+
decode_unicode=True,
|
370 |
+
delimiter="\n",
|
371 |
+
chunk_size=self.stream_chunk_size,
|
372 |
+
):
|
373 |
+
try:
|
374 |
+
if bool(value.strip()):
|
375 |
+
message_load += value + "\n"
|
376 |
+
resp: dict = dict(text=message_load)
|
377 |
+
yield value if raw else resp
|
378 |
+
self.last_response.update(resp)
|
379 |
+
except json.decoder.JSONDecodeError:
|
380 |
+
pass
|
381 |
+
self.conversation.update_chat_history(
|
382 |
+
prompt, self.get_message(self.last_response)
|
383 |
+
)
|
384 |
+
|
385 |
+
def for_non_stream():
|
386 |
+
for _ in for_stream():
|
387 |
+
pass
|
388 |
+
return self.last_response
|
389 |
+
|
390 |
+
return for_stream() if stream else for_non_stream()
|
391 |
+
|
392 |
+
def chat(
|
393 |
+
self,
|
394 |
+
prompt: str,
|
395 |
+
stream: bool = False,
|
396 |
+
optimizer: str = None,
|
397 |
+
conversationally: bool = False,
|
398 |
+
) -> str:
|
399 |
+
"""Generate response `str`
|
400 |
+
Args:
|
401 |
+
prompt (str): Prompt to be send.
|
402 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
403 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
404 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
405 |
+
Returns:
|
406 |
+
str: Response generated
|
407 |
+
"""
|
408 |
+
|
409 |
+
def for_stream():
|
410 |
+
for response in self.ask(
|
411 |
+
prompt, True, optimizer=optimizer, conversationally=conversationally
|
412 |
+
):
|
413 |
+
yield self.get_message(response)
|
414 |
+
|
415 |
+
def for_non_stream():
|
416 |
+
return self.get_message(
|
417 |
+
self.ask(
|
418 |
+
prompt,
|
419 |
+
False,
|
420 |
+
optimizer=optimizer,
|
421 |
+
conversationally=conversationally,
|
422 |
+
)
|
423 |
+
)
|
424 |
+
|
425 |
+
return for_stream() if stream else for_non_stream()
|
426 |
+
|
427 |
+
def get_message(self, response: dict) -> str:
|
428 |
+
"""Retrieves message only from response
|
429 |
+
|
430 |
+
Args:
|
431 |
+
response (str): Response generated by `self.ask`
|
432 |
+
|
433 |
+
Returns:
|
434 |
+
str: Message extracted
|
435 |
+
"""
|
436 |
+
assert isinstance(response, dict), "Response should be of dict data-type only"
|
437 |
+
return response["text"]
|
webscout/Provider/OpenGPT.py
ADDED
@@ -0,0 +1,487 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
import uuid
|
3 |
+
from selenium import webdriver
|
4 |
+
from selenium.webdriver.chrome.options import Options
|
5 |
+
from selenium.webdriver.common.by import By
|
6 |
+
from selenium.webdriver.support import expected_conditions as EC
|
7 |
+
from selenium.webdriver.support.ui import WebDriverWait
|
8 |
+
import click
|
9 |
+
import requests
|
10 |
+
from requests import get
|
11 |
+
from uuid import uuid4
|
12 |
+
from re import findall
|
13 |
+
from requests.exceptions import RequestException
|
14 |
+
from curl_cffi.requests import get, RequestsError
|
15 |
+
import g4f
|
16 |
+
from random import randint
|
17 |
+
from PIL import Image
|
18 |
+
import io
|
19 |
+
import re
|
20 |
+
import json
|
21 |
+
import yaml
|
22 |
+
from ..AIutel import Optimizers
|
23 |
+
from ..AIutel import Conversation
|
24 |
+
from ..AIutel import AwesomePrompts, sanitize_stream
|
25 |
+
from ..AIbase import Provider, AsyncProvider
|
26 |
+
from Helpingai_T2 import Perplexity
|
27 |
+
from webscout import exceptions
|
28 |
+
from typing import Any, AsyncGenerator, Dict
|
29 |
+
import logging
|
30 |
+
import httpx
|
31 |
+
#------------------------------------------------------OpenGPT-----------------------------------------------------------
|
32 |
+
class OPENGPT:
|
33 |
+
def __init__(
|
34 |
+
self,
|
35 |
+
assistant_id,
|
36 |
+
is_conversation: bool = True,
|
37 |
+
max_tokens: int = 600,
|
38 |
+
timeout: int = 30,
|
39 |
+
intro: str = None,
|
40 |
+
filepath: str = None,
|
41 |
+
update_file: bool = True,
|
42 |
+
proxies: dict = {},
|
43 |
+
history_offset: int = 10250,
|
44 |
+
act: str = None,
|
45 |
+
):
|
46 |
+
"""Instantiates OPENGPT
|
47 |
+
|
48 |
+
Args:
|
49 |
+
is_conversation (bool, optional): Flag for chatting conversationally. Defaults to True
|
50 |
+
max_tokens (int, optional): Maximum number of tokens to be generated upon completion. Defaults to 600.
|
51 |
+
timeout (int, optional): Http request timeout. Defaults to 30.
|
52 |
+
intro (str, optional): Conversation introductory prompt. Defaults to None.
|
53 |
+
filepath (str, optional): Path to file containing conversation history. Defaults to None.
|
54 |
+
update_file (bool, optional): Add new prompts and responses to the file. Defaults to True.
|
55 |
+
proxies (dict, optional): Http request proxies. Defaults to {}.
|
56 |
+
history_offset (int, optional): Limit conversation history to this number of last texts. Defaults to 10250.
|
57 |
+
act (str|int, optional): Awesome prompt key or index. (Used as intro). Defaults to None.
|
58 |
+
"""
|
59 |
+
self.session = requests.Session()
|
60 |
+
self.max_tokens_to_sample = max_tokens
|
61 |
+
self.is_conversation = is_conversation
|
62 |
+
self.chat_endpoint = (
|
63 |
+
"https://opengpts-example-vz4y4ooboq-uc.a.run.app/runs/stream"
|
64 |
+
)
|
65 |
+
self.stream_chunk_size = 64
|
66 |
+
self.timeout = timeout
|
67 |
+
self.last_response = {}
|
68 |
+
self.assistant_id = assistant_id
|
69 |
+
self.authority = "opengpts-example-vz4y4ooboq-uc.a.run.app"
|
70 |
+
|
71 |
+
self.headers = {
|
72 |
+
"authority": self.authority,
|
73 |
+
"accept": "text/event-stream",
|
74 |
+
"accept-language": "en-US,en;q=0.7",
|
75 |
+
"cache-control": "no-cache",
|
76 |
+
"content-type": "application/json",
|
77 |
+
"origin": "https://opengpts-example-vz4y4ooboq-uc.a.run.app",
|
78 |
+
"pragma": "no-cache",
|
79 |
+
"referer": "https://opengpts-example-vz4y4ooboq-uc.a.run.app/",
|
80 |
+
"sec-fetch-site": "same-origin",
|
81 |
+
"sec-gpc": "1",
|
82 |
+
"user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
83 |
+
}
|
84 |
+
|
85 |
+
self.__available_optimizers = (
|
86 |
+
method
|
87 |
+
for method in dir(Optimizers)
|
88 |
+
if callable(getattr(Optimizers, method)) and not method.startswith("__")
|
89 |
+
)
|
90 |
+
self.session.headers.update(self.headers)
|
91 |
+
Conversation.intro = (
|
92 |
+
AwesomePrompts().get_act(
|
93 |
+
act, raise_not_found=True, default=None, case_insensitive=True
|
94 |
+
)
|
95 |
+
if act
|
96 |
+
else intro or Conversation.intro
|
97 |
+
)
|
98 |
+
self.conversation = Conversation(
|
99 |
+
is_conversation, self.max_tokens_to_sample, filepath, update_file
|
100 |
+
)
|
101 |
+
self.conversation.history_offset = history_offset
|
102 |
+
self.session.proxies = proxies
|
103 |
+
|
104 |
+
def ask(
|
105 |
+
self,
|
106 |
+
prompt: str,
|
107 |
+
stream: bool = False,
|
108 |
+
raw: bool = False,
|
109 |
+
optimizer: str = None,
|
110 |
+
conversationally: bool = False,
|
111 |
+
) -> dict:
|
112 |
+
"""Chat with AI
|
113 |
+
|
114 |
+
Args:
|
115 |
+
prompt (str): Prompt to be send.
|
116 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
117 |
+
raw (bool, optional): Stream back raw response as received. Defaults to False.
|
118 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
119 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
120 |
+
Returns:
|
121 |
+
dict : {}
|
122 |
+
```json
|
123 |
+
{
|
124 |
+
"messages": [
|
125 |
+
{
|
126 |
+
"content": "Hello there",
|
127 |
+
"additional_kwargs": {},
|
128 |
+
"type": "human",
|
129 |
+
"example": false
|
130 |
+
},
|
131 |
+
{
|
132 |
+
"content": "Hello! How can I assist you today?",
|
133 |
+
"additional_kwargs": {
|
134 |
+
"agent": {
|
135 |
+
"return_values": {
|
136 |
+
"output": "Hello! How can I assist you today?"
|
137 |
+
},
|
138 |
+
"log": "Hello! How can I assist you today?",
|
139 |
+
"type": "AgentFinish"
|
140 |
+
}
|
141 |
+
},
|
142 |
+
"type": "ai",
|
143 |
+
"example": false
|
144 |
+
}]
|
145 |
+
}
|
146 |
+
```
|
147 |
+
"""
|
148 |
+
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
149 |
+
if optimizer:
|
150 |
+
if optimizer in self.__available_optimizers:
|
151 |
+
conversation_prompt = getattr(Optimizers, optimizer)(
|
152 |
+
conversation_prompt if conversationally else prompt
|
153 |
+
)
|
154 |
+
else:
|
155 |
+
raise Exception(
|
156 |
+
f"Optimizer is not one of {self.__available_optimizers}"
|
157 |
+
)
|
158 |
+
|
159 |
+
self.session.headers.update(self.headers)
|
160 |
+
self.session.headers.update(
|
161 |
+
dict(
|
162 |
+
cookie=f"opengpts_user_id={uuid4().__str__()}",
|
163 |
+
)
|
164 |
+
)
|
165 |
+
payload = {
|
166 |
+
"input": [
|
167 |
+
{
|
168 |
+
"content": conversation_prompt,
|
169 |
+
"additional_kwargs": {},
|
170 |
+
"type": "human",
|
171 |
+
"example": False,
|
172 |
+
},
|
173 |
+
],
|
174 |
+
"assistant_id": self.assistant_id,
|
175 |
+
"thread_id": "",
|
176 |
+
}
|
177 |
+
|
178 |
+
def for_stream():
|
179 |
+
response = self.session.post(
|
180 |
+
self.chat_endpoint, json=payload, stream=True, timeout=self.timeout
|
181 |
+
)
|
182 |
+
if (
|
183 |
+
not response.ok
|
184 |
+
or not response.headers.get("Content-Type")
|
185 |
+
== "text/event-stream; charset=utf-8"
|
186 |
+
):
|
187 |
+
raise Exception(
|
188 |
+
f"Failed to generate response - ({response.status_code}, {response.reason}) - {response.text}"
|
189 |
+
)
|
190 |
+
|
191 |
+
for value in response.iter_lines(
|
192 |
+
decode_unicode=True,
|
193 |
+
chunk_size=self.stream_chunk_size,
|
194 |
+
):
|
195 |
+
try:
|
196 |
+
modified_value = re.sub("data:", "", value)
|
197 |
+
resp = json.loads(modified_value)
|
198 |
+
if len(resp) == 1:
|
199 |
+
continue
|
200 |
+
self.last_response.update(resp[1])
|
201 |
+
yield value if raw else resp[1]
|
202 |
+
except json.decoder.JSONDecodeError:
|
203 |
+
pass
|
204 |
+
self.conversation.update_chat_history(
|
205 |
+
prompt, self.get_message(self.last_response)
|
206 |
+
)
|
207 |
+
|
208 |
+
def for_non_stream():
|
209 |
+
for _ in for_stream():
|
210 |
+
pass
|
211 |
+
return self.last_response
|
212 |
+
|
213 |
+
return for_stream() if stream else for_non_stream()
|
214 |
+
|
215 |
+
def chat(
|
216 |
+
self,
|
217 |
+
prompt: str,
|
218 |
+
stream: bool = False,
|
219 |
+
optimizer: str = None,
|
220 |
+
conversationally: bool = False,
|
221 |
+
) -> str:
|
222 |
+
"""Generate response `str`
|
223 |
+
Args:
|
224 |
+
prompt (str): Prompt to be send.
|
225 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
226 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
227 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
228 |
+
Returns:
|
229 |
+
str: Response generated
|
230 |
+
"""
|
231 |
+
|
232 |
+
def for_stream():
|
233 |
+
for response in self.ask(
|
234 |
+
prompt, True, optimizer=optimizer, conversationally=conversationally
|
235 |
+
):
|
236 |
+
yield self.get_message(response)
|
237 |
+
|
238 |
+
def for_non_stream():
|
239 |
+
return self.get_message(
|
240 |
+
self.ask(
|
241 |
+
prompt,
|
242 |
+
False,
|
243 |
+
optimizer=optimizer,
|
244 |
+
conversationally=conversationally,
|
245 |
+
)
|
246 |
+
)
|
247 |
+
|
248 |
+
return for_stream() if stream else for_non_stream()
|
249 |
+
|
250 |
+
def get_message(self, response: dict) -> str:
|
251 |
+
"""Retrieves message only from response
|
252 |
+
|
253 |
+
Args:
|
254 |
+
response (dict): Response generated by `self.ask`
|
255 |
+
|
256 |
+
Returns:
|
257 |
+
str: Message extracted
|
258 |
+
"""
|
259 |
+
assert isinstance(response, dict), "Response should be of dict data-type only"
|
260 |
+
return response["content"]
|
261 |
+
class AsyncOPENGPT(AsyncProvider):
|
262 |
+
def __init__(
|
263 |
+
self,
|
264 |
+
is_conversation: bool = True,
|
265 |
+
max_tokens: int = 600,
|
266 |
+
timeout: int = 30,
|
267 |
+
intro: str = None,
|
268 |
+
filepath: str = None,
|
269 |
+
update_file: bool = True,
|
270 |
+
proxies: dict = {},
|
271 |
+
history_offset: int = 10250,
|
272 |
+
act: str = None,
|
273 |
+
):
|
274 |
+
"""Instantiates OPENGPT
|
275 |
+
|
276 |
+
Args:
|
277 |
+
is_conversation (bool, optional): Flag for chatting conversationally. Defaults to True
|
278 |
+
max_tokens (int, optional): Maximum number of tokens to be generated upon completion. Defaults to 600.
|
279 |
+
timeout (int, optional): Http request timeout. Defaults to 30.
|
280 |
+
intro (str, optional): Conversation introductory prompt. Defaults to None.
|
281 |
+
filepath (str, optional): Path to file containing conversation history. Defaults to None.
|
282 |
+
update_file (bool, optional): Add new prompts and responses to the file. Defaults to True.
|
283 |
+
proxies (dict, optional): Http request proxies. Defaults to {}.
|
284 |
+
history_offset (int, optional): Limit conversation history to this number of last texts. Defaults to 10250.
|
285 |
+
act (str|int, optional): Awesome prompt key or index. (Used as intro). Defaults to None.
|
286 |
+
"""
|
287 |
+
self.max_tokens_to_sample = max_tokens
|
288 |
+
self.is_conversation = is_conversation
|
289 |
+
self.chat_endpoint = (
|
290 |
+
"https://opengpts-example-vz4y4ooboq-uc.a.run.app/runs/stream"
|
291 |
+
)
|
292 |
+
self.stream_chunk_size = 64
|
293 |
+
self.timeout = timeout
|
294 |
+
self.last_response = {}
|
295 |
+
self.assistant_id = "bca37014-6f97-4f2b-8928-81ea8d478d88"
|
296 |
+
self.authority = "opengpts-example-vz4y4ooboq-uc.a.run.app"
|
297 |
+
|
298 |
+
self.headers = {
|
299 |
+
"authority": self.authority,
|
300 |
+
"accept": "text/event-stream",
|
301 |
+
"accept-language": "en-US,en;q=0.7",
|
302 |
+
"cache-control": "no-cache",
|
303 |
+
"content-type": "application/json",
|
304 |
+
"origin": "https://opengpts-example-vz4y4ooboq-uc.a.run.app",
|
305 |
+
"pragma": "no-cache",
|
306 |
+
"referer": "https://opengpts-example-vz4y4ooboq-uc.a.run.app/",
|
307 |
+
"sec-fetch-site": "same-origin",
|
308 |
+
"sec-gpc": "1",
|
309 |
+
"user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
310 |
+
}
|
311 |
+
|
312 |
+
self.__available_optimizers = (
|
313 |
+
method
|
314 |
+
for method in dir(Optimizers)
|
315 |
+
if callable(getattr(Optimizers, method)) and not method.startswith("__")
|
316 |
+
)
|
317 |
+
Conversation.intro = (
|
318 |
+
AwesomePrompts().get_act(
|
319 |
+
act, raise_not_found=True, default=None, case_insensitive=True
|
320 |
+
)
|
321 |
+
if act
|
322 |
+
else intro or Conversation.intro
|
323 |
+
)
|
324 |
+
self.conversation = Conversation(
|
325 |
+
is_conversation, self.max_tokens_to_sample, filepath, update_file
|
326 |
+
)
|
327 |
+
self.conversation.history_offset = history_offset
|
328 |
+
self.session = httpx.AsyncClient(headers=self.headers, proxies=proxies)
|
329 |
+
|
330 |
+
async def ask(
|
331 |
+
self,
|
332 |
+
prompt: str,
|
333 |
+
stream: bool = False,
|
334 |
+
raw: bool = False,
|
335 |
+
optimizer: str = None,
|
336 |
+
conversationally: bool = False,
|
337 |
+
) -> dict | AsyncGenerator:
|
338 |
+
"""Chat with AI asynchronously
|
339 |
+
|
340 |
+
Args:
|
341 |
+
prompt (str): Prompt to be send.
|
342 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
343 |
+
raw (bool, optional): Stream back raw response as received. Defaults to False.
|
344 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
345 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
346 |
+
Returns:
|
347 |
+
dict|AsyncGenerator : ai content.
|
348 |
+
```json
|
349 |
+
{
|
350 |
+
"messages": [
|
351 |
+
{
|
352 |
+
"content": "Hello there",
|
353 |
+
"additional_kwargs": {},
|
354 |
+
"type": "human",
|
355 |
+
"example": false
|
356 |
+
},
|
357 |
+
{
|
358 |
+
"content": "Hello! How can I assist you today?",
|
359 |
+
"additional_kwargs": {
|
360 |
+
"agent": {
|
361 |
+
"return_values": {
|
362 |
+
"output": "Hello! How can I assist you today?"
|
363 |
+
},
|
364 |
+
"log": "Hello! How can I assist you today?",
|
365 |
+
"type": "AgentFinish"
|
366 |
+
}
|
367 |
+
},
|
368 |
+
"type": "ai",
|
369 |
+
"example": false
|
370 |
+
}]
|
371 |
+
}
|
372 |
+
```
|
373 |
+
"""
|
374 |
+
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
375 |
+
if optimizer:
|
376 |
+
if optimizer in self.__available_optimizers:
|
377 |
+
conversation_prompt = getattr(Optimizers, optimizer)(
|
378 |
+
conversation_prompt if conversationally else prompt
|
379 |
+
)
|
380 |
+
else:
|
381 |
+
raise Exception(
|
382 |
+
f"Optimizer is not one of {self.__available_optimizers}"
|
383 |
+
)
|
384 |
+
self.headers.update(
|
385 |
+
dict(
|
386 |
+
cookie=f"opengpts_user_id={uuid4().__str__()}",
|
387 |
+
)
|
388 |
+
)
|
389 |
+
payload = {
|
390 |
+
"input": [
|
391 |
+
{
|
392 |
+
"content": conversation_prompt,
|
393 |
+
"additional_kwargs": {},
|
394 |
+
"type": "human",
|
395 |
+
"example": False,
|
396 |
+
},
|
397 |
+
],
|
398 |
+
"assistant_id": self.assistant_id,
|
399 |
+
"thread_id": "",
|
400 |
+
}
|
401 |
+
|
402 |
+
async def for_stream():
|
403 |
+
async with self.session.stream(
|
404 |
+
"POST",
|
405 |
+
self.chat_endpoint,
|
406 |
+
json=payload,
|
407 |
+
timeout=self.timeout,
|
408 |
+
headers=self.headers,
|
409 |
+
) as response:
|
410 |
+
if (
|
411 |
+
not response.is_success
|
412 |
+
or not response.headers.get("Content-Type")
|
413 |
+
== "text/event-stream; charset=utf-8"
|
414 |
+
):
|
415 |
+
raise exceptions.FailedToGenerateResponseError(
|
416 |
+
f"Failed to generate response - ({response.status_code}, {response.reason_phrase}) - {response.text}"
|
417 |
+
)
|
418 |
+
|
419 |
+
async for value in response.aiter_lines():
|
420 |
+
try:
|
421 |
+
modified_value = re.sub("data:", "", value)
|
422 |
+
resp = json.loads(modified_value)
|
423 |
+
if len(resp) == 1:
|
424 |
+
continue
|
425 |
+
self.last_response.update(resp[1])
|
426 |
+
yield value if raw else resp[1]
|
427 |
+
except json.decoder.JSONDecodeError:
|
428 |
+
pass
|
429 |
+
|
430 |
+
self.conversation.update_chat_history(
|
431 |
+
prompt, await self.get_message(self.last_response)
|
432 |
+
)
|
433 |
+
|
434 |
+
async def for_non_stream():
|
435 |
+
async for _ in for_stream():
|
436 |
+
pass
|
437 |
+
return self.last_response
|
438 |
+
|
439 |
+
return for_stream() if stream else await for_non_stream()
|
440 |
+
|
441 |
+
async def chat(
|
442 |
+
self,
|
443 |
+
prompt: str,
|
444 |
+
stream: bool = False,
|
445 |
+
optimizer: str = None,
|
446 |
+
conversationally: bool = False,
|
447 |
+
) -> str | AsyncGenerator:
|
448 |
+
"""Generate response `str` asynchronously.
|
449 |
+
Args:
|
450 |
+
prompt (str): Prompt to be send.
|
451 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
452 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
453 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
454 |
+
Returns:
|
455 |
+
str|AsyncGenerator: Response generated
|
456 |
+
"""
|
457 |
+
|
458 |
+
async def for_stream():
|
459 |
+
async_ask = await self.ask(
|
460 |
+
prompt, True, optimizer=optimizer, conversationally=conversationally
|
461 |
+
)
|
462 |
+
async for response in async_ask:
|
463 |
+
yield await self.get_message(response)
|
464 |
+
|
465 |
+
async def for_non_stream():
|
466 |
+
return await self.get_message(
|
467 |
+
await self.ask(
|
468 |
+
prompt,
|
469 |
+
False,
|
470 |
+
optimizer=optimizer,
|
471 |
+
conversationally=conversationally,
|
472 |
+
)
|
473 |
+
)
|
474 |
+
|
475 |
+
return for_stream() if stream else await for_non_stream()
|
476 |
+
|
477 |
+
async def get_message(self, response: dict) -> str:
|
478 |
+
"""Retrieves message only from response
|
479 |
+
|
480 |
+
Args:
|
481 |
+
response (dict): Response generated by `self.ask`
|
482 |
+
|
483 |
+
Returns:
|
484 |
+
str: Message extracted
|
485 |
+
"""
|
486 |
+
assert isinstance(response, dict), "Response should be of dict data-type only"
|
487 |
+
return response["content"]
|
webscout/Provider/Openai.py
ADDED
@@ -0,0 +1,511 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
import uuid
|
3 |
+
from selenium import webdriver
|
4 |
+
from selenium.webdriver.chrome.options import Options
|
5 |
+
from selenium.webdriver.common.by import By
|
6 |
+
from selenium.webdriver.support import expected_conditions as EC
|
7 |
+
from selenium.webdriver.support.ui import WebDriverWait
|
8 |
+
import click
|
9 |
+
import requests
|
10 |
+
from requests import get
|
11 |
+
from uuid import uuid4
|
12 |
+
from re import findall
|
13 |
+
from requests.exceptions import RequestException
|
14 |
+
from curl_cffi.requests import get, RequestsError
|
15 |
+
import g4f
|
16 |
+
from random import randint
|
17 |
+
from PIL import Image
|
18 |
+
import io
|
19 |
+
import re
|
20 |
+
import json
|
21 |
+
import yaml
|
22 |
+
from ..AIutel import Optimizers
|
23 |
+
from ..AIutel import Conversation
|
24 |
+
from ..AIutel import AwesomePrompts, sanitize_stream
|
25 |
+
from ..AIbase import Provider, AsyncProvider
|
26 |
+
from Helpingai_T2 import Perplexity
|
27 |
+
from webscout import exceptions
|
28 |
+
from typing import Any, AsyncGenerator, Dict
|
29 |
+
import logging
|
30 |
+
import httpx
|
31 |
+
#----------------------------------------------------------OpenAI-----------------------------------
|
32 |
+
class OPENAI(Provider):
|
33 |
+
def __init__(
|
34 |
+
self,
|
35 |
+
api_key: str,
|
36 |
+
is_conversation: bool = True,
|
37 |
+
max_tokens: int = 600,
|
38 |
+
temperature: float = 1,
|
39 |
+
presence_penalty: int = 0,
|
40 |
+
frequency_penalty: int = 0,
|
41 |
+
top_p: float = 1,
|
42 |
+
model: str = "gpt-3.5-turbo",
|
43 |
+
timeout: int = 30,
|
44 |
+
intro: str = None,
|
45 |
+
filepath: str = None,
|
46 |
+
update_file: bool = True,
|
47 |
+
proxies: dict = {},
|
48 |
+
history_offset: int = 10250,
|
49 |
+
act: str = None,
|
50 |
+
):
|
51 |
+
"""Instantiates OPENAI
|
52 |
+
|
53 |
+
Args:
|
54 |
+
api_key (key): OpenAI's API key.
|
55 |
+
is_conversation (bool, optional): Flag for chatting conversationally. Defaults to True.
|
56 |
+
max_tokens (int, optional): Maximum number of tokens to be generated upon completion. Defaults to 600.
|
57 |
+
temperature (float, optional): Charge of the generated text's randomness. Defaults to 1.
|
58 |
+
presence_penalty (int, optional): Chances of topic being repeated. Defaults to 0.
|
59 |
+
frequency_penalty (int, optional): Chances of word being repeated. Defaults to 0.
|
60 |
+
top_p (float, optional): Sampling threshold during inference time. Defaults to 0.999.
|
61 |
+
model (str, optional): LLM model name. Defaults to "gpt-3.5-turbo".
|
62 |
+
timeout (int, optional): Http request timeout. Defaults to 30.
|
63 |
+
intro (str, optional): Conversation introductory prompt. Defaults to None.
|
64 |
+
filepath (str, optional): Path to file containing conversation history. Defaults to None.
|
65 |
+
update_file (bool, optional): Add new prompts and responses to the file. Defaults to True.
|
66 |
+
proxies (dict, optional): Http request proxies. Defaults to {}.
|
67 |
+
history_offset (int, optional): Limit conversation history to this number of last texts. Defaults to 10250.
|
68 |
+
act (str|int, optional): Awesome prompt key or index. (Used as intro). Defaults to None.
|
69 |
+
"""
|
70 |
+
self.is_conversation = is_conversation
|
71 |
+
self.max_tokens_to_sample = max_tokens
|
72 |
+
self.api_key = api_key
|
73 |
+
self.model = model
|
74 |
+
self.temperature = temperature
|
75 |
+
self.presence_penalty = presence_penalty
|
76 |
+
self.frequency_penalty = frequency_penalty
|
77 |
+
self.top_p = top_p
|
78 |
+
self.chat_endpoint = "https://api.openai.com/v1/chat/completions"
|
79 |
+
self.stream_chunk_size = 64
|
80 |
+
self.timeout = timeout
|
81 |
+
self.last_response = {}
|
82 |
+
self.headers = {
|
83 |
+
"Content-Type": "application/json",
|
84 |
+
"Authorization": f"Bearer {self.api_key}",
|
85 |
+
}
|
86 |
+
|
87 |
+
self.__available_optimizers = (
|
88 |
+
method
|
89 |
+
for method in dir(Optimizers)
|
90 |
+
if callable(getattr(Optimizers, method)) and not method.startswith("__")
|
91 |
+
)
|
92 |
+
self.session.headers.update(self.headers)
|
93 |
+
Conversation.intro = (
|
94 |
+
AwesomePrompts().get_act(
|
95 |
+
act, raise_not_found=True, default=None, case_insensitive=True
|
96 |
+
)
|
97 |
+
if act
|
98 |
+
else intro or Conversation.intro
|
99 |
+
)
|
100 |
+
self.conversation = Conversation(
|
101 |
+
is_conversation, self.max_tokens_to_sample, filepath, update_file
|
102 |
+
)
|
103 |
+
self.conversation.history_offset = history_offset
|
104 |
+
self.session.proxies = proxies
|
105 |
+
|
106 |
+
def ask(
|
107 |
+
self,
|
108 |
+
prompt: str,
|
109 |
+
stream: bool = False,
|
110 |
+
raw: bool = False,
|
111 |
+
optimizer: str = None,
|
112 |
+
conversationally: bool = False,
|
113 |
+
) -> dict:
|
114 |
+
"""Chat with AI
|
115 |
+
|
116 |
+
Args:
|
117 |
+
prompt (str): Prompt to be send.
|
118 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
119 |
+
raw (bool, optional): Stream back raw response as received. Defaults to False.
|
120 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
121 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
122 |
+
Returns:
|
123 |
+
dict : {}
|
124 |
+
```json
|
125 |
+
{
|
126 |
+
"id": "chatcmpl-TaREJpBZsRVQFRFic1wIA7Q7XfnaD",
|
127 |
+
"object": "chat.completion",
|
128 |
+
"created": 1704623244,
|
129 |
+
"model": "gpt-3.5-turbo",
|
130 |
+
"usage": {
|
131 |
+
"prompt_tokens": 0,
|
132 |
+
"completion_tokens": 0,
|
133 |
+
"total_tokens": 0
|
134 |
+
},
|
135 |
+
"choices": [
|
136 |
+
{
|
137 |
+
"message": {
|
138 |
+
"role": "assistant",
|
139 |
+
"content": "Hello! How can I assist you today?"
|
140 |
+
},
|
141 |
+
"finish_reason": "stop",
|
142 |
+
"index": 0
|
143 |
+
}
|
144 |
+
]
|
145 |
+
}
|
146 |
+
```
|
147 |
+
"""
|
148 |
+
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
149 |
+
if optimizer:
|
150 |
+
if optimizer in self.__available_optimizers:
|
151 |
+
conversation_prompt = getattr(Optimizers, optimizer)(
|
152 |
+
conversation_prompt if conversationally else prompt
|
153 |
+
)
|
154 |
+
else:
|
155 |
+
raise exceptions.FailedToGenerateResponseError(
|
156 |
+
f"Optimizer is not one of {self.__available_optimizers}"
|
157 |
+
)
|
158 |
+
self.session.headers.update(self.headers)
|
159 |
+
payload = {
|
160 |
+
"frequency_penalty": self.frequency_penalty,
|
161 |
+
"messages": [{"content": conversation_prompt, "role": "user"}],
|
162 |
+
"model": self.model,
|
163 |
+
"presence_penalty": self.presence_penalty,
|
164 |
+
"stream": stream,
|
165 |
+
"temperature": self.temperature,
|
166 |
+
"top_p": self.top_p,
|
167 |
+
}
|
168 |
+
|
169 |
+
def for_stream():
|
170 |
+
response = self.session.post(
|
171 |
+
self.chat_endpoint, json=payload, stream=True, timeout=self.timeout
|
172 |
+
)
|
173 |
+
if not response.ok:
|
174 |
+
raise exceptions.FailedToGenerateResponseError(
|
175 |
+
f"Failed to generate response - ({response.status_code}, {response.reason}) - {response.text}"
|
176 |
+
)
|
177 |
+
|
178 |
+
message_load = ""
|
179 |
+
for value in response.iter_lines(
|
180 |
+
decode_unicode=True,
|
181 |
+
delimiter="" if raw else "data:",
|
182 |
+
chunk_size=self.stream_chunk_size,
|
183 |
+
):
|
184 |
+
try:
|
185 |
+
resp = json.loads(value)
|
186 |
+
incomplete_message = self.get_message(resp)
|
187 |
+
if incomplete_message:
|
188 |
+
message_load += incomplete_message
|
189 |
+
resp["choices"][0]["delta"]["content"] = message_load
|
190 |
+
self.last_response.update(resp)
|
191 |
+
yield value if raw else resp
|
192 |
+
elif raw:
|
193 |
+
yield value
|
194 |
+
except json.decoder.JSONDecodeError:
|
195 |
+
pass
|
196 |
+
self.conversation.update_chat_history(
|
197 |
+
prompt, self.get_message(self.last_response)
|
198 |
+
)
|
199 |
+
|
200 |
+
def for_non_stream():
|
201 |
+
response = self.session.post(
|
202 |
+
self.chat_endpoint, json=payload, stream=False, timeout=self.timeout
|
203 |
+
)
|
204 |
+
if (
|
205 |
+
not response.ok
|
206 |
+
or not response.headers.get("Content-Type", "") == "application/json"
|
207 |
+
):
|
208 |
+
raise exceptions.FailedToGenerateResponseError(
|
209 |
+
f"Failed to generate response - ({response.status_code}, {response.reason}) - {response.text}"
|
210 |
+
)
|
211 |
+
resp = response.json()
|
212 |
+
self.last_response.update(resp)
|
213 |
+
self.conversation.update_chat_history(
|
214 |
+
prompt, self.get_message(self.last_response)
|
215 |
+
)
|
216 |
+
return resp
|
217 |
+
|
218 |
+
return for_stream() if stream else for_non_stream()
|
219 |
+
|
220 |
+
def chat(
|
221 |
+
self,
|
222 |
+
prompt: str,
|
223 |
+
stream: bool = False,
|
224 |
+
optimizer: str = None,
|
225 |
+
conversationally: bool = False,
|
226 |
+
) -> str:
|
227 |
+
"""Generate response `str`
|
228 |
+
Args:
|
229 |
+
prompt (str): Prompt to be send.
|
230 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
231 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
232 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
233 |
+
Returns:
|
234 |
+
str: Response generated
|
235 |
+
"""
|
236 |
+
|
237 |
+
def for_stream():
|
238 |
+
for response in self.ask(
|
239 |
+
prompt, True, optimizer=optimizer, conversationally=conversationally
|
240 |
+
):
|
241 |
+
yield self.get_message(response)
|
242 |
+
|
243 |
+
def for_non_stream():
|
244 |
+
return self.get_message(
|
245 |
+
self.ask(
|
246 |
+
prompt,
|
247 |
+
False,
|
248 |
+
optimizer=optimizer,
|
249 |
+
conversationally=conversationally,
|
250 |
+
)
|
251 |
+
)
|
252 |
+
|
253 |
+
return for_stream() if stream else for_non_stream()
|
254 |
+
|
255 |
+
def get_message(self, response: dict) -> str:
|
256 |
+
"""Retrieves message only from response
|
257 |
+
|
258 |
+
Args:
|
259 |
+
response (dict): Response generated by `self.ask`
|
260 |
+
|
261 |
+
Returns:
|
262 |
+
str: Message extracted
|
263 |
+
"""
|
264 |
+
assert isinstance(response, dict), "Response should be of dict data-type only"
|
265 |
+
try:
|
266 |
+
if response["choices"][0].get("delta"):
|
267 |
+
return response["choices"][0]["delta"]["content"]
|
268 |
+
return response["choices"][0]["message"]["content"]
|
269 |
+
except KeyError:
|
270 |
+
return ""
|
271 |
+
class AsyncOPENAI(AsyncProvider):
|
272 |
+
def __init__(
|
273 |
+
self,
|
274 |
+
api_key: str,
|
275 |
+
is_conversation: bool = True,
|
276 |
+
max_tokens: int = 600,
|
277 |
+
temperature: float = 1,
|
278 |
+
presence_penalty: int = 0,
|
279 |
+
frequency_penalty: int = 0,
|
280 |
+
top_p: float = 1,
|
281 |
+
model: str = "gpt-3.5-turbo",
|
282 |
+
timeout: int = 30,
|
283 |
+
intro: str = None,
|
284 |
+
filepath: str = None,
|
285 |
+
update_file: bool = True,
|
286 |
+
proxies: dict = {},
|
287 |
+
history_offset: int = 10250,
|
288 |
+
act: str = None,
|
289 |
+
):
|
290 |
+
"""Instantiates OPENAI
|
291 |
+
|
292 |
+
Args:
|
293 |
+
api_key (key): OpenAI's API key.
|
294 |
+
is_conversation (bool, optional): Flag for chatting conversationally. Defaults to True.
|
295 |
+
max_tokens (int, optional): Maximum number of tokens to be generated upon completion. Defaults to 600.
|
296 |
+
temperature (float, optional): Charge of the generated text's randomness. Defaults to 1.
|
297 |
+
presence_penalty (int, optional): Chances of topic being repeated. Defaults to 0.
|
298 |
+
frequency_penalty (int, optional): Chances of word being repeated. Defaults to 0.
|
299 |
+
top_p (float, optional): Sampling threshold during inference time. Defaults to 0.999.
|
300 |
+
model (str, optional): LLM model name. Defaults to "gpt-3.5-turbo".
|
301 |
+
timeout (int, optional): Http request timeout. Defaults to 30.
|
302 |
+
intro (str, optional): Conversation introductory prompt. Defaults to None.
|
303 |
+
filepath (str, optional): Path to file containing conversation history. Defaults to None.
|
304 |
+
update_file (bool, optional): Add new prompts and responses to the file. Defaults to True.
|
305 |
+
proxies (dict, optional): Http request proxies. Defaults to {}.
|
306 |
+
history_offset (int, optional): Limit conversation history to this number of last texts. Defaults to 10250.
|
307 |
+
act (str|int, optional): Awesome prompt key or index. (Used as intro). Defaults to None.
|
308 |
+
"""
|
309 |
+
self.is_conversation = is_conversation
|
310 |
+
self.max_tokens_to_sample = max_tokens
|
311 |
+
self.api_key = api_key
|
312 |
+
self.model = model
|
313 |
+
self.temperature = temperature
|
314 |
+
self.presence_penalty = presence_penalty
|
315 |
+
self.frequency_penalty = frequency_penalty
|
316 |
+
self.top_p = top_p
|
317 |
+
self.chat_endpoint = "https://api.openai.com/v1/chat/completions"
|
318 |
+
self.stream_chunk_size = 64
|
319 |
+
self.timeout = timeout
|
320 |
+
self.last_response = {}
|
321 |
+
self.headers = {
|
322 |
+
"Content-Type": "application/json",
|
323 |
+
"Authorization": f"Bearer {self.api_key}",
|
324 |
+
}
|
325 |
+
|
326 |
+
self.__available_optimizers = (
|
327 |
+
method
|
328 |
+
for method in dir(Optimizers)
|
329 |
+
if callable(getattr(Optimizers, method)) and not method.startswith("__")
|
330 |
+
)
|
331 |
+
Conversation.intro = (
|
332 |
+
AwesomePrompts().get_act(
|
333 |
+
act, raise_not_found=True, default=None, case_insensitive=True
|
334 |
+
)
|
335 |
+
if act
|
336 |
+
else intro or Conversation.intro
|
337 |
+
)
|
338 |
+
self.conversation = Conversation(
|
339 |
+
is_conversation, self.max_tokens_to_sample, filepath, update_file
|
340 |
+
)
|
341 |
+
self.conversation.history_offset = history_offset
|
342 |
+
self.session = httpx.AsyncClient(
|
343 |
+
headers=self.headers,
|
344 |
+
proxies=proxies,
|
345 |
+
)
|
346 |
+
|
347 |
+
async def ask(
|
348 |
+
self,
|
349 |
+
prompt: str,
|
350 |
+
stream: bool = False,
|
351 |
+
raw: bool = False,
|
352 |
+
optimizer: str = None,
|
353 |
+
conversationally: bool = False,
|
354 |
+
) -> dict | AsyncGenerator:
|
355 |
+
"""Chat with AI asynchronously.
|
356 |
+
|
357 |
+
Args:
|
358 |
+
prompt (str): Prompt to be send.
|
359 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
360 |
+
raw (bool, optional): Stream back raw response as received. Defaults to False.
|
361 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
362 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
363 |
+
Returns:
|
364 |
+
dict|AsyncGenerator : ai content.
|
365 |
+
```json
|
366 |
+
{
|
367 |
+
"id": "chatcmpl-TaREJpBZsRVQFRFic1wIA7Q7XfnaD",
|
368 |
+
"object": "chat.completion",
|
369 |
+
"created": 1704623244,
|
370 |
+
"model": "gpt-3.5-turbo",
|
371 |
+
"usage": {
|
372 |
+
"prompt_tokens": 0,
|
373 |
+
"completion_tokens": 0,
|
374 |
+
"total_tokens": 0
|
375 |
+
},
|
376 |
+
"choices": [
|
377 |
+
{
|
378 |
+
"message": {
|
379 |
+
"role": "assistant",
|
380 |
+
"content": "Hello! How can I assist you today?"
|
381 |
+
},
|
382 |
+
"finish_reason": "stop",
|
383 |
+
"index": 0
|
384 |
+
}
|
385 |
+
]
|
386 |
+
}
|
387 |
+
```
|
388 |
+
"""
|
389 |
+
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
390 |
+
if optimizer:
|
391 |
+
if optimizer in self.__available_optimizers:
|
392 |
+
conversation_prompt = getattr(Optimizers, optimizer)(
|
393 |
+
conversation_prompt if conversationally else prompt
|
394 |
+
)
|
395 |
+
else:
|
396 |
+
raise Exception(
|
397 |
+
f"Optimizer is not one of {self.__available_optimizers}"
|
398 |
+
)
|
399 |
+
payload = {
|
400 |
+
"frequency_penalty": self.frequency_penalty,
|
401 |
+
"messages": [{"content": conversation_prompt, "role": "user"}],
|
402 |
+
"model": self.model,
|
403 |
+
"presence_penalty": self.presence_penalty,
|
404 |
+
"stream": stream,
|
405 |
+
"temperature": self.temperature,
|
406 |
+
"top_p": self.top_p,
|
407 |
+
}
|
408 |
+
|
409 |
+
async def for_stream():
|
410 |
+
async with self.session.stream(
|
411 |
+
"POST", self.chat_endpoint, json=payload, timeout=self.timeout
|
412 |
+
) as response:
|
413 |
+
if not response.is_success:
|
414 |
+
raise Exception(
|
415 |
+
f"Failed to generate response - ({response.status_code}, {response.reason_phrase})"
|
416 |
+
)
|
417 |
+
|
418 |
+
message_load = ""
|
419 |
+
async for value in response.aiter_lines():
|
420 |
+
try:
|
421 |
+
|
422 |
+
resp = sanitize_stream(value)
|
423 |
+
incomplete_message = await self.get_message(resp)
|
424 |
+
if incomplete_message:
|
425 |
+
message_load += incomplete_message
|
426 |
+
resp["choices"][0]["delta"]["content"] = message_load
|
427 |
+
self.last_response.update(resp)
|
428 |
+
yield value if raw else resp
|
429 |
+
elif raw:
|
430 |
+
yield value
|
431 |
+
except json.decoder.JSONDecodeError:
|
432 |
+
pass
|
433 |
+
self.conversation.update_chat_history(
|
434 |
+
prompt, await self.get_message(self.last_response)
|
435 |
+
)
|
436 |
+
|
437 |
+
async def for_non_stream():
|
438 |
+
response = httpx.post(
|
439 |
+
self.chat_endpoint,
|
440 |
+
json=payload,
|
441 |
+
timeout=self.timeout,
|
442 |
+
headers=self.headers,
|
443 |
+
)
|
444 |
+
if (
|
445 |
+
not response.is_success
|
446 |
+
or not response.headers.get("Content-Type", "") == "application/json"
|
447 |
+
):
|
448 |
+
raise Exception(
|
449 |
+
f"Failed to generate response - ({response.status_code}, {response.reason_phrase})"
|
450 |
+
)
|
451 |
+
resp = response.json()
|
452 |
+
self.last_response.update(resp)
|
453 |
+
self.conversation.update_chat_history(
|
454 |
+
prompt, await self.get_message(self.last_response)
|
455 |
+
)
|
456 |
+
return resp
|
457 |
+
|
458 |
+
return for_stream() if stream else await for_non_stream()
|
459 |
+
|
460 |
+
async def chat(
|
461 |
+
self,
|
462 |
+
prompt: str,
|
463 |
+
stream: bool = False,
|
464 |
+
optimizer: str = None,
|
465 |
+
conversationally: bool = False,
|
466 |
+
) -> str | AsyncGenerator:
|
467 |
+
"""Generate response `str` asynchronously.
|
468 |
+
Args:
|
469 |
+
prompt (str): Prompt to be send.
|
470 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
471 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
472 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
473 |
+
Returns:
|
474 |
+
str|AsyncGenerator: Response generated
|
475 |
+
"""
|
476 |
+
|
477 |
+
async def for_stream():
|
478 |
+
async_ask = await self.ask(
|
479 |
+
prompt, True, optimizer=optimizer, conversationally=conversationally
|
480 |
+
)
|
481 |
+
async for response in async_ask:
|
482 |
+
yield await self.get_message(response)
|
483 |
+
|
484 |
+
async def for_non_stream():
|
485 |
+
return await self.get_message(
|
486 |
+
await self.ask(
|
487 |
+
prompt,
|
488 |
+
False,
|
489 |
+
optimizer=optimizer,
|
490 |
+
conversationally=conversationally,
|
491 |
+
)
|
492 |
+
)
|
493 |
+
|
494 |
+
return for_stream() if stream else await for_non_stream()
|
495 |
+
|
496 |
+
async def get_message(self, response: dict) -> str:
|
497 |
+
"""Retrieves message only from response asynchronously.
|
498 |
+
|
499 |
+
Args:
|
500 |
+
response (dict): Response generated by `self.ask`
|
501 |
+
|
502 |
+
Returns:
|
503 |
+
str: Message extracted
|
504 |
+
"""
|
505 |
+
assert isinstance(response, dict), "Response should be of dict data-type only"
|
506 |
+
try:
|
507 |
+
if response["choices"][0].get("delta"):
|
508 |
+
return response["choices"][0]["delta"]["content"]
|
509 |
+
return response["choices"][0]["message"]["content"]
|
510 |
+
except KeyError:
|
511 |
+
return ""
|
webscout/Provider/Perplexity.py
ADDED
@@ -0,0 +1,230 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
import uuid
|
3 |
+
from selenium import webdriver
|
4 |
+
from selenium.webdriver.chrome.options import Options
|
5 |
+
from selenium.webdriver.common.by import By
|
6 |
+
from selenium.webdriver.support import expected_conditions as EC
|
7 |
+
from selenium.webdriver.support.ui import WebDriverWait
|
8 |
+
import click
|
9 |
+
import requests
|
10 |
+
from requests import get
|
11 |
+
from uuid import uuid4
|
12 |
+
from re import findall
|
13 |
+
from requests.exceptions import RequestException
|
14 |
+
from curl_cffi.requests import get, RequestsError
|
15 |
+
import g4f
|
16 |
+
from random import randint
|
17 |
+
from PIL import Image
|
18 |
+
import io
|
19 |
+
import re
|
20 |
+
import json
|
21 |
+
import yaml
|
22 |
+
from ..AIutel import Optimizers
|
23 |
+
from ..AIutel import Conversation
|
24 |
+
from ..AIutel import AwesomePrompts, sanitize_stream
|
25 |
+
from ..AIbase import Provider, AsyncProvider
|
26 |
+
from Helpingai_T2 import Perplexity
|
27 |
+
from webscout import exceptions
|
28 |
+
from typing import Any, AsyncGenerator, Dict
|
29 |
+
import logging
|
30 |
+
import httpx
|
31 |
+
#------------------------------------------------------PERPLEXITY--------------------------------------------------------
|
32 |
+
class PERPLEXITY(Provider):
|
33 |
+
def __init__(
|
34 |
+
self,
|
35 |
+
is_conversation: bool = True,
|
36 |
+
max_tokens: int = 600,
|
37 |
+
timeout: int = 30,
|
38 |
+
intro: str = None,
|
39 |
+
filepath: str = None,
|
40 |
+
update_file: bool = True,
|
41 |
+
proxies: dict = {},
|
42 |
+
history_offset: int = 10250,
|
43 |
+
act: str = None,
|
44 |
+
quiet: bool = False,
|
45 |
+
):
|
46 |
+
"""Instantiates PERPLEXITY
|
47 |
+
|
48 |
+
Args:
|
49 |
+
is_conversation (bool, optional): Flag for chatting conversationally. Defaults to True
|
50 |
+
max_tokens (int, optional): Maximum number of tokens to be generated upon completion. Defaults to 600.
|
51 |
+
timeout (int, optional): Http request timeout. Defaults to 30.
|
52 |
+
intro (str, optional): Conversation introductory prompt. Defaults to None.
|
53 |
+
filepath (str, optional): Path to file containing conversation history. Defaults to None.
|
54 |
+
update_file (bool, optional): Add new prompts and responses to the file. Defaults to True.
|
55 |
+
proxies (dict, optional): Http request proxies. Defaults to {}.
|
56 |
+
history_offset (int, optional): Limit conversation history to this number of last texts. Defaults to 10250.
|
57 |
+
act (str|int, optional): Awesome prompt key or index. (Used as intro). Defaults to None.
|
58 |
+
quiet (bool, optional): Ignore web search-results and yield final response only. Defaults to False.
|
59 |
+
"""
|
60 |
+
self.max_tokens_to_sample = max_tokens
|
61 |
+
self.is_conversation = is_conversation
|
62 |
+
self.last_response = {}
|
63 |
+
self.web_results: dict = {}
|
64 |
+
self.quiet = quiet
|
65 |
+
|
66 |
+
self.__available_optimizers = (
|
67 |
+
method
|
68 |
+
for method in dir(Optimizers)
|
69 |
+
if callable(getattr(Optimizers, method)) and not method.startswith("__")
|
70 |
+
)
|
71 |
+
Conversation.intro = (
|
72 |
+
AwesomePrompts().get_act(
|
73 |
+
act, raise_not_found=True, default=None, case_insensitive=True
|
74 |
+
)
|
75 |
+
if act
|
76 |
+
else intro or Conversation.intro
|
77 |
+
)
|
78 |
+
self.conversation = Conversation(
|
79 |
+
is_conversation, self.max_tokens_to_sample, filepath, update_file
|
80 |
+
)
|
81 |
+
self.conversation.history_offset = history_offset
|
82 |
+
|
83 |
+
def ask(
|
84 |
+
self,
|
85 |
+
prompt: str,
|
86 |
+
stream: bool = False,
|
87 |
+
raw: bool = False,
|
88 |
+
optimizer: str = None,
|
89 |
+
conversationally: bool = False,
|
90 |
+
) -> dict:
|
91 |
+
"""Chat with AI
|
92 |
+
|
93 |
+
Args:
|
94 |
+
prompt (str): Prompt to be send.
|
95 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
96 |
+
raw (bool, optional): Stream back raw response as received. Defaults to False.
|
97 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
98 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
99 |
+
Returns:
|
100 |
+
dict : {}
|
101 |
+
```json
|
102 |
+
{
|
103 |
+
"status": "pending",
|
104 |
+
"uuid": "3604dfcc-611f-4b7d-989d-edca2a7233c7",
|
105 |
+
"read_write_token": null,
|
106 |
+
"frontend_context_uuid": "f6d43119-5231-481d-b692-f52e1f52d2c6",
|
107 |
+
"final": false,
|
108 |
+
"backend_uuid": "a6d6ec9e-da69-4841-af74-0de0409267a8",
|
109 |
+
"media_items": [],
|
110 |
+
"widget_data": [],
|
111 |
+
"knowledge_cards": [],
|
112 |
+
"expect_search_results": "false",
|
113 |
+
"mode": "concise",
|
114 |
+
"search_focus": "internet",
|
115 |
+
"gpt4": false,
|
116 |
+
"display_model": "turbo",
|
117 |
+
"attachments": null,
|
118 |
+
"answer": "",
|
119 |
+
"web_results": [],
|
120 |
+
"chunks": [],
|
121 |
+
"extra_web_results": []
|
122 |
+
}
|
123 |
+
```
|
124 |
+
"""
|
125 |
+
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
126 |
+
if optimizer:
|
127 |
+
if optimizer in self.__available_optimizers:
|
128 |
+
conversation_prompt = getattr(Optimizers, optimizer)(
|
129 |
+
conversation_prompt if conversationally else prompt
|
130 |
+
)
|
131 |
+
else:
|
132 |
+
raise Exception(
|
133 |
+
f"Optimizer is not one of {self.__available_optimizers}"
|
134 |
+
)
|
135 |
+
|
136 |
+
def for_stream():
|
137 |
+
for response in Perplexity().generate_answer(conversation_prompt):
|
138 |
+
yield json.dumps(response) if raw else response
|
139 |
+
self.last_response.update(response)
|
140 |
+
|
141 |
+
self.conversation.update_chat_history(
|
142 |
+
prompt,
|
143 |
+
self.get_message(self.last_response),
|
144 |
+
)
|
145 |
+
|
146 |
+
def for_non_stream():
|
147 |
+
for _ in for_stream():
|
148 |
+
pass
|
149 |
+
return self.last_response
|
150 |
+
|
151 |
+
return for_stream() if stream else for_non_stream()
|
152 |
+
|
153 |
+
def chat(
|
154 |
+
self,
|
155 |
+
prompt: str,
|
156 |
+
stream: bool = False,
|
157 |
+
optimizer: str = None,
|
158 |
+
conversationally: bool = False,
|
159 |
+
) -> str:
|
160 |
+
"""Generate response `str`
|
161 |
+
Args:
|
162 |
+
prompt (str): Prompt to be send.
|
163 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
164 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
165 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
166 |
+
Returns:
|
167 |
+
str: Response generated
|
168 |
+
"""
|
169 |
+
|
170 |
+
def for_stream():
|
171 |
+
for response in self.ask(
|
172 |
+
prompt, True, optimizer=optimizer, conversationally=conversationally
|
173 |
+
):
|
174 |
+
yield self.get_message(response)
|
175 |
+
|
176 |
+
def for_non_stream():
|
177 |
+
return self.get_message(
|
178 |
+
self.ask(
|
179 |
+
prompt,
|
180 |
+
False,
|
181 |
+
optimizer=optimizer,
|
182 |
+
conversationally=conversationally,
|
183 |
+
)
|
184 |
+
)
|
185 |
+
|
186 |
+
return for_stream() if stream else for_non_stream()
|
187 |
+
|
188 |
+
def get_message(self, response: dict) -> str:
|
189 |
+
"""Retrieves message only from response
|
190 |
+
|
191 |
+
Args:
|
192 |
+
response (dict): Response generated by `self.ask`
|
193 |
+
|
194 |
+
Returns:
|
195 |
+
str: Message extracted
|
196 |
+
"""
|
197 |
+
assert isinstance(response, dict), "Response should be of dict data-type only"
|
198 |
+
text_str: str = response.get("answer", "")
|
199 |
+
|
200 |
+
def update_web_results(web_results: list) -> None:
|
201 |
+
for index, results in enumerate(web_results, start=1):
|
202 |
+
self.web_results[str(index) + ". " + results["name"]] = dict(
|
203 |
+
url=results.get("url"), snippet=results.get("snippet")
|
204 |
+
)
|
205 |
+
|
206 |
+
if response.get("text"):
|
207 |
+
# last chunk
|
208 |
+
target: dict[str, Any] = json.loads(response.get("text"))
|
209 |
+
text_str = target.get("answer")
|
210 |
+
web_results: list[dict] = target.get("web_results")
|
211 |
+
self.web_results.clear()
|
212 |
+
update_web_results(web_results)
|
213 |
+
|
214 |
+
return (
|
215 |
+
text_str
|
216 |
+
if self.quiet or not self.web_results
|
217 |
+
else text_str + "\n\n# WEB-RESULTS\n\n" + yaml.dump(self.web_results)
|
218 |
+
)
|
219 |
+
|
220 |
+
else:
|
221 |
+
if str(response.get("expect_search_results")).lower() == "true":
|
222 |
+
return (
|
223 |
+
text_str
|
224 |
+
if self.quiet
|
225 |
+
else text_str
|
226 |
+
+ "\n\n# WEB-RESULTS\n\n"
|
227 |
+
+ yaml.dump(response.get("web_results"))
|
228 |
+
)
|
229 |
+
else:
|
230 |
+
return text_str
|
webscout/Provider/Phind.py
ADDED
@@ -0,0 +1,518 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
import uuid
|
3 |
+
from selenium import webdriver
|
4 |
+
from selenium.webdriver.chrome.options import Options
|
5 |
+
from selenium.webdriver.common.by import By
|
6 |
+
from selenium.webdriver.support import expected_conditions as EC
|
7 |
+
from selenium.webdriver.support.ui import WebDriverWait
|
8 |
+
import click
|
9 |
+
import requests
|
10 |
+
from requests import get
|
11 |
+
from uuid import uuid4
|
12 |
+
from re import findall
|
13 |
+
from requests.exceptions import RequestException
|
14 |
+
from curl_cffi.requests import get, RequestsError
|
15 |
+
import g4f
|
16 |
+
from random import randint
|
17 |
+
from PIL import Image
|
18 |
+
import io
|
19 |
+
import re
|
20 |
+
import json
|
21 |
+
import yaml
|
22 |
+
from ..AIutel import Optimizers
|
23 |
+
from ..AIutel import Conversation
|
24 |
+
from ..AIutel import AwesomePrompts, sanitize_stream
|
25 |
+
from ..AIbase import Provider, AsyncProvider
|
26 |
+
from Helpingai_T2 import Perplexity
|
27 |
+
from webscout import exceptions
|
28 |
+
from typing import Any, AsyncGenerator, Dict
|
29 |
+
import logging
|
30 |
+
import httpx
|
31 |
+
|
32 |
+
#------------------------------------------------------phind-------------------------------------------------------------
|
33 |
+
class PhindSearch:
|
34 |
+
# default_model = "Phind Model"
|
35 |
+
def __init__(
|
36 |
+
self,
|
37 |
+
is_conversation: bool = True,
|
38 |
+
max_tokens: int = 8000,
|
39 |
+
timeout: int = 30,
|
40 |
+
intro: str = None,
|
41 |
+
filepath: str = None,
|
42 |
+
update_file: bool = True,
|
43 |
+
proxies: dict = {},
|
44 |
+
history_offset: int = 10250,
|
45 |
+
act: str = None,
|
46 |
+
model: str = "Phind Model",
|
47 |
+
quiet: bool = False,
|
48 |
+
):
|
49 |
+
"""Instantiates PHIND
|
50 |
+
|
51 |
+
Args:
|
52 |
+
is_conversation (bool, optional): Flag for chatting conversationally. Defaults to True
|
53 |
+
max_tokens (int, optional): Maximum number of tokens to be generated upon completion. Defaults to 600.
|
54 |
+
timeout (int, optional): Http request timeout. Defaults to 30.
|
55 |
+
intro (str, optional): Conversation introductory prompt. Defaults to None.
|
56 |
+
filepath (str, optional): Path to file containing conversation history. Defaults to None.
|
57 |
+
update_file (bool, optional): Add new prompts and responses to the file. Defaults to True.
|
58 |
+
proxies (dict, optional): Http request proxies. Defaults to {}.
|
59 |
+
history_offset (int, optional): Limit conversation history to this number of last texts. Defaults to 10250.
|
60 |
+
act (str|int, optional): Awesome prompt key or index. (Used as intro). Defaults to None.
|
61 |
+
model (str, optional): Model name. Defaults to "Phind Model".
|
62 |
+
quiet (bool, optional): Ignore web search-results and yield final response only. Defaults to False.
|
63 |
+
"""
|
64 |
+
self.session = requests.Session()
|
65 |
+
self.max_tokens_to_sample = max_tokens
|
66 |
+
self.is_conversation = is_conversation
|
67 |
+
self.chat_endpoint = "https://https.extension.phind.com/agent/"
|
68 |
+
self.stream_chunk_size = 64
|
69 |
+
self.timeout = timeout
|
70 |
+
self.last_response = {}
|
71 |
+
self.model = model
|
72 |
+
self.quiet = quiet
|
73 |
+
|
74 |
+
self.headers = {
|
75 |
+
"Content-Type": "application/json",
|
76 |
+
"User-Agent": "",
|
77 |
+
"Accept": "*/*",
|
78 |
+
"Accept-Encoding": "Identity",
|
79 |
+
}
|
80 |
+
|
81 |
+
self.__available_optimizers = (
|
82 |
+
method
|
83 |
+
for method in dir(Optimizers)
|
84 |
+
if callable(getattr(Optimizers, method)) and not method.startswith("__")
|
85 |
+
)
|
86 |
+
self.session.headers.update(self.headers)
|
87 |
+
Conversation.intro = (
|
88 |
+
AwesomePrompts().get_act(
|
89 |
+
act, raise_not_found=True, default=None, case_insensitive=True
|
90 |
+
)
|
91 |
+
if act
|
92 |
+
else intro or Conversation.intro
|
93 |
+
)
|
94 |
+
self.conversation = Conversation(
|
95 |
+
is_conversation, self.max_tokens_to_sample, filepath, update_file
|
96 |
+
)
|
97 |
+
self.conversation.history_offset = history_offset
|
98 |
+
self.session.proxies = proxies
|
99 |
+
|
100 |
+
def ask(
|
101 |
+
self,
|
102 |
+
prompt: str,
|
103 |
+
stream: bool = False,
|
104 |
+
raw: bool = False,
|
105 |
+
optimizer: str = None,
|
106 |
+
conversationally: bool = False,
|
107 |
+
) -> dict:
|
108 |
+
"""Chat with AI
|
109 |
+
|
110 |
+
Args:
|
111 |
+
prompt (str): Prompt to be send.
|
112 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
113 |
+
raw (bool, optional): Stream back raw response as received. Defaults to False.
|
114 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
115 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
116 |
+
Returns:
|
117 |
+
dict : {}
|
118 |
+
```json
|
119 |
+
{
|
120 |
+
"id": "chatcmpl-r0wujizf2i2xb60mjiwt",
|
121 |
+
"object": "chat.completion.chunk",
|
122 |
+
"created": 1706775384,
|
123 |
+
"model": "trt-llm-phind-model-serving",
|
124 |
+
"choices": [
|
125 |
+
{
|
126 |
+
"index": 0,
|
127 |
+
"delta": {
|
128 |
+
"content": "Hello! How can I assist you with your programming today?"
|
129 |
+
},
|
130 |
+
"finish_reason": null
|
131 |
+
}
|
132 |
+
]
|
133 |
+
}
|
134 |
+
```
|
135 |
+
"""
|
136 |
+
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
137 |
+
if optimizer:
|
138 |
+
if optimizer in self.__available_optimizers:
|
139 |
+
conversation_prompt = getattr(Optimizers, optimizer)(
|
140 |
+
conversation_prompt if conversationally else prompt
|
141 |
+
)
|
142 |
+
else:
|
143 |
+
raise Exception(
|
144 |
+
f"Optimizer is not one of {self.__available_optimizers}"
|
145 |
+
)
|
146 |
+
|
147 |
+
self.session.headers.update(self.headers)
|
148 |
+
payload = {
|
149 |
+
"additional_extension_context": "",
|
150 |
+
"allow_magic_buttons": True,
|
151 |
+
"is_vscode_extension": True,
|
152 |
+
"message_history": [
|
153 |
+
{"content": conversation_prompt, "metadata": {}, "role": "user"}
|
154 |
+
],
|
155 |
+
"requested_model": self.model,
|
156 |
+
"user_input": prompt,
|
157 |
+
}
|
158 |
+
|
159 |
+
def for_stream():
|
160 |
+
response = self.session.post(
|
161 |
+
self.chat_endpoint, json=payload, stream=True, timeout=self.timeout
|
162 |
+
)
|
163 |
+
if (
|
164 |
+
not response.ok
|
165 |
+
or not response.headers.get("Content-Type")
|
166 |
+
== "text/event-stream; charset=utf-8"
|
167 |
+
):
|
168 |
+
raise exceptions.FailedToGenerateResponseError(
|
169 |
+
f"Failed to generate response - ({response.status_code}, {response.reason}) - {response.text}"
|
170 |
+
)
|
171 |
+
streaming_text = ""
|
172 |
+
for value in response.iter_lines(
|
173 |
+
decode_unicode=True,
|
174 |
+
chunk_size=self.stream_chunk_size,
|
175 |
+
):
|
176 |
+
try:
|
177 |
+
modified_value = re.sub("data:", "", value)
|
178 |
+
json_modified_value = json.loads(modified_value)
|
179 |
+
retrieved_text = self.get_message(json_modified_value)
|
180 |
+
if not retrieved_text:
|
181 |
+
continue
|
182 |
+
streaming_text += retrieved_text
|
183 |
+
json_modified_value["choices"][0]["delta"][
|
184 |
+
"content"
|
185 |
+
] = streaming_text
|
186 |
+
self.last_response.update(json_modified_value)
|
187 |
+
yield value if raw else json_modified_value
|
188 |
+
except json.decoder.JSONDecodeError:
|
189 |
+
pass
|
190 |
+
self.conversation.update_chat_history(
|
191 |
+
prompt, self.get_message(self.last_response)
|
192 |
+
)
|
193 |
+
|
194 |
+
def for_non_stream():
|
195 |
+
for _ in for_stream():
|
196 |
+
pass
|
197 |
+
return self.last_response
|
198 |
+
|
199 |
+
return for_stream() if stream else for_non_stream()
|
200 |
+
|
201 |
+
def chat(
|
202 |
+
self,
|
203 |
+
prompt: str,
|
204 |
+
stream: bool = False,
|
205 |
+
optimizer: str = None,
|
206 |
+
conversationally: bool = False,
|
207 |
+
) -> str:
|
208 |
+
"""Generate response `str`
|
209 |
+
Args:
|
210 |
+
prompt (str): Prompt to be send.
|
211 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
212 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
213 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
214 |
+
Returns:
|
215 |
+
str: Response generated
|
216 |
+
"""
|
217 |
+
|
218 |
+
def for_stream():
|
219 |
+
for response in self.ask(
|
220 |
+
prompt, True, optimizer=optimizer, conversationally=conversationally
|
221 |
+
):
|
222 |
+
yield self.get_message(response)
|
223 |
+
|
224 |
+
def for_non_stream():
|
225 |
+
return self.get_message(
|
226 |
+
self.ask(
|
227 |
+
prompt,
|
228 |
+
False,
|
229 |
+
optimizer=optimizer,
|
230 |
+
conversationally=conversationally,
|
231 |
+
)
|
232 |
+
)
|
233 |
+
|
234 |
+
return for_stream() if stream else for_non_stream()
|
235 |
+
|
236 |
+
def get_message(self, response: dict) -> str:
|
237 |
+
"""Retrieves message only from response
|
238 |
+
|
239 |
+
Args:
|
240 |
+
response (dict): Response generated by `self.ask`
|
241 |
+
|
242 |
+
Returns:
|
243 |
+
str: Message extracted
|
244 |
+
"""
|
245 |
+
assert isinstance(response, dict), "Response should be of dict data-type only"
|
246 |
+
if response.get("type", "") == "metadata":
|
247 |
+
return
|
248 |
+
|
249 |
+
delta: dict = response["choices"][0]["delta"]
|
250 |
+
|
251 |
+
if not delta:
|
252 |
+
return ""
|
253 |
+
|
254 |
+
elif delta.get("function_call"):
|
255 |
+
if self.quiet:
|
256 |
+
return ""
|
257 |
+
|
258 |
+
function_call: dict = delta["function_call"]
|
259 |
+
if function_call.get("name"):
|
260 |
+
return function_call["name"]
|
261 |
+
elif function_call.get("arguments"):
|
262 |
+
return function_call.get("arguments")
|
263 |
+
|
264 |
+
elif delta.get("metadata"):
|
265 |
+
if self.quiet:
|
266 |
+
return ""
|
267 |
+
return yaml.dump(delta["metadata"])
|
268 |
+
|
269 |
+
else:
|
270 |
+
return (
|
271 |
+
response["choices"][0]["delta"].get("content")
|
272 |
+
if response["choices"][0].get("finish_reason") is None
|
273 |
+
else ""
|
274 |
+
)
|
275 |
+
class AsyncPhindSearch(AsyncProvider):
|
276 |
+
def __init__(
|
277 |
+
self,
|
278 |
+
is_conversation: bool = True,
|
279 |
+
max_tokens: int = 600,
|
280 |
+
timeout: int = 30,
|
281 |
+
intro: str = None,
|
282 |
+
filepath: str = None,
|
283 |
+
update_file: bool = True,
|
284 |
+
proxies: dict = {},
|
285 |
+
history_offset: int = 10250,
|
286 |
+
act: str = None,
|
287 |
+
model: str = "Phind Model",
|
288 |
+
quiet: bool = False,
|
289 |
+
):
|
290 |
+
"""Instantiates PHIND
|
291 |
+
|
292 |
+
Args:
|
293 |
+
is_conversation (bool, optional): Flag for chatting conversationally. Defaults to True
|
294 |
+
max_tokens (int, optional): Maximum number of tokens to be generated upon completion. Defaults to 600.
|
295 |
+
timeout (int, optional): Http request timeout. Defaults to 30.
|
296 |
+
intro (str, optional): Conversation introductory prompt. Defaults to None.
|
297 |
+
filepath (str, optional): Path to file containing conversation history. Defaults to None.
|
298 |
+
update_file (bool, optional): Add new prompts and responses to the file. Defaults to True.
|
299 |
+
proxies (dict, optional): Http request proxies. Defaults to {}.
|
300 |
+
history_offset (int, optional): Limit conversation history to this number of last texts. Defaults to 10250.
|
301 |
+
act (str|int, optional): Awesome prompt key or index. (Used as intro). Defaults to None.
|
302 |
+
model (str, optional): Model name. Defaults to "Phind Model".
|
303 |
+
quiet (bool, optional): Ignore web search-results and yield final response only. Defaults to False.
|
304 |
+
"""
|
305 |
+
self.max_tokens_to_sample = max_tokens
|
306 |
+
self.is_conversation = is_conversation
|
307 |
+
self.chat_endpoint = "https://https.extension.phind.com/agent/"
|
308 |
+
self.stream_chunk_size = 64
|
309 |
+
self.timeout = timeout
|
310 |
+
self.last_response = {}
|
311 |
+
self.model = model
|
312 |
+
self.quiet = quiet
|
313 |
+
|
314 |
+
self.headers = {
|
315 |
+
"Content-Type": "application/json",
|
316 |
+
"User-Agent": "",
|
317 |
+
"Accept": "*/*",
|
318 |
+
"Accept-Encoding": "Identity",
|
319 |
+
}
|
320 |
+
|
321 |
+
self.__available_optimizers = (
|
322 |
+
method
|
323 |
+
for method in dir(Optimizers)
|
324 |
+
if callable(getattr(Optimizers, method)) and not method.startswith("__")
|
325 |
+
)
|
326 |
+
Conversation.intro = (
|
327 |
+
AwesomePrompts().get_act(
|
328 |
+
act, raise_not_found=True, default=None, case_insensitive=True
|
329 |
+
)
|
330 |
+
if act
|
331 |
+
else intro or Conversation.intro
|
332 |
+
)
|
333 |
+
self.conversation = Conversation(
|
334 |
+
is_conversation, self.max_tokens_to_sample, filepath, update_file
|
335 |
+
)
|
336 |
+
self.conversation.history_offset = history_offset
|
337 |
+
self.session = httpx.AsyncClient(headers=self.headers, proxies=proxies)
|
338 |
+
|
339 |
+
async def ask(
|
340 |
+
self,
|
341 |
+
prompt: str,
|
342 |
+
stream: bool = False,
|
343 |
+
raw: bool = False,
|
344 |
+
optimizer: str = None,
|
345 |
+
conversationally: bool = False,
|
346 |
+
synchronous_generator=False,
|
347 |
+
) -> dict | AsyncGenerator:
|
348 |
+
"""Asynchronously Chat with AI
|
349 |
+
|
350 |
+
Args:
|
351 |
+
prompt (str): Prompt to be send.
|
352 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
353 |
+
raw (bool, optional): Stream back raw response as received. Defaults to False.
|
354 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
355 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
356 |
+
Returns:
|
357 |
+
dict|AsyncGenerator : ai content.
|
358 |
+
```json
|
359 |
+
{
|
360 |
+
"id": "chatcmpl-r0wujizf2i2xb60mjiwt",
|
361 |
+
"object": "chat.completion.chunk",
|
362 |
+
"created": 1706775384,
|
363 |
+
"model": "trt-llm-phind-model-serving",
|
364 |
+
"choices": [
|
365 |
+
{
|
366 |
+
"index": 0,
|
367 |
+
"delta": {
|
368 |
+
"content": "Hello! How can I assist you with your programming today?"
|
369 |
+
},
|
370 |
+
"finish_reason": null
|
371 |
+
}
|
372 |
+
]
|
373 |
+
}
|
374 |
+
```
|
375 |
+
"""
|
376 |
+
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
377 |
+
if optimizer:
|
378 |
+
if optimizer in self.__available_optimizers:
|
379 |
+
conversation_prompt = getattr(Optimizers, optimizer)(
|
380 |
+
conversation_prompt if conversationally else prompt
|
381 |
+
)
|
382 |
+
else:
|
383 |
+
raise Exception(
|
384 |
+
f"Optimizer is not one of {self.__available_optimizers}"
|
385 |
+
)
|
386 |
+
|
387 |
+
payload = {
|
388 |
+
"additional_extension_context": "",
|
389 |
+
"allow_magic_buttons": True,
|
390 |
+
"is_vscode_extension": True,
|
391 |
+
"message_history": [
|
392 |
+
{"content": conversation_prompt, "metadata": {}, "role": "user"}
|
393 |
+
],
|
394 |
+
"requested_model": self.model,
|
395 |
+
"user_input": prompt,
|
396 |
+
}
|
397 |
+
|
398 |
+
async def for_stream():
|
399 |
+
async with self.session.stream(
|
400 |
+
"POST",
|
401 |
+
self.chat_endpoint,
|
402 |
+
json=payload,
|
403 |
+
timeout=self.timeout,
|
404 |
+
) as response:
|
405 |
+
if (
|
406 |
+
not response.is_success
|
407 |
+
or not response.headers.get("Content-Type")
|
408 |
+
== "text/event-stream; charset=utf-8"
|
409 |
+
):
|
410 |
+
raise exceptions.FailedToGenerateResponseError(
|
411 |
+
f"Failed to generate response - ({response.status_code}, {response.reason_phrase})"
|
412 |
+
)
|
413 |
+
streaming_text = ""
|
414 |
+
async for value in response.aiter_lines():
|
415 |
+
try:
|
416 |
+
modified_value = re.sub("data:", "", value)
|
417 |
+
json_modified_value = json.loads(modified_value)
|
418 |
+
retrieved_text = await self.get_message(json_modified_value)
|
419 |
+
if not retrieved_text:
|
420 |
+
continue
|
421 |
+
streaming_text += retrieved_text
|
422 |
+
json_modified_value["choices"][0]["delta"][
|
423 |
+
"content"
|
424 |
+
] = streaming_text
|
425 |
+
self.last_response.update(json_modified_value)
|
426 |
+
yield value if raw else json_modified_value
|
427 |
+
except json.decoder.JSONDecodeError:
|
428 |
+
pass
|
429 |
+
self.conversation.update_chat_history(
|
430 |
+
prompt, await self.get_message(self.last_response)
|
431 |
+
)
|
432 |
+
|
433 |
+
async def for_non_stream():
|
434 |
+
async for _ in for_stream():
|
435 |
+
pass
|
436 |
+
return self.last_response
|
437 |
+
|
438 |
+
return (
|
439 |
+
for_stream()
|
440 |
+
if stream and not synchronous_generator
|
441 |
+
else await for_non_stream()
|
442 |
+
)
|
443 |
+
|
444 |
+
async def chat(
|
445 |
+
self,
|
446 |
+
prompt: str,
|
447 |
+
stream: bool = False,
|
448 |
+
optimizer: str = None,
|
449 |
+
conversationally: bool = False,
|
450 |
+
) -> str | AsyncGenerator:
|
451 |
+
"""Generate response `str`
|
452 |
+
Args:
|
453 |
+
prompt (str): Prompt to be send.
|
454 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
455 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
456 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
457 |
+
Returns:
|
458 |
+
str|AsyncGenerator: Response generated
|
459 |
+
"""
|
460 |
+
|
461 |
+
async def for_stream():
|
462 |
+
ask_resp = await self.ask(
|
463 |
+
prompt, True, optimizer=optimizer, conversationally=conversationally
|
464 |
+
)
|
465 |
+
async for response in ask_resp:
|
466 |
+
yield await self.get_message(response)
|
467 |
+
|
468 |
+
async def for_non_stream():
|
469 |
+
return await self.get_message(
|
470 |
+
await self.ask(
|
471 |
+
prompt,
|
472 |
+
False,
|
473 |
+
optimizer=optimizer,
|
474 |
+
conversationally=conversationally,
|
475 |
+
)
|
476 |
+
)
|
477 |
+
|
478 |
+
return for_stream() if stream else await for_non_stream()
|
479 |
+
|
480 |
+
async def get_message(self, response: dict) -> str:
|
481 |
+
"""Retrieves message only from response
|
482 |
+
|
483 |
+
Args:
|
484 |
+
response (dict): Response generated by `self.ask`
|
485 |
+
|
486 |
+
Returns:
|
487 |
+
str: Message extracted
|
488 |
+
"""
|
489 |
+
assert isinstance(response, dict), "Response should be of dict data-type only"
|
490 |
+
if response.get("type", "") == "metadata":
|
491 |
+
return
|
492 |
+
|
493 |
+
delta: dict = response["choices"][0]["delta"]
|
494 |
+
|
495 |
+
if not delta:
|
496 |
+
return ""
|
497 |
+
|
498 |
+
elif delta.get("function_call"):
|
499 |
+
if self.quiet:
|
500 |
+
return ""
|
501 |
+
|
502 |
+
function_call: dict = delta["function_call"]
|
503 |
+
if function_call.get("name"):
|
504 |
+
return function_call["name"]
|
505 |
+
elif function_call.get("arguments"):
|
506 |
+
return function_call.get("arguments")
|
507 |
+
|
508 |
+
elif delta.get("metadata"):
|
509 |
+
if self.quiet:
|
510 |
+
return ""
|
511 |
+
return yaml.dump(delta["metadata"])
|
512 |
+
|
513 |
+
else:
|
514 |
+
return (
|
515 |
+
response["choices"][0]["delta"].get("content")
|
516 |
+
if response["choices"][0].get("finish_reason") is None
|
517 |
+
else ""
|
518 |
+
)
|
webscout/Provider/Poe.py
ADDED
@@ -0,0 +1,208 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from poe_api_wrapper import PoeApi
|
2 |
+
from poe_api_wrapper.api import BOTS_LIST
|
3 |
+
from ..AIbase import Provider
|
4 |
+
from ..AIutel import Conversation
|
5 |
+
from ..AIutel import Optimizers
|
6 |
+
from ..AIutel import AwesomePrompts
|
7 |
+
from pathlib import Path
|
8 |
+
from json import loads
|
9 |
+
from json import dumps
|
10 |
+
from loguru import logger
|
11 |
+
import logging
|
12 |
+
|
13 |
+
logger.remove()
|
14 |
+
|
15 |
+
|
16 |
+
class POE(Provider):
|
17 |
+
def __init__(
|
18 |
+
self,
|
19 |
+
cookie: str,
|
20 |
+
model: str = "Assistant",
|
21 |
+
proxy: bool = False,
|
22 |
+
timeout: int = 30,
|
23 |
+
filepath: str = None,
|
24 |
+
update_file: str = True,
|
25 |
+
intro: str = None,
|
26 |
+
act: str = None,
|
27 |
+
init: bool = True,
|
28 |
+
):
|
29 |
+
"""Initializes POE
|
30 |
+
|
31 |
+
Args:
|
32 |
+
cookie (str): Path to `poe.com.cookies.json` file or 'p-b' cookie-value.
|
33 |
+
model (str, optional): Model name. Default to Assistant.
|
34 |
+
proxy (bool, optional): Flag for Httpx request proxy. Defaults to False.
|
35 |
+
timeout (int, optional): Http request timeout. Defaults to 30.
|
36 |
+
filepath (str, optional): Path to save the chat history. Defaults to None.
|
37 |
+
update_file (str, optional): Flag for controlling chat history updates. Defaults to True.
|
38 |
+
intro (str, optional): Conversation introductory prompt. Defaults to None.
|
39 |
+
act (str|int, optional): Awesome prompt key or index. (Used as intro). Defaults to None.
|
40 |
+
init (bool, optional): Resend the intro prompt. Defaults to True.
|
41 |
+
"""
|
42 |
+
assert isinstance(
|
43 |
+
cookie, str
|
44 |
+
), f"Cookie must be of {str} datatype only not {type(cookie)}"
|
45 |
+
assert (
|
46 |
+
model in BOTS_LIST.keys()
|
47 |
+
), f"model name '{model}' is not one of {', '.join(list(BOTS_LIST.keys()))}"
|
48 |
+
cookie_path = Path(cookie)
|
49 |
+
|
50 |
+
if cookie_path.exists() or any(["/" in cookie, ".json" in cookie]):
|
51 |
+
cookie = None
|
52 |
+
all_cookies = loads(cookie_path.read_text())
|
53 |
+
for entry in all_cookies:
|
54 |
+
if entry["name"] == "p-b":
|
55 |
+
cookie = entry["value"]
|
56 |
+
assert (
|
57 |
+
cookie
|
58 |
+
), f'Required cookie value cannot be retrieved from the path "{cookie_path.as_posix()}"'
|
59 |
+
|
60 |
+
if proxy:
|
61 |
+
import poe_api_wrapper.proxies as proxies
|
62 |
+
|
63 |
+
proxies.PROXY = True
|
64 |
+
|
65 |
+
self.bot = BOTS_LIST[model]
|
66 |
+
self.session = PoeApi(cookie)
|
67 |
+
self.last_response = {}
|
68 |
+
self.__available_optimizers = (
|
69 |
+
method
|
70 |
+
for method in dir(Optimizers)
|
71 |
+
if callable(getattr(Optimizers, method)) and not method.startswith("__")
|
72 |
+
)
|
73 |
+
Conversation.intro = (
|
74 |
+
AwesomePrompts().get_act(
|
75 |
+
act, raise_not_found=True, default=None, case_insensitive=True
|
76 |
+
)
|
77 |
+
if act
|
78 |
+
else intro or Conversation.intro
|
79 |
+
)
|
80 |
+
self.conversation = Conversation(
|
81 |
+
status=False, filepath=filepath, update_file=update_file
|
82 |
+
)
|
83 |
+
if init:
|
84 |
+
self.ask(self.conversation.intro) # Init
|
85 |
+
|
86 |
+
def ask(
|
87 |
+
self,
|
88 |
+
prompt: str,
|
89 |
+
stream: bool = False,
|
90 |
+
raw: bool = False,
|
91 |
+
optimizer: str = None,
|
92 |
+
conversationally: bool = False,
|
93 |
+
) -> dict:
|
94 |
+
"""Chat with AI
|
95 |
+
|
96 |
+
Args:
|
97 |
+
prompt (str): Prompt to be send.
|
98 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
99 |
+
raw (bool, optional): Stream back raw response as received. Defaults to False.
|
100 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defeaults to None
|
101 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
102 |
+
Returns:
|
103 |
+
dict : {}
|
104 |
+
```json
|
105 |
+
{
|
106 |
+
"id": "TWVzc2FnZToxMTU0MzgyNDQ1ODU=",
|
107 |
+
"messageId": 115438244585,
|
108 |
+
"creationTime": 1707777376544407,
|
109 |
+
"clientNonce": null,
|
110 |
+
"state": "complete",
|
111 |
+
"text": "Hello! How can I assist you today?",
|
112 |
+
"author": "capybara",
|
113 |
+
"contentType": "text_markdown",
|
114 |
+
"sourceType": "chat_input",
|
115 |
+
"attachmentTruncationState": "not_truncated",
|
116 |
+
"attachments": [],
|
117 |
+
"vote": null,
|
118 |
+
"suggestedReplies": [],
|
119 |
+
"hasCitations": false,
|
120 |
+
"__isNode": "Message",
|
121 |
+
"textLengthOnCancellation": null,
|
122 |
+
"chatCode": "21a2jn0yrq9phxiy478",
|
123 |
+
"chatId": 328236777,
|
124 |
+
"title": null,
|
125 |
+
"response": ""
|
126 |
+
}
|
127 |
+
```
|
128 |
+
"""
|
129 |
+
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
130 |
+
if optimizer:
|
131 |
+
if optimizer in self.__available_optimizers:
|
132 |
+
conversation_prompt = getattr(Optimizers, optimizer)(
|
133 |
+
conversation_prompt if conversationally else prompt
|
134 |
+
)
|
135 |
+
else:
|
136 |
+
raise Exception(
|
137 |
+
f"Optimizer is not one of {self.__available_optimizers}"
|
138 |
+
)
|
139 |
+
|
140 |
+
def for_stream():
|
141 |
+
for response in self.session.send_message(self.bot, conversation_prompt):
|
142 |
+
if raw:
|
143 |
+
yield dumps(response)
|
144 |
+
else:
|
145 |
+
yield response
|
146 |
+
|
147 |
+
self.last_response.update(response)
|
148 |
+
|
149 |
+
self.conversation.update_chat_history(
|
150 |
+
prompt,
|
151 |
+
self.get_message(self.last_response),
|
152 |
+
force=True,
|
153 |
+
)
|
154 |
+
|
155 |
+
def for_non_stream():
|
156 |
+
# let's make use of stream
|
157 |
+
for _ in for_stream():
|
158 |
+
pass
|
159 |
+
return self.last_response
|
160 |
+
|
161 |
+
return for_stream() if stream else for_non_stream()
|
162 |
+
|
163 |
+
def chat(
|
164 |
+
self,
|
165 |
+
prompt: str,
|
166 |
+
stream: bool = False,
|
167 |
+
optimizer: str = None,
|
168 |
+
conversationally: bool = False,
|
169 |
+
) -> str:
|
170 |
+
"""Generate response `str`
|
171 |
+
Args:
|
172 |
+
prompt (str): Prompt to be send.
|
173 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
174 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
175 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
176 |
+
Returns:
|
177 |
+
str: Response generated
|
178 |
+
"""
|
179 |
+
|
180 |
+
def for_stream():
|
181 |
+
for response in self.ask(
|
182 |
+
prompt, True, optimizer=optimizer, conversationally=conversationally
|
183 |
+
):
|
184 |
+
yield self.get_message(response)
|
185 |
+
|
186 |
+
def for_non_stream():
|
187 |
+
return self.get_message(
|
188 |
+
self.ask(
|
189 |
+
prompt,
|
190 |
+
False,
|
191 |
+
optimizer=optimizer,
|
192 |
+
conversationally=conversationally,
|
193 |
+
)
|
194 |
+
)
|
195 |
+
|
196 |
+
return for_stream() if stream else for_non_stream()
|
197 |
+
|
198 |
+
def get_message(self, response: dict) -> str:
|
199 |
+
"""Retrieves message only from response
|
200 |
+
|
201 |
+
Args:
|
202 |
+
response (dict): Response generated by `self.ask`
|
203 |
+
|
204 |
+
Returns:
|
205 |
+
str: Message extracted
|
206 |
+
"""
|
207 |
+
assert isinstance(response, dict), "Response should be of dict data-type only"
|
208 |
+
return response["text"]
|
webscout/Provider/Reka.py
ADDED
@@ -0,0 +1,226 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
import uuid
|
3 |
+
from selenium import webdriver
|
4 |
+
from selenium.webdriver.chrome.options import Options
|
5 |
+
from selenium.webdriver.common.by import By
|
6 |
+
from selenium.webdriver.support import expected_conditions as EC
|
7 |
+
from selenium.webdriver.support.ui import WebDriverWait
|
8 |
+
import click
|
9 |
+
import requests
|
10 |
+
from requests import get
|
11 |
+
from uuid import uuid4
|
12 |
+
from re import findall
|
13 |
+
from requests.exceptions import RequestException
|
14 |
+
from curl_cffi.requests import get, RequestsError
|
15 |
+
import g4f
|
16 |
+
from random import randint
|
17 |
+
from PIL import Image
|
18 |
+
import io
|
19 |
+
import re
|
20 |
+
import json
|
21 |
+
import yaml
|
22 |
+
from ..AIutel import Optimizers
|
23 |
+
from ..AIutel import Conversation
|
24 |
+
from ..AIutel import AwesomePrompts, sanitize_stream
|
25 |
+
from ..AIbase import Provider, AsyncProvider
|
26 |
+
from Helpingai_T2 import Perplexity
|
27 |
+
from webscout import exceptions
|
28 |
+
from typing import Any, AsyncGenerator, Dict
|
29 |
+
import logging
|
30 |
+
import httpx
|
31 |
+
#-----------------------------------------------REKA-----------------------------------------------
|
32 |
+
class REKA(Provider):
|
33 |
+
def __init__(
|
34 |
+
self,
|
35 |
+
api_key: str,
|
36 |
+
is_conversation: bool = True,
|
37 |
+
max_tokens: int = 600,
|
38 |
+
timeout: int = 30,
|
39 |
+
intro: str = None,
|
40 |
+
filepath: str = None,
|
41 |
+
update_file: bool = True,
|
42 |
+
proxies: dict = {},
|
43 |
+
history_offset: int = 10250,
|
44 |
+
act: str = None,
|
45 |
+
model: str = "reka-core",
|
46 |
+
system_prompt: str = "Be Helpful and Friendly. Keep your response straightforward, short and concise",
|
47 |
+
use_search_engine: bool = False,
|
48 |
+
use_code_interpreter: bool = False,
|
49 |
+
):
|
50 |
+
"""Instantiates REKA
|
51 |
+
|
52 |
+
Args:
|
53 |
+
is_conversation (bool, optional): Flag for chatting conversationally. Defaults to True
|
54 |
+
max_tokens (int, optional): Maximum number of tokens to be generated upon completion. Defaults to 600.
|
55 |
+
timeout (int, optional): Http request timeout. Defaults to 30.
|
56 |
+
intro (str, optional): Conversation introductory prompt. Defaults to None.
|
57 |
+
filepath (str, optional): Path to file containing conversation history. Defaults to None.
|
58 |
+
update_file (bool, optional): Add new prompts and responses to the file. Defaults to True.
|
59 |
+
proxies (dict, optional): Http request proxies. Defaults to {}.
|
60 |
+
history_offset (int, optional): Limit conversation history to this number of last texts. Defaults to 10250.
|
61 |
+
act (str|int, optional): Awesome prompt key or index. (Used as intro). Defaults to None.
|
62 |
+
model (str, optional): REKA model name. Defaults to "reka-core".
|
63 |
+
system_prompt (str, optional): System prompt for REKA. Defaults to "Be Helpful and Friendly. Keep your response straightforward, short and concise".
|
64 |
+
use_search_engine (bool, optional): Whether to use the search engine. Defaults to False.
|
65 |
+
use_code_interpreter (bool, optional): Whether to use the code interpreter. Defaults to False.
|
66 |
+
"""
|
67 |
+
self.session = requests.Session()
|
68 |
+
self.is_conversation = is_conversation
|
69 |
+
self.max_tokens_to_sample = max_tokens
|
70 |
+
self.api_endpoint = "https://chat.reka.ai/api/chat"
|
71 |
+
self.stream_chunk_size = 64
|
72 |
+
self.timeout = timeout
|
73 |
+
self.last_response = {}
|
74 |
+
self.model = model
|
75 |
+
self.system_prompt = system_prompt
|
76 |
+
self.use_search_engine = use_search_engine
|
77 |
+
self.use_code_interpreter = use_code_interpreter
|
78 |
+
self.access_token = api_key
|
79 |
+
self.headers = {
|
80 |
+
"Authorization": f"Bearer {self.access_token}",
|
81 |
+
}
|
82 |
+
|
83 |
+
self.__available_optimizers = (
|
84 |
+
method
|
85 |
+
for method in dir(Optimizers)
|
86 |
+
if callable(getattr(Optimizers, method)) and not method.startswith("__")
|
87 |
+
)
|
88 |
+
self.session.headers.update(self.headers)
|
89 |
+
Conversation.intro = (
|
90 |
+
AwesomePrompts().get_act(
|
91 |
+
act, raise_not_found=True, default=None, case_insensitive=True
|
92 |
+
)
|
93 |
+
if act
|
94 |
+
else intro or Conversation.intro
|
95 |
+
)
|
96 |
+
self.conversation = Conversation(
|
97 |
+
is_conversation, self.max_tokens_to_sample, filepath, update_file
|
98 |
+
)
|
99 |
+
self.conversation.history_offset = history_offset
|
100 |
+
self.session.proxies = proxies
|
101 |
+
|
102 |
+
def ask(
|
103 |
+
self,
|
104 |
+
prompt: str,
|
105 |
+
stream: bool = False,
|
106 |
+
raw: bool = False,
|
107 |
+
optimizer: str = None,
|
108 |
+
conversationally: bool = False,
|
109 |
+
) -> dict:
|
110 |
+
"""Chat with AI
|
111 |
+
|
112 |
+
Args:
|
113 |
+
prompt (str): Prompt to be send.
|
114 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
115 |
+
raw (bool, optional): Stream back raw response as received. Defaults to False.
|
116 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
117 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
118 |
+
Returns:
|
119 |
+
dict : {}
|
120 |
+
```json
|
121 |
+
{
|
122 |
+
"text" : "How may I assist you today?"
|
123 |
+
}
|
124 |
+
```
|
125 |
+
"""
|
126 |
+
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
127 |
+
if optimizer:
|
128 |
+
if optimizer in self.__available_optimizers:
|
129 |
+
conversation_prompt = getattr(Optimizers, optimizer)(
|
130 |
+
conversation_prompt if conversationally else prompt
|
131 |
+
)
|
132 |
+
else:
|
133 |
+
raise Exception(
|
134 |
+
f"Optimizer is not one of {self.__available_optimizers}"
|
135 |
+
)
|
136 |
+
|
137 |
+
self.session.headers.update(self.headers)
|
138 |
+
payload = {
|
139 |
+
|
140 |
+
"conversation_history": [
|
141 |
+
{"type": "human", "text": f"## SYSTEM PROMPT: {self.system_prompt}\n\n## QUERY: {conversation_prompt}"},
|
142 |
+
],
|
143 |
+
|
144 |
+
"stream": stream,
|
145 |
+
"use_search_engine": self.use_search_engine,
|
146 |
+
"use_code_interpreter": self.use_code_interpreter,
|
147 |
+
"model_name": self.model,
|
148 |
+
# "model_name": "reka-flash",
|
149 |
+
# "model_name": "reka-edge",
|
150 |
+
}
|
151 |
+
|
152 |
+
def for_stream():
|
153 |
+
response = self.session.post(self.api_endpoint, json=payload, stream=True, timeout=self.timeout)
|
154 |
+
if not response.ok:
|
155 |
+
raise Exception(
|
156 |
+
f"Failed to generate response - ({response.status_code}, {response.reason}) - {response.text}"
|
157 |
+
)
|
158 |
+
|
159 |
+
for value in response.iter_lines(
|
160 |
+
decode_unicode=True,
|
161 |
+
chunk_size=self.stream_chunk_size,
|
162 |
+
):
|
163 |
+
try:
|
164 |
+
resp = json.loads(value)
|
165 |
+
self.last_response.update(resp)
|
166 |
+
yield value if raw else resp
|
167 |
+
except json.decoder.JSONDecodeError:
|
168 |
+
pass
|
169 |
+
self.conversation.update_chat_history(
|
170 |
+
prompt, self.get_message(self.last_response)
|
171 |
+
)
|
172 |
+
|
173 |
+
def for_non_stream():
|
174 |
+
# let's make use of stream
|
175 |
+
for _ in for_stream():
|
176 |
+
pass
|
177 |
+
return self.last_response
|
178 |
+
|
179 |
+
return for_stream() if stream else for_non_stream()
|
180 |
+
|
181 |
+
def chat(
|
182 |
+
self,
|
183 |
+
prompt: str,
|
184 |
+
stream: bool = False,
|
185 |
+
optimizer: str = None,
|
186 |
+
conversationally: bool = False,
|
187 |
+
) -> str:
|
188 |
+
"""Generate response `str`
|
189 |
+
Args:
|
190 |
+
prompt (str): Prompt to be send.
|
191 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
192 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
193 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
194 |
+
Returns:
|
195 |
+
str: Response generated
|
196 |
+
"""
|
197 |
+
|
198 |
+
def for_stream():
|
199 |
+
for response in self.ask(
|
200 |
+
prompt, True, optimizer=optimizer, conversationally=conversationally
|
201 |
+
):
|
202 |
+
yield self.get_message(response)
|
203 |
+
|
204 |
+
def for_non_stream():
|
205 |
+
return self.get_message(
|
206 |
+
self.ask(
|
207 |
+
prompt,
|
208 |
+
False,
|
209 |
+
optimizer=optimizer,
|
210 |
+
conversationally=conversationally,
|
211 |
+
)
|
212 |
+
)
|
213 |
+
|
214 |
+
return for_stream() if stream else for_non_stream()
|
215 |
+
|
216 |
+
def get_message(self, response: dict) -> str:
|
217 |
+
"""Retrieves message only from response
|
218 |
+
|
219 |
+
Args:
|
220 |
+
response (dict): Response generated by `self.ask`
|
221 |
+
|
222 |
+
Returns:
|
223 |
+
str: Message extracted
|
224 |
+
"""
|
225 |
+
assert isinstance(response, dict), "Response should be of dict data-type only"
|
226 |
+
return response.get("text")
|
webscout/Provider/ThinkAnyAI.py
ADDED
@@ -0,0 +1,280 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
import uuid
|
3 |
+
from selenium import webdriver
|
4 |
+
from selenium.webdriver.chrome.options import Options
|
5 |
+
from selenium.webdriver.common.by import By
|
6 |
+
from selenium.webdriver.support import expected_conditions as EC
|
7 |
+
from selenium.webdriver.support.ui import WebDriverWait
|
8 |
+
import click
|
9 |
+
import requests
|
10 |
+
from requests import get
|
11 |
+
from uuid import uuid4
|
12 |
+
from re import findall
|
13 |
+
from requests.exceptions import RequestException
|
14 |
+
from curl_cffi.requests import get, RequestsError
|
15 |
+
import g4f
|
16 |
+
from random import randint
|
17 |
+
from PIL import Image
|
18 |
+
import io
|
19 |
+
import re
|
20 |
+
import json
|
21 |
+
import yaml
|
22 |
+
from ..AIutel import Optimizers
|
23 |
+
from ..AIutel import Conversation
|
24 |
+
from ..AIutel import AwesomePrompts, sanitize_stream
|
25 |
+
from ..AIbase import Provider, AsyncProvider
|
26 |
+
from Helpingai_T2 import Perplexity
|
27 |
+
from webscout import exceptions
|
28 |
+
from typing import Any, AsyncGenerator, Dict
|
29 |
+
import logging
|
30 |
+
import httpx
|
31 |
+
#------------------------------------ThinkAnyAI------------
|
32 |
+
class ThinkAnyAI(Provider):
|
33 |
+
def __init__(
|
34 |
+
self,
|
35 |
+
model: str = "claude-3-haiku",
|
36 |
+
locale: str = "en",
|
37 |
+
web_search: bool = False,
|
38 |
+
chunk_size: int = 1,
|
39 |
+
streaming: bool = True,
|
40 |
+
is_conversation: bool = True,
|
41 |
+
max_tokens: int = 600,
|
42 |
+
timeout: int = 30,
|
43 |
+
intro: str = None,
|
44 |
+
filepath: str = None,
|
45 |
+
update_file: bool = True,
|
46 |
+
proxies: dict = {},
|
47 |
+
history_offset: int = 10250,
|
48 |
+
act: str = None,
|
49 |
+
):
|
50 |
+
"""Initializes ThinkAnyAI
|
51 |
+
|
52 |
+
Args:
|
53 |
+
model (str): The AI model to be used for generating responses. Defaults to "claude-3-haiku".
|
54 |
+
locale (str): The language locale. Defaults to "en" (English).
|
55 |
+
web_search (bool): Whether to include web search results in the response. Defaults to False.
|
56 |
+
chunk_size (int): The size of data chunks when streaming responses. Defaults to 1.
|
57 |
+
streaming (bool): Whether to stream response data. Defaults to True.
|
58 |
+
is_conversation (bool, optional): Flag for chatting conversationally. Defaults to True.
|
59 |
+
max_tokens (int, optional): Maximum number of tokens to be generated upon completion. Defaults to 600.
|
60 |
+
timeout (int, optional): Http request timeout. Defaults to 30.
|
61 |
+
intro (str, optional): Conversation introductory prompt. Defaults to None.
|
62 |
+
filepath (str, optional): Path to file containing conversation history. Defaults to None.
|
63 |
+
update_file (bool, optional): Add new prompts and responses to the file. Defaults to True.
|
64 |
+
proxies (dict, optional): Http request proxies. Defaults to {}.
|
65 |
+
history_offset (int, optional): Limit conversation history to this number of last texts. Defaults to 10250.
|
66 |
+
act (str|int, optional): Awesome prompt key or index. (Used as intro). Defaults to None.
|
67 |
+
"""
|
68 |
+
self.base_url = "https://thinkany.ai/api"
|
69 |
+
self.model = model
|
70 |
+
self.locale = locale
|
71 |
+
self.web_search = web_search
|
72 |
+
self.chunk_size = chunk_size
|
73 |
+
self.streaming = streaming
|
74 |
+
self.last_response = {}
|
75 |
+
self.session = requests.Session()
|
76 |
+
self.session.proxies = proxies
|
77 |
+
|
78 |
+
self.__available_optimizers = (
|
79 |
+
method
|
80 |
+
for method in dir(Optimizers)
|
81 |
+
if callable(getattr(Optimizers, method)) and not method.startswith("__")
|
82 |
+
)
|
83 |
+
|
84 |
+
Conversation.intro = (
|
85 |
+
AwesomePrompts().get_act(
|
86 |
+
act, raise_not_found=True, default=None, case_insensitive=True
|
87 |
+
)
|
88 |
+
if act
|
89 |
+
else intro or Conversation.intro
|
90 |
+
)
|
91 |
+
self.conversation = Conversation(
|
92 |
+
is_conversation, max_tokens, filepath, update_file
|
93 |
+
)
|
94 |
+
self.conversation.history_offset = history_offset
|
95 |
+
|
96 |
+
def ask(
|
97 |
+
self,
|
98 |
+
prompt: str,
|
99 |
+
stream: bool = False,
|
100 |
+
raw: bool = False,
|
101 |
+
optimizer: str = None,
|
102 |
+
conversationally: bool = False,
|
103 |
+
) -> dict | AsyncGenerator:
|
104 |
+
"""Chat with AI asynchronously.
|
105 |
+
|
106 |
+
Args:
|
107 |
+
prompt (str): Prompt to be send.
|
108 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
109 |
+
raw (bool, optional): Stream back raw response as received. Defaults to False.
|
110 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defeaults to None
|
111 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
112 |
+
Returns:
|
113 |
+
dict : {}
|
114 |
+
```json
|
115 |
+
{
|
116 |
+
"content": "General Kenobi! \n\n(I couldn't help but respond with the iconic Star Wars greeting since you used it first. )\n\nIs there anything I can help you with today?\n[Image of Hello there General Kenobi]",
|
117 |
+
"conversation_id": "c_f13f6217f9a997aa",
|
118 |
+
"response_id": "r_d3665f95975c368f",
|
119 |
+
"factualityQueries": null,
|
120 |
+
"textQuery": [
|
121 |
+
"hello there",
|
122 |
+
1
|
123 |
+
],
|
124 |
+
"choices": [
|
125 |
+
{
|
126 |
+
"id": "rc_ea075c9671bfd8cb",
|
127 |
+
"content": [
|
128 |
+
"General Kenobi! \n\n(I couldn't help but respond with the iconic Star Wars greeting since you used it first. )\n\nIs there anything I can help you with today?\n[Image of Hello there General Kenobi]"
|
129 |
+
]
|
130 |
+
},
|
131 |
+
{
|
132 |
+
"id": "rc_de6dd3fb793a5402",
|
133 |
+
"content": [
|
134 |
+
"General Kenobi! (or just a friendly hello, whichever you prefer!). \n\nI see you're a person of culture as well. *Star Wars* references are always appreciated. \n\nHow can I help you today?\n"
|
135 |
+
]
|
136 |
+
},
|
137 |
+
{
|
138 |
+
"id": "rc_a672ac089caf32db",
|
139 |
+
"content": [
|
140 |
+
"General Kenobi! (or just a friendly hello if you're not a Star Wars fan!). \n\nHow can I help you today? Feel free to ask me anything, or tell me what you'd like to chat about. I'm here to assist in any way I can.\n[Image of Obi-Wan Kenobi saying hello there]"
|
141 |
+
]
|
142 |
+
}
|
143 |
+
],
|
144 |
+
|
145 |
+
"images": [
|
146 |
+
"https://i.pinimg.com/originals/40/74/60/407460925c9e419d82b93313f0b42f71.jpg"
|
147 |
+
]
|
148 |
+
}
|
149 |
+
|
150 |
+
```
|
151 |
+
"""
|
152 |
+
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
153 |
+
if optimizer:
|
154 |
+
if optimizer in self.__available_optimizers:
|
155 |
+
conversation_prompt = getattr(Optimizers, optimizer)(
|
156 |
+
conversation_prompt if conversationally else prompt
|
157 |
+
)
|
158 |
+
else:
|
159 |
+
raise Exception(
|
160 |
+
f"Optimizer is not one of {self.__available_optimizers}"
|
161 |
+
)
|
162 |
+
|
163 |
+
def initiate_conversation(query: str) -> str:
|
164 |
+
"""
|
165 |
+
Initiates a new conversation with the ThinkAny AI API.
|
166 |
+
|
167 |
+
Args:
|
168 |
+
query (str): The initial query to start the conversation.
|
169 |
+
|
170 |
+
Returns:
|
171 |
+
str: The UUID (Unique Identifier) of the conversation.
|
172 |
+
"""
|
173 |
+
url = f"{self.base_url}/new-conversation"
|
174 |
+
payload = {
|
175 |
+
"content": query,
|
176 |
+
"locale": self.locale,
|
177 |
+
"mode": "search" if self.web_search else "chat",
|
178 |
+
"model": self.model,
|
179 |
+
"source": "all",
|
180 |
+
}
|
181 |
+
response = self.session.post(url, json=payload)
|
182 |
+
return response.json().get("data", {}).get("uuid", "DevsDoCode")
|
183 |
+
|
184 |
+
def RAG_search(uuid: str) -> tuple[bool, list]:
|
185 |
+
"""
|
186 |
+
Performs a web search using the Retrieve And Generate (RAG) model.
|
187 |
+
|
188 |
+
Args:
|
189 |
+
uuid (str): The UUID of the conversation.
|
190 |
+
|
191 |
+
Returns:
|
192 |
+
tuple: A tuple containing a boolean indicating the success of the search
|
193 |
+
and a list of search result links.
|
194 |
+
"""
|
195 |
+
if not self.web_search:
|
196 |
+
return True, []
|
197 |
+
url = f"{self.base_url}/rag-search"
|
198 |
+
payload = {"conv_uuid": uuid}
|
199 |
+
response = self.session.post(url, json=payload)
|
200 |
+
links = [source["link"] for source in response.json().get("data", [])]
|
201 |
+
return response.json().get("message", "").strip(), links
|
202 |
+
|
203 |
+
def for_stream():
|
204 |
+
conversation_uuid = initiate_conversation(conversation_prompt)
|
205 |
+
web_search_result, links = RAG_search(conversation_uuid)
|
206 |
+
if not web_search_result:
|
207 |
+
print("Failed to generate WEB response. Making normal Query...")
|
208 |
+
|
209 |
+
url = f"{self.base_url}/chat"
|
210 |
+
payload = {
|
211 |
+
"role": "user",
|
212 |
+
"content": prompt,
|
213 |
+
"conv_uuid": conversation_uuid,
|
214 |
+
"model": self.model,
|
215 |
+
}
|
216 |
+
response = self.session.post(url, json=payload, stream=True)
|
217 |
+
complete_content = ""
|
218 |
+
for content in response.iter_content(
|
219 |
+
decode_unicode=True, chunk_size=self.chunk_size
|
220 |
+
):
|
221 |
+
complete_content += content
|
222 |
+
yield content if raw else dict(text=complete_content)
|
223 |
+
self.last_response.update(dict(text=complete_content, links=links))
|
224 |
+
self.conversation.update_chat_history(
|
225 |
+
prompt, self.get_message(self.last_response)
|
226 |
+
)
|
227 |
+
|
228 |
+
def for_non_stream():
|
229 |
+
for _ in for_stream():
|
230 |
+
pass
|
231 |
+
return self.last_response
|
232 |
+
|
233 |
+
return for_stream() if stream else for_non_stream()
|
234 |
+
|
235 |
+
def chat(
|
236 |
+
self,
|
237 |
+
prompt: str,
|
238 |
+
stream: bool = False,
|
239 |
+
optimizer: str = None,
|
240 |
+
conversationally: bool = False,
|
241 |
+
) -> str:
|
242 |
+
"""Generate response `str`
|
243 |
+
Args:
|
244 |
+
prompt (str): Prompt to be send.
|
245 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
246 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
247 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
248 |
+
Returns:
|
249 |
+
str: Response generated
|
250 |
+
"""
|
251 |
+
|
252 |
+
def for_stream():
|
253 |
+
for response in self.ask(
|
254 |
+
prompt, True, optimizer=optimizer, conversationally=conversationally
|
255 |
+
):
|
256 |
+
yield self.get_message(response)
|
257 |
+
|
258 |
+
def for_non_stream():
|
259 |
+
return self.get_message(
|
260 |
+
self.ask(
|
261 |
+
prompt,
|
262 |
+
False,
|
263 |
+
optimizer=optimizer,
|
264 |
+
conversationally=conversationally,
|
265 |
+
)
|
266 |
+
)
|
267 |
+
|
268 |
+
return for_stream() if stream else for_non_stream()
|
269 |
+
|
270 |
+
def get_message(self, response: Dict[str, Any]) -> str:
|
271 |
+
"""Retrieves message only from response
|
272 |
+
|
273 |
+
Args:
|
274 |
+
response (dict): Response generated by `self.ask`
|
275 |
+
|
276 |
+
Returns:
|
277 |
+
str: Message extracted
|
278 |
+
"""
|
279 |
+
assert isinstance(response, dict), "Response should be of dict data-type only"
|
280 |
+
return response["text"]
|
webscout/Provider/Xjai.py
ADDED
@@ -0,0 +1,230 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
import uuid
|
3 |
+
from selenium import webdriver
|
4 |
+
from selenium.webdriver.chrome.options import Options
|
5 |
+
from selenium.webdriver.common.by import By
|
6 |
+
from selenium.webdriver.support import expected_conditions as EC
|
7 |
+
from selenium.webdriver.support.ui import WebDriverWait
|
8 |
+
import click
|
9 |
+
import requests
|
10 |
+
from requests import get
|
11 |
+
from uuid import uuid4
|
12 |
+
from re import findall
|
13 |
+
from requests.exceptions import RequestException
|
14 |
+
from curl_cffi.requests import get, RequestsError
|
15 |
+
import g4f
|
16 |
+
from random import randint
|
17 |
+
from PIL import Image
|
18 |
+
import io
|
19 |
+
import re
|
20 |
+
import json
|
21 |
+
import yaml
|
22 |
+
from ..AIutel import Optimizers
|
23 |
+
from ..AIutel import Conversation
|
24 |
+
from ..AIutel import AwesomePrompts, sanitize_stream
|
25 |
+
from ..AIbase import Provider, AsyncProvider
|
26 |
+
from webscout import exceptions
|
27 |
+
from typing import Any, AsyncGenerator, Dict
|
28 |
+
import logging
|
29 |
+
import httpx
|
30 |
+
#-----------------------------------------------xjai-------------------------------------------
|
31 |
+
class Xjai(Provider):
|
32 |
+
def __init__(
|
33 |
+
self,
|
34 |
+
is_conversation: bool = True,
|
35 |
+
max_tokens: int = 600,
|
36 |
+
temperature: float = 0.8,
|
37 |
+
top_p: float = 1,
|
38 |
+
timeout: int = 30,
|
39 |
+
intro: str = None,
|
40 |
+
filepath: str = None,
|
41 |
+
update_file: bool = True,
|
42 |
+
proxies: dict = {},
|
43 |
+
history_offset: int = 10250,
|
44 |
+
act: str = None,
|
45 |
+
):
|
46 |
+
"""
|
47 |
+
Initializes the Xjai class for interacting with the Xjai AI chat API.
|
48 |
+
|
49 |
+
Args:
|
50 |
+
is_conversation (bool, optional): Flag for chatting conversationally. Defaults to True.
|
51 |
+
max_tokens (int, optional): Maximum number of tokens to be generated upon completion. Defaults to 600.
|
52 |
+
temperature (float, optional): The creativity level of the AI's response. Defaults to 0.8.
|
53 |
+
top_p (float, optional): The probability threshold for token selection. Defaults to 1.
|
54 |
+
timeout (int, optional): Http request timeout. Defaults to 30.
|
55 |
+
intro (str, optional): Conversation introductory prompt. Defaults to None.
|
56 |
+
filepath (str, optional): Path to file containing conversation history. Defaults to None.
|
57 |
+
update_file (bool, optional): Add new prompts and responses to the file. Defaults to True.
|
58 |
+
proxies (dict, optional): Http request proxies. Defaults to {}.
|
59 |
+
history_offset (int, optional): Limit conversation history to this number of last texts. Defaults to 10250.
|
60 |
+
act (str|int, optional): Awesome prompt key or index. (Used as intro). Defaults to None.
|
61 |
+
"""
|
62 |
+
self.session = requests.Session()
|
63 |
+
self.is_conversation = is_conversation
|
64 |
+
self.max_tokens_to_sample = max_tokens
|
65 |
+
self.temperature = temperature
|
66 |
+
self.top_p = top_p
|
67 |
+
self.chat_endpoint = "https://p1api.xjai.pro/freeapi/chat-process"
|
68 |
+
self.stream_chunk_size = 1 # Process response line by line
|
69 |
+
self.timeout = timeout
|
70 |
+
self.last_response = {}
|
71 |
+
|
72 |
+
self.__available_optimizers = (
|
73 |
+
method
|
74 |
+
for method in dir(Optimizers)
|
75 |
+
if callable(getattr(Optimizers, method)) and not method.startswith("__")
|
76 |
+
)
|
77 |
+
Conversation.intro = (
|
78 |
+
AwesomePrompts().get_act(
|
79 |
+
act, raise_not_found=True, default=None, case_insensitive=True
|
80 |
+
)
|
81 |
+
if act
|
82 |
+
else intro or Conversation.intro
|
83 |
+
)
|
84 |
+
self.conversation = Conversation(
|
85 |
+
is_conversation, self.max_tokens_to_sample, filepath, update_file
|
86 |
+
)
|
87 |
+
self.conversation.history_offset = history_offset
|
88 |
+
self.session.proxies = proxies
|
89 |
+
|
90 |
+
def ask(
|
91 |
+
self,
|
92 |
+
prompt: str,
|
93 |
+
stream: bool = False,
|
94 |
+
raw: bool = False,
|
95 |
+
optimizer: str = None,
|
96 |
+
conversationally: bool = False,
|
97 |
+
) -> Any:
|
98 |
+
"""
|
99 |
+
Sends a chat request to the Xjai AI chat API and returns the response.
|
100 |
+
|
101 |
+
Args:
|
102 |
+
prompt (str): The query to send to the AI.
|
103 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
104 |
+
raw (bool, optional): Stream back raw response as received. Defaults to False.
|
105 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
106 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
107 |
+
|
108 |
+
Returns:
|
109 |
+
Any: The response from the AI, either as a dictionary or a generator
|
110 |
+
depending on the `stream` and `raw` parameters.
|
111 |
+
"""
|
112 |
+
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
113 |
+
if optimizer:
|
114 |
+
if optimizer in self.__available_optimizers:
|
115 |
+
conversation_prompt = getattr(Optimizers, optimizer)(
|
116 |
+
conversation_prompt if conversationally else prompt
|
117 |
+
)
|
118 |
+
else:
|
119 |
+
raise Exception(
|
120 |
+
f"Optimizer is not one of {self.__available_optimizers}"
|
121 |
+
)
|
122 |
+
|
123 |
+
headers = {
|
124 |
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
|
125 |
+
"(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
|
126 |
+
}
|
127 |
+
|
128 |
+
payload = {
|
129 |
+
"prompt": conversation_prompt + "\n\nReply in English Only",
|
130 |
+
"systemMessage": "Reply in English Only",
|
131 |
+
"temperature": self.temperature,
|
132 |
+
"top_p": self.top_p
|
133 |
+
}
|
134 |
+
|
135 |
+
def generate_response():
|
136 |
+
response = self.session.post(
|
137 |
+
self.chat_endpoint, headers=headers, json=payload, stream=True, timeout=self.timeout
|
138 |
+
)
|
139 |
+
output = ""
|
140 |
+
print_next = False
|
141 |
+
|
142 |
+
for line in response.iter_lines(decode_unicode=True, chunk_size=self.stream_chunk_size):
|
143 |
+
line_content = line.decode("utf-8")
|
144 |
+
# Filter out irrelevant content
|
145 |
+
if '[ChatAI](https://srv.aiflarepro.com/#/?cid=4111)' in line_content:
|
146 |
+
continue
|
147 |
+
if '&KFw6loC9Qvy&' in line_content:
|
148 |
+
parts = line_content.split('&KFw6loC9Qvy&')
|
149 |
+
if print_next:
|
150 |
+
output += parts[0]
|
151 |
+
print_next = False
|
152 |
+
else:
|
153 |
+
output += parts[1]
|
154 |
+
print_next = True
|
155 |
+
if len(parts) > 2:
|
156 |
+
print_next = False
|
157 |
+
elif print_next:
|
158 |
+
output += line_content + '\n'
|
159 |
+
|
160 |
+
# Update chat history
|
161 |
+
self.conversation.update_chat_history(prompt, output)
|
162 |
+
|
163 |
+
return output
|
164 |
+
|
165 |
+
def for_stream():
|
166 |
+
response = generate_response()
|
167 |
+
for line in response.splitlines():
|
168 |
+
yield line if raw else dict(text=line)
|
169 |
+
|
170 |
+
def for_non_stream():
|
171 |
+
response = generate_response()
|
172 |
+
return response if raw else dict(text=response)
|
173 |
+
|
174 |
+
return for_stream() if stream else for_non_stream()
|
175 |
+
|
176 |
+
def chat(
|
177 |
+
self,
|
178 |
+
prompt: str,
|
179 |
+
stream: bool = False,
|
180 |
+
optimizer: str = None,
|
181 |
+
conversationally: bool = False,
|
182 |
+
) -> Any:
|
183 |
+
"""
|
184 |
+
Generates a response from the Xjai AI chat API.
|
185 |
+
|
186 |
+
Args:
|
187 |
+
prompt (str): The query to send to the AI.
|
188 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
189 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
190 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
191 |
+
|
192 |
+
Returns:
|
193 |
+
Any: The response from the AI, either as a string or a generator
|
194 |
+
depending on the `stream` parameter.
|
195 |
+
"""
|
196 |
+
|
197 |
+
def for_stream():
|
198 |
+
for response in self.ask(
|
199 |
+
prompt, True, optimizer=optimizer, conversationally=conversationally
|
200 |
+
):
|
201 |
+
yield self.get_message(response)
|
202 |
+
|
203 |
+
def for_non_stream():
|
204 |
+
return self.get_message(
|
205 |
+
self.ask(
|
206 |
+
prompt,
|
207 |
+
False,
|
208 |
+
optimizer=optimizer,
|
209 |
+
conversationally=conversationally,
|
210 |
+
)
|
211 |
+
)
|
212 |
+
|
213 |
+
return for_stream() if stream else for_non_stream()
|
214 |
+
|
215 |
+
def get_message(self, response: Any) -> str:
|
216 |
+
"""
|
217 |
+
Retrieves the message from the AI's response.
|
218 |
+
|
219 |
+
Args:
|
220 |
+
response (Any): The response from the AI, either a dictionary
|
221 |
+
or a raw string.
|
222 |
+
|
223 |
+
Returns:
|
224 |
+
str: The extracted message from the AI's response.
|
225 |
+
"""
|
226 |
+
if isinstance(response, dict):
|
227 |
+
return response["text"]
|
228 |
+
else: # Assume raw string
|
229 |
+
return response
|
230 |
+
|
webscout/Provider/Yepchat.py
ADDED
@@ -0,0 +1,478 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
import uuid
|
3 |
+
from selenium import webdriver
|
4 |
+
from selenium.webdriver.chrome.options import Options
|
5 |
+
from selenium.webdriver.common.by import By
|
6 |
+
from selenium.webdriver.support import expected_conditions as EC
|
7 |
+
from selenium.webdriver.support.ui import WebDriverWait
|
8 |
+
import click
|
9 |
+
import requests
|
10 |
+
from requests import get
|
11 |
+
from uuid import uuid4
|
12 |
+
from re import findall
|
13 |
+
from requests.exceptions import RequestException
|
14 |
+
from curl_cffi.requests import get, RequestsError
|
15 |
+
import g4f
|
16 |
+
from random import randint
|
17 |
+
from PIL import Image
|
18 |
+
import io
|
19 |
+
import re
|
20 |
+
import json
|
21 |
+
import yaml
|
22 |
+
from ..AIutel import Optimizers
|
23 |
+
from ..AIutel import Conversation
|
24 |
+
from ..AIutel import AwesomePrompts, sanitize_stream
|
25 |
+
from ..AIbase import Provider, AsyncProvider
|
26 |
+
from Helpingai_T2 import Perplexity
|
27 |
+
from webscout import exceptions
|
28 |
+
from typing import Any, AsyncGenerator, Dict
|
29 |
+
import logging
|
30 |
+
import httpx
|
31 |
+
|
32 |
+
#-------------------------------------------------------yep.com--------------------------------------------------------
|
33 |
+
class YEPCHAT(Provider):
|
34 |
+
def __init__(
|
35 |
+
self,
|
36 |
+
is_conversation: bool = True,
|
37 |
+
max_tokens: int = 600,
|
38 |
+
temperature: float = 0.6,
|
39 |
+
presence_penalty: int = 0,
|
40 |
+
frequency_penalty: int = 0,
|
41 |
+
top_p: float = 0.7,
|
42 |
+
model: str = "Mixtral-8x7B-Instruct-v0.1",
|
43 |
+
timeout: int = 30,
|
44 |
+
intro: str = None,
|
45 |
+
filepath: str = None,
|
46 |
+
update_file: bool = True,
|
47 |
+
proxies: dict = {},
|
48 |
+
history_offset: int = 10250,
|
49 |
+
act: str = None,
|
50 |
+
):
|
51 |
+
"""Instantiates YEPCHAT
|
52 |
+
|
53 |
+
Args:
|
54 |
+
is_conversation (bool, optional): Flag for chatting conversationally. Defaults to True.
|
55 |
+
max_tokens (int, optional): Maximum number of tokens to be generated upon completion. Defaults to 600.
|
56 |
+
temperature (float, optional): Charge of the generated text's randomness. Defaults to 0.6.
|
57 |
+
presence_penalty (int, optional): Chances of topic being repeated. Defaults to 0.
|
58 |
+
frequency_penalty (int, optional): Chances of word being repeated. Defaults to 0.
|
59 |
+
top_p (float, optional): Sampling threshold during inference time. Defaults to 0.7.
|
60 |
+
model (str, optional): LLM model name. Defaults to "gpt-3.5-turbo".
|
61 |
+
timeout (int, optional): Http request timeout. Defaults to 30.
|
62 |
+
intro (str, optional): Conversation introductory prompt. Defaults to None.
|
63 |
+
filepath (str, optional): Path to file containing conversation history. Defaults to None.
|
64 |
+
update_file (bool, optional): Add new prompts and responses to the file. Defaults to True.
|
65 |
+
proxies (dict, optional): Http request proxies. Defaults to {}.
|
66 |
+
history_offset (int, optional): Limit conversation history to this number of last texts. Defaults to 10250.
|
67 |
+
act (str|int, optional): Awesome prompt key or index. (Used as intro). Defaults to None.
|
68 |
+
"""
|
69 |
+
self.session = requests.Session()
|
70 |
+
self.is_conversation = is_conversation
|
71 |
+
self.max_tokens_to_sample = max_tokens
|
72 |
+
self.model = model
|
73 |
+
self.temperature = temperature
|
74 |
+
self.presence_penalty = presence_penalty
|
75 |
+
self.frequency_penalty = frequency_penalty
|
76 |
+
self.top_p = top_p
|
77 |
+
self.chat_endpoint = "https://api.yep.com/v1/chat/completions"
|
78 |
+
self.stream_chunk_size = 64
|
79 |
+
self.timeout = timeout
|
80 |
+
self.last_response = {}
|
81 |
+
self.headers = {
|
82 |
+
"Accept": "*/*",
|
83 |
+
"Accept-Encoding": "gzip, deflate",
|
84 |
+
"Accept-Language": "en-US,en;q=0.9",
|
85 |
+
"Content-Type": "application/json; charset=utf-8",
|
86 |
+
"Origin": "https://yep.com",
|
87 |
+
"Referer": "https://yep.com/",
|
88 |
+
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
89 |
+
}
|
90 |
+
|
91 |
+
self.__available_optimizers = (
|
92 |
+
method
|
93 |
+
for method in dir(Optimizers)
|
94 |
+
if callable(getattr(Optimizers, method)) and not method.startswith("__")
|
95 |
+
)
|
96 |
+
self.session.headers.update(self.headers)
|
97 |
+
Conversation.intro = (
|
98 |
+
AwesomePrompts().get_act(
|
99 |
+
act, raise_not_found=True, default=None, case_insensitive=True
|
100 |
+
)
|
101 |
+
if act
|
102 |
+
else intro or Conversation.intro
|
103 |
+
)
|
104 |
+
self.conversation = Conversation(
|
105 |
+
is_conversation, self.max_tokens_to_sample, filepath, update_file
|
106 |
+
)
|
107 |
+
self.conversation.history_offset = history_offset
|
108 |
+
self.session.proxies = proxies
|
109 |
+
|
110 |
+
def ask(
|
111 |
+
self,
|
112 |
+
prompt: str,
|
113 |
+
stream: bool = False,
|
114 |
+
raw: bool = False,
|
115 |
+
optimizer: str = None,
|
116 |
+
conversationally: bool = False,
|
117 |
+
) -> dict:
|
118 |
+
"""Chat with AI
|
119 |
+
|
120 |
+
Args:
|
121 |
+
prompt (str): Prompt to be send.
|
122 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
123 |
+
raw (bool, optional): Stream back raw response as received. Defaults to False.
|
124 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
125 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
126 |
+
Returns:
|
127 |
+
dict : {}
|
128 |
+
```json
|
129 |
+
{
|
130 |
+
"id": "cmpl-c61c1c88de4e4ad3a79134775d17ea0c",
|
131 |
+
"object": "chat.completion.chunk",
|
132 |
+
"created": 1713876886,
|
133 |
+
"model": "Mixtral-8x7B-Instruct-v0.1",
|
134 |
+
"choices": [
|
135 |
+
{
|
136 |
+
"index": 0,
|
137 |
+
"delta": {
|
138 |
+
"role": null,
|
139 |
+
"content": " Sure, I can help with that. Are you looking for information on how to start coding, or do you need help with a specific coding problem? We can discuss various programming languages like Python, JavaScript, Java, C++, or others. Please provide more details so I can assist you better."
|
140 |
+
},
|
141 |
+
"finish_reason": null
|
142 |
+
}
|
143 |
+
]
|
144 |
+
}
|
145 |
+
```
|
146 |
+
"""
|
147 |
+
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
148 |
+
if optimizer:
|
149 |
+
if optimizer in self.__available_optimizers:
|
150 |
+
conversation_prompt = getattr(Optimizers, optimizer)(
|
151 |
+
conversation_prompt if conversationally else prompt
|
152 |
+
)
|
153 |
+
else:
|
154 |
+
raise Exception(
|
155 |
+
f"Optimizer is not one of {self.__available_optimizers}"
|
156 |
+
)
|
157 |
+
self.session.headers.update(self.headers)
|
158 |
+
payload = {
|
159 |
+
"stream": True,
|
160 |
+
"max_tokens": 1280,
|
161 |
+
"top_p": self.top_p,
|
162 |
+
"temperature": self.temperature,
|
163 |
+
"messages": [{"content": conversation_prompt, "role": "user"}],
|
164 |
+
"model": self.model,
|
165 |
+
}
|
166 |
+
|
167 |
+
def for_stream():
|
168 |
+
response = self.session.post(
|
169 |
+
self.chat_endpoint, json=payload, stream=True, timeout=self.timeout
|
170 |
+
)
|
171 |
+
if not response.ok:
|
172 |
+
raise exceptions.FailedToGenerateResponseError(
|
173 |
+
f"Failed to generate response - ({response.status_code}, {response.reason}) - {response.text}"
|
174 |
+
)
|
175 |
+
|
176 |
+
message_load = ""
|
177 |
+
for value in response.iter_lines(
|
178 |
+
decode_unicode=True,
|
179 |
+
delimiter="" if raw else "data:",
|
180 |
+
chunk_size=self.stream_chunk_size,
|
181 |
+
):
|
182 |
+
try:
|
183 |
+
resp = json.loads(value)
|
184 |
+
incomplete_message = self.get_message(resp)
|
185 |
+
if incomplete_message:
|
186 |
+
message_load += incomplete_message
|
187 |
+
resp["choices"][0]["delta"]["content"] = message_load
|
188 |
+
self.last_response.update(resp)
|
189 |
+
yield value if raw else resp
|
190 |
+
elif raw:
|
191 |
+
yield value
|
192 |
+
except json.decoder.JSONDecodeError:
|
193 |
+
pass
|
194 |
+
self.conversation.update_chat_history(
|
195 |
+
prompt, self.get_message(self.last_response)
|
196 |
+
)
|
197 |
+
|
198 |
+
def for_non_stream():
|
199 |
+
for _ in for_stream():
|
200 |
+
pass
|
201 |
+
return self.last_response
|
202 |
+
|
203 |
+
return for_stream() if stream else for_non_stream()
|
204 |
+
|
205 |
+
def chat(
|
206 |
+
self,
|
207 |
+
prompt: str,
|
208 |
+
stream: bool = False,
|
209 |
+
optimizer: str = None,
|
210 |
+
conversationally: bool = False,
|
211 |
+
) -> str:
|
212 |
+
"""Generate response `str`
|
213 |
+
Args:
|
214 |
+
prompt (str): Prompt to be send.
|
215 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
216 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
217 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
218 |
+
Returns:
|
219 |
+
str: Response generated
|
220 |
+
"""
|
221 |
+
|
222 |
+
def for_stream():
|
223 |
+
for response in self.ask(
|
224 |
+
prompt, True, optimizer=optimizer, conversationally=conversationally
|
225 |
+
):
|
226 |
+
yield self.get_message(response)
|
227 |
+
|
228 |
+
def for_non_stream():
|
229 |
+
return self.get_message(
|
230 |
+
self.ask(
|
231 |
+
prompt,
|
232 |
+
False,
|
233 |
+
optimizer=optimizer,
|
234 |
+
conversationally=conversationally,
|
235 |
+
)
|
236 |
+
)
|
237 |
+
|
238 |
+
return for_stream() if stream else for_non_stream()
|
239 |
+
|
240 |
+
def get_message(self, response: dict) -> str:
|
241 |
+
"""Retrieves message only from response
|
242 |
+
|
243 |
+
Args:
|
244 |
+
response (dict): Response generated by `self.ask`
|
245 |
+
|
246 |
+
Returns:
|
247 |
+
str: Message extracted
|
248 |
+
"""
|
249 |
+
assert isinstance(response, dict), "Response should be of dict data-type only"
|
250 |
+
try:
|
251 |
+
if response["choices"][0].get("delta"):
|
252 |
+
return response["choices"][0]["delta"]["content"]
|
253 |
+
return response["choices"][0]["message"]["content"]
|
254 |
+
except KeyError:
|
255 |
+
return ""
|
256 |
+
class AsyncYEPCHAT(AsyncProvider):
|
257 |
+
def __init__(
|
258 |
+
self,
|
259 |
+
is_conversation: bool = True,
|
260 |
+
max_tokens: int = 600,
|
261 |
+
temperature: float = 0.6,
|
262 |
+
presence_penalty: int = 0,
|
263 |
+
frequency_penalty: int = 0,
|
264 |
+
top_p: float = 0.7,
|
265 |
+
model: str = "Mixtral-8x7B-Instruct-v0.1",
|
266 |
+
timeout: int = 30,
|
267 |
+
intro: str = None,
|
268 |
+
filepath: str = None,
|
269 |
+
update_file: bool = True,
|
270 |
+
proxies: dict = {},
|
271 |
+
history_offset: int = 10250,
|
272 |
+
act: str = None,
|
273 |
+
):
|
274 |
+
"""Instantiates YEPCHAT
|
275 |
+
|
276 |
+
Args:
|
277 |
+
is_conversation (bool, optional): Flag for chatting conversationally. Defaults to True.
|
278 |
+
max_tokens (int, optional): Maximum number of tokens to be generated upon completion. Defaults to 600.
|
279 |
+
temperature (float, optional): Charge of the generated text's randomness. Defaults to 0.6.
|
280 |
+
presence_penalty (int, optional): Chances of topic being repeated. Defaults to 0.
|
281 |
+
frequency_penalty (int, optional): Chances of word being repeated. Defaults to 0.
|
282 |
+
top_p (float, optional): Sampling threshold during inference time. Defaults to 0.7.
|
283 |
+
model (str, optional): LLM model name. Defaults to "gpt-3.5-turbo".
|
284 |
+
timeout (int, optional): Http request timeout. Defaults to 30.
|
285 |
+
intro (str, optional): Conversation introductory prompt. Defaults to None.
|
286 |
+
filepath (str, optional): Path to file containing conversation history. Defaults to None.
|
287 |
+
update_file (bool, optional): Add new prompts and responses to the file. Defaults to True.
|
288 |
+
proxies (dict, optional): Http request proxies. Defaults to {}.
|
289 |
+
history_offset (int, optional): Limit conversation history to this number of last texts. Defaults to 10250.
|
290 |
+
act (str|int, optional): Awesome prompt key or index. (Used as intro). Defaults to None.
|
291 |
+
"""
|
292 |
+
self.session = requests.Session()
|
293 |
+
self.is_conversation = is_conversation
|
294 |
+
self.max_tokens_to_sample = max_tokens
|
295 |
+
self.model = model
|
296 |
+
self.temperature = temperature
|
297 |
+
self.presence_penalty = presence_penalty
|
298 |
+
self.frequency_penalty = frequency_penalty
|
299 |
+
self.top_p = top_p
|
300 |
+
self.chat_endpoint = "https://api.yep.com/v1/chat/completions"
|
301 |
+
self.stream_chunk_size = 64
|
302 |
+
self.timeout = timeout
|
303 |
+
self.last_response = {}
|
304 |
+
self.headers = {
|
305 |
+
"Accept": "*/*",
|
306 |
+
"Accept-Encoding": "gzip, deflate",
|
307 |
+
"Accept-Language": "en-US,en;q=0.9",
|
308 |
+
"Content-Type": "application/json; charset=utf-8",
|
309 |
+
"Origin": "https://yep.com",
|
310 |
+
"Referer": "https://yep.com/",
|
311 |
+
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
312 |
+
}
|
313 |
+
|
314 |
+
self.__available_optimizers = (
|
315 |
+
method
|
316 |
+
for method in dir(Optimizers)
|
317 |
+
if callable(getattr(Optimizers, method)) and not method.startswith("__")
|
318 |
+
)
|
319 |
+
Conversation.intro = (
|
320 |
+
AwesomePrompts().get_act(
|
321 |
+
act, raise_not_found=True, default=None, case_insensitive=True
|
322 |
+
)
|
323 |
+
if act
|
324 |
+
else intro or Conversation.intro
|
325 |
+
)
|
326 |
+
self.conversation = Conversation(
|
327 |
+
is_conversation, self.max_tokens_to_sample, filepath, update_file
|
328 |
+
)
|
329 |
+
self.conversation.history_offset = history_offset
|
330 |
+
self.session = httpx.AsyncClient(
|
331 |
+
headers=self.headers,
|
332 |
+
proxies=proxies,
|
333 |
+
)
|
334 |
+
|
335 |
+
async def ask(
|
336 |
+
self,
|
337 |
+
prompt: str,
|
338 |
+
stream: bool = False,
|
339 |
+
raw: bool = False,
|
340 |
+
optimizer: str = None,
|
341 |
+
conversationally: bool = False,
|
342 |
+
) -> dict:
|
343 |
+
"""Chat with AI asynchronously.
|
344 |
+
|
345 |
+
Args:
|
346 |
+
prompt (str): Prompt to be send.
|
347 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
348 |
+
raw (bool, optional): Stream back raw response as received. Defaults to False.
|
349 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
350 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
351 |
+
Returns:
|
352 |
+
dict : {}
|
353 |
+
```json
|
354 |
+
{
|
355 |
+
"id": "cmpl-c61c1c88de4e4ad3a79134775d17ea0c",
|
356 |
+
"object": "chat.completion.chunk",
|
357 |
+
"created": 1713876886,
|
358 |
+
"model": "Mixtral-8x7B-Instruct-v0.1",
|
359 |
+
"choices": [
|
360 |
+
{
|
361 |
+
"index": 0,
|
362 |
+
"delta": {
|
363 |
+
"role": null,
|
364 |
+
"content": " Sure, I can help with that. Are you looking for information on how to start coding, or do you need help with a specific coding problem? We can discuss various programming languages like Python, JavaScript, Java, C++, or others. Please provide more details so I can assist you better."
|
365 |
+
},
|
366 |
+
"finish_reason": null
|
367 |
+
}
|
368 |
+
]
|
369 |
+
}
|
370 |
+
```
|
371 |
+
"""
|
372 |
+
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
373 |
+
if optimizer:
|
374 |
+
if optimizer in self.__available_optimizers:
|
375 |
+
conversation_prompt = getattr(Optimizers, optimizer)(
|
376 |
+
conversation_prompt if conversationally else prompt
|
377 |
+
)
|
378 |
+
else:
|
379 |
+
raise Exception(
|
380 |
+
f"Optimizer is not one of {self.__available_optimizers}"
|
381 |
+
)
|
382 |
+
payload = {
|
383 |
+
"stream": True,
|
384 |
+
"max_tokens": 1280,
|
385 |
+
"top_p": self.top_p,
|
386 |
+
"temperature": self.temperature,
|
387 |
+
"messages": [{"content": conversation_prompt, "role": "user"}],
|
388 |
+
"model": self.model,
|
389 |
+
}
|
390 |
+
|
391 |
+
async def for_stream():
|
392 |
+
async with self.session.stream(
|
393 |
+
"POST", self.chat_endpoint, json=payload, timeout=self.timeout
|
394 |
+
) as response:
|
395 |
+
if not response.is_success:
|
396 |
+
raise exceptions.FailedToGenerateResponseError(
|
397 |
+
f"Failed to generate response - ({response.status_code}, {response.reason_phrase}) - {response.text}"
|
398 |
+
)
|
399 |
+
|
400 |
+
message_load = ""
|
401 |
+
async for value in response.aiter_lines():
|
402 |
+
try:
|
403 |
+
resp = sanitize_stream(value)
|
404 |
+
incomplete_message = await self.get_message(resp)
|
405 |
+
if incomplete_message:
|
406 |
+
message_load += incomplete_message
|
407 |
+
resp["choices"][0]["delta"]["content"] = message_load
|
408 |
+
self.last_response.update(resp)
|
409 |
+
yield value if raw else resp
|
410 |
+
elif raw:
|
411 |
+
yield value
|
412 |
+
except json.decoder.JSONDecodeError:
|
413 |
+
pass
|
414 |
+
|
415 |
+
self.conversation.update_chat_history(
|
416 |
+
prompt, await self.get_message(self.last_response)
|
417 |
+
)
|
418 |
+
|
419 |
+
async def for_non_stream():
|
420 |
+
async for _ in for_stream():
|
421 |
+
pass
|
422 |
+
return self.last_response
|
423 |
+
|
424 |
+
return for_stream() if stream else await for_non_stream()
|
425 |
+
|
426 |
+
async def chat(
|
427 |
+
self,
|
428 |
+
prompt: str,
|
429 |
+
stream: bool = False,
|
430 |
+
optimizer: str = None,
|
431 |
+
conversationally: bool = False,
|
432 |
+
) -> str:
|
433 |
+
"""Generate response `str` asynchronously.
|
434 |
+
Args:
|
435 |
+
prompt (str): Prompt to be send.
|
436 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
437 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
438 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
439 |
+
Returns:
|
440 |
+
str: Response generated
|
441 |
+
"""
|
442 |
+
|
443 |
+
async def for_stream():
|
444 |
+
async_ask = await self.ask(
|
445 |
+
prompt, True, optimizer=optimizer, conversationally=conversationally
|
446 |
+
)
|
447 |
+
|
448 |
+
async for response in async_ask:
|
449 |
+
yield await self.get_message(response)
|
450 |
+
|
451 |
+
async def for_non_stream():
|
452 |
+
return await self.get_message(
|
453 |
+
await self.ask(
|
454 |
+
prompt,
|
455 |
+
False,
|
456 |
+
optimizer=optimizer,
|
457 |
+
conversationally=conversationally,
|
458 |
+
)
|
459 |
+
)
|
460 |
+
|
461 |
+
return for_stream() if stream else await for_non_stream()
|
462 |
+
|
463 |
+
async def get_message(self, response: dict) -> str:
|
464 |
+
"""Retrieves message only from response
|
465 |
+
|
466 |
+
Args:
|
467 |
+
response (dict): Response generated by `self.ask`
|
468 |
+
|
469 |
+
Returns:
|
470 |
+
str: Message extracted
|
471 |
+
"""
|
472 |
+
assert isinstance(response, dict), "Response should be of dict data-type only"
|
473 |
+
try:
|
474 |
+
if response["choices"][0].get("delta"):
|
475 |
+
return response["choices"][0]["delta"]["content"]
|
476 |
+
return response["choices"][0]["message"]["content"]
|
477 |
+
except KeyError:
|
478 |
+
return ""
|
webscout/Provider/Youchat.py
ADDED
@@ -0,0 +1,221 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
import uuid
|
3 |
+
from selenium import webdriver
|
4 |
+
from selenium.webdriver.chrome.options import Options
|
5 |
+
from selenium.webdriver.common.by import By
|
6 |
+
from selenium.webdriver.support import expected_conditions as EC
|
7 |
+
from selenium.webdriver.support.ui import WebDriverWait
|
8 |
+
import click
|
9 |
+
import requests
|
10 |
+
from requests import get
|
11 |
+
from uuid import uuid4
|
12 |
+
from re import findall
|
13 |
+
from requests.exceptions import RequestException
|
14 |
+
from curl_cffi.requests import get, RequestsError
|
15 |
+
import g4f
|
16 |
+
from random import randint
|
17 |
+
from PIL import Image
|
18 |
+
import io
|
19 |
+
import re
|
20 |
+
import json
|
21 |
+
import yaml
|
22 |
+
from ..AIutel import Optimizers
|
23 |
+
from ..AIutel import Conversation
|
24 |
+
from ..AIutel import AwesomePrompts, sanitize_stream
|
25 |
+
from ..AIbase import Provider, AsyncProvider
|
26 |
+
from Helpingai_T2 import Perplexity
|
27 |
+
from webscout import exceptions
|
28 |
+
from typing import Any, AsyncGenerator, Dict
|
29 |
+
import logging
|
30 |
+
import httpx
|
31 |
+
|
32 |
+
#-------------------------------------------------------youchat--------------------------------------------------------
|
33 |
+
class YouChat(Provider):
|
34 |
+
def __init__(
|
35 |
+
self,
|
36 |
+
is_conversation: bool = True,
|
37 |
+
max_tokens: int = 600,
|
38 |
+
timeout: int = 30,
|
39 |
+
intro: str = None,
|
40 |
+
filepath: str = None,
|
41 |
+
update_file: bool = True,
|
42 |
+
proxies: dict = {},
|
43 |
+
history_offset: int = 10250,
|
44 |
+
act: str = None,
|
45 |
+
):
|
46 |
+
self.session = requests.Session()
|
47 |
+
self.is_conversation = is_conversation
|
48 |
+
self.max_tokens_to_sample = max_tokens
|
49 |
+
self.chat_endpoint = "https://you.com/api/streamingSearch"
|
50 |
+
self.stream_chunk_size = 64
|
51 |
+
self.timeout = timeout
|
52 |
+
self.last_response = {}
|
53 |
+
|
54 |
+
self.payload = {
|
55 |
+
"q": "",
|
56 |
+
"page": 1,
|
57 |
+
"count": 10,
|
58 |
+
"safeSearch": "Off",
|
59 |
+
"onShoppingPage": False,
|
60 |
+
"mkt": "",
|
61 |
+
"responseFilter": "WebPages,Translations,TimeZone,Computation,RelatedSearches",
|
62 |
+
"domain": "youchat",
|
63 |
+
"queryTraceId": uuid.uuid4(),
|
64 |
+
"conversationTurnId": uuid.uuid4(),
|
65 |
+
"pastChatLength": 0,
|
66 |
+
"selectedChatMode": "default",
|
67 |
+
"chat": "[]",
|
68 |
+
}
|
69 |
+
|
70 |
+
self.headers = {
|
71 |
+
"cache-control": "no-cache",
|
72 |
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
|
73 |
+
'Referer': f'https://you.com/search?q={self.payload["q"]}&fromSearchBar=true&tbm=youchat&chatMode=default'
|
74 |
+
}
|
75 |
+
|
76 |
+
self.__available_optimizers = (
|
77 |
+
method
|
78 |
+
for method in dir(Optimizers)
|
79 |
+
if callable(getattr(Optimizers, method)) and not method.startswith("__")
|
80 |
+
)
|
81 |
+
self.session.headers.update(self.headers)
|
82 |
+
Conversation.intro = (
|
83 |
+
AwesomePrompts().get_act(
|
84 |
+
act, raise_not_found=True, default=None, case_insensitive=True
|
85 |
+
)
|
86 |
+
if act
|
87 |
+
else intro or Conversation.intro
|
88 |
+
)
|
89 |
+
self.conversation = Conversation(
|
90 |
+
is_conversation, self.max_tokens_to_sample, filepath, update_file
|
91 |
+
)
|
92 |
+
self.conversation.history_offset = history_offset
|
93 |
+
self.session.proxies = proxies
|
94 |
+
|
95 |
+
def ask(
|
96 |
+
self,
|
97 |
+
prompt: str,
|
98 |
+
stream: bool = False,
|
99 |
+
raw: bool = False,
|
100 |
+
optimizer: str = None,
|
101 |
+
conversationally: bool = False,
|
102 |
+
) -> dict:
|
103 |
+
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
104 |
+
if optimizer:
|
105 |
+
if optimizer in self.__available_optimizers:
|
106 |
+
conversation_prompt = getattr(Optimizers, optimizer)(
|
107 |
+
conversation_prompt if conversationally else prompt
|
108 |
+
)
|
109 |
+
else:
|
110 |
+
raise Exception(
|
111 |
+
f"Optimizer is not one of {self.__available_optimizers}"
|
112 |
+
)
|
113 |
+
self.session.headers.update(self.headers)
|
114 |
+
self.session.headers.update(
|
115 |
+
dict(
|
116 |
+
cookie=f"safesearch_guest=Off; uuid_guest={str(uuid4())}",
|
117 |
+
)
|
118 |
+
)
|
119 |
+
self.payload["q"] = prompt
|
120 |
+
|
121 |
+
def for_stream():
|
122 |
+
response = self.session.get(
|
123 |
+
self.chat_endpoint,
|
124 |
+
params=self.payload,
|
125 |
+
stream=True,
|
126 |
+
timeout=self.timeout,
|
127 |
+
)
|
128 |
+
|
129 |
+
if not response.ok:
|
130 |
+
raise exceptions.FailedToGenerateResponseError(
|
131 |
+
f"Failed to generate response - ({response.status_code}, {response.reason})"
|
132 |
+
)
|
133 |
+
|
134 |
+
streaming_response = ""
|
135 |
+
for line in response.iter_lines(decode_unicode=True, chunk_size=64):
|
136 |
+
if line:
|
137 |
+
modified_value = re.sub("data:", "", line)
|
138 |
+
try:
|
139 |
+
json_modified_value = json.loads(modified_value)
|
140 |
+
if "youChatToken" in json_modified_value:
|
141 |
+
streaming_response += json_modified_value["youChatToken"]
|
142 |
+
if print:
|
143 |
+
print(json_modified_value["youChatToken"], end="")
|
144 |
+
except:
|
145 |
+
continue
|
146 |
+
self.last_response.update(dict(text=streaming_response))
|
147 |
+
self.conversation.update_chat_history(
|
148 |
+
prompt, self.get_message(self.last_response)
|
149 |
+
)
|
150 |
+
return streaming_response
|
151 |
+
|
152 |
+
def for_non_stream():
|
153 |
+
for _ in for_stream():
|
154 |
+
pass
|
155 |
+
return self.last_response
|
156 |
+
|
157 |
+
return for_stream() if stream else for_non_stream()
|
158 |
+
|
159 |
+
def chat(
|
160 |
+
self,
|
161 |
+
prompt: str,
|
162 |
+
stream: bool = False,
|
163 |
+
optimizer: str = None,
|
164 |
+
conversationally: bool = False,
|
165 |
+
) -> str:
|
166 |
+
"""Generate response `str`
|
167 |
+
Args:
|
168 |
+
prompt (str): Prompt to be send.
|
169 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
170 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
171 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
172 |
+
Returns:
|
173 |
+
str: Response generated
|
174 |
+
"""
|
175 |
+
|
176 |
+
def chat(
|
177 |
+
self,
|
178 |
+
prompt: str,
|
179 |
+
stream: bool = False,
|
180 |
+
optimizer: str = None,
|
181 |
+
conversationally: bool = False,
|
182 |
+
) -> str:
|
183 |
+
"""Generate response `str`
|
184 |
+
Args:
|
185 |
+
prompt (str): Prompt to be send.
|
186 |
+
stream (bool, optional): Flag for streaming response. Defaults to False.
|
187 |
+
optimizer (str, optional): Prompt optimizer name - `[code, shell_command]`. Defaults to None.
|
188 |
+
conversationally (bool, optional): Chat conversationally when using optimizer. Defaults to False.
|
189 |
+
Returns:
|
190 |
+
str: Response generated
|
191 |
+
"""
|
192 |
+
|
193 |
+
def for_stream():
|
194 |
+
for response in self.ask(
|
195 |
+
prompt, True, optimizer=optimizer, conversationally=conversationally
|
196 |
+
):
|
197 |
+
yield self.get_message(response)
|
198 |
+
|
199 |
+
def for_non_stream():
|
200 |
+
return self.get_message(
|
201 |
+
self.ask(
|
202 |
+
prompt,
|
203 |
+
False,
|
204 |
+
optimizer=optimizer,
|
205 |
+
conversationally=conversationally,
|
206 |
+
)
|
207 |
+
)
|
208 |
+
|
209 |
+
return for_stream() if stream else for_non_stream()
|
210 |
+
|
211 |
+
def get_message(self, response: dict) -> str:
|
212 |
+
"""Retrieves message only from response
|
213 |
+
|
214 |
+
Args:
|
215 |
+
response (dict): Response generated by `self.ask`
|
216 |
+
|
217 |
+
Returns:
|
218 |
+
str: Message extracted
|
219 |
+
"""
|
220 |
+
assert isinstance(response, dict), "Response should be of dict data-type only"
|
221 |
+
return response["text"]
|
webscout/Provider/__init__.py
ADDED
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# webscout/providers/__init__.py
|
2 |
+
|
3 |
+
from .ThinkAnyAI import ThinkAnyAI
|
4 |
+
from .Xjai import Xjai
|
5 |
+
from .Llama2 import LLAMA2
|
6 |
+
from .Llama2 import AsyncLLAMA2
|
7 |
+
from .Cohere import Cohere
|
8 |
+
from .Reka import REKA
|
9 |
+
from .Groq import GROQ
|
10 |
+
from .Groq import AsyncGROQ
|
11 |
+
from .Openai import OPENAI
|
12 |
+
from .Openai import AsyncOPENAI
|
13 |
+
from .Leo import LEO
|
14 |
+
from .Leo import AsyncLEO
|
15 |
+
from .Koboldai import KOBOLDAI
|
16 |
+
from .Koboldai import AsyncKOBOLDAI
|
17 |
+
from .OpenGPT import OPENGPT
|
18 |
+
from .OpenGPT import AsyncOPENGPT
|
19 |
+
from .Perplexity import PERPLEXITY
|
20 |
+
from .Blackboxai import BLACKBOXAI
|
21 |
+
from .Blackboxai import AsyncBLACKBOXAI
|
22 |
+
from .Phind import PhindSearch
|
23 |
+
from .Phind import AsyncPhindSearch
|
24 |
+
from .Yepchat import YEPCHAT
|
25 |
+
from .Yepchat import AsyncYEPCHAT
|
26 |
+
from .Youchat import YouChat
|
27 |
+
from .Gemini import GEMINI
|
28 |
+
from .Berlin4h import Berlin4h
|
29 |
+
from .ChatGPTUK import ChatGPTUK
|
30 |
+
from .Poe import POE
|
31 |
+
from .BasedGPT import *
|
32 |
+
__all__ = [
|
33 |
+
'ThinkAnyAI',
|
34 |
+
'Xjai',
|
35 |
+
'LLAMA2',
|
36 |
+
'AsyncLLAMA2',
|
37 |
+
'Cohere',
|
38 |
+
'REKA',
|
39 |
+
'GROQ',
|
40 |
+
'AsyncGROQ',
|
41 |
+
'OPENAI',
|
42 |
+
'AsyncOPENAI',
|
43 |
+
'LEO',
|
44 |
+
'AsyncLEO',
|
45 |
+
'KOBOLDAI',
|
46 |
+
'AsyncKOBOLDAI',
|
47 |
+
'OPENGPT',
|
48 |
+
'AsyncOPENGPT',
|
49 |
+
'PERPLEXITY',
|
50 |
+
'BLACKBOXAI',
|
51 |
+
'AsyncBLACKBOXAI',
|
52 |
+
'PhindSearch',
|
53 |
+
'AsyncPhindSearch',
|
54 |
+
'YEPCHAT',
|
55 |
+
'AsyncYEPCHAT',
|
56 |
+
'YouChat',
|
57 |
+
'GEMINI',
|
58 |
+
'Berlin4h',
|
59 |
+
'ChatGPTUK',
|
60 |
+
'POE'
|
61 |
+
]
|
webscout/Provider/__pycache__/BasedGPT.cpython-311.pyc
ADDED
Binary file (11 kB). View file
|
|
webscout/Provider/__pycache__/Berlin4h.cpython-311.pyc
ADDED
Binary file (10.6 kB). View file
|
|
webscout/Provider/__pycache__/Blackboxai.cpython-311.pyc
ADDED
Binary file (19.7 kB). View file
|
|
webscout/Provider/__pycache__/ChatGPTUK.cpython-311.pyc
ADDED
Binary file (10.8 kB). View file
|
|
webscout/Provider/__pycache__/ChatGPTlogin.cpython-311.pyc
ADDED
Binary file (11.7 kB). View file
|
|
webscout/Provider/__pycache__/Cohere.cpython-311.pyc
ADDED
Binary file (11.5 kB). View file
|
|
webscout/Provider/__pycache__/Gemini.cpython-311.pyc
ADDED
Binary file (11.6 kB). View file
|
|
webscout/Provider/__pycache__/Groq.cpython-311.pyc
ADDED
Binary file (23.6 kB). View file
|
|
webscout/Provider/__pycache__/Koboldai.cpython-311.pyc
ADDED
Binary file (18.4 kB). View file
|
|