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
|
@@ -7,19 +7,16 @@ import queue
|
|
|
7
7
|
import tempfile
|
|
8
8
|
import threading
|
|
9
9
|
import subprocess
|
|
10
|
+
from typing import Optional, Generator, List, Tuple, Dict, Any, NamedTuple
|
|
10
11
|
from rich.panel import Panel
|
|
11
12
|
from rich.syntax import Syntax
|
|
12
|
-
from rich.console import Console
|
|
13
|
+
from rich.console import Console
|
|
13
14
|
from rich.markdown import Markdown
|
|
14
15
|
from rich.table import Table
|
|
15
16
|
from rich.theme import Theme
|
|
16
17
|
from rich.live import Live
|
|
17
|
-
from rich.rule import Rule
|
|
18
18
|
from rich.box import ROUNDED
|
|
19
|
-
from typing import Optional, Generator, List, Tuple, Dict, Any
|
|
20
|
-
from webscout.AIutel import run_system_command
|
|
21
19
|
from .autocoder_utiles import get_intro_prompt
|
|
22
|
-
|
|
23
20
|
# Initialize LitLogger with custom format and colors
|
|
24
21
|
default_path = tempfile.mkdtemp(prefix="webscout_autocoder")
|
|
25
22
|
|
|
@@ -34,6 +31,70 @@ CUSTOM_THEME = Theme({
|
|
|
34
31
|
})
|
|
35
32
|
|
|
36
33
|
console = Console(theme=CUSTOM_THEME)
|
|
34
|
+
class CommandResult(NamedTuple):
|
|
35
|
+
"""Result of a system command execution."""
|
|
36
|
+
success: bool
|
|
37
|
+
stdout: str
|
|
38
|
+
stderr: str
|
|
39
|
+
|
|
40
|
+
def run_system_command(
|
|
41
|
+
command: str,
|
|
42
|
+
exit_on_error: bool = False,
|
|
43
|
+
stdout_error: bool = False,
|
|
44
|
+
help: Optional[str] = None
|
|
45
|
+
) -> Tuple[bool, CommandResult]:
|
|
46
|
+
"""Execute a system command and return the result.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
command (str): Command to execute
|
|
50
|
+
exit_on_error (bool): Whether to exit on error. Defaults to False.
|
|
51
|
+
stdout_error (bool): Whether to include stdout in error messages. Defaults to False.
|
|
52
|
+
help (str, optional): Help message for errors. Defaults to None.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Tuple[bool, CommandResult]: Success status and command result containing stdout/stderr
|
|
56
|
+
"""
|
|
57
|
+
try:
|
|
58
|
+
# Execute command and capture output
|
|
59
|
+
process = subprocess.Popen(
|
|
60
|
+
command,
|
|
61
|
+
stdout=subprocess.PIPE,
|
|
62
|
+
stderr=subprocess.PIPE,
|
|
63
|
+
shell=True,
|
|
64
|
+
text=True
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Get stdout and stderr
|
|
68
|
+
stdout, stderr = process.communicate()
|
|
69
|
+
success = process.returncode == 0
|
|
70
|
+
|
|
71
|
+
# Create result object
|
|
72
|
+
result = CommandResult(
|
|
73
|
+
success=success,
|
|
74
|
+
stdout=stdout.strip() if stdout else "",
|
|
75
|
+
stderr=stderr.strip() if stderr else ""
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Handle errors if needed
|
|
79
|
+
if not success and exit_on_error:
|
|
80
|
+
error_msg = stderr if stderr else stdout if stdout_error else "Command failed"
|
|
81
|
+
if help:
|
|
82
|
+
error_msg += f"\n{help}"
|
|
83
|
+
sys.exit(error_msg)
|
|
84
|
+
|
|
85
|
+
return success, result
|
|
86
|
+
|
|
87
|
+
except Exception as e:
|
|
88
|
+
# Handle execution errors
|
|
89
|
+
error_msg = str(e)
|
|
90
|
+
if help:
|
|
91
|
+
error_msg += f"\n{help}"
|
|
92
|
+
|
|
93
|
+
if exit_on_error:
|
|
94
|
+
sys.exit(error_msg)
|
|
95
|
+
|
|
96
|
+
return False, CommandResult(success=False, stdout="", stderr=error_msg)
|
|
97
|
+
|
|
37
98
|
|
|
38
99
|
class AutoCoder:
|
|
39
100
|
"""Generate and auto-execute Python scripts in the CLI with advanced error handling and retry logic.
|
webscout/Extra/gguf.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Convert Hugging Face models to GGUF format with advanced features.
|
|
3
3
|
|
|
4
|
+
For detailed documentation, see: webscout/Extra/gguf.md
|
|
5
|
+
|
|
4
6
|
>>> python -m webscout.Extra.gguf convert -m "OEvortex/HelpingAI-Lite-1.5T" -q "q4_k_m,q5_k_m"
|
|
5
7
|
>>> # With upload options:
|
|
6
8
|
>>> python -m webscout.Extra.gguf convert -m "your-model" -u "username" -t "token" -q "q4_k_m"
|
|
@@ -69,17 +69,15 @@ class Scira(AISearch):
|
|
|
69
69
|
"""
|
|
70
70
|
|
|
71
71
|
AVAILABLE_MODELS = {
|
|
72
|
-
"scira-default": "Grok3",
|
|
73
|
-
"scira-grok-3
|
|
72
|
+
"scira-default": "Grok3-mini", # thinking model
|
|
73
|
+
"scira-grok-3": "Grok3",
|
|
74
|
+
"scira-anthropic": "Sonnet 3.7 thinking",
|
|
74
75
|
"scira-vision" : "Grok2-Vision", # vision model
|
|
75
76
|
"scira-4.1-mini": "GPT4.1-mini",
|
|
76
77
|
"scira-qwq": "QWQ-32B",
|
|
77
78
|
"scira-o4-mini": "o4-mini",
|
|
78
79
|
"scira-google": "gemini 2.5 flash"
|
|
79
|
-
|
|
80
|
-
|
|
81
80
|
}
|
|
82
|
-
|
|
83
81
|
def __init__(
|
|
84
82
|
self,
|
|
85
83
|
timeout: int = 60,
|
webscout/Provider/Aitopia.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
from curl_cffi import CurlError
|
|
2
|
+
from curl_cffi.requests import Session
|
|
2
3
|
import json
|
|
3
4
|
import uuid
|
|
4
5
|
import time
|
|
@@ -6,8 +7,8 @@ import hashlib
|
|
|
6
7
|
from typing import Any, Dict, Optional, Generator, Union
|
|
7
8
|
|
|
8
9
|
from webscout.AIutel import Optimizers
|
|
9
|
-
from webscout.AIutel import Conversation
|
|
10
|
-
from webscout.AIutel import AwesomePrompts
|
|
10
|
+
from webscout.AIutel import Conversation, sanitize_stream # Import sanitize_stream
|
|
11
|
+
from webscout.AIutel import AwesomePrompts
|
|
11
12
|
from webscout.AIbase import Provider, AsyncProvider
|
|
12
13
|
from webscout import exceptions
|
|
13
14
|
from webscout.litagent import LitAgent
|
|
@@ -67,9 +68,9 @@ class Aitopia(Provider):
|
|
|
67
68
|
"user-agent": self.fingerprint["user_agent"]
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
self.session =
|
|
71
|
+
self.session = Session() # Use curl_cffi Session
|
|
71
72
|
self.session.headers.update(self.headers)
|
|
72
|
-
self.session.proxies
|
|
73
|
+
self.session.proxies = proxies # Assign proxies directly
|
|
73
74
|
|
|
74
75
|
self.is_conversation = is_conversation
|
|
75
76
|
self.max_tokens_to_sample = max_tokens
|
|
@@ -129,6 +130,20 @@ class Aitopia(Provider):
|
|
|
129
130
|
random_str = str(uuid.uuid4()) + str(time.time())
|
|
130
131
|
return hashlib.md5(random_str.encode()).hexdigest()
|
|
131
132
|
|
|
133
|
+
@staticmethod
|
|
134
|
+
def _aitopia_extractor(chunk: Union[str, Dict[str, Any]]) -> Optional[str]:
|
|
135
|
+
"""Extracts content from Aitopia stream JSON objects."""
|
|
136
|
+
if isinstance(chunk, dict):
|
|
137
|
+
# Handle Claude 3 Haiku response format
|
|
138
|
+
if "delta" in chunk and "text" in chunk["delta"]:
|
|
139
|
+
return chunk["delta"]["text"]
|
|
140
|
+
# Handle GPT-4o Mini response format
|
|
141
|
+
elif "choices" in chunk and "0" in chunk["choices"]:
|
|
142
|
+
return chunk["choices"]["0"]["delta"].get("content")
|
|
143
|
+
# Add other potential formats here if needed
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
|
|
132
147
|
def ask(
|
|
133
148
|
self,
|
|
134
149
|
prompt: str,
|
|
@@ -184,63 +199,72 @@ class Aitopia(Provider):
|
|
|
184
199
|
}
|
|
185
200
|
|
|
186
201
|
def for_stream():
|
|
202
|
+
streaming_text = "" # Initialize outside try block
|
|
187
203
|
try:
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
yield resp if raw else resp
|
|
219
|
-
except json.JSONDecodeError:
|
|
220
|
-
continue
|
|
221
|
-
|
|
204
|
+
response = self.session.post(
|
|
205
|
+
self.url, headers=self.headers, json=payload, stream=True, timeout=self.timeout,
|
|
206
|
+
impersonate="chrome120" # Add impersonate
|
|
207
|
+
)
|
|
208
|
+
response.raise_for_status()
|
|
209
|
+
|
|
210
|
+
# Use sanitize_stream
|
|
211
|
+
processed_stream = sanitize_stream(
|
|
212
|
+
data=response.iter_content(chunk_size=None), # Pass byte iterator
|
|
213
|
+
intro_value="data:",
|
|
214
|
+
to_json=True, # Stream sends JSON
|
|
215
|
+
skip_markers=["[DONE]"],
|
|
216
|
+
content_extractor=self._aitopia_extractor, # Use the specific extractor
|
|
217
|
+
yield_raw_on_error=False # Skip non-JSON lines or lines where extractor fails
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
for content_chunk in processed_stream:
|
|
221
|
+
# content_chunk is the string extracted by _aitopia_extractor
|
|
222
|
+
if content_chunk and isinstance(content_chunk, str):
|
|
223
|
+
streaming_text += content_chunk
|
|
224
|
+
resp = dict(text=content_chunk)
|
|
225
|
+
yield resp if not raw else content_chunk
|
|
226
|
+
|
|
227
|
+
except CurlError as e:
|
|
228
|
+
raise exceptions.FailedToGenerateResponseError(f"Request failed (CurlError): {str(e)}") from e
|
|
229
|
+
except Exception as e:
|
|
230
|
+
raise exceptions.FailedToGenerateResponseError(f"Request failed: {str(e)}")
|
|
231
|
+
finally:
|
|
232
|
+
# Update history after stream finishes or fails
|
|
233
|
+
if streaming_text:
|
|
222
234
|
self.last_response = {"text": streaming_text}
|
|
223
235
|
self.conversation.update_chat_history(prompt, streaming_text)
|
|
224
|
-
|
|
225
|
-
except requests.RequestException as e:
|
|
226
|
-
raise exceptions.FailedToGenerateResponseError(f"Request failed: {str(e)}")
|
|
227
236
|
|
|
228
237
|
def for_non_stream():
|
|
229
238
|
try:
|
|
230
|
-
response =
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
239
|
+
response = self.session.post(
|
|
240
|
+
self.url, headers=self.headers, json=payload, timeout=self.timeout,
|
|
241
|
+
impersonate="chrome120" # Add impersonate
|
|
242
|
+
)
|
|
243
|
+
response.raise_for_status()
|
|
244
|
+
|
|
245
|
+
response_text_raw = response.text # Get raw text
|
|
246
|
+
|
|
247
|
+
# Use sanitize_stream to parse the non-streaming JSON response
|
|
248
|
+
# Assuming non-stream uses the GPT format based on original code
|
|
249
|
+
processed_stream = sanitize_stream(
|
|
250
|
+
data=response_text_raw,
|
|
251
|
+
to_json=True, # Parse the whole text as JSON
|
|
252
|
+
intro_value=None,
|
|
253
|
+
content_extractor=lambda chunk: chunk.get("choices", [{}])[0].get("message", {}).get("content") if isinstance(chunk, dict) else None,
|
|
254
|
+
yield_raw_on_error=False
|
|
255
|
+
)
|
|
256
|
+
# Extract the single result
|
|
257
|
+
content = next(processed_stream, None)
|
|
258
|
+
content = content if isinstance(content, str) else "" # Ensure it's a string
|
|
235
259
|
|
|
236
|
-
|
|
237
|
-
if 'choices' in response_data and len(response_data['choices']) > 0:
|
|
238
|
-
content = response_data['choices'][0].get('message', {}).get('content', '')
|
|
260
|
+
if content: # Check if content was successfully extracted
|
|
239
261
|
self.last_response = {"text": content}
|
|
240
262
|
self.conversation.update_chat_history(prompt, content)
|
|
241
263
|
return {"text": content}
|
|
242
264
|
else:
|
|
243
|
-
raise exceptions.FailedToGenerateResponseError("No response content found")
|
|
265
|
+
raise exceptions.FailedToGenerateResponseError("No response content found or failed to parse")
|
|
266
|
+
except CurlError as e:
|
|
267
|
+
raise exceptions.FailedToGenerateResponseError(f"Request failed (CurlError): {e}") from e
|
|
244
268
|
except Exception as e:
|
|
245
269
|
raise exceptions.FailedToGenerateResponseError(f"Request failed: {e}")
|
|
246
270
|
|
webscout/Provider/AllenAI.py
CHANGED
|
@@ -156,19 +156,15 @@ class AllenAI(Provider):
|
|
|
156
156
|
err_text = getattr(e, 'response', None) and getattr(e.response, 'text', '')
|
|
157
157
|
return {"client": temp_id, "error": f"{type(e).__name__}: {e} - {err_text}"}
|
|
158
158
|
|
|
159
|
-
|
|
160
|
-
def
|
|
161
|
-
"""
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
result += parsed.get("content", "")
|
|
169
|
-
except:
|
|
170
|
-
continue
|
|
171
|
-
return result
|
|
159
|
+
@staticmethod
|
|
160
|
+
def _allenai_extractor(chunk: Union[str, Dict[str, Any]]) -> Optional[str]:
|
|
161
|
+
"""Extracts content from AllenAI stream JSON objects."""
|
|
162
|
+
if isinstance(chunk, dict):
|
|
163
|
+
if chunk.get("message", "").startswith("msg_") and "content" in chunk:
|
|
164
|
+
return chunk.get("content")
|
|
165
|
+
elif "message" in chunk and chunk.get("content"): # Legacy handling
|
|
166
|
+
return chunk.get("content")
|
|
167
|
+
return None
|
|
172
168
|
|
|
173
169
|
def ask(
|
|
174
170
|
self,
|
|
@@ -269,57 +265,49 @@ class AllenAI(Provider):
|
|
|
269
265
|
)
|
|
270
266
|
response.raise_for_status() # Check for HTTP errors
|
|
271
267
|
|
|
272
|
-
#
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
if isinstance(data, dict):
|
|
288
|
-
content_found = False
|
|
289
|
-
if data.get("message", "").startswith("msg_") and "content" in data:
|
|
290
|
-
content = data.get("content", "")
|
|
291
|
-
content_found = True
|
|
292
|
-
# Legacy handling for older API
|
|
293
|
-
elif "message" in data and data.get("content"):
|
|
294
|
-
content = data.get("content")
|
|
295
|
-
content_found = True
|
|
296
|
-
|
|
297
|
-
if content_found and content:
|
|
298
|
-
streaming_text += content
|
|
299
|
-
resp = dict(text=content)
|
|
300
|
-
yield resp if not raw else content
|
|
301
|
-
|
|
302
|
-
# Update parent ID if present
|
|
303
|
-
if data.get("id"):
|
|
304
|
-
current_parent = data.get("id")
|
|
305
|
-
elif data.get("children"):
|
|
306
|
-
for child in data["children"]:
|
|
307
|
-
if child.get("role") == "assistant":
|
|
308
|
-
current_parent = child.get("id")
|
|
309
|
-
break
|
|
310
|
-
|
|
311
|
-
# Handle completion
|
|
312
|
-
if data.get("final") or data.get("finish_reason") == "stop":
|
|
313
|
-
if current_parent:
|
|
314
|
-
self.parent = current_parent
|
|
315
|
-
|
|
316
|
-
# Update conversation history
|
|
317
|
-
self.conversation.update_chat_history(prompt, streaming_text)
|
|
318
|
-
self.last_response = {"text": streaming_text}
|
|
319
|
-
return # End the generator
|
|
268
|
+
# Use sanitize_stream
|
|
269
|
+
processed_stream = sanitize_stream(
|
|
270
|
+
data=response.iter_content(chunk_size=None), # Pass byte iterator
|
|
271
|
+
intro_value=None, # No prefix
|
|
272
|
+
to_json=True, # Stream sends JSON lines
|
|
273
|
+
content_extractor=self._allenai_extractor, # Use the specific extractor
|
|
274
|
+
yield_raw_on_error=False # Skip non-JSON lines or lines where extractor fails
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
for content_chunk in processed_stream:
|
|
278
|
+
# content_chunk is the string extracted by _allenai_extractor
|
|
279
|
+
if content_chunk and isinstance(content_chunk, str):
|
|
280
|
+
streaming_text += content_chunk
|
|
281
|
+
resp = dict(text=content_chunk)
|
|
282
|
+
yield resp if not raw else content_chunk
|
|
320
283
|
|
|
321
|
-
|
|
322
|
-
|
|
284
|
+
# Try to extract parent ID from the *last* raw line (less reliable than before)
|
|
285
|
+
# This part is tricky as sanitize_stream consumes the raw lines.
|
|
286
|
+
# We might need to re-fetch or adjust if parent ID is critical per stream.
|
|
287
|
+
# For now, we'll rely on the non-stream request to update parent ID more reliably.
|
|
288
|
+
# Example placeholder logic (might not work reliably):
|
|
289
|
+
try:
|
|
290
|
+
last_line_data = json.loads(response.text.splitlines()[-1]) # Get last line if possible
|
|
291
|
+
if last_line_data.get("id"):
|
|
292
|
+
current_parent = last_line_data.get("id")
|
|
293
|
+
elif last_line_data.get("children"):
|
|
294
|
+
for child in last_line_data["children"]: # Use last_line_data here
|
|
295
|
+
if child.get("role") == "assistant":
|
|
296
|
+
current_parent = child.get("id")
|
|
297
|
+
break
|
|
298
|
+
|
|
299
|
+
# Handle completion
|
|
300
|
+
if last_line_data.get("final") or last_line_data.get("finish_reason") == "stop":
|
|
301
|
+
if current_parent:
|
|
302
|
+
self.parent = current_parent
|
|
303
|
+
|
|
304
|
+
# Update conversation history
|
|
305
|
+
self.conversation.update_chat_history(prompt, streaming_text)
|
|
306
|
+
self.last_response = {"text": streaming_text} # Update last response here
|
|
307
|
+
return # End the generator
|
|
308
|
+
except Exception as e:
|
|
309
|
+
# Log the error but continue with the rest of the function
|
|
310
|
+
print(f"Error processing response data: {str(e)}")
|
|
323
311
|
|
|
324
312
|
# If loop finishes without returning (e.g., no final message), update history
|
|
325
313
|
if current_parent:
|
|
@@ -348,10 +336,19 @@ class AllenAI(Provider):
|
|
|
348
336
|
)
|
|
349
337
|
response.raise_for_status() # Check for HTTP errors
|
|
350
338
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
339
|
+
raw_response = response.text # Get raw text
|
|
340
|
+
|
|
341
|
+
# Process the full text using sanitize_stream line by line
|
|
342
|
+
processed_stream = sanitize_stream(
|
|
343
|
+
data=raw_response.splitlines(), # Split into lines
|
|
344
|
+
intro_value=None,
|
|
345
|
+
to_json=True,
|
|
346
|
+
content_extractor=self._allenai_extractor,
|
|
347
|
+
yield_raw_on_error=False
|
|
348
|
+
)
|
|
349
|
+
# Aggregate the results
|
|
350
|
+
parsed_response = "".join(list(processed_stream))
|
|
351
|
+
|
|
355
352
|
# Update parent ID from the full response if possible (might need adjustment based on actual non-stream response structure)
|
|
356
353
|
# This part is speculative as the non-stream structure isn't fully clear from the stream logic
|
|
357
354
|
try:
|
|
@@ -10,7 +10,7 @@ from datetime import date
|
|
|
10
10
|
|
|
11
11
|
from webscout.AIutel import Optimizers
|
|
12
12
|
from webscout.AIutel import Conversation
|
|
13
|
-
from webscout.AIutel import AwesomePrompts
|
|
13
|
+
from webscout.AIutel import AwesomePrompts, sanitize_stream # Import sanitize_stream
|
|
14
14
|
from webscout.AIbase import Provider
|
|
15
15
|
from webscout import WEBS, exceptions
|
|
16
16
|
# from webscout.litagent import LitAgent
|
|
@@ -105,6 +105,17 @@ class ChatGPTClone(Provider):
|
|
|
105
105
|
self.session.headers.update(self.headers)
|
|
106
106
|
return self.impersonate
|
|
107
107
|
|
|
108
|
+
@staticmethod
|
|
109
|
+
def _chatgptclone_extractor(chunk: Union[str, Dict[str, Any]]) -> Optional[str]:
|
|
110
|
+
"""Extracts content from the ChatGPTClone stream format '0:"..."'."""
|
|
111
|
+
if isinstance(chunk, str):
|
|
112
|
+
match = re.search(r'0:"((?:[^\\"]|\\.)*)"', chunk) # Use the existing regex
|
|
113
|
+
if match:
|
|
114
|
+
content = match.group(1)
|
|
115
|
+
# Decode JSON string escapes and then unicode escapes
|
|
116
|
+
decoded_content = json.loads(f'"{content}"').encode().decode('unicode_escape')
|
|
117
|
+
return decoded_content
|
|
118
|
+
return None
|
|
108
119
|
def ask(
|
|
109
120
|
self,
|
|
110
121
|
prompt: str,
|
|
@@ -154,44 +165,31 @@ class ChatGPTClone(Provider):
|
|
|
154
165
|
|
|
155
166
|
def for_stream():
|
|
156
167
|
response = _make_request()
|
|
157
|
-
streaming_text = ""
|
|
158
|
-
buffer = ""
|
|
168
|
+
streaming_text = "" # Initialize outside try block
|
|
159
169
|
try:
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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)
|
|
170
|
+
# Use sanitize_stream
|
|
171
|
+
processed_stream = sanitize_stream(
|
|
172
|
+
data=response.iter_content(chunk_size=None), # Pass byte iterator
|
|
173
|
+
intro_value=None, # No simple prefix
|
|
174
|
+
to_json=False, # Content is text after extraction
|
|
175
|
+
content_extractor=self._chatgptclone_extractor, # Use the specific extractor
|
|
176
|
+
yield_raw_on_error=True # Yield even if extractor fails (might get metadata lines)
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
for content_chunk in processed_stream:
|
|
180
|
+
# content_chunk is the string extracted by _chatgptclone_extractor
|
|
181
|
+
if content_chunk and isinstance(content_chunk, str):
|
|
182
|
+
streaming_text += content_chunk
|
|
183
|
+
yield content_chunk if raw else dict(text=content_chunk)
|
|
184
|
+
|
|
190
185
|
except RequestsError as e:
|
|
191
186
|
raise exceptions.FailedToGenerateResponseError(f"Stream interrupted by request error: {e}") from e
|
|
192
187
|
except Exception as e:
|
|
193
188
|
raise exceptions.FailedToGenerateResponseError(f"Error processing stream: {e}") from e
|
|
194
189
|
finally:
|
|
190
|
+
# Update history after stream finishes or fails
|
|
191
|
+
self.last_response.update(dict(text=streaming_text))
|
|
192
|
+
self.conversation.update_chat_history(prompt, streaming_text)
|
|
195
193
|
response.close()
|
|
196
194
|
|
|
197
195
|
def for_non_stream():
|
|
@@ -227,7 +225,8 @@ class ChatGPTClone(Provider):
|
|
|
227
225
|
assert isinstance(response, dict)
|
|
228
226
|
if not isinstance(response, dict) or "text" not in response:
|
|
229
227
|
return str(response)
|
|
230
|
-
|
|
228
|
+
# Extractor handles formatting
|
|
229
|
+
formatted_text = response.get("text", "")
|
|
231
230
|
return formatted_text
|
|
232
231
|
|
|
233
232
|
if __name__ == "__main__":
|