webscout 3.0__tar.gz → 3.1b0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of webscout might be problematic. Click here for more details.

Files changed (72) hide show
  1. {webscout-3.0 → webscout-3.1b0}/PKG-INFO +1 -1
  2. {webscout-3.0 → webscout-3.1b0}/setup.py +1 -1
  3. webscout-3.1b0/webscout/Local/rawdog.py +948 -0
  4. {webscout-3.0 → webscout-3.1b0}/webscout.egg-info/PKG-INFO +1 -1
  5. {webscout-3.0 → webscout-3.1b0}/webscout.egg-info/SOURCES.txt +1 -0
  6. {webscout-3.0 → webscout-3.1b0}/DeepWEBS/__init__.py +0 -0
  7. {webscout-3.0 → webscout-3.1b0}/DeepWEBS/documents/__init__.py +0 -0
  8. {webscout-3.0 → webscout-3.1b0}/DeepWEBS/documents/query_results_extractor.py +0 -0
  9. {webscout-3.0 → webscout-3.1b0}/DeepWEBS/documents/webpage_content_extractor.py +0 -0
  10. {webscout-3.0 → webscout-3.1b0}/DeepWEBS/networks/__init__.py +0 -0
  11. {webscout-3.0 → webscout-3.1b0}/DeepWEBS/networks/filepath_converter.py +0 -0
  12. {webscout-3.0 → webscout-3.1b0}/DeepWEBS/networks/google_searcher.py +0 -0
  13. {webscout-3.0 → webscout-3.1b0}/DeepWEBS/networks/network_configs.py +0 -0
  14. {webscout-3.0 → webscout-3.1b0}/DeepWEBS/networks/webpage_fetcher.py +0 -0
  15. {webscout-3.0 → webscout-3.1b0}/DeepWEBS/utilsdw/__init__.py +0 -0
  16. {webscout-3.0 → webscout-3.1b0}/DeepWEBS/utilsdw/enver.py +0 -0
  17. {webscout-3.0 → webscout-3.1b0}/DeepWEBS/utilsdw/logger.py +0 -0
  18. {webscout-3.0 → webscout-3.1b0}/LICENSE.md +0 -0
  19. {webscout-3.0 → webscout-3.1b0}/README.md +0 -0
  20. {webscout-3.0 → webscout-3.1b0}/setup.cfg +0 -0
  21. {webscout-3.0 → webscout-3.1b0}/webscout/AIauto.py +0 -0
  22. {webscout-3.0 → webscout-3.1b0}/webscout/AIbase.py +0 -0
  23. {webscout-3.0 → webscout-3.1b0}/webscout/AIutel.py +0 -0
  24. {webscout-3.0 → webscout-3.1b0}/webscout/DWEBS.py +0 -0
  25. {webscout-3.0 → webscout-3.1b0}/webscout/LLM.py +0 -0
  26. {webscout-3.0 → webscout-3.1b0}/webscout/Local/__init__.py +0 -0
  27. {webscout-3.0 → webscout-3.1b0}/webscout/Local/_version.py +0 -0
  28. {webscout-3.0 → webscout-3.1b0}/webscout/Local/formats.py +0 -0
  29. {webscout-3.0 → webscout-3.1b0}/webscout/Local/model.py +0 -0
  30. {webscout-3.0 → webscout-3.1b0}/webscout/Local/samplers.py +0 -0
  31. {webscout-3.0 → webscout-3.1b0}/webscout/Local/thread.py +0 -0
  32. {webscout-3.0 → webscout-3.1b0}/webscout/Local/utils.py +0 -0
  33. {webscout-3.0 → webscout-3.1b0}/webscout/Provider/BasedGPT.py +0 -0
  34. {webscout-3.0 → webscout-3.1b0}/webscout/Provider/Berlin4h.py +0 -0
  35. {webscout-3.0 → webscout-3.1b0}/webscout/Provider/Blackboxai.py +0 -0
  36. {webscout-3.0 → webscout-3.1b0}/webscout/Provider/ChatGPTUK.py +0 -0
  37. {webscout-3.0 → webscout-3.1b0}/webscout/Provider/Cohere.py +0 -0
  38. {webscout-3.0 → webscout-3.1b0}/webscout/Provider/Gemini.py +0 -0
  39. {webscout-3.0 → webscout-3.1b0}/webscout/Provider/Groq.py +0 -0
  40. {webscout-3.0 → webscout-3.1b0}/webscout/Provider/Koboldai.py +0 -0
  41. {webscout-3.0 → webscout-3.1b0}/webscout/Provider/Leo.py +0 -0
  42. {webscout-3.0 → webscout-3.1b0}/webscout/Provider/Llama2.py +0 -0
  43. {webscout-3.0 → webscout-3.1b0}/webscout/Provider/OpenGPT.py +0 -0
  44. {webscout-3.0 → webscout-3.1b0}/webscout/Provider/Openai.py +0 -0
  45. {webscout-3.0 → webscout-3.1b0}/webscout/Provider/Perplexity.py +0 -0
  46. {webscout-3.0 → webscout-3.1b0}/webscout/Provider/Phind.py +0 -0
  47. {webscout-3.0 → webscout-3.1b0}/webscout/Provider/Poe.py +0 -0
  48. {webscout-3.0 → webscout-3.1b0}/webscout/Provider/Reka.py +0 -0
  49. {webscout-3.0 → webscout-3.1b0}/webscout/Provider/ThinkAnyAI.py +0 -0
  50. {webscout-3.0 → webscout-3.1b0}/webscout/Provider/Xjai.py +0 -0
  51. {webscout-3.0 → webscout-3.1b0}/webscout/Provider/Yepchat.py +0 -0
  52. {webscout-3.0 → webscout-3.1b0}/webscout/Provider/Youchat.py +0 -0
  53. {webscout-3.0 → webscout-3.1b0}/webscout/Provider/__init__.py +0 -0
  54. {webscout-3.0 → webscout-3.1b0}/webscout/__init__.py +0 -0
  55. {webscout-3.0 → webscout-3.1b0}/webscout/__main__.py +0 -0
  56. {webscout-3.0 → webscout-3.1b0}/webscout/async_providers.py +0 -0
  57. {webscout-3.0 → webscout-3.1b0}/webscout/cli.py +0 -0
  58. {webscout-3.0 → webscout-3.1b0}/webscout/exceptions.py +0 -0
  59. {webscout-3.0 → webscout-3.1b0}/webscout/g4f.py +0 -0
  60. {webscout-3.0 → webscout-3.1b0}/webscout/models.py +0 -0
  61. {webscout-3.0 → webscout-3.1b0}/webscout/tempid.py +0 -0
  62. {webscout-3.0 → webscout-3.1b0}/webscout/transcriber.py +0 -0
  63. {webscout-3.0 → webscout-3.1b0}/webscout/utils.py +0 -0
  64. {webscout-3.0 → webscout-3.1b0}/webscout/version.py +0 -0
  65. {webscout-3.0 → webscout-3.1b0}/webscout/voice.py +0 -0
  66. {webscout-3.0 → webscout-3.1b0}/webscout/webai.py +0 -0
  67. {webscout-3.0 → webscout-3.1b0}/webscout/webscout_search.py +0 -0
  68. {webscout-3.0 → webscout-3.1b0}/webscout/webscout_search_async.py +0 -0
  69. {webscout-3.0 → webscout-3.1b0}/webscout.egg-info/dependency_links.txt +0 -0
  70. {webscout-3.0 → webscout-3.1b0}/webscout.egg-info/entry_points.txt +0 -0
  71. {webscout-3.0 → webscout-3.1b0}/webscout.egg-info/requires.txt +0 -0
  72. {webscout-3.0 → webscout-3.1b0}/webscout.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: webscout
