webscout 8.2.3__py3-none-any.whl → 8.2.4__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.
- inferno/lol.py +589 -0
- webscout/AIutel.py +226 -14
- webscout/Bard.py +579 -206
- webscout/DWEBS.py +78 -35
- webscout/Extra/tempmail/base.py +1 -1
- webscout/Provider/AISEARCH/hika_search.py +4 -0
- webscout/Provider/AllenAI.py +163 -126
- webscout/Provider/ChatGPTClone.py +96 -84
- webscout/Provider/Deepinfra.py +95 -67
- webscout/Provider/ElectronHub.py +55 -0
- webscout/Provider/GPTWeb.py +96 -46
- webscout/Provider/Groq.py +194 -91
- webscout/Provider/HeckAI.py +89 -47
- webscout/Provider/HuggingFaceChat.py +113 -106
- webscout/Provider/Hunyuan.py +94 -83
- webscout/Provider/Jadve.py +107 -75
- webscout/Provider/LambdaChat.py +106 -64
- webscout/Provider/Llama3.py +94 -39
- webscout/Provider/MCPCore.py +318 -0
- webscout/Provider/Marcus.py +85 -36
- webscout/Provider/Netwrck.py +76 -43
- webscout/Provider/OPENAI/__init__.py +4 -1
- webscout/Provider/OPENAI/ai4chat.py +286 -0
- webscout/Provider/OPENAI/chatgptclone.py +35 -14
- webscout/Provider/OPENAI/deepinfra.py +37 -0
- webscout/Provider/OPENAI/groq.py +354 -0
- webscout/Provider/OPENAI/heckai.py +6 -2
- webscout/Provider/OPENAI/mcpcore.py +376 -0
- webscout/Provider/OPENAI/multichat.py +368 -0
- webscout/Provider/OPENAI/netwrck.py +3 -1
- webscout/Provider/OpenGPT.py +48 -38
- webscout/Provider/PI.py +168 -92
- webscout/Provider/PizzaGPT.py +66 -36
- webscout/Provider/TeachAnything.py +85 -51
- webscout/Provider/TextPollinationsAI.py +109 -51
- webscout/Provider/TwoAI.py +109 -60
- webscout/Provider/Venice.py +93 -56
- webscout/Provider/VercelAI.py +2 -2
- webscout/Provider/WiseCat.py +65 -28
- webscout/Provider/Writecream.py +37 -11
- webscout/Provider/WritingMate.py +135 -63
- webscout/Provider/__init__.py +3 -21
- webscout/Provider/ai4chat.py +6 -7
- webscout/Provider/copilot.py +0 -3
- webscout/Provider/elmo.py +101 -58
- webscout/Provider/granite.py +91 -46
- webscout/Provider/hermes.py +87 -47
- webscout/Provider/koala.py +1 -1
- webscout/Provider/learnfastai.py +104 -50
- webscout/Provider/llama3mitril.py +86 -51
- webscout/Provider/llmchat.py +88 -46
- webscout/Provider/llmchatco.py +74 -49
- webscout/Provider/meta.py +41 -37
- webscout/Provider/multichat.py +54 -25
- webscout/Provider/scnet.py +93 -43
- webscout/Provider/searchchat.py +82 -75
- webscout/Provider/sonus.py +103 -51
- webscout/Provider/toolbaz.py +132 -77
- webscout/Provider/turboseek.py +92 -41
- webscout/Provider/tutorai.py +82 -64
- webscout/Provider/typefully.py +75 -33
- webscout/Provider/typegpt.py +96 -35
- webscout/Provider/uncovr.py +112 -62
- webscout/Provider/x0gpt.py +69 -26
- webscout/Provider/yep.py +79 -66
- webscout/conversation.py +35 -21
- webscout/exceptions.py +20 -0
- webscout/prompt_manager.py +56 -42
- webscout/version.py +1 -1
- webscout/webscout_search.py +65 -47
- webscout/webscout_search_async.py +81 -126
- webscout/yep_search.py +93 -43
- {webscout-8.2.3.dist-info → webscout-8.2.4.dist-info}/METADATA +22 -10
- {webscout-8.2.3.dist-info → webscout-8.2.4.dist-info}/RECORD +78 -81
- {webscout-8.2.3.dist-info → webscout-8.2.4.dist-info}/WHEEL +1 -1
- webscout/Provider/C4ai.py +0 -432
- webscout/Provider/ChatGPTES.py +0 -237
- webscout/Provider/DeepSeek.py +0 -196
- webscout/Provider/Llama.py +0 -200
- webscout/Provider/Phind.py +0 -535
- webscout/Provider/WebSim.py +0 -228
- webscout/Provider/labyrinth.py +0 -340
- webscout/Provider/lepton.py +0 -194
- webscout/Provider/llamatutor.py +0 -192
- {webscout-8.2.3.dist-info → webscout-8.2.4.dist-info}/entry_points.txt +0 -0
- {webscout-8.2.3.dist-info → webscout-8.2.4.dist-info/licenses}/LICENSE.md +0 -0
- {webscout-8.2.3.dist-info → webscout-8.2.4.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import time
|
|
2
2
|
import uuid
|
|
3
|
-
import cloudscraper
|
|
3
|
+
# import cloudscraper
|
|
4
|
+
from curl_cffi.requests import Session, RequestsError
|
|
4
5
|
import json
|
|
5
6
|
import re
|
|
6
7
|
from typing import Any, Dict, Optional, Generator, Union
|
|
@@ -12,7 +13,7 @@ from webscout.AIutel import Conversation
|
|
|
12
13
|
from webscout.AIutel import AwesomePrompts
|
|
13
14
|
from webscout.AIbase import Provider
|
|
14
15
|
from webscout import WEBS, exceptions
|
|
15
|
-
from webscout.litagent import LitAgent
|
|
16
|
+
# from webscout.litagent import LitAgent
|
|
16
17
|
|
|
17
18
|
class ChatGPTClone(Provider):
|
|
18
19
|
"""
|
|
@@ -22,6 +23,11 @@ class ChatGPTClone(Provider):
|
|
|
22
23
|
|
|
23
24
|
url = "https://chatgpt-clone-ten-nu.vercel.app"
|
|
24
25
|
AVAILABLE_MODELS = ["gpt-4", "gpt-3.5-turbo"]
|
|
26
|
+
SUPPORTED_IMPERSONATION = [
|
|
27
|
+
"chrome110", "chrome116", "chrome119", "chrome120",
|
|
28
|
+
"chrome99_android", "edge99", "edge101",
|
|
29
|
+
"safari15_3", "safari15_6_1", "safari17_0", "safari17_2_1"
|
|
30
|
+
]
|
|
25
31
|
|
|
26
32
|
def __init__(
|
|
27
33
|
self,
|
|
@@ -37,15 +43,18 @@ class ChatGPTClone(Provider):
|
|
|
37
43
|
model: str = "gpt-4",
|
|
38
44
|
temperature: float = 0.6,
|
|
39
45
|
top_p: float = 0.7,
|
|
40
|
-
|
|
46
|
+
impersonate: str = "chrome120",
|
|
41
47
|
system_prompt: str = "You are a helpful assistant."
|
|
42
48
|
):
|
|
43
|
-
"""Initialize the ChatGPT Clone client."""
|
|
49
|
+
"""Initialize the ChatGPT Clone client using curl_cffi."""
|
|
44
50
|
if model not in self.AVAILABLE_MODELS:
|
|
45
51
|
raise ValueError(f"Invalid model: {model}. Choose from: {self.AVAILABLE_MODELS}")
|
|
52
|
+
if impersonate not in self.SUPPORTED_IMPERSONATION:
|
|
53
|
+
raise ValueError(f"Invalid impersonate browser: {impersonate}. Choose from: {self.SUPPORTED_IMPERSONATION}")
|
|
46
54
|
|
|
47
55
|
self.model = model
|
|
48
|
-
self.
|
|
56
|
+
self.impersonate = impersonate
|
|
57
|
+
self.session = Session(impersonate=self.impersonate, proxies=proxies, timeout=timeout)
|
|
49
58
|
self.is_conversation = is_conversation
|
|
50
59
|
self.max_tokens_to_sample = max_tokens
|
|
51
60
|
self.timeout = timeout
|
|
@@ -54,28 +63,17 @@ class ChatGPTClone(Provider):
|
|
|
54
63
|
self.top_p = top_p
|
|
55
64
|
self.system_prompt = system_prompt
|
|
56
65
|
|
|
57
|
-
# Initialize LitAgent for user agent generation
|
|
58
|
-
self.agent = LitAgent()
|
|
59
|
-
# Use fingerprinting to create a consistent browser identity
|
|
60
|
-
self.fingerprint = self.agent.generate_fingerprint(browser)
|
|
61
|
-
|
|
62
|
-
# Use the fingerprint for headers
|
|
63
66
|
self.headers = {
|
|
64
|
-
"Accept": self.fingerprint["accept"],
|
|
65
|
-
"Accept-Encoding": "gzip, deflate, br, zstd",
|
|
66
|
-
"Accept-Language": self.fingerprint["accept_language"],
|
|
67
67
|
"Content-Type": "application/json",
|
|
68
|
-
"DNT": "1",
|
|
69
68
|
"Origin": self.url,
|
|
70
69
|
"Referer": f"{self.url}/",
|
|
71
|
-
"
|
|
72
|
-
"Sec-
|
|
73
|
-
"Sec-
|
|
74
|
-
"
|
|
70
|
+
"DNT": "1",
|
|
71
|
+
"Sec-Fetch-Dest": "empty",
|
|
72
|
+
"Sec-Fetch-Mode": "cors",
|
|
73
|
+
"Sec-Fetch-Site": "same-origin",
|
|
74
|
+
"TE": "trailers"
|
|
75
75
|
}
|
|
76
|
-
|
|
77
|
-
# Create session cookies with unique identifiers
|
|
78
|
-
self.cookies = {"__Host-session": uuid.uuid4().hex, '__cf_bm': uuid.uuid4().hex}
|
|
76
|
+
self.session.headers.update(self.headers)
|
|
79
77
|
|
|
80
78
|
self.__available_optimizers = (
|
|
81
79
|
method
|
|
@@ -92,34 +90,20 @@ class ChatGPTClone(Provider):
|
|
|
92
90
|
is_conversation, self.max_tokens_to_sample, filepath, update_file
|
|
93
91
|
)
|
|
94
92
|
self.conversation.history_offset = history_offset
|
|
95
|
-
self.session.proxies = proxies
|
|
96
|
-
|
|
97
|
-
# Set consistent headers for the scraper session
|
|
98
|
-
for header, value in self.headers.items():
|
|
99
|
-
self.session.headers[header] = value
|
|
100
93
|
|
|
101
|
-
def refresh_identity(self,
|
|
102
|
-
"""
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
self.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
# Update session headers
|
|
116
|
-
for header, value in self.headers.items():
|
|
117
|
-
self.session.headers[header] = value
|
|
118
|
-
|
|
119
|
-
# Generate new cookies
|
|
120
|
-
self.cookies = {"__Host-session": uuid.uuid4().hex, '__cf_bm': uuid.uuid4().hex}
|
|
121
|
-
|
|
122
|
-
return self.fingerprint
|
|
94
|
+
def refresh_identity(self, impersonate: str = None):
|
|
95
|
+
"""Re-initializes the curl_cffi session with a new impersonation target."""
|
|
96
|
+
impersonate = impersonate or self.impersonate
|
|
97
|
+
if impersonate not in self.SUPPORTED_IMPERSONATION:
|
|
98
|
+
raise ValueError(f"Invalid impersonate browser: {impersonate}. Choose from: {self.SUPPORTED_IMPERSONATION}")
|
|
99
|
+
self.impersonate = impersonate
|
|
100
|
+
self.session = Session(
|
|
101
|
+
impersonate=self.impersonate,
|
|
102
|
+
proxies=self.session.proxies,
|
|
103
|
+
timeout=self.timeout
|
|
104
|
+
)
|
|
105
|
+
self.session.headers.update(self.headers)
|
|
106
|
+
return self.impersonate
|
|
123
107
|
|
|
124
108
|
def ask(
|
|
125
109
|
self,
|
|
@@ -129,7 +113,7 @@ class ChatGPTClone(Provider):
|
|
|
129
113
|
optimizer: str = None,
|
|
130
114
|
conversationally: bool = False,
|
|
131
115
|
) -> Union[Dict[str, Any], Generator]:
|
|
132
|
-
"""Send a message to the ChatGPT Clone API"""
|
|
116
|
+
"""Send a message to the ChatGPT Clone API using curl_cffi"""
|
|
133
117
|
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
|
134
118
|
if optimizer:
|
|
135
119
|
if optimizer in self.__available_optimizers:
|
|
@@ -149,38 +133,66 @@ class ChatGPTClone(Provider):
|
|
|
149
133
|
"model": self.model
|
|
150
134
|
}
|
|
151
135
|
|
|
152
|
-
|
|
136
|
+
api_url = f"{self.url}/api/chat"
|
|
137
|
+
|
|
138
|
+
def _make_request(attempt_refresh=True):
|
|
153
139
|
try:
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
f"Failed to generate response - ({response.status_code}, {response.reason}) - {response.text}"
|
|
169
|
-
)
|
|
140
|
+
response = self.session.post(api_url, json=payload, stream=True)
|
|
141
|
+
response.raise_for_status()
|
|
142
|
+
return response
|
|
143
|
+
except RequestsError as e:
|
|
144
|
+
if attempt_refresh and e.response and e.response.status_code in [403, 429]:
|
|
145
|
+
self.refresh_identity()
|
|
146
|
+
return _make_request(attempt_refresh=False)
|
|
147
|
+
else:
|
|
148
|
+
err_msg = f"Request failed: {e}"
|
|
149
|
+
if e.response is not None:
|
|
150
|
+
err_msg = f"Failed to generate response - ({e.response.status_code}, {e.response.reason}) - {e.response.text}"
|
|
151
|
+
raise exceptions.FailedToGenerateResponseError(err_msg) from e
|
|
152
|
+
except Exception as e:
|
|
153
|
+
raise exceptions.FailedToGenerateResponseError(f"An unexpected error occurred: {e}") from e
|
|
170
154
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
155
|
+
def for_stream():
|
|
156
|
+
response = _make_request()
|
|
157
|
+
streaming_text = ""
|
|
158
|
+
buffer = ""
|
|
159
|
+
try:
|
|
160
|
+
for line in response.iter_content():
|
|
161
|
+
if line:
|
|
162
|
+
# decode bytes to str before concatenation
|
|
163
|
+
if isinstance(line, bytes):
|
|
164
|
+
line = line.decode("utf-8", errors="replace")
|
|
165
|
+
buffer += line
|
|
166
|
+
match = re.search(r'0:"((?:[^\\"]|\\.)*)"', buffer)
|
|
167
|
+
if match:
|
|
168
|
+
content = match.group(1)
|
|
169
|
+
try:
|
|
170
|
+
decoded_content = json.loads(f'"{content}"')
|
|
171
|
+
except json.JSONDecodeError:
|
|
172
|
+
decoded_content = content.encode().decode('unicode_escape')
|
|
173
|
+
streaming_text += decoded_content
|
|
174
|
+
yield decoded_content if raw else dict(text=decoded_content)
|
|
175
|
+
buffer = ""
|
|
176
|
+
elif len(buffer) > 1024:
|
|
177
|
+
buffer = ""
|
|
178
|
+
if buffer:
|
|
179
|
+
match = re.search(r'0:"((?:[^\\"]|\\.)*)"', buffer)
|
|
180
|
+
if match:
|
|
181
|
+
content = match.group(1)
|
|
182
|
+
try:
|
|
183
|
+
decoded_content = json.loads(f'"{content}"')
|
|
184
|
+
except json.JSONDecodeError:
|
|
185
|
+
decoded_content = content.encode().decode('unicode_escape')
|
|
186
|
+
streaming_text += decoded_content
|
|
187
|
+
yield decoded_content if raw else dict(text=decoded_content)
|
|
188
|
+
self.last_response.update(dict(text=streaming_text))
|
|
189
|
+
self.conversation.update_chat_history(prompt, streaming_text)
|
|
190
|
+
except RequestsError as e:
|
|
191
|
+
raise exceptions.FailedToGenerateResponseError(f"Stream interrupted by request error: {e}") from e
|
|
182
192
|
except Exception as e:
|
|
183
|
-
raise exceptions.FailedToGenerateResponseError(f"
|
|
193
|
+
raise exceptions.FailedToGenerateResponseError(f"Error processing stream: {e}") from e
|
|
194
|
+
finally:
|
|
195
|
+
response.close()
|
|
184
196
|
|
|
185
197
|
def for_non_stream():
|
|
186
198
|
for _ in for_stream():
|
|
@@ -202,25 +214,25 @@ class ChatGPTClone(Provider):
|
|
|
202
214
|
prompt, True, optimizer=optimizer, conversationally=conversationally
|
|
203
215
|
):
|
|
204
216
|
yield self.get_message(response)
|
|
205
|
-
|
|
206
217
|
def for_non_stream():
|
|
207
218
|
return self.get_message(
|
|
208
219
|
self.ask(
|
|
209
220
|
prompt, False, optimizer=optimizer, conversationally=conversationally
|
|
210
221
|
)
|
|
211
222
|
)
|
|
212
|
-
|
|
213
223
|
return for_stream() if stream else for_non_stream()
|
|
214
224
|
|
|
215
225
|
def get_message(self, response: dict) -> str:
|
|
216
226
|
"""Extract message text from response"""
|
|
217
227
|
assert isinstance(response, dict)
|
|
218
|
-
|
|
228
|
+
if not isinstance(response, dict) or "text" not in response:
|
|
229
|
+
return str(response)
|
|
230
|
+
formatted_text = response["text"]
|
|
219
231
|
return formatted_text
|
|
220
232
|
|
|
221
233
|
if __name__ == "__main__":
|
|
222
234
|
from rich import print
|
|
223
|
-
ai = ChatGPTClone(timeout=
|
|
235
|
+
ai = ChatGPTClone(timeout=120, impersonate="chrome120")
|
|
224
236
|
response = ai.chat("write a poem about AI", stream=True)
|
|
225
237
|
for chunk in response:
|
|
226
|
-
print(chunk, end="", flush=True)
|
|
238
|
+
print(chunk, end="", flush=True)
|
webscout/Provider/Deepinfra.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
from curl_cffi.requests import Session
|
|
2
|
+
from curl_cffi import CurlError
|
|
2
3
|
import json
|
|
3
4
|
import os
|
|
4
5
|
from typing import Any, Dict, Optional, Generator, Union
|
|
@@ -65,6 +66,9 @@ class DeepInfra(Provider):
|
|
|
65
66
|
# "Qwen/Qwen2.5-7B-Instruct", # >>>> NOT WORKING
|
|
66
67
|
"Qwen/Qwen2.5-72B-Instruct",
|
|
67
68
|
"Qwen/Qwen2.5-Coder-32B-Instruct",
|
|
69
|
+
"Qwen/Qwen3-14B",
|
|
70
|
+
"Qwen/Qwen3-30B-A3B",
|
|
71
|
+
"Qwen/Qwen3-32B",
|
|
68
72
|
# "Sao10K/L3.1-70B-Euryale-v2.2", # >>>> NOT WORKING
|
|
69
73
|
# "Sao10K/L3.3-70B-Euryale-v2.3", # >>>> NOT WORKING
|
|
70
74
|
]
|
|
@@ -72,7 +76,7 @@ class DeepInfra(Provider):
|
|
|
72
76
|
def __init__(
|
|
73
77
|
self,
|
|
74
78
|
is_conversation: bool = True,
|
|
75
|
-
max_tokens: int = 2049,
|
|
79
|
+
max_tokens: int = 2049,
|
|
76
80
|
timeout: int = 30,
|
|
77
81
|
intro: str = None,
|
|
78
82
|
filepath: str = None,
|
|
@@ -82,7 +86,7 @@ class DeepInfra(Provider):
|
|
|
82
86
|
act: str = None,
|
|
83
87
|
model: str = "meta-llama/Llama-3.3-70B-Instruct-Turbo",
|
|
84
88
|
system_prompt: str = "You are a helpful assistant.",
|
|
85
|
-
browser: str = "chrome"
|
|
89
|
+
browser: str = "chrome" # Note: browser fingerprinting might be less effective with impersonate
|
|
86
90
|
):
|
|
87
91
|
"""Initializes the DeepInfra API client."""
|
|
88
92
|
if model not in self.AVAILABLE_MODELS:
|
|
@@ -90,35 +94,31 @@ class DeepInfra(Provider):
|
|
|
90
94
|
|
|
91
95
|
self.url = "https://api.deepinfra.com/v1/openai/chat/completions"
|
|
92
96
|
|
|
93
|
-
# Initialize LitAgent for
|
|
97
|
+
# Initialize LitAgent (keep if needed for other headers or logic)
|
|
94
98
|
self.agent = LitAgent()
|
|
95
|
-
#
|
|
99
|
+
# Fingerprint generation might be less relevant with impersonate
|
|
96
100
|
self.fingerprint = self.agent.generate_fingerprint(browser)
|
|
97
101
|
|
|
98
|
-
# Use the fingerprint for headers
|
|
102
|
+
# Use the fingerprint for headers (keep relevant ones)
|
|
99
103
|
self.headers = {
|
|
100
|
-
"Accept": self.fingerprint["accept"],
|
|
101
|
-
"Accept-
|
|
102
|
-
"Accept-Language": self.fingerprint["accept_language"],
|
|
104
|
+
"Accept": self.fingerprint["accept"], # Keep Accept
|
|
105
|
+
"Accept-Language": self.fingerprint["accept_language"], # Keep Accept-Language
|
|
103
106
|
"Content-Type": "application/json",
|
|
104
|
-
"Cache-Control": "no-cache",
|
|
105
|
-
"
|
|
106
|
-
"
|
|
107
|
-
"
|
|
108
|
-
"
|
|
109
|
-
"Sec-Fetch-Dest": "empty",
|
|
107
|
+
"Cache-Control": "no-cache", # Keep Cache-Control
|
|
108
|
+
"Origin": "https://deepinfra.com", # Keep Origin
|
|
109
|
+
"Pragma": "no-cache", # Keep Pragma
|
|
110
|
+
"Referer": "https://deepinfra.com/", # Keep Referer
|
|
111
|
+
"Sec-Fetch-Dest": "empty", # Keep Sec-Fetch-*
|
|
110
112
|
"Sec-Fetch-Mode": "cors",
|
|
111
113
|
"Sec-Fetch-Site": "same-site",
|
|
112
|
-
"X-Deepinfra-Source": "web-embed",
|
|
113
|
-
"Sec-CH-UA": self.fingerprint["sec_ch_ua"] or '"Not)A;Brand";v="99", "Microsoft Edge";v="127", "Chromium";v="127"',
|
|
114
|
-
"Sec-CH-UA-Mobile": "?0",
|
|
115
|
-
"Sec-CH-UA-Platform": f'"{self.fingerprint["platform"]}"',
|
|
116
|
-
"User-Agent": self.fingerprint["user_agent"],
|
|
114
|
+
"X-Deepinfra-Source": "web-embed", # Keep custom headers
|
|
117
115
|
}
|
|
118
116
|
|
|
119
|
-
|
|
117
|
+
# Initialize curl_cffi Session
|
|
118
|
+
self.session = Session()
|
|
119
|
+
# Update curl_cffi session headers and proxies
|
|
120
120
|
self.session.headers.update(self.headers)
|
|
121
|
-
self.session.proxies
|
|
121
|
+
self.session.proxies = proxies # Assign proxies directly
|
|
122
122
|
self.system_prompt = system_prompt
|
|
123
123
|
self.is_conversation = is_conversation
|
|
124
124
|
self.max_tokens_to_sample = max_tokens
|
|
@@ -154,25 +154,21 @@ class DeepInfra(Provider):
|
|
|
154
154
|
browser = browser or self.fingerprint.get("browser_type", "chrome")
|
|
155
155
|
self.fingerprint = self.agent.generate_fingerprint(browser)
|
|
156
156
|
|
|
157
|
-
# Update headers with new fingerprint
|
|
157
|
+
# Update headers with new fingerprint (only relevant ones)
|
|
158
158
|
self.headers.update({
|
|
159
159
|
"Accept": self.fingerprint["accept"],
|
|
160
160
|
"Accept-Language": self.fingerprint["accept_language"],
|
|
161
|
-
"Sec-CH-UA": self.fingerprint["sec_ch_ua"] or self.headers["Sec-CH-UA"],
|
|
162
|
-
"Sec-CH-UA-Platform": f'"{self.fingerprint["platform"]}"',
|
|
163
|
-
"User-Agent": self.fingerprint["user_agent"],
|
|
164
161
|
})
|
|
165
162
|
|
|
166
163
|
# Update session headers
|
|
167
|
-
|
|
168
|
-
self.session.headers[header] = value
|
|
164
|
+
self.session.headers.update(self.headers) # Update only relevant headers
|
|
169
165
|
|
|
170
166
|
return self.fingerprint
|
|
171
167
|
|
|
172
168
|
def ask(
|
|
173
169
|
self,
|
|
174
170
|
prompt: str,
|
|
175
|
-
stream: bool = False,
|
|
171
|
+
stream: bool = False, # API supports streaming
|
|
176
172
|
raw: bool = False,
|
|
177
173
|
optimizer: str = None,
|
|
178
174
|
conversationally: bool = False,
|
|
@@ -193,61 +189,82 @@ class DeepInfra(Provider):
|
|
|
193
189
|
{"role": "system", "content": self.system_prompt},
|
|
194
190
|
{"role": "user", "content": conversation_prompt},
|
|
195
191
|
],
|
|
196
|
-
"stream": stream
|
|
192
|
+
"stream": stream # Pass stream argument to payload
|
|
197
193
|
}
|
|
198
194
|
|
|
199
195
|
def for_stream():
|
|
196
|
+
streaming_text = "" # Initialize outside try block
|
|
200
197
|
try:
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
198
|
+
# Use curl_cffi session post with impersonate
|
|
199
|
+
response = self.session.post(
|
|
200
|
+
self.url,
|
|
201
|
+
# headers are set on the session
|
|
202
|
+
data=json.dumps(payload),
|
|
203
|
+
stream=True,
|
|
204
|
+
timeout=self.timeout,
|
|
205
|
+
impersonate="chrome110" # Use a common impersonation profile
|
|
206
|
+
)
|
|
207
|
+
response.raise_for_status() # Check for HTTP errors
|
|
206
208
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
209
|
+
# Iterate over bytes and decode manually
|
|
210
|
+
for line_bytes in response.iter_lines():
|
|
211
|
+
if line_bytes:
|
|
212
|
+
try:
|
|
213
|
+
line = line_bytes.decode('utf-8').strip()
|
|
211
214
|
if line.startswith("data: "):
|
|
212
215
|
json_str = line[6:]
|
|
213
216
|
if json_str == "[DONE]":
|
|
214
217
|
break
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
218
|
+
json_data = json.loads(json_str)
|
|
219
|
+
if 'choices' in json_data:
|
|
220
|
+
choice = json_data['choices'][0]
|
|
221
|
+
if 'delta' in choice and 'content' in choice['delta']:
|
|
222
|
+
content = choice['delta']['content']
|
|
223
|
+
if content: # Ensure content is not None or empty
|
|
221
224
|
streaming_text += content
|
|
222
225
|
resp = dict(text=content)
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
+
# Yield dict or raw string chunk
|
|
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
|
|
226
230
|
|
|
227
|
-
|
|
228
|
-
|
|
231
|
+
# Update history after stream finishes
|
|
232
|
+
self.last_response = {"text": streaming_text}
|
|
233
|
+
self.conversation.update_chat_history(prompt, streaming_text)
|
|
229
234
|
|
|
230
|
-
except
|
|
231
|
-
raise exceptions.FailedToGenerateResponseError(f"Request failed: {str(e)}")
|
|
235
|
+
except CurlError as e: # Catch CurlError
|
|
236
|
+
raise exceptions.FailedToGenerateResponseError(f"Request failed (CurlError): {str(e)}") from e
|
|
237
|
+
except Exception as e: # Catch other potential exceptions (like HTTPError)
|
|
238
|
+
err_text = getattr(e, 'response', None) and getattr(e.response, 'text', '')
|
|
239
|
+
raise exceptions.FailedToGenerateResponseError(f"Request failed ({type(e).__name__}): {str(e)} - {err_text}") from e
|
|
240
|
+
|
|
232
241
|
|
|
233
242
|
def for_non_stream():
|
|
234
243
|
try:
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
)
|
|
244
|
+
# Use curl_cffi session post with impersonate for non-streaming
|
|
245
|
+
response = self.session.post(
|
|
246
|
+
self.url,
|
|
247
|
+
# headers are set on the session
|
|
248
|
+
data=json.dumps(payload),
|
|
249
|
+
timeout=self.timeout,
|
|
250
|
+
impersonate="chrome110" # Use a common impersonation profile
|
|
251
|
+
)
|
|
252
|
+
response.raise_for_status() # Check for HTTP errors
|
|
240
253
|
|
|
241
254
|
response_data = response.json()
|
|
242
255
|
if 'choices' in response_data and len(response_data['choices']) > 0:
|
|
243
256
|
content = response_data['choices'][0].get('message', {}).get('content', '')
|
|
244
257
|
self.last_response = {"text": content}
|
|
245
258
|
self.conversation.update_chat_history(prompt, content)
|
|
246
|
-
return
|
|
259
|
+
return self.last_response if not raw else content # Return dict or raw string
|
|
247
260
|
else:
|
|
248
261
|
raise exceptions.FailedToGenerateResponseError("No response content found")
|
|
249
|
-
except
|
|
250
|
-
raise exceptions.FailedToGenerateResponseError(f"Request failed: {e}")
|
|
262
|
+
except CurlError as e: # Catch CurlError
|
|
263
|
+
raise exceptions.FailedToGenerateResponseError(f"Request failed (CurlError): {e}") from e
|
|
264
|
+
except Exception as e: # Catch other potential exceptions (like HTTPError, JSONDecodeError)
|
|
265
|
+
err_text = getattr(e, 'response', None) and getattr(e.response, 'text', '')
|
|
266
|
+
raise exceptions.FailedToGenerateResponseError(f"Request failed ({type(e).__name__}): {e} - {err_text}") from e
|
|
267
|
+
|
|
251
268
|
|
|
252
269
|
return for_stream() if stream else for_non_stream()
|
|
253
270
|
|
|
@@ -258,20 +275,31 @@ class DeepInfra(Provider):
|
|
|
258
275
|
optimizer: str = None,
|
|
259
276
|
conversationally: bool = False,
|
|
260
277
|
) -> Union[str, Generator[str, None, None]]:
|
|
261
|
-
def
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
self.ask(prompt, False, optimizer=optimizer, conversationally=conversationally)
|
|
278
|
+
def for_stream_chat():
|
|
279
|
+
# ask() yields dicts or strings when streaming
|
|
280
|
+
gen = self.ask(
|
|
281
|
+
prompt, stream=True, raw=False, # Ensure ask yields dicts
|
|
282
|
+
optimizer=optimizer, conversationally=conversationally
|
|
267
283
|
)
|
|
268
|
-
|
|
284
|
+
for response_dict in gen:
|
|
285
|
+
yield self.get_message(response_dict) # get_message expects dict
|
|
286
|
+
|
|
287
|
+
def for_non_stream_chat():
|
|
288
|
+
# ask() returns dict or str when not streaming
|
|
289
|
+
response_data = self.ask(
|
|
290
|
+
prompt, stream=False, raw=False, # Ensure ask returns dict
|
|
291
|
+
optimizer=optimizer, conversationally=conversationally
|
|
292
|
+
)
|
|
293
|
+
return self.get_message(response_data) # get_message expects dict
|
|
294
|
+
|
|
295
|
+
return for_stream_chat() if stream else for_non_stream_chat()
|
|
269
296
|
|
|
270
297
|
def get_message(self, response: dict) -> str:
|
|
271
298
|
assert isinstance(response, dict), "Response should be of dict data-type only"
|
|
272
299
|
return response["text"]
|
|
273
300
|
|
|
274
301
|
if __name__ == "__main__":
|
|
302
|
+
# Ensure curl_cffi is installed
|
|
275
303
|
print("-" * 80)
|
|
276
304
|
print(f"{'Model':<50} {'Status':<10} {'Response'}")
|
|
277
305
|
print("-" * 80)
|
webscout/Provider/ElectronHub.py
CHANGED
|
@@ -15,6 +15,7 @@ class ElectronHub(Provider):
|
|
|
15
15
|
A class to interact with the ElectronHub API with LitAgent user-agent.
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
+
# Default models list (will be updated dynamically)
|
|
18
19
|
AVAILABLE_MODELS = [
|
|
19
20
|
# OpenAI GPT models
|
|
20
21
|
"gpt-3.5-turbo",
|
|
@@ -498,6 +499,56 @@ class ElectronHub(Provider):
|
|
|
498
499
|
"text-moderation-stable",
|
|
499
500
|
"text-moderation-007"
|
|
500
501
|
]
|
|
502
|
+
|
|
503
|
+
@classmethod
|
|
504
|
+
def get_models(cls, api_key: str = None):
|
|
505
|
+
"""Fetch available models from ElectronHub API.
|
|
506
|
+
|
|
507
|
+
Args:
|
|
508
|
+
api_key (str, optional): ElectronHub API key. If not provided, returns default models.
|
|
509
|
+
|
|
510
|
+
Returns:
|
|
511
|
+
list: List of available model IDs
|
|
512
|
+
"""
|
|
513
|
+
if not api_key:
|
|
514
|
+
return cls.AVAILABLE_MODELS
|
|
515
|
+
|
|
516
|
+
try:
|
|
517
|
+
headers = {
|
|
518
|
+
'Content-Type': 'application/json',
|
|
519
|
+
'Accept': '*/*',
|
|
520
|
+
'User-Agent': LitAgent().random(),
|
|
521
|
+
'Authorization': f'Bearer {api_key}'
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
response = requests.get(
|
|
525
|
+
"https://api.electronhub.top/v1/models",
|
|
526
|
+
headers=headers,
|
|
527
|
+
timeout=10
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
if response.status_code != 200:
|
|
531
|
+
return cls.AVAILABLE_MODELS
|
|
532
|
+
|
|
533
|
+
data = response.json()
|
|
534
|
+
if "data" in data and isinstance(data["data"], list):
|
|
535
|
+
return [model["id"] for model in data["data"]]
|
|
536
|
+
return cls.AVAILABLE_MODELS
|
|
537
|
+
|
|
538
|
+
except Exception:
|
|
539
|
+
# Fallback to default models list if fetching fails
|
|
540
|
+
return cls.AVAILABLE_MODELS
|
|
541
|
+
|
|
542
|
+
@classmethod
|
|
543
|
+
def update_available_models(cls, api_key=None):
|
|
544
|
+
"""Update the available models list from ElectronHub API"""
|
|
545
|
+
try:
|
|
546
|
+
models = cls.get_models(api_key)
|
|
547
|
+
if models and len(models) > 0:
|
|
548
|
+
cls.AVAILABLE_MODELS = models
|
|
549
|
+
except Exception:
|
|
550
|
+
# Fallback to default models list if fetching fails
|
|
551
|
+
pass
|
|
501
552
|
|
|
502
553
|
def __init__(
|
|
503
554
|
self,
|
|
@@ -515,6 +566,10 @@ class ElectronHub(Provider):
|
|
|
515
566
|
api_key: str = None
|
|
516
567
|
):
|
|
517
568
|
"""Initializes the ElectronHub API client."""
|
|
569
|
+
# Update available models from API
|
|
570
|
+
self.update_available_models(api_key)
|
|
571
|
+
|
|
572
|
+
# Validate model after updating available models
|
|
518
573
|
if model not in self.AVAILABLE_MODELS:
|
|
519
574
|
raise ValueError(f"Invalid model: {model}. Choose from: {self.AVAILABLE_MODELS}")
|
|
520
575
|
|