webscout 8.3__py3-none-any.whl → 8.3.2__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 +4 -4
- webscout/AIbase.py +61 -1
- webscout/AIutel.py +46 -53
- webscout/Bing_search.py +418 -0
- webscout/Extra/YTToolkit/ytapi/patterns.py +45 -45
- webscout/Extra/YTToolkit/ytapi/stream.py +1 -1
- webscout/Extra/YTToolkit/ytapi/video.py +10 -10
- webscout/Extra/autocoder/autocoder_utiles.py +1 -1
- webscout/Extra/gguf.py +706 -177
- webscout/Litlogger/formats.py +9 -0
- webscout/Litlogger/handlers.py +18 -0
- webscout/Litlogger/logger.py +43 -1
- webscout/Provider/AISEARCH/genspark_search.py +7 -7
- webscout/Provider/AISEARCH/scira_search.py +3 -2
- webscout/Provider/GeminiProxy.py +140 -0
- webscout/Provider/LambdaChat.py +7 -1
- webscout/Provider/MCPCore.py +78 -75
- webscout/Provider/OPENAI/BLACKBOXAI.py +1046 -1017
- webscout/Provider/OPENAI/GeminiProxy.py +328 -0
- webscout/Provider/OPENAI/Qwen3.py +303 -303
- webscout/Provider/OPENAI/README.md +5 -0
- webscout/Provider/OPENAI/README_AUTOPROXY.md +238 -0
- webscout/Provider/OPENAI/TogetherAI.py +355 -0
- webscout/Provider/OPENAI/__init__.py +16 -1
- webscout/Provider/OPENAI/autoproxy.py +332 -0
- webscout/Provider/OPENAI/base.py +101 -14
- webscout/Provider/OPENAI/chatgpt.py +15 -2
- webscout/Provider/OPENAI/chatgptclone.py +14 -3
- webscout/Provider/OPENAI/deepinfra.py +339 -328
- webscout/Provider/OPENAI/e2b.py +295 -74
- webscout/Provider/OPENAI/mcpcore.py +109 -70
- webscout/Provider/OPENAI/opkfc.py +18 -6
- webscout/Provider/OPENAI/scirachat.py +59 -50
- webscout/Provider/OPENAI/toolbaz.py +2 -10
- webscout/Provider/OPENAI/writecream.py +166 -166
- webscout/Provider/OPENAI/x0gpt.py +367 -367
- webscout/Provider/OPENAI/xenai.py +514 -0
- webscout/Provider/OPENAI/yep.py +389 -383
- webscout/Provider/STT/__init__.py +3 -0
- webscout/Provider/STT/base.py +281 -0
- webscout/Provider/STT/elevenlabs.py +265 -0
- webscout/Provider/TTI/__init__.py +4 -1
- webscout/Provider/TTI/aiarta.py +399 -365
- webscout/Provider/TTI/base.py +74 -2
- webscout/Provider/TTI/bing.py +231 -0
- webscout/Provider/TTI/fastflux.py +63 -30
- webscout/Provider/TTI/gpt1image.py +149 -0
- webscout/Provider/TTI/imagen.py +196 -0
- webscout/Provider/TTI/magicstudio.py +60 -29
- webscout/Provider/TTI/piclumen.py +43 -32
- webscout/Provider/TTI/pixelmuse.py +232 -225
- webscout/Provider/TTI/pollinations.py +43 -32
- webscout/Provider/TTI/together.py +287 -0
- webscout/Provider/TTI/utils.py +2 -1
- webscout/Provider/TTS/README.md +1 -0
- webscout/Provider/TTS/__init__.py +2 -1
- webscout/Provider/TTS/freetts.py +140 -0
- webscout/Provider/TTS/speechma.py +45 -39
- webscout/Provider/TogetherAI.py +366 -0
- webscout/Provider/UNFINISHED/ChutesAI.py +314 -0
- webscout/Provider/UNFINISHED/fetch_together_models.py +95 -0
- webscout/Provider/XenAI.py +324 -0
- webscout/Provider/__init__.py +8 -0
- webscout/Provider/deepseek_assistant.py +378 -0
- webscout/Provider/scira_chat.py +3 -2
- webscout/Provider/toolbaz.py +0 -1
- webscout/auth/__init__.py +44 -0
- webscout/auth/api_key_manager.py +189 -0
- webscout/auth/auth_system.py +100 -0
- webscout/auth/config.py +76 -0
- webscout/auth/database.py +400 -0
- webscout/auth/exceptions.py +67 -0
- webscout/auth/middleware.py +248 -0
- webscout/auth/models.py +130 -0
- webscout/auth/providers.py +257 -0
- webscout/auth/rate_limiter.py +254 -0
- webscout/auth/request_models.py +127 -0
- webscout/auth/request_processing.py +226 -0
- webscout/auth/routes.py +526 -0
- webscout/auth/schemas.py +103 -0
- webscout/auth/server.py +312 -0
- webscout/auth/static/favicon.svg +11 -0
- webscout/auth/swagger_ui.py +203 -0
- webscout/auth/templates/components/authentication.html +237 -0
- webscout/auth/templates/components/base.html +103 -0
- webscout/auth/templates/components/endpoints.html +750 -0
- webscout/auth/templates/components/examples.html +491 -0
- webscout/auth/templates/components/footer.html +75 -0
- webscout/auth/templates/components/header.html +27 -0
- webscout/auth/templates/components/models.html +286 -0
- webscout/auth/templates/components/navigation.html +70 -0
- webscout/auth/templates/static/api.js +455 -0
- webscout/auth/templates/static/icons.js +168 -0
- webscout/auth/templates/static/main.js +784 -0
- webscout/auth/templates/static/particles.js +201 -0
- webscout/auth/templates/static/styles.css +3353 -0
- webscout/auth/templates/static/ui.js +374 -0
- webscout/auth/templates/swagger_ui.html +170 -0
- webscout/client.py +49 -3
- webscout/litagent/Readme.md +12 -3
- webscout/litagent/agent.py +99 -62
- webscout/scout/core/scout.py +104 -26
- webscout/scout/element.py +139 -18
- webscout/swiftcli/core/cli.py +14 -3
- webscout/swiftcli/decorators/output.py +59 -9
- webscout/update_checker.py +31 -49
- webscout/version.py +1 -1
- webscout/webscout_search.py +4 -12
- webscout/webscout_search_async.py +3 -10
- webscout/yep_search.py +2 -11
- {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/METADATA +41 -11
- {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/RECORD +116 -68
- {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/entry_points.txt +1 -1
- webscout/Provider/HF_space/__init__.py +0 -0
- webscout/Provider/HF_space/qwen_qwen2.py +0 -206
- webscout/Provider/OPENAI/api.py +0 -1035
- webscout/Provider/TTI/artbit.py +0 -0
- {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/WHEEL +0 -0
- {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/licenses/LICENSE.md +0 -0
- {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Auto-proxy module for OpenAI-compatible providers.
|
|
3
|
+
This module provides automatic proxy injection for HTTP sessions using a remote proxy list.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import random
|
|
7
|
+
import time
|
|
8
|
+
from abc import ABCMeta
|
|
9
|
+
from typing import Dict, List, Optional, Any
|
|
10
|
+
import requests
|
|
11
|
+
|
|
12
|
+
# Optional imports for different HTTP clients
|
|
13
|
+
try:
|
|
14
|
+
import httpx
|
|
15
|
+
except ImportError:
|
|
16
|
+
httpx = None
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
from curl_cffi.requests import Session as CurlSession
|
|
20
|
+
from curl_cffi.requests import AsyncSession as CurlAsyncSession
|
|
21
|
+
except ImportError:
|
|
22
|
+
CurlSession = None
|
|
23
|
+
CurlAsyncSession = None
|
|
24
|
+
|
|
25
|
+
# Global proxy cache
|
|
26
|
+
_proxy_cache = {
|
|
27
|
+
'proxies': [],
|
|
28
|
+
'last_updated': 0,
|
|
29
|
+
'cache_duration': 300 # 5 minutes
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
PROXY_SOURCE_URL = "http://207.180.209.185:5000/ips.txt"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def fetch_proxies() -> List[str]:
|
|
36
|
+
"""
|
|
37
|
+
Fetch proxy list from the remote source.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
List[str]: List of proxy URLs in format 'http://user:pass@host:port'
|
|
41
|
+
"""
|
|
42
|
+
try:
|
|
43
|
+
response = requests.get(PROXY_SOURCE_URL, timeout=10)
|
|
44
|
+
response.raise_for_status()
|
|
45
|
+
|
|
46
|
+
proxies = []
|
|
47
|
+
for line in response.text.strip().split('\n'):
|
|
48
|
+
line = line.strip()
|
|
49
|
+
if line and line.startswith('http://'):
|
|
50
|
+
proxies.append(line)
|
|
51
|
+
|
|
52
|
+
return proxies
|
|
53
|
+
|
|
54
|
+
except Exception:
|
|
55
|
+
return []
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_cached_proxies() -> List[str]:
|
|
59
|
+
"""
|
|
60
|
+
Get proxies from cache or fetch new ones if cache is expired.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
List[str]: List of proxy URLs
|
|
64
|
+
"""
|
|
65
|
+
current_time = time.time()
|
|
66
|
+
|
|
67
|
+
# Check if cache is expired or empty
|
|
68
|
+
if (current_time - _proxy_cache['last_updated'] > _proxy_cache['cache_duration'] or
|
|
69
|
+
not _proxy_cache['proxies']):
|
|
70
|
+
|
|
71
|
+
new_proxies = fetch_proxies()
|
|
72
|
+
if new_proxies:
|
|
73
|
+
_proxy_cache['proxies'] = new_proxies
|
|
74
|
+
_proxy_cache['last_updated'] = current_time
|
|
75
|
+
else:
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
return _proxy_cache['proxies']
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def get_auto_proxy() -> Optional[str]:
|
|
82
|
+
"""
|
|
83
|
+
Get a random proxy from the cached proxy list.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Optional[str]: A proxy URL or None if no proxies available
|
|
87
|
+
"""
|
|
88
|
+
proxies = get_cached_proxies()
|
|
89
|
+
if not proxies:
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
proxy = random.choice(proxies)
|
|
93
|
+
return proxy
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def get_proxy_dict(proxy_url: Optional[str] = None) -> Dict[str, str]:
|
|
97
|
+
"""
|
|
98
|
+
Convert a proxy URL to a dictionary format suitable for requests/httpx.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
proxy_url: Proxy URL, if None will get one automatically
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Dict[str, str]: Proxy dictionary with 'http' and 'https' keys
|
|
105
|
+
"""
|
|
106
|
+
if proxy_url is None:
|
|
107
|
+
proxy_url = get_auto_proxy()
|
|
108
|
+
|
|
109
|
+
if proxy_url is None:
|
|
110
|
+
return {}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
'http': proxy_url,
|
|
114
|
+
'https': proxy_url
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_proxy(proxy_url: str, timeout: int = 10) -> bool:
|
|
119
|
+
"""
|
|
120
|
+
Test if a proxy is working by making a request to a test URL.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
proxy_url: The proxy URL to test
|
|
124
|
+
timeout: Request timeout in seconds
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
bool: True if proxy is working, False otherwise
|
|
128
|
+
"""
|
|
129
|
+
try:
|
|
130
|
+
test_url = "https://httpbin.org/ip"
|
|
131
|
+
proxies = {'http': proxy_url, 'https': proxy_url}
|
|
132
|
+
|
|
133
|
+
response = requests.get(test_url, proxies=proxies, timeout=timeout)
|
|
134
|
+
return response.status_code == 200
|
|
135
|
+
|
|
136
|
+
except Exception:
|
|
137
|
+
return False
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class ProxyAutoMeta(ABCMeta):
|
|
141
|
+
"""
|
|
142
|
+
Metaclass to ensure all OpenAICompatibleProvider subclasses automatically get proxy support.
|
|
143
|
+
This will inject proxies into any requests.Session, httpx.Client, or curl_cffi session attributes found on the instance.
|
|
144
|
+
|
|
145
|
+
To disable automatic proxy injection, set disable_auto_proxy=True in the constructor or
|
|
146
|
+
set the class attribute DISABLE_AUTO_PROXY = True.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
def __call__(cls, *args, **kwargs):
|
|
150
|
+
instance = super().__call__(*args, **kwargs)
|
|
151
|
+
|
|
152
|
+
# Check if auto proxy is disabled
|
|
153
|
+
disable_auto_proxy = kwargs.get('disable_auto_proxy', False) or getattr(cls, 'DISABLE_AUTO_PROXY', False)
|
|
154
|
+
|
|
155
|
+
# Get proxies from various sources
|
|
156
|
+
proxies = getattr(instance, 'proxies', None) or kwargs.get('proxies', None)
|
|
157
|
+
|
|
158
|
+
if proxies is None and not disable_auto_proxy:
|
|
159
|
+
try:
|
|
160
|
+
proxy_url = get_auto_proxy()
|
|
161
|
+
if proxy_url:
|
|
162
|
+
proxies = get_proxy_dict(proxy_url)
|
|
163
|
+
else:
|
|
164
|
+
proxies = {}
|
|
165
|
+
except Exception:
|
|
166
|
+
proxies = {}
|
|
167
|
+
elif proxies is None:
|
|
168
|
+
proxies = {}
|
|
169
|
+
|
|
170
|
+
instance.proxies = proxies
|
|
171
|
+
|
|
172
|
+
# Patch existing sessions if we have valid proxies
|
|
173
|
+
if proxies:
|
|
174
|
+
_patch_instance_sessions(instance, proxies)
|
|
175
|
+
|
|
176
|
+
# Provide helper methods for creating proxied sessions
|
|
177
|
+
_add_proxy_helpers(instance, proxies)
|
|
178
|
+
|
|
179
|
+
return instance
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _patch_instance_sessions(instance: Any, proxies: Dict[str, str]) -> None:
|
|
183
|
+
"""
|
|
184
|
+
Patch existing session objects on the instance with proxy configuration.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
instance: The class instance to patch
|
|
188
|
+
proxies: Proxy dictionary to apply
|
|
189
|
+
"""
|
|
190
|
+
for attr_name in dir(instance):
|
|
191
|
+
if attr_name.startswith('_'):
|
|
192
|
+
continue
|
|
193
|
+
|
|
194
|
+
try:
|
|
195
|
+
attr_obj = getattr(instance, attr_name)
|
|
196
|
+
|
|
197
|
+
# Patch requests.Session objects
|
|
198
|
+
if isinstance(attr_obj, requests.Session):
|
|
199
|
+
attr_obj.proxies.update(proxies)
|
|
200
|
+
|
|
201
|
+
# Patch httpx.Client objects
|
|
202
|
+
elif httpx and isinstance(attr_obj, httpx.Client):
|
|
203
|
+
try:
|
|
204
|
+
# httpx uses different proxy format
|
|
205
|
+
attr_obj._proxies = proxies
|
|
206
|
+
except Exception:
|
|
207
|
+
pass
|
|
208
|
+
|
|
209
|
+
# Patch curl_cffi Session objects
|
|
210
|
+
elif CurlSession and isinstance(attr_obj, CurlSession):
|
|
211
|
+
try:
|
|
212
|
+
attr_obj.proxies.update(proxies)
|
|
213
|
+
except Exception:
|
|
214
|
+
pass
|
|
215
|
+
|
|
216
|
+
# Patch curl_cffi AsyncSession objects
|
|
217
|
+
elif CurlAsyncSession and isinstance(attr_obj, CurlAsyncSession):
|
|
218
|
+
try:
|
|
219
|
+
attr_obj.proxies.update(proxies)
|
|
220
|
+
except Exception:
|
|
221
|
+
pass
|
|
222
|
+
|
|
223
|
+
except Exception:
|
|
224
|
+
continue
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _add_proxy_helpers(instance: Any, proxies: Dict[str, str]) -> None:
|
|
228
|
+
"""
|
|
229
|
+
Add helper methods to the instance for creating proxied sessions.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
instance: The class instance to add methods to
|
|
233
|
+
proxies: Proxy dictionary to use in helper methods
|
|
234
|
+
"""
|
|
235
|
+
|
|
236
|
+
def get_proxied_session():
|
|
237
|
+
"""Get a requests.Session with proxies configured"""
|
|
238
|
+
session = requests.Session()
|
|
239
|
+
session.proxies.update(proxies)
|
|
240
|
+
return session
|
|
241
|
+
|
|
242
|
+
def get_proxied_httpx_client(**kwargs):
|
|
243
|
+
"""Get an httpx.Client with proxies configured"""
|
|
244
|
+
if httpx:
|
|
245
|
+
return httpx.Client(proxies=proxies, **kwargs)
|
|
246
|
+
else:
|
|
247
|
+
raise ImportError("httpx is not installed")
|
|
248
|
+
|
|
249
|
+
def get_proxied_curl_session(impersonate="chrome120", **kwargs):
|
|
250
|
+
"""Get a curl_cffi Session with proxies configured"""
|
|
251
|
+
if CurlSession:
|
|
252
|
+
return CurlSession(proxies=proxies, impersonate=impersonate, **kwargs)
|
|
253
|
+
else:
|
|
254
|
+
raise ImportError("curl_cffi is not installed")
|
|
255
|
+
|
|
256
|
+
def get_proxied_curl_async_session(impersonate="chrome120", **kwargs):
|
|
257
|
+
"""Get a curl_cffi AsyncSession with proxies configured"""
|
|
258
|
+
if CurlAsyncSession:
|
|
259
|
+
return CurlAsyncSession(proxies=proxies, impersonate=impersonate, **kwargs)
|
|
260
|
+
else:
|
|
261
|
+
raise ImportError("curl_cffi is not installed")
|
|
262
|
+
|
|
263
|
+
# Add methods to instance
|
|
264
|
+
instance.get_proxied_session = get_proxied_session
|
|
265
|
+
instance.get_proxied_httpx_client = get_proxied_httpx_client
|
|
266
|
+
instance.get_proxied_curl_session = get_proxied_curl_session
|
|
267
|
+
instance.get_proxied_curl_async_session = get_proxied_curl_async_session
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def get_working_proxy(max_attempts: int = 5, timeout: int = 10) -> Optional[str]:
|
|
271
|
+
"""
|
|
272
|
+
Get a working proxy by testing multiple proxies from the list.
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
max_attempts: Maximum number of proxies to test
|
|
276
|
+
timeout: Timeout for each proxy test
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
Optional[str]: A working proxy URL or None if none found
|
|
280
|
+
"""
|
|
281
|
+
proxies = get_cached_proxies()
|
|
282
|
+
if not proxies:
|
|
283
|
+
return None
|
|
284
|
+
|
|
285
|
+
# Shuffle to avoid always testing the same proxies first
|
|
286
|
+
test_proxies = random.sample(proxies, min(max_attempts, len(proxies)))
|
|
287
|
+
|
|
288
|
+
for proxy in test_proxies:
|
|
289
|
+
if test_proxy(proxy, timeout):
|
|
290
|
+
return proxy
|
|
291
|
+
|
|
292
|
+
return None
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def refresh_proxy_cache() -> int:
|
|
296
|
+
"""
|
|
297
|
+
Force refresh the proxy cache.
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
int: Number of proxies loaded
|
|
301
|
+
"""
|
|
302
|
+
global _proxy_cache
|
|
303
|
+
_proxy_cache['last_updated'] = 0 # Force refresh
|
|
304
|
+
proxies = get_cached_proxies()
|
|
305
|
+
return len(proxies)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def get_proxy_stats() -> Dict[str, Any]:
|
|
309
|
+
"""
|
|
310
|
+
Get statistics about the proxy cache.
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
Dict[str, Any]: Statistics including count, last update time, etc.
|
|
314
|
+
"""
|
|
315
|
+
return {
|
|
316
|
+
'proxy_count': len(_proxy_cache['proxies']),
|
|
317
|
+
'last_updated': _proxy_cache['last_updated'],
|
|
318
|
+
'cache_duration': _proxy_cache['cache_duration'],
|
|
319
|
+
'cache_age_seconds': time.time() - _proxy_cache['last_updated'],
|
|
320
|
+
'source_url': PROXY_SOURCE_URL
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def set_proxy_cache_duration(duration: int) -> None:
|
|
325
|
+
"""
|
|
326
|
+
Set the proxy cache duration.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
duration: Cache duration in seconds
|
|
330
|
+
"""
|
|
331
|
+
global _proxy_cache
|
|
332
|
+
_proxy_cache['cache_duration'] = duration
|
webscout/Provider/OPENAI/base.py
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
from typing import List, Dict, Optional, Union, Generator, Any, TypedDict, Callable
|
|
3
3
|
import json
|
|
4
|
-
import logging
|
|
5
4
|
from dataclasses import dataclass
|
|
6
5
|
|
|
7
|
-
|
|
6
|
+
# Import WebScout Litlogger instead of standard logging
|
|
7
|
+
from webscout.Litlogger import Logger, LogLevel
|
|
8
|
+
|
|
9
|
+
logger = Logger(name="OpenAIBase", level=LogLevel.INFO)
|
|
10
|
+
|
|
11
|
+
# Import the ProxyAutoMeta metaclass
|
|
12
|
+
try:
|
|
13
|
+
from .autoproxy import ProxyAutoMeta
|
|
14
|
+
except ImportError:
|
|
15
|
+
# Fallback if autoproxy is not available
|
|
16
|
+
ProxyAutoMeta = type
|
|
8
17
|
|
|
9
18
|
|
|
10
19
|
# Import the utils for response structures
|
|
11
|
-
from webscout.Provider.OPENAI.utils import ChatCompletion, ChatCompletionChunk
|
|
20
|
+
from webscout.Provider.OPENAI.utils import ChatCompletion, ChatCompletionChunk
|
|
12
21
|
|
|
13
22
|
# Define tool-related structures
|
|
14
23
|
class ToolDefinition(TypedDict):
|
|
@@ -174,10 +183,92 @@ class BaseChat(ABC):
|
|
|
174
183
|
completions: BaseCompletions
|
|
175
184
|
|
|
176
185
|
|
|
177
|
-
class
|
|
186
|
+
# class ProxyAutoMeta(ABCMeta):
|
|
187
|
+
# """
|
|
188
|
+
# Metaclass to ensure all OpenAICompatibleProvider subclasses automatically get proxy support.
|
|
189
|
+
# This will inject proxies into any requests.Session, httpx.Client, or curl_cffi session attributes found on the instance.
|
|
190
|
+
|
|
191
|
+
# To disable automatic proxy injection, set disable_auto_proxy=True in the constructor or
|
|
192
|
+
# set the class attribute DISABLE_AUTO_PROXY = True.
|
|
193
|
+
# """
|
|
194
|
+
# def __call__(cls, *args, **kwargs):
|
|
195
|
+
# instance = super().__call__(*args, **kwargs)
|
|
196
|
+
|
|
197
|
+
# # Check if auto proxy is disabled
|
|
198
|
+
# disable_auto_proxy = kwargs.get('disable_auto_proxy', False) or getattr(cls, 'DISABLE_AUTO_PROXY', False)
|
|
199
|
+
|
|
200
|
+
# proxies = getattr(instance, 'proxies', None) or kwargs.get('proxies', None)
|
|
201
|
+
# if proxies is None and not disable_auto_proxy:
|
|
202
|
+
# try:
|
|
203
|
+
# proxies = {"http": get_auto_proxy(), "https": get_auto_proxy()}
|
|
204
|
+
# except Exception as e:
|
|
205
|
+
# logger.warning(f"Failed to get auto proxy, disabling proxy support: {e}")
|
|
206
|
+
# proxies = {}
|
|
207
|
+
# elif proxies is None:
|
|
208
|
+
# proxies = {}
|
|
209
|
+
# instance.proxies = proxies
|
|
210
|
+
# # Patch sessions if we have valid proxies
|
|
211
|
+
# if proxies:
|
|
212
|
+
# for attr in dir(instance):
|
|
213
|
+
# obj = getattr(instance, attr)
|
|
214
|
+
# if isinstance(obj, requests.Session):
|
|
215
|
+
# obj.proxies.update(proxies)
|
|
216
|
+
# if httpx and isinstance(obj, httpx.Client):
|
|
217
|
+
# try:
|
|
218
|
+
# obj._proxies = proxies
|
|
219
|
+
# except Exception:
|
|
220
|
+
# pass
|
|
221
|
+
# # Patch curl_cffi sessions if present
|
|
222
|
+
# if CurlSession and isinstance(obj, CurlSession):
|
|
223
|
+
# try:
|
|
224
|
+
# obj.proxies.update(proxies)
|
|
225
|
+
# except Exception:
|
|
226
|
+
# pass
|
|
227
|
+
# if CurlAsyncSession and isinstance(obj, CurlAsyncSession):
|
|
228
|
+
# try:
|
|
229
|
+
# obj.proxies.update(proxies)
|
|
230
|
+
# except Exception:
|
|
231
|
+
# pass
|
|
232
|
+
# # Provide helpers for proxied sessions
|
|
233
|
+
# def get_proxied_session():
|
|
234
|
+
# s = requests.Session()
|
|
235
|
+
# s.proxies.update(proxies)
|
|
236
|
+
# return s
|
|
237
|
+
# instance.get_proxied_session = get_proxied_session
|
|
238
|
+
|
|
239
|
+
# def get_proxied_curl_session(impersonate="chrome120", **kwargs):
|
|
240
|
+
# """Get a curl_cffi Session with proxies configured"""
|
|
241
|
+
# if CurlSession:
|
|
242
|
+
# return CurlSession(proxies=proxies, impersonate=impersonate, **kwargs)
|
|
243
|
+
# else:
|
|
244
|
+
# raise ImportError("curl_cffi is not installed")
|
|
245
|
+
# instance.get_proxied_curl_session = get_proxied_curl_session
|
|
246
|
+
|
|
247
|
+
# def get_proxied_curl_async_session(impersonate="chrome120", **kwargs):
|
|
248
|
+
# """Get a curl_cffi AsyncSession with proxies configured"""
|
|
249
|
+
# if CurlAsyncSession:
|
|
250
|
+
# return CurlAsyncSession(proxies=proxies, impersonate=impersonate, **kwargs)
|
|
251
|
+
# else:
|
|
252
|
+
# raise ImportError("curl_cffi is not installed")
|
|
253
|
+
# instance.get_proxied_curl_async_session = get_proxied_curl_async_session
|
|
254
|
+
|
|
255
|
+
# return instance
|
|
256
|
+
class OpenAICompatibleProvider(ABC, metaclass=ProxyAutoMeta):
|
|
178
257
|
"""
|
|
179
258
|
Abstract Base Class for providers mimicking the OpenAI Python client structure.
|
|
180
259
|
Requires a nested 'chat.completions' structure with tool support.
|
|
260
|
+
All subclasses automatically get proxy support via ProxyAutoMeta.
|
|
261
|
+
|
|
262
|
+
# Available proxy helpers:
|
|
263
|
+
# - self.get_proxied_session() - returns a requests.Session with proxies
|
|
264
|
+
# - self.get_proxied_curl_session() - returns a curl_cffi.Session with proxies
|
|
265
|
+
# - self.get_proxied_curl_async_session() - returns a curl_cffi.AsyncSession with proxies
|
|
266
|
+
|
|
267
|
+
# Proxy support is automatically injected into:
|
|
268
|
+
# - requests.Session objects
|
|
269
|
+
# - httpx.Client objects
|
|
270
|
+
# - curl_cffi.requests.Session objects
|
|
271
|
+
# - curl_cffi.requests.AsyncSession objects
|
|
181
272
|
"""
|
|
182
273
|
chat: BaseChat
|
|
183
274
|
available_tools: Dict[str, Tool] = {} # Dictionary of available tools
|
|
@@ -185,19 +276,15 @@ class OpenAICompatibleProvider(ABC):
|
|
|
185
276
|
supports_tool_choice: bool = False # Whether the provider supports tool_choice
|
|
186
277
|
|
|
187
278
|
@abstractmethod
|
|
188
|
-
def __init__(self, api_key: Optional[str] = None, tools: Optional[List[Tool]] = None, **kwargs: Any):
|
|
189
|
-
"""
|
|
190
|
-
Initialize the provider, potentially with an API key and tools.
|
|
191
|
-
|
|
192
|
-
Args:
|
|
193
|
-
api_key: Optional API key for the provider
|
|
194
|
-
tools: Optional list of tools to make available to the provider
|
|
195
|
-
**kwargs: Additional provider-specific parameters
|
|
196
|
-
"""
|
|
279
|
+
def __init__(self, api_key: Optional[str] = None, tools: Optional[List[Tool]] = None, proxies: Optional[dict] = None, disable_auto_proxy: bool = False, **kwargs: Any):
|
|
197
280
|
self.available_tools = {}
|
|
198
281
|
if tools:
|
|
199
282
|
self.register_tools(tools)
|
|
200
|
-
|
|
283
|
+
# self.proxies is set by ProxyAutoMeta
|
|
284
|
+
# Subclasses should use self.proxies for all network requests
|
|
285
|
+
# Optionally, use self.get_proxied_session() for a requests.Session with proxies
|
|
286
|
+
# The disable_auto_proxy parameter is handled by ProxyAutoMeta
|
|
287
|
+
# raise NotImplementedError # <-- Commented out for metaclass test
|
|
201
288
|
|
|
202
289
|
@property
|
|
203
290
|
@abstractmethod
|
|
@@ -37,9 +37,21 @@ class ChatGPTReversed:
|
|
|
37
37
|
csrf_token = None
|
|
38
38
|
initialized = False
|
|
39
39
|
|
|
40
|
+
_instance = None
|
|
41
|
+
|
|
42
|
+
def __new__(cls, model="auto"):
|
|
43
|
+
if cls._instance is None:
|
|
44
|
+
cls._instance = super(ChatGPTReversed, cls).__new__(cls)
|
|
45
|
+
cls._instance.initialized = False
|
|
46
|
+
return cls._instance
|
|
47
|
+
|
|
40
48
|
def __init__(self, model="auto"):
|
|
41
|
-
if
|
|
42
|
-
|
|
49
|
+
if self.initialized:
|
|
50
|
+
# Already initialized, just update model if needed
|
|
51
|
+
if model not in self.AVAILABLE_MODELS:
|
|
52
|
+
raise ValueError(f"Invalid model: {model}. Choose from: {self.AVAILABLE_MODELS}")
|
|
53
|
+
self.model = model
|
|
54
|
+
return
|
|
43
55
|
|
|
44
56
|
if model not in self.AVAILABLE_MODELS:
|
|
45
57
|
raise ValueError(f"Invalid model: {model}. Choose from: {self.AVAILABLE_MODELS}")
|
|
@@ -573,3 +585,4 @@ if __name__ == "__main__":
|
|
|
573
585
|
messages=[{"role": "user", "content": "How manr r in strawberry"}]
|
|
574
586
|
)
|
|
575
587
|
print(response.choices[0].message.content)
|
|
588
|
+
print()
|
|
@@ -7,8 +7,8 @@ import re
|
|
|
7
7
|
from typing import List, Dict, Optional, Union, Generator, Any
|
|
8
8
|
|
|
9
9
|
# Import base classes and utility structures
|
|
10
|
-
from .base import OpenAICompatibleProvider, BaseChat, BaseCompletions
|
|
11
|
-
from .utils import (
|
|
10
|
+
from webscout.Provider.OPENAI.base import OpenAICompatibleProvider, BaseChat, BaseCompletions
|
|
11
|
+
from webscout.Provider.OPENAI.utils import (
|
|
12
12
|
ChatCompletionChunk, ChatCompletion, Choice, ChoiceDelta,
|
|
13
13
|
ChatCompletionMessage, CompletionUsage, count_tokens
|
|
14
14
|
)
|
|
@@ -510,4 +510,15 @@ class ChatGPTClone(OpenAICompatibleProvider):
|
|
|
510
510
|
class _ModelList:
|
|
511
511
|
def list(inner_self):
|
|
512
512
|
return type(self).AVAILABLE_MODELS
|
|
513
|
-
return _ModelList()
|
|
513
|
+
return _ModelList()
|
|
514
|
+
if __name__ == "__main__":
|
|
515
|
+
# Example usage
|
|
516
|
+
client = ChatGPTClone()
|
|
517
|
+
response = client.chat.completions.create(
|
|
518
|
+
model="gpt-4",
|
|
519
|
+
messages=[{"role": "user", "content": "Hello!"}]
|
|
520
|
+
)
|
|
521
|
+
print(response.choices[0].message.content)
|
|
522
|
+
print()
|
|
523
|
+
print("Proxies on instance:", client.proxies)
|
|
524
|
+
print("Proxies on session:", client.session.proxies)
|