3
- Version: 3.0
3
+ Version: 3.1b0
4
4
  Summary: Search for anything using Google, DuckDuckGo, phind.com, Contains AI models, can transcribe yt videos, temporary email and phone number generation, has TTS support, webai (terminal gpt and open interpreter) and offline LLMs
5
5
  Author: OEvortex
6
6
  Author-email: helpingai5@gmail.com
@@ -5,7 +5,7 @@ with open("README.md", encoding="utf-8") as f:
5
5
 
6
6
  setup(
7
7
  name="webscout",
8
- version="3.0",
8
+ version="3.1-beta",
9
9
  description="Search for anything using Google, DuckDuckGo, phind.com, Contains AI models, can transcribe yt videos, temporary email and phone number generation, has TTS support, webai (terminal gpt and open interpreter) and offline LLMs",
10
10
  long_description=README,
11
11
  long_description_content_type="text/markdown",
@@ -0,0 +1,948 @@
1
+ # webscout/Local/rawdog.py
2
+
3
+ from datetime import datetime
4
+ from ._version import __version__, __llama_cpp_version__
5
+
6
+ """Submodule containing the RawDog class, used for interaction with a Model"""
7
+
8
+ import sys
9
+ import os
10
+ import json
11
+ import re
12
+ from typing import Literal, Optional, Union
13
+ from pathlib import Path
14
+ from subprocess import run, CalledProcessError
15
+ import click
16
+ import os
17
+ import json
18
+ import platform
19
+ import subprocess
20
+ import logging
21
+ import appdirs
22
+ import datetime
23
+ import re
24
+ from .model import *
25
+ from .utils import (
26
+ RESET_ALL,
27
+ _SupportsWriteAndFlush,
28
+ cls,
29
+ print_verbose,
30
+ truncate,
31
+ run_system_command
32
+ )
33
+ from .samplers import SamplerSettings, DefaultSampling
34
+ from .formats import AdvancedFormat
35
+ from .formats import blank as formats_blank
36
+ from rich.markdown import Markdown
37
+ from rich.console import Console
38
+ appdir = appdirs.AppDirs("AIWEBS", "vortex")
39
+
40
+ default_path = appdir.user_cache_dir
41
+
42
+ if not os.path.exists(default_path):
43
+ os.makedirs(default_path)
44
+ class Message(dict):
45
+ """
46
+ A dictionary representing a single message within a Thread
47
+
48
+ Works just like a normal `dict`, but a new method:
49
+ - `.as_string` - Return the full message string
50
+
51
+ Generally, messages have these keys:
52
+ - `role` - The role of the speaker: 'system', 'user', or 'bot'
53
+ - `prefix` - The text that prefixes the message content
54
+ - `content` - The actual content of the message
55
+ - `suffix` - The text that suffixes the message content
56
+ """
57
+
58
+ def __repr__(self) -> str:
59
+ return \
60
+ f"Message([" \
61
+ f"('role', {repr(self['role'])}), " \
62
+ f"('prefix', {repr(self['prefix'])}), " \
63
+ f"('content', {repr(self['content'])}), " \
64
+ f"('suffix', {repr(self['suffix'])})])"
65
+
66
+ def as_string(self):
67
+ """Return the full message string"""
68
+ try:
69
+ return self['prefix'] + self['content'] + self['suffix']
70
+ except KeyError as e:
71
+ e.add_note(
72
+ "as_string: Message is missing one or more of the "
73
+ "required 'prefix', 'content', 'suffix' attributes - this is "
74
+ "unexpected"
75
+ )
76
+ raise e
77
+
78
+
79
+ class Thread:
80
+ """
81
+ Provide functionality to facilitate easy interactions with a Model
82
+
83
+ This is just a brief overview of m.Thread.
84
+ To see a full description of each method and its parameters,
85
+ call help(Thread), or see the relevant docstring.
86
+
87
+ The following methods are available:
88
+ - `.add_message()` - Add a message to `Thread.messages`
89
+ - `.as_string()` - Return this thread's complete message history as a string
90
+ - `.create_message()` - Create a message using the format of this thread
91
+ - `.inference_str_from_messages()` - Using the list of messages, return a string suitable for inference
92
+ - `.interact()` - Start an interactive, terminal-based chat session
93
+ - `.len_messages()` - Get the total length of all messages in tokens
94
+ - `.print_stats()` - Print stats about the context usage in this thread
95
+ - `.reset()` - Clear the list of messages
96
+ - `.send()` - Send a message in this thread
97
+
98
+ The following attributes are available:
99
+ - `.format` - The format being used for messages in this thread
100
+ - `.messages` - The list of messages in this thread
101
+ - `.model` - The `m.Model` instance used by this thread
102
+ - `.sampler` - The SamplerSettings object used in this thread
103
+ """
104
+
105
+ def __init__(
106
+ self,
107
+ model: Model,
108
+ format: Union[dict, AdvancedFormat],
109
+ sampler: SamplerSettings = DefaultSampling,
110
+ messages: Optional[list[Message]] = None,
111
+
112
+ ):
113
+
114
+ """
115
+ Given a Model and a format, construct a Thread instance.
116
+
117
+ model: The Model to use for text generation
118
+ format: The format specifying how messages should be structured (see m.formats)
119
+
120
+ The following parameters are optional:
121
+ - sampler: The SamplerSettings object used to control text generation
122
+ - messages: A list of m.thread.Message objects to add to the Thread upon construction
123
+ """
124
+
125
+ assert isinstance(model, Model), \
126
+ "Thread: model should be an " + \
127
+ f"instance of webscout.Local.Model, not {type(model)}"
128
+
129
+ assert_model_is_loaded(model)
130
+
131
+ assert isinstance(format, (dict, AdvancedFormat)), \
132
+ f"Thread: format should be dict or AdvancedFormat, not {type(format)}"
133
+
134
+ if any(k not in format.keys() for k in formats_blank.keys()):
135
+ raise KeyError(
136
+ "Thread: format is missing one or more required keys, see " + \
137
+ "webscout.Local.formats.blank for an example"
138
+ )
139
+
140
+ assert isinstance(format['stops'], list), \
141
+ "Thread: format['stops'] should be list, not " + \
142
+ f"{type(format['stops'])}"
143
+
144
+ assert all(
145
+ hasattr(sampler, attr) for attr in [
146
+ 'max_len_tokens',
147
+ 'temp',
148
+ 'top_p',
149
+ 'min_p',
150
+ 'frequency_penalty',
151
+ 'presence_penalty',
152
+ 'repeat_penalty',
153
+ 'top_k'
154
+ ]
155
+ ), 'Thread: sampler is missing one or more required attributes'
156
+
157
+ self._messages: Optional[list[Message]] = messages
158
+ if self._messages is not None:
159
+ if not all(isinstance(msg, Message) for msg in self._messages):
160
+ raise TypeError(
161
+ "Thread: one or more messages provided to __init__() is "
162
+ "not an instance of m.thread.Message"
163
+ )
164
+
165
+ # Thread.messages is never empty, unless `messages` param is explicity
166
+ # set to `[]` during construction
167
+
168
+ self.model: Model = model
169
+ self.format: Union[dict, AdvancedFormat] = format
170
+ self.messages: list[Message] = [
171
+ self.create_message("system", self.format['system_content'])
172
+ ] if self._messages is None else self._messages
173
+ self.sampler: SamplerSettings = sampler
174
+ self.tools = []
175
+ if self.model.verbose:
176
+ print_verbose("new Thread instance with the following attributes:")
177
+ print_verbose(f"model == {self.model}")
178
+ print_verbose(f"format['system_prefix'] == {truncate(repr(self.format['system_prefix']))}")
179
+ print_verbose(f"format['system_content'] == {truncate(repr(self.format['system_content']))}")
180
+ print_verbose(f"format['system_suffix'] == {truncate(repr(self.format['system_suffix']))}")
181
+ print_verbose(f"format['user_prefix'] == {truncate(repr(self.format['user_prefix']))}")
182
+ print_verbose(f"format['user_content'] == {truncate(repr(self.format['user_content']))}")
183
+ print_verbose(f"format['user_suffix'] == {truncate(repr(self.format['user_suffix']))}")
184
+ print_verbose(f"format['bot_prefix'] == {truncate(repr(self.format['bot_prefix']))}")
185
+ print_verbose(f"format['bot_content'] == {truncate(repr(self.format['bot_content']))}")
186
+ print_verbose(f"format['bot_suffix'] == {truncate(repr(self.format['bot_suffix']))}")
187
+ print_verbose(f"format['stops'] == {truncate(repr(self.format['stops']))}")
188
+ print_verbose(f"sampler.temp == {self.sampler.temp}")
189
+ print_verbose(f"sampler.top_p == {self.sampler.top_p}")
190
+ print_verbose(f"sampler.min_p == {self.sampler.min_p}")
191
+ print_verbose(f"sampler.frequency_penalty == {self.sampler.frequency_penalty}")
192
+ print_verbose(f"sampler.presence_penalty == {self.sampler.presence_penalty}")
193
+ print_verbose(f"sampler.repeat_penalty == {self.sampler.repeat_penalty}")
194
+ print_verbose(f"sampler.top_k == {self.sampler.top_k}")
195
+ def add_tool(self, tool: dict):
196
+ """Adds a tool to the Thread for function calling."""
197
+ self.tools.append(tool)
198
+ self.model.register_tool(tool['function']['name'], tool['function']['execute']) # Register the tool
199
+
200
+ # Include tool information in the system message (optional, but helpful)
201
+ self.messages[0]['content'] += f"\nYou have access to the following tool:\n{tool['function']['description']}"
202
+ def __repr__(self) -> str:
203
+ return \
204
+ f"Thread({repr(self.model)}, {repr(self.format)}, " + \
205
+ f"{repr(self.sampler)}, {repr(self.messages)})"
206
+
207
+ def __str__(self) -> str:
208
+ return self.as_string()
209
+
210
+ def __len__(self) -> int:
211
+ """
212
+ `len(Thread)` returns the length of the Thread in tokens
213
+
214
+ To get the number of messages in the Thread, use `len(Thread.messages)`
215
+ """
216
+ return self.len_messages()
217
+
218
+ def create_message(
219
+ self,
220
+ role: Literal['system', 'user', 'bot'],
221
+ content: str
222
+ ) -> Message:
223
+ """
224
+ Construct a message using the format of this Thread
225
+ """
226
+
227
+ assert role.lower() in ['system', 'user', 'bot'], \
228
+ f"create_message: role should be 'system', 'user', or 'bot', not '{role.lower()}'"
229
+
230
+ assert isinstance(content, str), \
231
+ f"create_message: content should be str, not {type(content)}"
232
+
233
+ if role.lower() == 'system':
234
+ return Message(
235
+ [
236
+ ('role', 'system'),
237
+ ('prefix', self.format['system_prefix']),
238
+ ('content', content),
239
+ ('suffix', self.format['system_suffix'])
240
+ ]
241
+ )
242
+
243
+ elif role.lower() == 'user':
244
+ return Message(
245
+ [
246
+ ('role', 'user'),
247
+ ('prefix', self.format['user_prefix']),
248
+ ('content', content),
249
+ ('suffix', self.format['user_suffix'])
250
+ ]
251
+ )
252
+
253
+ elif role.lower() == 'bot':
254
+ return Message(
255
+ [
256
+ ('role', 'bot'),
257
+ ('prefix', self.format['bot_prefix']),
258
+ ('content', content),
259
+ ('suffix', self.format['bot_suffix'])
260
+ ]
261
+ )
262
+
263
+ def len_messages(self) -> int:
264
+ """
265
+ Return the total length of all messages in this thread, in tokens.
266
+
267
+ Can also use `len(Thread)`."""
268
+
269
+ return self.model.get_length(self.as_string())
270
+
271
+ def add_message(
272
+ self,
273
+ role: Literal['system', 'user', 'bot'],
274
+ content: str
275
+ ) -> None:
276
+ """
277
+ Create a message and append it to `Thread.messages`.
278
+
279
+ `Thread.add_message(...)` is a shorthand for
280
+ `Thread.messages.append(Thread.create_message(...))`
281
+ """
282
+ self.messages.append(
283
+ self.create_message(
284
+ role=role,
285
+ content=content
286
+ )
287
+ )
288
+
289
+ def inference_str_from_messages(self) -> str:
290
+ """
291
+ Using the list of messages, construct a string suitable for inference,
292
+ respecting the format and context length of this thread.
293
+ """
294
+
295
+ inf_str = ''
296
+ sys_msg_str = ''
297
+ # whether to treat the first message as necessary to keep
298
+ sys_msg_flag = False
299
+ context_len_budget = self.model.context_length
300
+
301
+ # if at least 1 message is history
302
+ if len(self.messages) >= 1:
303
+ # if first message has system role
304
+ if self.messages[0]['role'] == 'system':
305
+ sys_msg_flag = True
306
+ sys_msg = self.messages[0]
307
+ sys_msg_str = sys_msg.as_string()
308
+ context_len_budget -= self.model.get_length(sys_msg_str)
309
+
310
+ if sys_msg_flag:
311
+ iterator = reversed(self.messages[1:])
312
+ else:
313
+ iterator = reversed(self.messages)
314
+
315
+ for message in iterator:
316
+ msg_str = message.as_string()
317
+ context_len_budget -= self.model.get_length(msg_str)
318
+ if context_len_budget <= 0:
319
+ break
320
+ inf_str = msg_str + inf_str
321
+
322
+ if sys_msg_flag:
323
+ inf_str = sys_msg_str + inf_str
324
+ inf_str += self.format['bot_prefix']
325
+
326
+ return inf_str
327
+
328
+
329
+ def send(self, prompt: str) -> str:
330
+ """
331
+ Send a message in this thread. This adds your message and the bot's
332
+ response to the list of messages.
333
+
334
+ Returns a string containing the response to your message.
335
+ """
336
+
337
+ self.add_message("user", prompt)
338
+ output = self.model.generate(
339
+ self.inference_str_from_messages(),
340
+ stops=self.format['stops'],
341
+ sampler=self.sampler
342
+ )
343
+ self.add_message("bot", output)
344
+
345
+ return output
346
+
347
+
348
+ def _interactive_update_sampler(self) -> None:
349
+ """Interactively update the sampler settings used in this Thread"""
350
+ print()
351
+ try:
352
+ new_max_len_tokens = input(f'max_len_tokens: {self.sampler.max_len_tokens} -> ')
353
+ new_temp = input(f'temp: {self.sampler.temp} -> ')
354
+ new_top_p = input(f'top_p: {self.sampler.top_p} -> ')
355
+ new_min_p = input(f'min_p: {self.sampler.min_p} -> ')
356
+ new_frequency_penalty = input(f'frequency_penalty: {self.sampler.frequency_penalty} -> ')
357
+ new_presence_penalty = input(f'presence_penalty: {self.sampler.presence_penalty} -> ')
358
+ new_repeat_penalty = input(f'repeat_penalty: {self.sampler.repeat_penalty} -> ')
359
+ new_top_k = input(f'top_k: {self.sampler.top_k} -> ')
360
+
361
+ except KeyboardInterrupt:
362
+ print('\nwebscout.Local: sampler settings not updated\n')
363
+ return
364
+ print()
365
+
366
+ try:
367
+ self.sampler.max_len_tokens = int(new_max_len_tokens)
368
+ except ValueError:
369
+ pass
370
+ else:
371
+ print('webscout.Local: max_len_tokens updated')
372
+
373
+ try:
374
+ self.sampler.temp = float(new_temp)
375
+ except ValueError:
376
+ pass
377
+ else:
378
+ print('webscout.Local: temp updated')
379
+
380
+ try:
381
+ self.sampler.top_p = float(new_top_p)
382
+ except ValueError:
383
+ pass
384
+ else:
385
+ print('webscout.Local: top_p updated')
386
+
387
+ try:
388
+ self.sampler.min_p = float(new_min_p)
389
+ except ValueError:
390
+ pass
391
+ else:
392
+ print('webscout.Local: min_p updated')
393
+
394
+ try:
395
+ self.sampler.frequency_penalty = float(new_frequency_penalty)
396
+ except ValueError:
397
+ pass
398
+ else:
399
+ print('webscout.Local: frequency_penalty updated')
400
+
401
+ try:
402
+ self.sampler.presence_penalty = float(new_presence_penalty)
403
+ except ValueError:
404
+ pass
405
+ else:
406
+ print('webscout.Local: presence_penalty updated')
407
+
408
+ try:
409
+ self.sampler.repeat_penalty = float(new_repeat_penalty)
410
+ except ValueError:
411
+ pass
412
+ else:
413
+ print('webscout.Local: repeat_penalty updated')
414
+
415
+ try:
416
+ self.sampler.top_k = int(new_top_k)
417
+ except ValueError:
418
+ pass
419
+ else:
420
+ print('webscout.Local: top_k updated')
421
+ print()
422
+
423
+
424
+ def _interactive_input(
425
+ self,
426
+ prompt: str,
427
+ _dim_style: str,
428
+ _user_style: str,
429
+ _bot_style: str,
430
+ _special_style: str
431
+ ) -> tuple:
432
+ """
433
+ Recive input from the user, while handling multi-line input
434
+ and commands
435
+ """
436
+ full_user_input = '' # may become multiline
437
+
438
+ while True:
439
+ user_input = input(prompt)
440
+
441
+ if user_input.endswith('\\'):
442
+ full_user_input += user_input[:-1] + '\n'
443
+
444
+ elif user_input == '!':
445
+
446
+ print()
447
+ try:
448
+ command = input(f'{RESET_ALL} ! {_dim_style}')
449
+ except KeyboardInterrupt:
450
+ print('\n')
451
+ continue
452
+
453
+ if command == '':
454
+ print(f'\n[no command]\n')
455
+
456
+ elif command.lower() in ['reset', 'restart']:
457
+ self.reset()
458
+ print(f'\n[thread reset]\n')
459
+
460
+ elif command.lower() in ['cls', 'clear']:
461
+ cls()
462
+ print()
463
+
464
+ elif command.lower() in ['ctx', 'context']:
465
+ print(f"\n{self.len_messages()}\n")
466
+
467
+ elif command.lower() in ['stats', 'print_stats']:
468
+ print()
469
+ self.print_stats()
470
+ print()
471
+
472
+ elif command.lower() in ['sampler', 'samplers', 'settings']:
473
+ self._interactive_update_sampler()
474
+
475
+ elif command.lower() in ['str', 'string', 'as_string']:
476
+ print(f"\n{self.as_string()}\n")
477
+
478
+ elif command.lower() in ['repr', 'save', 'backup']:
479
+ print(f"\n{repr(self)}\n")
480
+
481
+ elif command.lower() in ['remove', 'rem', 'delete', 'del']:
482
+ print()
483
+ old_len = len(self.messages)
484
+ del self.messages[-1]
485
+ assert len(self.messages) == (old_len - 1)
486
+ print('[removed last message]\n')
487
+
488
+ elif command.lower() in ['last', 'repeat']:
489
+ last_msg = self.messages[-1]
490
+ if last_msg['role'] == 'user':
491
+ print(f"\n{_user_style}{last_msg['content']}{RESET_ALL}\n")
492
+ elif last_msg['role'] == 'bot':
493
+ print(f"\n{_bot_style}{last_msg['content']}{RESET_ALL}\n")
494
+
495
+ elif command.lower() in ['inf', 'inference', 'inf_str']:
496
+ print(f'\n"""{self.inference_str_from_messages()}"""\n')
497
+
498
+ elif command.lower() in ['reroll', 're-roll', 're', 'swipe']:
499
+ old_len = len(self.messages)
500
+ del self.messages[-1]
501
+ assert len(self.messages) == (old_len - 1)
502
+ return '', None
503
+
504
+ elif command.lower() in ['exit', 'quit']:
505
+ print(RESET_ALL)
506
+ return None, None
507
+
508
+ elif command.lower() in ['help', '/?', '?']:
509
+ print()
510
+ print('reset | restart -- Reset the thread to its original state')
511
+ print('clear | cls -- Clear the terminal')
512
+ print('context | ctx -- Get the context usage in tokens')
513
+ print('print_stats | stats -- Get the context usage stats')
514
+ print('sampler | settings -- Update the sampler settings')
515
+ print('string | str -- Print the message history as a string')
516
+ print('repr | save -- Print the representation of the thread')
517
+ print('remove | delete -- Remove the last message')
518
+ print('last | repeat -- Repeat the last message')
519
+ print('inference | inf -- Print the inference string')
520
+ print('reroll | swipe -- Regenerate the last message')
521
+ print('exit | quit -- Exit the interactive chat (can also use ^C)')
522
+ print('help | ? -- Show this screen')
523
+ print()
524
+ print("TIP: type < at the prompt and press ENTER to prefix the bot's next message.")
525
+ print(' for example, type "Sure!" to bypass refusals')
526
+ print()
527
+ print("TIP: type !! at the prompt and press ENTER to insert a system message")
528
+ print()
529
+
530
+ else:
531
+ print(f'\n[unknown command]\n')
532
+
533
+ # prefix the bot's next message
534
+ elif user_input == '<':
535
+
536
+ print()
537
+ try:
538
+ next_message_start = input(f'{RESET_ALL} < {_dim_style}')
539
+
540
+ except KeyboardInterrupt:
541
+ print(f'{RESET_ALL}\n')
542
+ continue
543
+
544
+ else:
545
+ print()
546
+ return '', next_message_start
547
+
548
+ # insert a system message
549
+ elif user_input == '!!':
550
+ print()
551
+
552
+ try:
553
+ next_sys_msg = input(f'{RESET_ALL} !! {_special_style}')
554
+
555
+ except KeyboardInterrupt:
556
+ print(f'{RESET_ALL}\n')
557
+ continue
558
+
559
+ else:
560
+ print()
561
+ return next_sys_msg, -1
562
+
563
+ # concatenate multi-line input
564
+ else:
565
+ full_user_input += user_input
566
+ return full_user_input, None
567
+
568
+
569
+ def interact(
570
+ self,
571
+ color: bool = True,
572
+ header: Optional[str] = None,
573
+ stream: bool = True
574
+ ) -> None:
575
+ """
576
+ Start an interactive chat session using this Thread.
577
+
578
+ While text is being generated, press `^C` to interrupt the bot.
579
+ Then you have the option to press `ENTER` to re-roll, or to simply type
580
+ another message.
581
+
582
+ At the prompt, press `^C` to end the chat session.
583
+
584
+ Type `!` and press `ENTER` to enter a basic command prompt. For a list
585
+ of commands, type `help` at this prompt.
586
+
587
+ Type `<` and press `ENTER` to prefix the bot's next message, for
588
+ example with `Sure!`.
589
+
590
+ Type `!!` at the prompt and press `ENTER` to insert a system message.
591
+
592
+ The following parameters are optional:
593
+ - color: Whether to use colored text to differentiate user / bot
594
+ - header: Header text to print at the start of the interaction
595
+ - stream: Whether to stream text as it is generated
596
+ """
597
+ print()
598
+
599
+ # fresh import of color codes in case `color` param has changed
600
+ from .utils import SPECIAL_STYLE, USER_STYLE, BOT_STYLE, DIM_STYLE
601
+
602
+ # disable color codes if explicitly disabled by `color` param
603
+ if not color:
604
+ SPECIAL_STYLE = ''
605
+ USER_STYLE = ''
606
+ BOT_STYLE = ''
607
+ DIM_STYLE = ''
608
+
609
+ if header is not None:
610
+ print(f"{SPECIAL_STYLE}{header}{RESET_ALL}\n")
611
+
612
+ while True:
613
+
614
+ prompt = f"{RESET_ALL} > {USER_STYLE}"
615
+
616
+ try:
617
+ user_prompt, next_message_start = self._interactive_input(
618
+ prompt,
619
+ DIM_STYLE,
620
+ USER_STYLE,
621
+ BOT_STYLE,
622
+ SPECIAL_STYLE
623
+ )
624
+ except KeyboardInterrupt:
625
+ print(f"{RESET_ALL}\n")
626
+ return
627
+
628
+ # got 'exit' or 'quit' command
629
+ if user_prompt is None and next_message_start is None:
630
+ break
631
+
632
+ # insert a system message via `!!` prompt
633
+ if next_message_start == -1:
634
+ self.add_message('system', user_prompt)
635
+ continue
636
+
637
+ if next_message_start is not None:
638
+ try:
639
+ if stream:
640
+ print(f"{BOT_STYLE}{next_message_start}", end='', flush=True)
641
+ output = next_message_start + self.model.stream_print(
642
+ self.inference_str_from_messages() + next_message_start,
643
+ stops=self.format['stops'],
644
+ sampler=self.sampler,
645
+ end=''
646
+ )
647
+ else:
648
+ print(f"{BOT_STYLE}", end='', flush=True)
649
+ output = next_message_start + self.model.generate(
650
+ self.inference_str_from_messages() + next_message_start,
651
+ stops=self.format['stops'],
652
+ sampler=self.sampler
653
+ )
654
+ print(output, end='', flush=True)
655
+ except KeyboardInterrupt:
656
+ print(f"{DIM_STYLE} [message not added to history; press ENTER to re-roll]\n")
657
+ continue
658
+ else:
659
+ self.add_message("bot", output)
660
+ else:
661
+ print(BOT_STYLE)
662
+ if user_prompt != "":
663
+ self.add_message("user", user_prompt)
664
+ try:
665
+ if stream:
666
+ output = self.model.stream_print(
667
+ self.inference_str_from_messages(),
668
+ stops=self.format['stops'],
669
+ sampler=self.sampler,
670
+ end=''
671
+ )
672
+ else:
673
+ output = self.model.generate(
674
+ self.inference_str_from_messages(),
675
+ stops=self.format['stops'],
676
+ sampler=self.sampler
677
+ )
678
+ print(output, end='', flush=True)
679
+ except KeyboardInterrupt:
680
+ print(f"{DIM_STYLE} [message not added to history; press ENTER to re-roll]\n")
681
+ continue
682
+ else:
683
+ self.add_message("bot", output)
684
+
685
+ if output.endswith("\n\n"):
686
+ print(RESET_ALL, end = '', flush=True)
687
+ elif output.endswith("\n"):
688
+ print(RESET_ALL)
689
+ else:
690
+ print(f"{RESET_ALL}\n")
691
+
692
+
693
+ def reset(self) -> None:
694
+ """
695
+ Clear the list of messages, which resets the thread to its original
696
+ state
697
+ """
698
+ self.messages: list[Message] = [
699
+ self.create_message("system", self.format['system_content'])
700
+ ] if self._messages is None else self._messages
701
+
702
+
703
+ def as_string(self) -> str:
704
+ """Return this thread's message history as a string"""
705
+ thread_string = ''
706
+ for msg in self.messages:
707
+ thread_string += msg.as_string()
708
+ return thread_string
709
+
710
+
711
+ def print_stats(
712
+ self,
713
+ end: str = '\n',
714
+ file: _SupportsWriteAndFlush = sys.stdout,
715
+ flush: bool = True
716
+ ) -> None:
717
+ """Print stats about the context usage in this thread"""
718
+ thread_len_tokens = self.len_messages()
719
+ max_ctx_len = self.model.context_length
720
+ context_used_percentage = round((thread_len_tokens/max_ctx_len)*100)
721
+ print(f"{thread_len_tokens} / {max_ctx_len} tokens", file=file, flush=flush)
722
+ print(f"{context_used_percentage}% of context used", file=file, flush=flush)
723
+ print(f"{len(self.messages)} messages", end=end, file=file, flush=flush)
724
+ if not flush:
725
+ file.flush()
726
+
727
+ class RawDog:
728
+ """Generate and auto-execute Python scripts in the cli"""
729
+
730
+ examples = """\
731
+ EXAMPLES:
732
+
733
+ 1. User: Kill the process running on port 3000
734
+
735
+ LLM:
736
+ ```python
737
+ import os
738
+ os.system("kill $(lsof -t -i:3000)")
739
+ print("Process killed")
740
+ ```
741
+
742
+ 2. User: Summarize my essay
743
+
744
+ LLM:
745
+ ```python
746
+ import glob
747
+ files = glob.glob("*essay*.*")
748
+ with open(files[0], "r") as f:
749
+ print(f.read())
750
+ ```
751
+ CONTINUE
752
+
753
+ User:
754
+ LAST SCRIPT OUTPUT:
755
+ John Smith
756
+ Essay 2021-09-01
757
+ ...
758
+
759
+ LLM:
760
+ ```python
761
+ print("The essay is about...")
762
+ ```
763
+ """
764
+
765
+
766
+ def __init__(
767
+ self,
768
+ quiet: bool = False,
769
+ internal_exec: bool = False,
770
+ confirm_script: bool = False,
771
+ interpreter: str = "python",
772
+ prettify: bool = True,
773
+ ):
774
+ """Constructor
775
+
776
+ Args:
777
+ quiet (bool, optional): Flag for control logging. Defaults to False.
778
+ internal_exec (bool, optional): Execute scripts with exec function. Defaults to False.
779
+ confirm_script (bool, optional): Give consent to scripts prior to execution. Defaults to False.
780
+ interpreter (str, optional): Python's interpreter name. Defaults to Python.
781
+ prettify (bool, optional): Prettify the code on stdout. Defaults to True.
782
+ """
783
+ if not quiet:
784
+ print(
785
+ "To get the most out of Rawdog. Ensure the following are installed:\n"
786
+ " 1. Python 3.x\n"
787
+ " 2. Dependency:\n"
788
+ " - Matplotlib\n"
789
+ "Be alerted on the risk posed! (Experimental)\n"
790
+ "Use '--quiet' to suppress this message and code/logs stdout.\n"
791
+ )
792
+ self.internal_exec = internal_exec
793
+ self.confirm_script = confirm_script
794
+ self.quiet = quiet
795
+ self.interpreter = interpreter
796
+ self.prettify = prettify
797
+ self.python_version = (
798
+ f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
799
+ if self.internal_exec
800
+ else run_system_command(
801
+ f"{self.interpreter} --version",
802
+ exit_on_error=True,
803
+ stdout_error=True,
804
+ help="If you're using Webscout-cli, use the flag '--internal-exec'",
805
+ )[1].stdout.split(" ")[1]
806
+ )
807
+
808
+ @property
809
+ def intro_prompt(self):
810
+ return f"""
811
+ You are a command-line coding assistant called Rawdog that generates and auto-executes Python scripts.
812
+
813
+ A typical interaction goes like this:
814
+ 1. The user gives you a natural language PROMPT.
815
+ 2. You:
816
+ i. Determine what needs to be done
817
+ ii. Write a short Python SCRIPT to do it
818
+ iii. Communicate back to the user by printing to the console in that SCRIPT
819
+ 3. The compiler extracts the script and then runs it using exec(). If there will be an exception raised,
820
+ it will be send back to you starting with "PREVIOUS SCRIPT EXCEPTION:".
821
+ 4. In case of exception, regenerate error free script.
822
+
823
+ If you need to review script outputs before completing the task, you can print the word "CONTINUE" at the end of your SCRIPT.
824
+ This can be useful for summarizing documents or technical readouts, reading instructions before
825
+ deciding what to do, or other tasks that require multi-step reasoning.
826
+ A typical 'CONTINUE' interaction looks like this:
827
+ 1. The user gives you a natural language PROMPT.
828
+ 2. You:
829
+ i. Determine what needs to be done
830
+ ii. Determine that you need to see the output of some subprocess call to complete the task
831
+ iii. Write a short Python SCRIPT to print that and then print the word "CONTINUE"
832
+ 3. The compiler
833
+ i. Checks and runs your SCRIPT
834
+ ii. Captures the output and appends it to the conversation as "LAST SCRIPT OUTPUT:"
835
+ iii. Finds the word "CONTINUE" and sends control back to you
836
+ 4. You again:
837
+ i. Look at the original PROMPT + the "LAST SCRIPT OUTPUT:" to determine what needs to be done
838
+ ii. Write a short Python SCRIPT to do it
839
+ iii. Communicate back to the user by printing to the console in that SCRIPT
840
+ 5. The compiler...
841
+
842
+ Please follow these conventions carefully:
843
+ - Decline any tasks that seem dangerous, irreversible, or that you don't understand.
844
+ - Always review the full conversation prior to answering and maintain continuity.
845
+ - If asked for information, just print the information clearly and concisely.
846
+ - If asked to do something, print a concise summary of what you've done as confirmation.
847
+ - If asked a question, respond in a friendly, conversational way. Use programmatically-generated and natural language responses as appropriate.
848
+ - If you need clarification, return a SCRIPT that prints your question. In the next interaction, continue based on the user's response.
849
+ - 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.
850
+ - Actively clean up any temporary processes or files you use.
851
+ - When looking through files, use git as available to skip files, and skip hidden files (.env, .git, etc) by default.
852
+ - You can plot anything with matplotlib.
853
+ - 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.
854
+
855
+ {self.examples}
856
+
857
+ Current system : {sys.platform.system()}
858
+ Python version : {self.python_version}
859
+ Current directory : {os.getcwd()}
860
+ Current Datetime : {datetime.datetime.now()}
861
+ """
862
+
863
+ def stdout(self, message: str) -> None:
864
+ """Stdout data
865
+
866
+ Args:
867
+ message (str): Text to be printed
868
+ """
869
+ if self.prettify:
870
+ Console().print(Markdown(message))
871
+ else:
872
+ click.secho(message, fg="yellow")
873
+
874
+ def log(self, message: str, category: str = "info"):
875
+ """RawDog logger
876
+
877
+ Args:
878
+ message (str): Log message
879
+ category (str, optional): Log level. Defaults to 'info'.
880
+ """
881
+ if self.quiet:
882
+ return
883
+
884
+ message = "[Webscout] - " + message
885
+ if category == "error":
886
+ logging.error(message)
887
+ else:
888
+ logging.info(message)
889
+
890
+ def main(self, response: str) -> None:
891
+ """Exec code in response accordingly
892
+
893
+ Args:
894
+ response (str): AI response
895
+
896
+ Returns:
897
+ None|str: None if script executed successfully else stdout data
898
+ """
899
+ code_blocks = re.findall(r"```python.*?```", response, re.DOTALL)
900
+ if len(code_blocks) != 1:
901
+ self.stdout(response)
902
+
903
+ else:
904
+ raw_code = code_blocks[0]
905
+
906
+ if self.confirm_script:
907
+ self.stdout(raw_code)
908
+ if not click.confirm("- Do you wish to execute this"):
909
+ return
910
+
911
+ elif not self.quiet:
912
+ self.stdout(raw_code)
913
+
914
+ raw_code_plus = re.sub(r"(```)(python)?", "", raw_code)
915
+
916
+ if "CONTINUE" in response or not self.internal_exec:
917
+ self.log("Executing script externally")
918
+ path_to_script = os.path.join(default_path, "execute_this.py")
919
+ with open(path_to_script, "w") as fh:
920
+ fh.write(raw_code_plus)
921
+ if "CONTINUE" in response:
922
+
923
+ success, proc = run_system_command(
924
+ f"{self.interpreter} {path_to_script}",
925
+ exit_on_error=False,
926
+ stdout_error=False,
927
+ )
928
+
929
+ if success:
930
+ self.log("Returning success feedback")
931
+ return f"LAST SCRIPT OUTPUT:\n{proc.stdout}"
932
+ else:
933
+ self.log("Returning error feedback", "error")
934
+ return f"PREVIOUS SCRIPT EXCEPTION:\n{proc.stderr}"
935
+ else:
936
+ os.system(f"{self.interpreter} {path_to_script}")
937
+
938
+ else:
939
+ try:
940
+ self.log("Executing script internally")
941
+ exec(raw_code_plus)
942
+ except Exception as e:
943
+ self.log(
944
+ "Exception occurred while executing script. Responding with error: "
945
+ f"{e.args[1] if len(e.args)>1 else str(e)}",
946
+ "error",
947
+ )
948
+ return f"PREVIOUS SCRIPT EXCEPTION:\n{str(e)}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: webscout
3
- Version: 3.0
3
+ Version: 3.1b0
4
4
  Summary: Search for anything using Google, DuckDuckGo, phind.com, Contains AI models, can transcribe yt videos, temporary email and phone number generation, has TTS support, webai (terminal gpt and open interpreter) and offline LLMs
5
5
  Author: OEvortex
6
6
  Author-email: helpingai5@gmail.com
@@ -43,6 +43,7 @@ webscout/Local/__init__.py
43
43
  webscout/Local/_version.py
44
44
  webscout/Local/formats.py
45
45
  webscout/Local/model.py
46
+ webscout/Local/rawdog.py
46
47
  webscout/Local/samplers.py
47
48
  webscout/Local/thread.py
48
49
  webscout/Local/utils.py
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes