webscout 8.2.4__py3-none-any.whl → 8.2.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of webscout might be problematic. Click here for more details.
- webscout/AIauto.py +112 -22
- webscout/AIutel.py +240 -344
- webscout/Extra/autocoder/autocoder.py +66 -5
- webscout/Extra/gguf.py +2 -0
- webscout/Provider/AISEARCH/scira_search.py +3 -5
- webscout/Provider/Aitopia.py +75 -51
- webscout/Provider/AllenAI.py +64 -67
- webscout/Provider/ChatGPTClone.py +33 -34
- webscout/Provider/ChatSandbox.py +342 -0
- webscout/Provider/Cloudflare.py +79 -32
- webscout/Provider/Deepinfra.py +69 -56
- webscout/Provider/ElectronHub.py +48 -39
- webscout/Provider/ExaChat.py +36 -20
- webscout/Provider/GPTWeb.py +24 -18
- webscout/Provider/GithubChat.py +52 -49
- webscout/Provider/GizAI.py +285 -0
- webscout/Provider/Glider.py +39 -28
- webscout/Provider/Groq.py +48 -20
- webscout/Provider/HeckAI.py +18 -36
- webscout/Provider/Jadve.py +30 -37
- webscout/Provider/LambdaChat.py +36 -59
- webscout/Provider/MCPCore.py +18 -21
- webscout/Provider/Marcus.py +23 -14
- webscout/Provider/Nemotron.py +218 -0
- webscout/Provider/Netwrck.py +35 -26
- webscout/Provider/OPENAI/__init__.py +1 -1
- webscout/Provider/OPENAI/exachat.py +4 -0
- webscout/Provider/OPENAI/scirachat.py +3 -4
- webscout/Provider/OPENAI/textpollinations.py +20 -22
- webscout/Provider/OPENAI/toolbaz.py +1 -0
- webscout/Provider/PI.py +22 -13
- webscout/Provider/StandardInput.py +42 -30
- webscout/Provider/TeachAnything.py +24 -12
- webscout/Provider/TextPollinationsAI.py +78 -76
- webscout/Provider/TwoAI.py +120 -88
- webscout/Provider/TypliAI.py +305 -0
- webscout/Provider/Venice.py +24 -22
- webscout/Provider/VercelAI.py +31 -12
- webscout/Provider/WiseCat.py +1 -1
- webscout/Provider/WrDoChat.py +370 -0
- webscout/Provider/__init__.py +11 -13
- webscout/Provider/ai4chat.py +5 -3
- webscout/Provider/akashgpt.py +59 -66
- webscout/Provider/asksteve.py +53 -44
- webscout/Provider/cerebras.py +77 -31
- webscout/Provider/chatglm.py +47 -37
- webscout/Provider/elmo.py +38 -32
- webscout/Provider/freeaichat.py +57 -43
- webscout/Provider/granite.py +24 -21
- webscout/Provider/hermes.py +27 -20
- webscout/Provider/learnfastai.py +25 -20
- webscout/Provider/llmchatco.py +48 -78
- webscout/Provider/multichat.py +13 -3
- webscout/Provider/scira_chat.py +50 -30
- webscout/Provider/scnet.py +27 -21
- webscout/Provider/searchchat.py +16 -24
- webscout/Provider/sonus.py +37 -39
- webscout/Provider/toolbaz.py +24 -46
- webscout/Provider/turboseek.py +37 -41
- webscout/Provider/typefully.py +30 -22
- webscout/Provider/typegpt.py +47 -51
- webscout/Provider/uncovr.py +46 -40
- webscout/__init__.py +0 -1
- webscout/cli.py +256 -0
- webscout/conversation.py +305 -448
- webscout/exceptions.py +3 -0
- webscout/swiftcli/__init__.py +80 -794
- webscout/swiftcli/core/__init__.py +7 -0
- webscout/swiftcli/core/cli.py +297 -0
- webscout/swiftcli/core/context.py +104 -0
- webscout/swiftcli/core/group.py +241 -0
- webscout/swiftcli/decorators/__init__.py +28 -0
- webscout/swiftcli/decorators/command.py +221 -0
- webscout/swiftcli/decorators/options.py +220 -0
- webscout/swiftcli/decorators/output.py +252 -0
- webscout/swiftcli/exceptions.py +21 -0
- webscout/swiftcli/plugins/__init__.py +9 -0
- webscout/swiftcli/plugins/base.py +135 -0
- webscout/swiftcli/plugins/manager.py +262 -0
- webscout/swiftcli/utils/__init__.py +59 -0
- webscout/swiftcli/utils/formatting.py +252 -0
- webscout/swiftcli/utils/parsing.py +267 -0
- webscout/version.py +1 -1
- {webscout-8.2.4.dist-info → webscout-8.2.6.dist-info}/METADATA +166 -45
- {webscout-8.2.4.dist-info → webscout-8.2.6.dist-info}/RECORD +89 -89
- {webscout-8.2.4.dist-info → webscout-8.2.6.dist-info}/WHEEL +1 -1
- webscout-8.2.6.dist-info/entry_points.txt +3 -0
- {webscout-8.2.4.dist-info → webscout-8.2.6.dist-info}/top_level.txt +0 -1
- inferno/__init__.py +0 -6
- inferno/__main__.py +0 -9
- inferno/cli.py +0 -6
- inferno/lol.py +0 -589
- webscout/LLM.py +0 -442
- webscout/Local/__init__.py +0 -12
- webscout/Local/__main__.py +0 -9
- webscout/Local/api.py +0 -576
- webscout/Local/cli.py +0 -516
- webscout/Local/config.py +0 -75
- webscout/Local/llm.py +0 -287
- webscout/Local/model_manager.py +0 -253
- webscout/Local/server.py +0 -721
- webscout/Local/utils.py +0 -93
- webscout/Provider/Chatify.py +0 -175
- webscout/Provider/PizzaGPT.py +0 -228
- webscout/Provider/askmyai.py +0 -158
- webscout/Provider/gaurish.py +0 -244
- webscout/Provider/promptrefine.py +0 -193
- webscout/Provider/tutorai.py +0 -270
- webscout-8.2.4.dist-info/entry_points.txt +0 -5
- {webscout-8.2.4.dist-info → webscout-8.2.6.dist-info}/licenses/LICENSE.md +0 -0
webscout/Provider/scira_chat.py
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
from os import system
|
|
2
|
-
import
|
|
2
|
+
from curl_cffi import CurlError
|
|
3
|
+
from curl_cffi.requests import Session
|
|
3
4
|
import json
|
|
4
5
|
import uuid
|
|
5
6
|
import re
|
|
6
|
-
from typing import Any, Dict, Optional, Union
|
|
7
|
+
from typing import Any, Dict, Optional, Union, List
|
|
7
8
|
from webscout.AIutel import Optimizers
|
|
8
|
-
from webscout.AIutel import Conversation
|
|
9
|
+
from webscout.AIutel import Conversation, sanitize_stream # Import sanitize_stream
|
|
9
10
|
from webscout.AIutel import AwesomePrompts
|
|
10
11
|
from webscout.AIbase import Provider
|
|
11
12
|
from webscout import exceptions
|
|
@@ -17,15 +18,14 @@ class SciraAI(Provider):
|
|
|
17
18
|
"""
|
|
18
19
|
|
|
19
20
|
AVAILABLE_MODELS = {
|
|
20
|
-
"scira-default": "Grok3",
|
|
21
|
-
"scira-grok-3
|
|
21
|
+
"scira-default": "Grok3-mini", # thinking model
|
|
22
|
+
"scira-grok-3": "Grok3",
|
|
23
|
+
"scira-anthropic": "Sonnet 3.7 thinking",
|
|
22
24
|
"scira-vision" : "Grok2-Vision", # vision model
|
|
23
25
|
"scira-4.1-mini": "GPT4.1-mini",
|
|
24
26
|
"scira-qwq": "QWQ-32B",
|
|
25
27
|
"scira-o4-mini": "o4-mini",
|
|
26
28
|
"scira-google": "gemini 2.5 flash"
|
|
27
|
-
|
|
28
|
-
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
def __init__(
|
|
@@ -92,9 +92,9 @@ class SciraAI(Provider):
|
|
|
92
92
|
"Sec-Fetch-Site": "same-origin"
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
self.session =
|
|
95
|
+
self.session = Session() # Use curl_cffi Session
|
|
96
96
|
self.session.headers.update(self.headers)
|
|
97
|
-
self.session.proxies
|
|
97
|
+
self.session.proxies = proxies # Assign proxies directly
|
|
98
98
|
|
|
99
99
|
self.is_conversation = is_conversation
|
|
100
100
|
self.max_tokens_to_sample = max_tokens
|
|
@@ -150,12 +150,23 @@ class SciraAI(Provider):
|
|
|
150
150
|
|
|
151
151
|
return self.fingerprint
|
|
152
152
|
|
|
153
|
+
@staticmethod
|
|
154
|
+
def _scira_extractor(chunk: Union[str, Dict[str, Any]]) -> Optional[str]:
|
|
155
|
+
"""Extracts content from the Scira stream format '0:"..."'."""
|
|
156
|
+
if isinstance(chunk, str):
|
|
157
|
+
match = re.search(r'0:"(.*?)"(?=,|$)', chunk) # Look for 0:"...", possibly followed by comma or end of string
|
|
158
|
+
if match:
|
|
159
|
+
# Decode potential unicode escapes like \u00e9 and handle escaped quotes/backslashes
|
|
160
|
+
content = match.group(1).encode().decode('unicode_escape')
|
|
161
|
+
return content.replace('\\\\', '\\').replace('\\"', '"')
|
|
162
|
+
return None
|
|
163
|
+
|
|
153
164
|
def ask(
|
|
154
165
|
self,
|
|
155
166
|
prompt: str,
|
|
156
167
|
optimizer: str = None,
|
|
157
168
|
conversationally: bool = False,
|
|
158
|
-
) -> Dict[str, Any]:
|
|
169
|
+
) -> Dict[str, Any]: # Note: Stream parameter removed as API doesn't seem to support it
|
|
159
170
|
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
|
160
171
|
if optimizer:
|
|
161
172
|
if optimizer in self.__available_optimizers:
|
|
@@ -181,10 +192,16 @@ class SciraAI(Provider):
|
|
|
181
192
|
}
|
|
182
193
|
|
|
183
194
|
try:
|
|
184
|
-
|
|
195
|
+
# Use curl_cffi post with impersonate
|
|
196
|
+
response = self.session.post(
|
|
197
|
+
self.url,
|
|
198
|
+
json=payload,
|
|
199
|
+
timeout=self.timeout,
|
|
200
|
+
impersonate="chrome120" # Add impersonate
|
|
201
|
+
)
|
|
185
202
|
if response.status_code != 200:
|
|
186
203
|
# Try to get response content for better error messages
|
|
187
|
-
try:
|
|
204
|
+
try: # Use try-except for reading response content
|
|
188
205
|
error_content = response.text
|
|
189
206
|
except:
|
|
190
207
|
error_content = "<could not read response content>"
|
|
@@ -192,7 +209,10 @@ class SciraAI(Provider):
|
|
|
192
209
|
if response.status_code in [403, 429]:
|
|
193
210
|
print(f"Received status code {response.status_code}, refreshing identity...")
|
|
194
211
|
self.refresh_identity()
|
|
195
|
-
response = self.session.post(
|
|
212
|
+
response = self.session.post(
|
|
213
|
+
self.url, json=payload, timeout=self.timeout,
|
|
214
|
+
impersonate="chrome120" # Add impersonate to retry
|
|
215
|
+
)
|
|
196
216
|
if not response.ok:
|
|
197
217
|
raise exceptions.FailedToGenerateResponseError(
|
|
198
218
|
f"Failed to generate response after identity refresh - ({response.status_code}, {response.reason}) - {error_content}"
|
|
@@ -203,28 +223,27 @@ class SciraAI(Provider):
|
|
|
203
223
|
f"Request failed with status code {response.status_code}. Response: {error_content}"
|
|
204
224
|
)
|
|
205
225
|
|
|
206
|
-
|
|
207
|
-
debug_lines = []
|
|
208
|
-
|
|
209
|
-
# Collect the first few lines for debugging
|
|
210
|
-
for i, line in enumerate(response.iter_lines()):
|
|
211
|
-
if line:
|
|
212
|
-
try:
|
|
213
|
-
line_str = line.decode('utf-8')
|
|
214
|
-
debug_lines.append(line_str)
|
|
226
|
+
response_text_raw = response.text # Get raw response text
|
|
215
227
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
228
|
+
# Process the text using sanitize_stream line by line
|
|
229
|
+
processed_stream = sanitize_stream(
|
|
230
|
+
data=response_text_raw.splitlines(), # Split into lines
|
|
231
|
+
intro_value=None, # No simple prefix
|
|
232
|
+
to_json=False, # Content is not JSON
|
|
233
|
+
content_extractor=self._scira_extractor # Use the specific extractor
|
|
234
|
+
)
|
|
222
235
|
|
|
236
|
+
# Aggregate the results from the generator
|
|
237
|
+
full_response = ""
|
|
238
|
+
for content in processed_stream:
|
|
239
|
+
if content and isinstance(content, str):
|
|
240
|
+
full_response += content
|
|
223
241
|
|
|
224
|
-
except: pass
|
|
225
242
|
self.last_response = {"text": full_response}
|
|
226
243
|
self.conversation.update_chat_history(prompt, full_response)
|
|
227
244
|
return {"text": full_response}
|
|
245
|
+
except CurlError as e: # Catch CurlError
|
|
246
|
+
raise exceptions.FailedToGenerateResponseError(f"Request failed (CurlError): {e}") from e
|
|
228
247
|
except Exception as e:
|
|
229
248
|
raise exceptions.FailedToGenerateResponseError(f"Request failed: {e}")
|
|
230
249
|
|
|
@@ -242,7 +261,8 @@ class SciraAI(Provider):
|
|
|
242
261
|
|
|
243
262
|
def get_message(self, response: dict) -> str:
|
|
244
263
|
assert isinstance(response, dict), "Response should be of dict data-type only"
|
|
245
|
-
|
|
264
|
+
# Extractor handles formatting
|
|
265
|
+
return response.get("text", "").replace('\\n', '\n').replace('\\n\\n', '\n\n')
|
|
246
266
|
|
|
247
267
|
if __name__ == "__main__":
|
|
248
268
|
print("-" * 100)
|
webscout/Provider/scnet.py
CHANGED
|
@@ -4,7 +4,7 @@ import json
|
|
|
4
4
|
import secrets
|
|
5
5
|
from typing import Any, Dict, Optional, Generator, Union
|
|
6
6
|
|
|
7
|
-
from webscout.AIutel import Optimizers, Conversation, AwesomePrompts
|
|
7
|
+
from webscout.AIutel import Optimizers, Conversation, AwesomePrompts, sanitize_stream
|
|
8
8
|
from webscout.AIbase import Provider
|
|
9
9
|
from webscout import exceptions
|
|
10
10
|
|
|
@@ -29,7 +29,10 @@ class SCNet(Provider):
|
|
|
29
29
|
is_conversation: bool = True,
|
|
30
30
|
max_tokens: int = 2048, # Note: max_tokens is not used by this API
|
|
31
31
|
timeout: int = 30,
|
|
32
|
-
intro: Optional[str] =
|
|
32
|
+
intro: Optional[str] = ("You are a helpful, advanced LLM assistant. "
|
|
33
|
+
"You must always answer in English, regardless of the user's language. "
|
|
34
|
+
"If the user asks in another language, politely respond in English only. "
|
|
35
|
+
"Be clear, concise, and helpful."),
|
|
33
36
|
filepath: Optional[str] = None,
|
|
34
37
|
update_file: bool = True,
|
|
35
38
|
proxies: Optional[dict] = None,
|
|
@@ -86,6 +89,13 @@ class SCNet(Provider):
|
|
|
86
89
|
self.conversation = Conversation(is_conversation, max_tokens, filepath, update_file)
|
|
87
90
|
self.conversation.history_offset = history_offset
|
|
88
91
|
|
|
92
|
+
@staticmethod
|
|
93
|
+
def _scnet_extractor(chunk: Union[str, Dict[str, Any]]) -> Optional[str]:
|
|
94
|
+
"""Extracts content from SCNet stream JSON objects."""
|
|
95
|
+
if isinstance(chunk, dict):
|
|
96
|
+
return chunk.get("content")
|
|
97
|
+
return None
|
|
98
|
+
|
|
89
99
|
def ask(
|
|
90
100
|
self,
|
|
91
101
|
prompt: str,
|
|
@@ -126,25 +136,21 @@ class SCNet(Provider):
|
|
|
126
136
|
response.raise_for_status() # Check for HTTP errors
|
|
127
137
|
|
|
128
138
|
streaming_text = ""
|
|
129
|
-
#
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
continue
|
|
145
|
-
elif data == "[done]":
|
|
146
|
-
break
|
|
147
|
-
|
|
139
|
+
# Use sanitize_stream
|
|
140
|
+
processed_stream = sanitize_stream(
|
|
141
|
+
data=response.iter_content(chunk_size=None), # Pass byte iterator
|
|
142
|
+
intro_value="data:",
|
|
143
|
+
to_json=True, # Stream sends JSON
|
|
144
|
+
skip_markers=["[done]"],
|
|
145
|
+
content_extractor=self._scnet_extractor, # Use the specific extractor
|
|
146
|
+
yield_raw_on_error=False # Skip non-JSON lines or lines where extractor fails
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
for content_chunk in processed_stream:
|
|
150
|
+
# content_chunk is the string extracted by _scnet_extractor
|
|
151
|
+
if content_chunk and isinstance(content_chunk, str):
|
|
152
|
+
streaming_text += content_chunk
|
|
153
|
+
yield {"text": content_chunk} if not raw else content_chunk
|
|
148
154
|
# Update history and last response after stream finishes
|
|
149
155
|
self.last_response = {"text": streaming_text}
|
|
150
156
|
self.conversation.update_chat_history(prompt, streaming_text)
|
webscout/Provider/searchchat.py
CHANGED
|
@@ -6,7 +6,7 @@ from typing import Any, Dict, Optional, Generator, Union
|
|
|
6
6
|
|
|
7
7
|
from webscout.AIutel import Optimizers
|
|
8
8
|
from webscout.AIutel import Conversation
|
|
9
|
-
from webscout.AIutel import AwesomePrompts
|
|
9
|
+
from webscout.AIutel import AwesomePrompts, sanitize_stream # Import sanitize_stream
|
|
10
10
|
from webscout.AIbase import Provider
|
|
11
11
|
from webscout import exceptions
|
|
12
12
|
from webscout.litagent import LitAgent
|
|
@@ -183,33 +183,25 @@ class SearchChatAI(Provider):
|
|
|
183
183
|
)
|
|
184
184
|
|
|
185
185
|
streaming_text = ""
|
|
186
|
-
#
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
content = delta["content"]
|
|
202
|
-
streaming_text += content
|
|
203
|
-
resp = dict(text=content)
|
|
204
|
-
# Yield dict or raw string
|
|
205
|
-
yield resp if not raw else content
|
|
206
|
-
except (json.JSONDecodeError, UnicodeDecodeError):
|
|
207
|
-
continue
|
|
186
|
+
# Use sanitize_stream
|
|
187
|
+
processed_stream = sanitize_stream(
|
|
188
|
+
data=response.iter_content(chunk_size=None), # Pass byte iterator
|
|
189
|
+
intro_value="data:",
|
|
190
|
+
to_json=True, # Stream sends JSON
|
|
191
|
+
skip_markers=["[DONE]"],
|
|
192
|
+
content_extractor=lambda chunk: chunk.get('choices', [{}])[0].get('delta', {}).get('content') if isinstance(chunk, dict) else None,
|
|
193
|
+
yield_raw_on_error=False # Skip non-JSON or lines where extractor fails
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
for content_chunk in processed_stream:
|
|
197
|
+
# content_chunk is the string extracted by the content_extractor
|
|
198
|
+
if content_chunk and isinstance(content_chunk, str):
|
|
199
|
+
streaming_text += content_chunk
|
|
200
|
+
yield dict(text=content_chunk) if not raw else content_chunk
|
|
208
201
|
|
|
209
202
|
# Update history and last response after stream finishes
|
|
210
203
|
self.last_response = {"text": streaming_text}
|
|
211
204
|
self.conversation.update_chat_history(prompt, streaming_text)
|
|
212
|
-
|
|
213
205
|
except CurlError as e: # Catch CurlError
|
|
214
206
|
raise exceptions.FailedToGenerateResponseError(f"Request failed (CurlError): {str(e)}") from e
|
|
215
207
|
except Exception as e: # Catch other potential exceptions
|
webscout/Provider/sonus.py
CHANGED
|
@@ -4,7 +4,7 @@ import json
|
|
|
4
4
|
from typing import Any, Dict, Optional, Generator, Union
|
|
5
5
|
from webscout.AIutel import Optimizers
|
|
6
6
|
from webscout.AIutel import Conversation
|
|
7
|
-
from webscout.AIutel import AwesomePrompts
|
|
7
|
+
from webscout.AIutel import AwesomePrompts, sanitize_stream # Import sanitize_stream
|
|
8
8
|
from webscout.AIbase import Provider
|
|
9
9
|
from webscout import exceptions
|
|
10
10
|
from webscout.litagent import LitAgent
|
|
@@ -78,6 +78,13 @@ class SonusAI(Provider):
|
|
|
78
78
|
)
|
|
79
79
|
self.conversation.history_offset = history_offset
|
|
80
80
|
|
|
81
|
+
@staticmethod
|
|
82
|
+
def _sonus_extractor(chunk: Union[str, Dict[str, Any]]) -> Optional[str]:
|
|
83
|
+
"""Extracts content from Sonus stream JSON objects."""
|
|
84
|
+
if isinstance(chunk, dict) and "content" in chunk:
|
|
85
|
+
return chunk.get("content")
|
|
86
|
+
return None
|
|
87
|
+
|
|
81
88
|
def ask(
|
|
82
89
|
self,
|
|
83
90
|
prompt: str,
|
|
@@ -124,30 +131,22 @@ class SonusAI(Provider):
|
|
|
124
131
|
raise exceptions.FailedToGenerateResponseError(
|
|
125
132
|
f"Request failed with status code {response.status_code} - {response.text}"
|
|
126
133
|
)
|
|
127
|
-
|
|
134
|
+
|
|
128
135
|
streaming_text = ""
|
|
129
|
-
#
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
# Handle potential empty lines after prefix removal
|
|
139
|
-
if not line.strip():
|
|
140
|
-
continue
|
|
136
|
+
# Use sanitize_stream
|
|
137
|
+
processed_stream = sanitize_stream(
|
|
138
|
+
data=response.iter_content(chunk_size=None), # Pass byte iterator
|
|
139
|
+
intro_value="data:",
|
|
140
|
+
to_json=True, # Stream sends JSON
|
|
141
|
+
content_extractor=self._sonus_extractor, # Use the specific extractor
|
|
142
|
+
yield_raw_on_error=False # Skip non-JSON lines or lines where extractor fails
|
|
143
|
+
)
|
|
141
144
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
# Yield dict or raw string
|
|
148
|
-
yield resp if raw else resp
|
|
149
|
-
except (json.JSONDecodeError, UnicodeDecodeError):
|
|
150
|
-
continue
|
|
145
|
+
for content_chunk in processed_stream:
|
|
146
|
+
# content_chunk is the string extracted by _sonus_extractor
|
|
147
|
+
if content_chunk and isinstance(content_chunk, str):
|
|
148
|
+
streaming_text += content_chunk
|
|
149
|
+
yield dict(text=content_chunk) if not raw else content_chunk
|
|
151
150
|
|
|
152
151
|
# Update history and last response after stream finishes
|
|
153
152
|
self.last_response = {"text": streaming_text}
|
|
@@ -173,23 +172,22 @@ class SonusAI(Provider):
|
|
|
173
172
|
f"Request failed with status code {response.status_code} - {response.text}"
|
|
174
173
|
)
|
|
175
174
|
|
|
175
|
+
response_text_raw = response.text # Get raw text
|
|
176
|
+
|
|
177
|
+
# Use sanitize_stream to process the non-streaming text
|
|
178
|
+
processed_stream = sanitize_stream(
|
|
179
|
+
data=response_text_raw.splitlines(), # Split into lines
|
|
180
|
+
intro_value="data:",
|
|
181
|
+
to_json=True,
|
|
182
|
+
content_extractor=self._sonus_extractor,
|
|
183
|
+
yield_raw_on_error=False
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# Aggregate the results
|
|
176
187
|
full_response = ""
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if line:
|
|
181
|
-
try:
|
|
182
|
-
if line.startswith('data: '):
|
|
183
|
-
line = line[6:]
|
|
184
|
-
|
|
185
|
-
if not line.strip():
|
|
186
|
-
continue
|
|
187
|
-
|
|
188
|
-
data = json.loads(line)
|
|
189
|
-
if "content" in data:
|
|
190
|
-
full_response += data["content"]
|
|
191
|
-
except (json.JSONDecodeError, UnicodeDecodeError):
|
|
192
|
-
continue
|
|
188
|
+
for content in processed_stream:
|
|
189
|
+
if content and isinstance(content, str):
|
|
190
|
+
full_response += content
|
|
193
191
|
|
|
194
192
|
self.last_response = {"text": full_response}
|
|
195
193
|
self.conversation.update_chat_history(prompt, full_response)
|
webscout/Provider/toolbaz.py
CHANGED
|
@@ -13,7 +13,7 @@ from typing import Any, Dict, Optional, Generator, Union, List
|
|
|
13
13
|
from webscout import exceptions
|
|
14
14
|
from webscout.AIutel import Optimizers
|
|
15
15
|
from webscout.AIutel import Conversation
|
|
16
|
-
from webscout.AIutel import AwesomePrompts
|
|
16
|
+
from webscout.AIutel import AwesomePrompts, sanitize_stream # Import sanitize_stream
|
|
17
17
|
from webscout.AIbase import Provider
|
|
18
18
|
|
|
19
19
|
class Toolbaz(Provider):
|
|
@@ -26,6 +26,7 @@ class Toolbaz(Provider):
|
|
|
26
26
|
"gemini-2.0-flash-thinking",
|
|
27
27
|
"gemini-2.0-flash",
|
|
28
28
|
"gemini-1.5-flash",
|
|
29
|
+
"o3-mini",
|
|
29
30
|
"gpt-4o-latest",
|
|
30
31
|
"gpt-4o",
|
|
31
32
|
"deepseek-r1",
|
|
@@ -111,6 +112,13 @@ class Toolbaz(Provider):
|
|
|
111
112
|
)
|
|
112
113
|
self.conversation.history_offset = history_offset
|
|
113
114
|
|
|
115
|
+
@staticmethod
|
|
116
|
+
def _toolbaz_extractor(chunk: Union[str, Dict[str, Any]]) -> Optional[str]:
|
|
117
|
+
"""Removes [model:...] tags from a string chunk."""
|
|
118
|
+
if isinstance(chunk, str):
|
|
119
|
+
return re.sub(r"\[model:.*?\]", "", chunk)
|
|
120
|
+
return None
|
|
121
|
+
|
|
114
122
|
def random_string(self, length):
|
|
115
123
|
return ''.join(random.choices(string.ascii_letters + string.digits, k=length))
|
|
116
124
|
|
|
@@ -207,53 +215,23 @@ class Toolbaz(Provider):
|
|
|
207
215
|
)
|
|
208
216
|
resp.raise_for_status()
|
|
209
217
|
|
|
210
|
-
buffer = ""
|
|
211
|
-
tag_start = "[model:"
|
|
212
218
|
streaming_text = ""
|
|
213
219
|
|
|
214
|
-
#
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
# Add remaining text after the last complete tag
|
|
231
|
-
processed_buffer += buffer[last_processed_index:]
|
|
232
|
-
|
|
233
|
-
# Now, check for incomplete tag at the end
|
|
234
|
-
last_tag_start_index = processed_buffer.rfind(tag_start)
|
|
235
|
-
|
|
236
|
-
if last_tag_start_index != -1:
|
|
237
|
-
# Text before the potential incomplete tag
|
|
238
|
-
text_to_yield = processed_buffer[:last_tag_start_index]
|
|
239
|
-
# Keep the potential incomplete tag start for the next iteration
|
|
240
|
-
buffer = processed_buffer[last_tag_start_index:]
|
|
241
|
-
else:
|
|
242
|
-
# No potential incomplete tag found, yield everything processed
|
|
243
|
-
text_to_yield = processed_buffer
|
|
244
|
-
buffer = "" # Clear buffer as everything is processed
|
|
245
|
-
|
|
246
|
-
if text_to_yield:
|
|
247
|
-
streaming_text += text_to_yield
|
|
248
|
-
# Yield dict or raw string
|
|
249
|
-
yield {"text": text_to_yield} if not raw else text_to_yield
|
|
250
|
-
|
|
251
|
-
# Process any remaining text in the buffer after the loop finishes
|
|
252
|
-
# Remove any potential tags (complete or incomplete)
|
|
253
|
-
final_text = re.sub(r"\[model:.*?\]", "", buffer)
|
|
254
|
-
if final_text:
|
|
255
|
-
streaming_text += final_text
|
|
256
|
-
yield {"text": final_text} if not raw else final_text
|
|
220
|
+
# Use sanitize_stream with the custom extractor
|
|
221
|
+
# It will decode bytes and yield processed string chunks
|
|
222
|
+
processed_stream = sanitize_stream(
|
|
223
|
+
data=resp.iter_content(chunk_size=None), # Pass byte iterator
|
|
224
|
+
intro_value=None, # No simple prefix
|
|
225
|
+
to_json=False, # Content is text
|
|
226
|
+
content_extractor=self._toolbaz_extractor, # Use the tag remover
|
|
227
|
+
yield_raw_on_error=True # Yield even if extractor somehow fails (though unlikely for regex)
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
for content_chunk in processed_stream:
|
|
231
|
+
# content_chunk is the string with tags removed
|
|
232
|
+
if content_chunk and isinstance(content_chunk, str):
|
|
233
|
+
streaming_text += content_chunk
|
|
234
|
+
yield {"text": content_chunk} if not raw else content_chunk
|
|
257
235
|
|
|
258
236
|
self.last_response = {"text": streaming_text}
|
|
259
237
|
self.conversation.update_chat_history(prompt, streaming_text)
|
webscout/Provider/turboseek.py
CHANGED
|
@@ -4,10 +4,10 @@ import json
|
|
|
4
4
|
|
|
5
5
|
from webscout.AIutel import Optimizers
|
|
6
6
|
from webscout.AIutel import Conversation
|
|
7
|
-
from webscout.AIutel import AwesomePrompts, sanitize_stream
|
|
7
|
+
from webscout.AIutel import AwesomePrompts, sanitize_stream # Import sanitize_stream
|
|
8
8
|
from webscout.AIbase import Provider
|
|
9
9
|
from webscout import exceptions
|
|
10
|
-
from typing import Union, Any, AsyncGenerator, Dict
|
|
10
|
+
from typing import Optional, Union, Any, AsyncGenerator, Dict
|
|
11
11
|
from webscout.litagent import LitAgent
|
|
12
12
|
|
|
13
13
|
class TurboSeek(Provider):
|
|
@@ -88,6 +88,13 @@ class TurboSeek(Provider):
|
|
|
88
88
|
)
|
|
89
89
|
self.conversation.history_offset = history_offset
|
|
90
90
|
|
|
91
|
+
@staticmethod
|
|
92
|
+
def _turboseek_extractor(chunk: Union[str, Dict[str, Any]]) -> Optional[str]:
|
|
93
|
+
"""Extracts content from TurboSeek stream JSON objects."""
|
|
94
|
+
if isinstance(chunk, dict) and "text" in chunk:
|
|
95
|
+
return chunk.get("text") # json.loads already handles unicode escapes
|
|
96
|
+
return None
|
|
97
|
+
|
|
91
98
|
def ask(
|
|
92
99
|
self,
|
|
93
100
|
prompt: str,
|
|
@@ -142,24 +149,24 @@ class TurboSeek(Provider):
|
|
|
142
149
|
raise exceptions.FailedToGenerateResponseError(
|
|
143
150
|
f"Failed to generate response - ({response.status_code}, {response.reason}) - {response.text}"
|
|
144
151
|
)
|
|
152
|
+
|
|
145
153
|
streaming_text = ""
|
|
146
|
-
#
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
pass # Ignore lines that are not valid JSON or cannot be decoded
|
|
154
|
+
# Use sanitize_stream with the custom extractor
|
|
155
|
+
processed_stream = sanitize_stream(
|
|
156
|
+
data=response.iter_content(chunk_size=None), # Pass byte iterator
|
|
157
|
+
intro_value="data:",
|
|
158
|
+
to_json=True, # Stream sends JSON
|
|
159
|
+
content_extractor=self._turboseek_extractor, # Use the specific extractor
|
|
160
|
+
yield_raw_on_error=False # Skip non-JSON lines or lines where extractor fails
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
for content_chunk in processed_stream:
|
|
164
|
+
# content_chunk is the string extracted by _turboseek_extractor
|
|
165
|
+
if content_chunk and isinstance(content_chunk, str):
|
|
166
|
+
streaming_text += content_chunk
|
|
167
|
+
self.last_response.update(dict(text=streaming_text)) # Update last_response incrementally
|
|
168
|
+
yield dict(text=content_chunk) if not raw else content_chunk # Yield dict or raw string
|
|
169
|
+
|
|
163
170
|
# Update conversation history after stream finishes
|
|
164
171
|
if streaming_text: # Only update if content was received
|
|
165
172
|
self.conversation.update_chat_history(
|
|
@@ -174,21 +181,15 @@ class TurboSeek(Provider):
|
|
|
174
181
|
def for_non_stream():
|
|
175
182
|
# Aggregate the stream using the updated for_stream logic
|
|
176
183
|
full_text = ""
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
data = json.loads(line)
|
|
187
|
-
if "text" in data:
|
|
188
|
-
content = data["text"].encode().decode('unicode_escape')
|
|
189
|
-
full_text += content
|
|
190
|
-
except (json.decoder.JSONDecodeError, UnicodeDecodeError):
|
|
191
|
-
pass
|
|
184
|
+
try:
|
|
185
|
+
# Ensure raw=False so for_stream yields dicts
|
|
186
|
+
for chunk_data in for_stream():
|
|
187
|
+
if isinstance(chunk_data, dict) and "text" in chunk_data:
|
|
188
|
+
full_text += chunk_data["text"]
|
|
189
|
+
elif isinstance(chunk_data, str): # Handle case where raw=True was passed
|
|
190
|
+
full_text += chunk_data
|
|
191
|
+
except Exception as e:
|
|
192
|
+
raise exceptions.FailedToGenerateResponseError(f"Failed to aggregate non-stream response: {e}") from e
|
|
192
193
|
# last_response and history are updated within for_stream
|
|
193
194
|
# Ensure last_response reflects the complete aggregated text
|
|
194
195
|
self.last_response = {"text": full_text}
|
|
@@ -241,7 +242,7 @@ class TurboSeek(Provider):
|
|
|
241
242
|
str: Message extracted
|
|
242
243
|
"""
|
|
243
244
|
assert isinstance(response, dict), "Response should be of dict data-type only"
|
|
244
|
-
#
|
|
245
|
+
# Unicode escapes are handled by json.loads within sanitize_stream
|
|
245
246
|
return response.get("text", "")
|
|
246
247
|
|
|
247
248
|
if __name__ == '__main__':
|
|
@@ -250,13 +251,9 @@ if __name__ == '__main__':
|
|
|
250
251
|
try: # Add try-except block for testing
|
|
251
252
|
ai = TurboSeek(timeout=60)
|
|
252
253
|
print("[bold blue]Testing Stream:[/bold blue]")
|
|
253
|
-
response_stream = ai.chat("
|
|
254
|
-
full_stream_response = ""
|
|
254
|
+
response_stream = ai.chat("yooooooooooo", stream=True)
|
|
255
255
|
for chunk in response_stream:
|
|
256
256
|
print(chunk, end="", flush=True)
|
|
257
|
-
full_stream_response += chunk
|
|
258
|
-
print("\n[bold green]Stream Test Complete.[/bold green]\n")
|
|
259
|
-
|
|
260
257
|
# Optional: Test non-stream
|
|
261
258
|
# print("[bold blue]Testing Non-Stream:[/bold blue]")
|
|
262
259
|
# response_non_stream = ai.chat("What is the capital of France?", stream=False)
|
|
@@ -267,4 +264,3 @@ if __name__ == '__main__':
|
|
|
267
264
|
print(f"\n[bold red]API Error:[/bold red] {e}")
|
|
268
265
|
except Exception as e:
|
|
269
266
|
print(f"\n[bold red]An unexpected error occurred:[/bold red] {e}")
|
|
270
|
-
|