Abhaykoul commited on
Commit
9e7090f
1 Parent(s): 0b77379

Upload 85 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. webscout/AIauto.py +493 -0
  2. webscout/AIbase.py +138 -0
  3. webscout/AIutel.py +995 -0
  4. webscout/DWEBS.py +197 -0
  5. webscout/LLM.py +45 -0
  6. webscout/Local/__init__.py +10 -0
  7. webscout/Local/__pycache__/__init__.cpython-311.pyc +0 -0
  8. webscout/Local/__pycache__/_version.cpython-311.pyc +0 -0
  9. webscout/Local/__pycache__/formats.cpython-311.pyc +0 -0
  10. webscout/Local/__pycache__/model.cpython-311.pyc +0 -0
  11. webscout/Local/__pycache__/samplers.cpython-311.pyc +0 -0
  12. webscout/Local/__pycache__/test.cpython-311.pyc +0 -0
  13. webscout/Local/__pycache__/thread.cpython-311.pyc +0 -0
  14. webscout/Local/__pycache__/utils.cpython-311.pyc +0 -0
  15. webscout/Local/_version.py +3 -0
  16. webscout/Local/formats.py +535 -0
  17. webscout/Local/model.py +702 -0
  18. webscout/Local/samplers.py +161 -0
  19. webscout/Local/thread.py +690 -0
  20. webscout/Local/utils.py +185 -0
  21. webscout/Provider/BasedGPT.py +226 -0
  22. webscout/Provider/Berlin4h.py +211 -0
  23. webscout/Provider/Blackboxai.py +440 -0
  24. webscout/Provider/ChatGPTUK.py +214 -0
  25. webscout/Provider/Cohere.py +223 -0
  26. webscout/Provider/Gemini.py +217 -0
  27. webscout/Provider/Groq.py +512 -0
  28. webscout/Provider/Koboldai.py +402 -0
  29. webscout/Provider/Leo.py +469 -0
  30. webscout/Provider/Llama2.py +437 -0
  31. webscout/Provider/OpenGPT.py +487 -0
  32. webscout/Provider/Openai.py +511 -0
  33. webscout/Provider/Perplexity.py +230 -0
  34. webscout/Provider/Phind.py +518 -0
  35. webscout/Provider/Poe.py +208 -0
  36. webscout/Provider/Reka.py +226 -0
  37. webscout/Provider/ThinkAnyAI.py +280 -0
  38. webscout/Provider/Xjai.py +230 -0
  39. webscout/Provider/Yepchat.py +478 -0
  40. webscout/Provider/Youchat.py +221 -0
  41. webscout/Provider/__init__.py +61 -0
  42. webscout/Provider/__pycache__/BasedGPT.cpython-311.pyc +0 -0
  43. webscout/Provider/__pycache__/Berlin4h.cpython-311.pyc +0 -0
  44. webscout/Provider/__pycache__/Blackboxai.cpython-311.pyc +0 -0
  45. webscout/Provider/__pycache__/ChatGPTUK.cpython-311.pyc +0 -0
  46. webscout/Provider/__pycache__/ChatGPTlogin.cpython-311.pyc +0 -0
  47. webscout/Provider/__pycache__/Cohere.cpython-311.pyc +0 -0
  48. webscout/Provider/__pycache__/Gemini.cpython-311.pyc +0 -0
  49. webscout/Provider/__pycache__/Groq.cpython-311.pyc +0 -0
  50. 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