webscout 8.2.4__py3-none-any.whl → 8.2.5__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/Extra/gguf.py +2 -0
- webscout/Provider/AISEARCH/scira_search.py +2 -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 +283 -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/Netwrck.py +35 -26
- webscout/Provider/OPENAI/__init__.py +1 -1
- webscout/Provider/OPENAI/exachat.py +4 -0
- webscout/Provider/OPENAI/scirachat.py +2 -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 +16 -7
- 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/__init__.py +7 -7
- 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/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 +49 -30
- webscout/Provider/scnet.py +23 -20
- 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/cli.py +256 -0
- webscout/conversation.py +0 -2
- webscout/exceptions.py +3 -0
- webscout/version.py +1 -1
- {webscout-8.2.4.dist-info → webscout-8.2.5.dist-info}/METADATA +166 -45
- {webscout-8.2.4.dist-info → webscout-8.2.5.dist-info}/RECORD +63 -76
- {webscout-8.2.4.dist-info → webscout-8.2.5.dist-info}/WHEEL +1 -1
- webscout-8.2.5.dist-info/entry_points.txt +3 -0
- {webscout-8.2.4.dist-info → webscout-8.2.5.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/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/askmyai.py +0 -158
- webscout/Provider/gaurish.py +0 -244
- webscout-8.2.4.dist-info/entry_points.txt +0 -5
- {webscout-8.2.4.dist-info → webscout-8.2.5.dist-info}/licenses/LICENSE.md +0 -0
webscout/Provider/Deepinfra.py
CHANGED
|
@@ -2,11 +2,11 @@ from curl_cffi.requests import Session
|
|
|
2
2
|
from curl_cffi import CurlError
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
|
-
from typing import Any, Dict, Optional, Generator, Union
|
|
5
|
+
from typing import Any, Dict, Optional, Generator, Union, List
|
|
6
6
|
|
|
7
7
|
from webscout.AIutel import Optimizers
|
|
8
8
|
from webscout.AIutel import Conversation
|
|
9
|
-
from webscout.AIutel import AwesomePrompts, sanitize_stream
|
|
9
|
+
from webscout.AIutel import AwesomePrompts, sanitize_stream # Import sanitize_stream
|
|
10
10
|
from webscout.AIbase import Provider, AsyncProvider
|
|
11
11
|
from webscout import exceptions
|
|
12
12
|
from webscout.litagent import LitAgent
|
|
@@ -73,6 +73,13 @@ class DeepInfra(Provider):
|
|
|
73
73
|
# "Sao10K/L3.3-70B-Euryale-v2.3", # >>>> NOT WORKING
|
|
74
74
|
]
|
|
75
75
|
|
|
76
|
+
@staticmethod
|
|
77
|
+
def _deepinfra_extractor(chunk: Union[str, Dict[str, Any]]) -> Optional[str]:
|
|
78
|
+
"""Extracts content from DeepInfra stream JSON objects."""
|
|
79
|
+
if isinstance(chunk, dict):
|
|
80
|
+
return chunk.get("choices", [{}])[0].get("delta", {}).get("content")
|
|
81
|
+
return None
|
|
82
|
+
|
|
76
83
|
def __init__(
|
|
77
84
|
self,
|
|
78
85
|
is_conversation: bool = True,
|
|
@@ -91,14 +98,14 @@ class DeepInfra(Provider):
|
|
|
91
98
|
"""Initializes the DeepInfra API client."""
|
|
92
99
|
if model not in self.AVAILABLE_MODELS:
|
|
93
100
|
raise ValueError(f"Invalid model: {model}. Choose from: {self.AVAILABLE_MODELS}")
|
|
94
|
-
|
|
101
|
+
|
|
95
102
|
self.url = "https://api.deepinfra.com/v1/openai/chat/completions"
|
|
96
|
-
|
|
103
|
+
|
|
97
104
|
# Initialize LitAgent (keep if needed for other headers or logic)
|
|
98
105
|
self.agent = LitAgent()
|
|
99
106
|
# Fingerprint generation might be less relevant with impersonate
|
|
100
107
|
self.fingerprint = self.agent.generate_fingerprint(browser)
|
|
101
|
-
|
|
108
|
+
|
|
102
109
|
# Use the fingerprint for headers (keep relevant ones)
|
|
103
110
|
self.headers = {
|
|
104
111
|
"Accept": self.fingerprint["accept"], # Keep Accept
|
|
@@ -113,7 +120,7 @@ class DeepInfra(Provider):
|
|
|
113
120
|
"Sec-Fetch-Site": "same-site",
|
|
114
121
|
"X-Deepinfra-Source": "web-embed", # Keep custom headers
|
|
115
122
|
}
|
|
116
|
-
|
|
123
|
+
|
|
117
124
|
# Initialize curl_cffi Session
|
|
118
125
|
self.session = Session()
|
|
119
126
|
# Update curl_cffi session headers and proxies
|
|
@@ -147,22 +154,22 @@ class DeepInfra(Provider):
|
|
|
147
154
|
def refresh_identity(self, browser: str = None):
|
|
148
155
|
"""
|
|
149
156
|
Refreshes the browser identity fingerprint.
|
|
150
|
-
|
|
157
|
+
|
|
151
158
|
Args:
|
|
152
159
|
browser: Specific browser to use for the new fingerprint
|
|
153
160
|
"""
|
|
154
161
|
browser = browser or self.fingerprint.get("browser_type", "chrome")
|
|
155
162
|
self.fingerprint = self.agent.generate_fingerprint(browser)
|
|
156
|
-
|
|
163
|
+
|
|
157
164
|
# Update headers with new fingerprint (only relevant ones)
|
|
158
165
|
self.headers.update({
|
|
159
166
|
"Accept": self.fingerprint["accept"],
|
|
160
167
|
"Accept-Language": self.fingerprint["accept_language"],
|
|
161
168
|
})
|
|
162
|
-
|
|
169
|
+
|
|
163
170
|
# Update session headers
|
|
164
171
|
self.session.headers.update(self.headers) # Update only relevant headers
|
|
165
|
-
|
|
172
|
+
|
|
166
173
|
return self.fingerprint
|
|
167
174
|
|
|
168
175
|
def ask(
|
|
@@ -197,68 +204,74 @@ class DeepInfra(Provider):
|
|
|
197
204
|
try:
|
|
198
205
|
# Use curl_cffi session post with impersonate
|
|
199
206
|
response = self.session.post(
|
|
200
|
-
self.url,
|
|
207
|
+
self.url,
|
|
201
208
|
# headers are set on the session
|
|
202
|
-
data=json.dumps(payload),
|
|
203
|
-
stream=True,
|
|
209
|
+
data=json.dumps(payload),
|
|
210
|
+
stream=True,
|
|
204
211
|
timeout=self.timeout,
|
|
205
212
|
impersonate="chrome110" # Use a common impersonation profile
|
|
206
213
|
)
|
|
207
214
|
response.raise_for_status() # Check for HTTP errors
|
|
208
|
-
|
|
209
|
-
#
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
yield resp if not raw else content
|
|
228
|
-
except (json.JSONDecodeError, UnicodeDecodeError):
|
|
229
|
-
continue # Ignore lines that are not valid JSON or cannot be decoded
|
|
230
|
-
|
|
231
|
-
# Update history after stream finishes
|
|
232
|
-
self.last_response = {"text": streaming_text}
|
|
233
|
-
self.conversation.update_chat_history(prompt, streaming_text)
|
|
234
|
-
|
|
235
|
-
except CurlError as e: # Catch CurlError
|
|
215
|
+
|
|
216
|
+
# Use sanitize_stream
|
|
217
|
+
processed_stream = sanitize_stream(
|
|
218
|
+
data=response.iter_content(chunk_size=None), # Pass byte iterator
|
|
219
|
+
intro_value="data:",
|
|
220
|
+
to_json=True, # Stream sends JSON
|
|
221
|
+
skip_markers=["[DONE]"],
|
|
222
|
+
content_extractor=self._deepinfra_extractor, # Use the specific extractor
|
|
223
|
+
yield_raw_on_error=False # Skip non-JSON lines or lines where extractor fails
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
for content_chunk in processed_stream:
|
|
227
|
+
# content_chunk is the string extracted by _deepinfra_extractor
|
|
228
|
+
if content_chunk and isinstance(content_chunk, str):
|
|
229
|
+
streaming_text += content_chunk
|
|
230
|
+
resp = dict(text=content_chunk)
|
|
231
|
+
yield resp if not raw else content_chunk
|
|
232
|
+
|
|
233
|
+
except CurlError as e:
|
|
236
234
|
raise exceptions.FailedToGenerateResponseError(f"Request failed (CurlError): {str(e)}") from e
|
|
237
|
-
except Exception as e:
|
|
238
|
-
|
|
239
|
-
|
|
235
|
+
except Exception as e:
|
|
236
|
+
raise exceptions.FailedToGenerateResponseError(f"Request failed ({type(e).__name__}): {str(e)}") from e
|
|
237
|
+
finally:
|
|
238
|
+
# Update history after stream finishes or fails
|
|
239
|
+
if streaming_text:
|
|
240
|
+
self.last_response = {"text": streaming_text}
|
|
241
|
+
self.conversation.update_chat_history(prompt, streaming_text)
|
|
240
242
|
|
|
241
243
|
|
|
242
244
|
def for_non_stream():
|
|
243
245
|
try:
|
|
244
246
|
# Use curl_cffi session post with impersonate for non-streaming
|
|
245
247
|
response = self.session.post(
|
|
246
|
-
self.url,
|
|
248
|
+
self.url,
|
|
247
249
|
# headers are set on the session
|
|
248
|
-
data=json.dumps(payload),
|
|
250
|
+
data=json.dumps(payload),
|
|
249
251
|
timeout=self.timeout,
|
|
250
252
|
impersonate="chrome110" # Use a common impersonation profile
|
|
251
253
|
)
|
|
252
254
|
response.raise_for_status() # Check for HTTP errors
|
|
253
255
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
256
|
+
response_text = response.text # Get raw text
|
|
257
|
+
|
|
258
|
+
# Use sanitize_stream to parse the non-streaming JSON response
|
|
259
|
+
processed_stream = sanitize_stream(
|
|
260
|
+
data=response_text,
|
|
261
|
+
to_json=True, # Parse the whole text as JSON
|
|
262
|
+
intro_value=None,
|
|
263
|
+
# Extractor for non-stream structure
|
|
264
|
+
content_extractor=lambda chunk: chunk.get("choices", [{}])[0].get("message", {}).get("content") if isinstance(chunk, dict) else None,
|
|
265
|
+
yield_raw_on_error=False
|
|
266
|
+
)
|
|
267
|
+
# Extract the single result
|
|
268
|
+
content = next(processed_stream, None)
|
|
269
|
+
content = content if isinstance(content, str) else "" # Ensure it's a string
|
|
270
|
+
|
|
271
|
+
self.last_response = {"text": content}
|
|
272
|
+
self.conversation.update_chat_history(prompt, content)
|
|
273
|
+
return self.last_response if not raw else content # Return dict or raw string
|
|
274
|
+
|
|
262
275
|
except CurlError as e: # Catch CurlError
|
|
263
276
|
raise exceptions.FailedToGenerateResponseError(f"Request failed (CurlError): {e}") from e
|
|
264
277
|
except Exception as e: # Catch other potential exceptions (like HTTPError, JSONDecodeError)
|
|
@@ -311,7 +324,7 @@ if __name__ == "__main__":
|
|
|
311
324
|
response_text = ""
|
|
312
325
|
for chunk in response:
|
|
313
326
|
response_text += chunk
|
|
314
|
-
|
|
327
|
+
|
|
315
328
|
if response_text and len(response_text.strip()) > 0:
|
|
316
329
|
status = "✓"
|
|
317
330
|
# Clean and truncate response
|
|
@@ -322,4 +335,4 @@ if __name__ == "__main__":
|
|
|
322
335
|
display_text = "Empty or invalid response"
|
|
323
336
|
print(f"\r{model:<50} {status:<10} {display_text}")
|
|
324
337
|
except Exception as e:
|
|
325
|
-
print(f"\r{model:<50} {'✗':<10} {str(e)}")
|
|
338
|
+
print(f"\r{model:<50} {'✗':<10} {str(e)}")
|
webscout/Provider/ElectronHub.py
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import
|
|
1
|
+
from curl_cffi import CurlError
|
|
2
|
+
from curl_cffi.requests import Session
|
|
2
3
|
import json
|
|
3
4
|
import os
|
|
4
|
-
from typing import Any, Dict, Optional, Generator, Union
|
|
5
|
+
from typing import Any, Dict, Optional, Generator, Union, List
|
|
6
|
+
|
|
7
|
+
import requests
|
|
5
8
|
|
|
6
9
|
from webscout.AIutel import Optimizers
|
|
7
|
-
from webscout.AIutel import Conversation
|
|
10
|
+
from webscout.AIutel import Conversation, sanitize_stream # Import sanitize_stream
|
|
8
11
|
from webscout.AIutel import AwesomePrompts, sanitize_stream
|
|
9
12
|
from webscout.AIbase import Provider, AsyncProvider
|
|
10
13
|
from webscout import exceptions
|
|
@@ -550,6 +553,13 @@ class ElectronHub(Provider):
|
|
|
550
553
|
# Fallback to default models list if fetching fails
|
|
551
554
|
pass
|
|
552
555
|
|
|
556
|
+
@staticmethod
|
|
557
|
+
def _electronhub_extractor(chunk: Union[str, Dict[str, Any]]) -> Optional[str]:
|
|
558
|
+
"""Extracts content from ElectronHub stream JSON objects."""
|
|
559
|
+
if isinstance(chunk, dict):
|
|
560
|
+
return chunk.get("choices", [{}])[0].get("delta", {}).get("content")
|
|
561
|
+
return None
|
|
562
|
+
|
|
553
563
|
def __init__(
|
|
554
564
|
self,
|
|
555
565
|
is_conversation: bool = True,
|
|
@@ -593,9 +603,9 @@ class ElectronHub(Provider):
|
|
|
593
603
|
if api_key:
|
|
594
604
|
self.headers['Authorization'] = f'Bearer {api_key}'
|
|
595
605
|
self.system_prompt = system_prompt
|
|
596
|
-
self.session =
|
|
606
|
+
self.session = Session() # Use curl_cffi Session
|
|
597
607
|
self.session.headers.update(self.headers)
|
|
598
|
-
self.session.proxies
|
|
608
|
+
self.session.proxies = proxies # Assign proxies directly
|
|
599
609
|
|
|
600
610
|
self.is_conversation = is_conversation
|
|
601
611
|
self.max_tokens = max_tokens
|
|
@@ -663,41 +673,38 @@ class ElectronHub(Provider):
|
|
|
663
673
|
|
|
664
674
|
def for_stream():
|
|
665
675
|
try:
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
except Exception as e:
|
|
694
|
-
print(f"Error processing chunk: {e}")
|
|
695
|
-
continue
|
|
696
|
-
|
|
697
|
-
self.conversation.update_chat_history(prompt, streaming_text)
|
|
698
|
-
|
|
699
|
-
except requests.RequestException as e:
|
|
676
|
+
response = self.session.post(
|
|
677
|
+
self.url, headers=self.headers, data=json.dumps(payload), stream=True, timeout=self.timeout,
|
|
678
|
+
impersonate="chrome120" # Add impersonate
|
|
679
|
+
)
|
|
680
|
+
response.raise_for_status()
|
|
681
|
+
|
|
682
|
+
streaming_text = ""
|
|
683
|
+
# Use sanitize_stream
|
|
684
|
+
processed_stream = sanitize_stream(
|
|
685
|
+
data=response.iter_content(chunk_size=None), # Pass byte iterator
|
|
686
|
+
intro_value="data:",
|
|
687
|
+
to_json=True, # Stream sends JSON
|
|
688
|
+
skip_markers=["[DONE]"],
|
|
689
|
+
content_extractor=self._electronhub_extractor, # Use the specific extractor
|
|
690
|
+
yield_raw_on_error=False # Skip non-JSON lines or lines where extractor fails
|
|
691
|
+
)
|
|
692
|
+
|
|
693
|
+
for content_chunk in processed_stream:
|
|
694
|
+
# content_chunk is the string extracted by _electronhub_extractor
|
|
695
|
+
if content_chunk and isinstance(content_chunk, str):
|
|
696
|
+
streaming_text += content_chunk
|
|
697
|
+
resp = dict(text=content_chunk)
|
|
698
|
+
yield resp if not raw else content_chunk
|
|
699
|
+
|
|
700
|
+
except CurlError as e:
|
|
701
|
+
raise exceptions.FailedToGenerateResponseError(f"Request failed (CurlError): {str(e)}") from e
|
|
702
|
+
except Exception as e:
|
|
700
703
|
raise exceptions.FailedToGenerateResponseError(f"Request failed: {str(e)}")
|
|
704
|
+
finally:
|
|
705
|
+
# Update history after stream finishes or fails
|
|
706
|
+
if streaming_text:
|
|
707
|
+
self.conversation.update_chat_history(prompt, streaming_text)
|
|
701
708
|
|
|
702
709
|
def for_non_stream():
|
|
703
710
|
collected_response = ""
|
|
@@ -710,7 +717,9 @@ class ElectronHub(Provider):
|
|
|
710
717
|
except Exception as e:
|
|
711
718
|
raise exceptions.FailedToGenerateResponseError(f"Error during non-stream processing: {str(e)}")
|
|
712
719
|
|
|
720
|
+
# Update history and last_response after aggregation
|
|
713
721
|
self.last_response = {"text": collected_response}
|
|
722
|
+
self.conversation.update_chat_history(prompt, collected_response)
|
|
714
723
|
return self.last_response
|
|
715
724
|
|
|
716
725
|
return for_stream() if stream else for_non_stream()
|
webscout/Provider/ExaChat.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import
|
|
1
|
+
from curl_cffi import CurlError
|
|
2
|
+
from curl_cffi.requests import Session, Response # Import Response
|
|
2
3
|
import json
|
|
3
4
|
import uuid
|
|
4
|
-
from typing import Any, Dict, Union, Optional
|
|
5
|
+
from typing import Any, Dict, Union, Optional, List
|
|
5
6
|
from datetime import datetime
|
|
6
|
-
from webscout.AIutel import Optimizers, Conversation, AwesomePrompts
|
|
7
|
-
from webscout.AIbase import Provider
|
|
7
|
+
from webscout.AIutel import Optimizers, Conversation, AwesomePrompts, sanitize_stream # Import sanitize_stream
|
|
8
|
+
from webscout.AIbase import Provider
|
|
8
9
|
from webscout import exceptions
|
|
9
10
|
from webscout.litagent import LitAgent
|
|
10
11
|
|
|
@@ -22,6 +23,8 @@ MODEL_CONFIGS = {
|
|
|
22
23
|
"gemini-2.0-flash-thinking-exp-01-21",
|
|
23
24
|
"gemini-2.5-pro-exp-03-25",
|
|
24
25
|
"gemini-2.0-pro-exp-02-05",
|
|
26
|
+
"gemini-2.5-flash-preview-04-17",
|
|
27
|
+
|
|
25
28
|
|
|
26
29
|
],
|
|
27
30
|
},
|
|
@@ -87,6 +90,7 @@ class ExaChat(Provider):
|
|
|
87
90
|
"gemini-2.0-flash-thinking-exp-01-21",
|
|
88
91
|
"gemini-2.5-pro-exp-03-25",
|
|
89
92
|
"gemini-2.0-pro-exp-02-05",
|
|
93
|
+
"gemini-2.5-flash-preview-04-17",
|
|
90
94
|
|
|
91
95
|
# OpenRouter Models
|
|
92
96
|
"mistralai/mistral-small-3.1-24b-instruct:free",
|
|
@@ -141,7 +145,7 @@ class ExaChat(Provider):
|
|
|
141
145
|
if model not in self.AVAILABLE_MODELS:
|
|
142
146
|
raise ValueError(f"Invalid model: {model}. Choose from: {self.AVAILABLE_MODELS}")
|
|
143
147
|
|
|
144
|
-
self.session =
|
|
148
|
+
self.session = Session() # Use curl_cffi Session
|
|
145
149
|
self.is_conversation = is_conversation
|
|
146
150
|
self.max_tokens_to_sample = max_tokens
|
|
147
151
|
self.timeout = timeout
|
|
@@ -166,7 +170,7 @@ class ExaChat(Provider):
|
|
|
166
170
|
}
|
|
167
171
|
|
|
168
172
|
self.session.headers.update(self.headers)
|
|
169
|
-
self.session.proxies = proxies
|
|
173
|
+
self.session.proxies = proxies # Assign proxies directly
|
|
170
174
|
self.session.cookies.update({"session": uuid.uuid4().hex})
|
|
171
175
|
|
|
172
176
|
self.__available_optimizers = (
|
|
@@ -208,18 +212,27 @@ class ExaChat(Provider):
|
|
|
208
212
|
error_msg = f"Invalid model: {model}\nAvailable models: {', '.join(available_models)}"
|
|
209
213
|
raise ValueError(error_msg)
|
|
210
214
|
|
|
211
|
-
|
|
215
|
+
@staticmethod
|
|
216
|
+
def _exachat_extractor(chunk: Union[str, Dict[str, Any]]) -> Optional[str]:
|
|
217
|
+
"""Extracts content from ExaChat stream JSON objects."""
|
|
218
|
+
if isinstance(chunk, dict):
|
|
219
|
+
return chunk.get("choices", [{}])[0].get("delta", {}).get("content")
|
|
220
|
+
return None
|
|
221
|
+
|
|
222
|
+
def _make_request(self, payload: Dict[str, Any]) -> Response: # Change type hint to Response
|
|
212
223
|
"""Make the API request with proper error handling."""
|
|
213
224
|
try:
|
|
214
225
|
response = self.session.post(
|
|
215
226
|
self._get_endpoint(),
|
|
216
227
|
headers=self.headers,
|
|
217
228
|
json=payload,
|
|
218
|
-
timeout=self.timeout,
|
|
229
|
+
timeout=self.timeout, # type: ignore
|
|
230
|
+
stream=True, # Enable streaming for the request
|
|
231
|
+
impersonate="chrome120" # Add impersonate
|
|
219
232
|
)
|
|
220
233
|
response.raise_for_status()
|
|
221
234
|
return response
|
|
222
|
-
except
|
|
235
|
+
except (CurlError, exceptions.FailedToGenerateResponseError, Exception) as e: # Catch CurlError and others
|
|
223
236
|
raise exceptions.FailedToGenerateResponseError(f"API request failed: {e}") from e
|
|
224
237
|
|
|
225
238
|
def _build_payload(self, conversation_prompt: str) -> Dict[str, Any]:
|
|
@@ -271,20 +284,23 @@ class ExaChat(Provider):
|
|
|
271
284
|
|
|
272
285
|
try:
|
|
273
286
|
full_response = ""
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
287
|
+
# Use sanitize_stream to process the response
|
|
288
|
+
processed_stream = sanitize_stream(
|
|
289
|
+
data=response.iter_content(chunk_size=None), # Pass byte iterator
|
|
290
|
+
intro_value=None, # API doesn't seem to use 'data:' prefix
|
|
291
|
+
to_json=True, # Stream sends JSON lines
|
|
292
|
+
content_extractor=self._exachat_extractor, # Use the specific extractor
|
|
293
|
+
yield_raw_on_error=False # Skip non-JSON lines or lines where extractor fails
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
for content_chunk in processed_stream:
|
|
297
|
+
# content_chunk is the string extracted by _exachat_extractor
|
|
298
|
+
if content_chunk and isinstance(content_chunk, str):
|
|
299
|
+
full_response += content_chunk
|
|
284
300
|
|
|
285
301
|
self.last_response = {"text": full_response}
|
|
286
302
|
self.conversation.update_chat_history(prompt, full_response)
|
|
287
|
-
return self.last_response
|
|
303
|
+
return self.last_response if not raw else full_response # Return dict or raw string
|
|
288
304
|
|
|
289
305
|
except json.JSONDecodeError as e:
|
|
290
306
|
raise exceptions.FailedToGenerateResponseError(f"Invalid JSON response: {e}") from e
|
webscout/Provider/GPTWeb.py
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
from typing import Generator, Union
|
|
1
|
+
from typing import Any, Dict, Generator, Optional, Union
|
|
2
2
|
from curl_cffi.requests import Session
|
|
3
3
|
from curl_cffi import CurlError
|
|
4
4
|
import json
|
|
5
5
|
|
|
6
6
|
from webscout import exceptions
|
|
7
7
|
from webscout.AIutel import Optimizers
|
|
8
|
-
from webscout.AIutel import Conversation
|
|
8
|
+
from webscout.AIutel import Conversation, sanitize_stream # Import sanitize_stream
|
|
9
9
|
from webscout.AIutel import AwesomePrompts
|
|
10
10
|
from webscout.AIbase import Provider
|
|
11
11
|
|
|
@@ -75,6 +75,13 @@ class GPTWeb(Provider):
|
|
|
75
75
|
)
|
|
76
76
|
self.conversation.history_offset = history_offset
|
|
77
77
|
|
|
78
|
+
@staticmethod
|
|
79
|
+
def _gptweb_extractor(chunk: Union[str, Dict[str, Any]]) -> Optional[str]:
|
|
80
|
+
"""Extracts content from GPTWeb stream JSON objects."""
|
|
81
|
+
if isinstance(chunk, dict):
|
|
82
|
+
return chunk.get("gpt")
|
|
83
|
+
return None
|
|
84
|
+
|
|
78
85
|
def ask(
|
|
79
86
|
self,
|
|
80
87
|
prompt: str,
|
|
@@ -129,22 +136,21 @@ class GPTWeb(Provider):
|
|
|
129
136
|
)
|
|
130
137
|
response.raise_for_status() # Check for HTTP errors
|
|
131
138
|
|
|
132
|
-
#
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
continue # Ignore lines that are not valid JSON or cannot be decoded
|
|
139
|
+
# Use sanitize_stream
|
|
140
|
+
processed_stream = sanitize_stream(
|
|
141
|
+
data=response.iter_content(chunk_size=None), # Pass byte iterator
|
|
142
|
+
intro_value=None, # No standard prefix, potential '_' handled by json.loads
|
|
143
|
+
to_json=True, # Stream sends JSON lines
|
|
144
|
+
content_extractor=self._gptweb_extractor, # Use the specific extractor
|
|
145
|
+
yield_raw_on_error=False # Skip non-JSON lines or lines where extractor fails
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
for content_chunk in processed_stream:
|
|
149
|
+
# content_chunk is the full text extracted by _gptweb_extractor
|
|
150
|
+
if content_chunk and isinstance(content_chunk, str):
|
|
151
|
+
full_response = content_chunk # API sends full response each time
|
|
152
|
+
resp = dict(text=full_response)
|
|
153
|
+
yield resp if not raw else full_response
|
|
148
154
|
|
|
149
155
|
# Update history after stream finishes (using the final full response)
|
|
150
156
|
self.last_response = dict(text=full_response)
|