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.

Files changed (110) hide show
  1. webscout/AIauto.py +112 -22
  2. webscout/AIutel.py +240 -344
  3. webscout/Extra/autocoder/autocoder.py +66 -5
  4. webscout/Extra/gguf.py +2 -0
  5. webscout/Provider/AISEARCH/scira_search.py +3 -5
  6. webscout/Provider/Aitopia.py +75 -51
  7. webscout/Provider/AllenAI.py +64 -67
  8. webscout/Provider/ChatGPTClone.py +33 -34
  9. webscout/Provider/ChatSandbox.py +342 -0
  10. webscout/Provider/Cloudflare.py +79 -32
  11. webscout/Provider/Deepinfra.py +69 -56
  12. webscout/Provider/ElectronHub.py +48 -39
  13. webscout/Provider/ExaChat.py +36 -20
  14. webscout/Provider/GPTWeb.py +24 -18
  15. webscout/Provider/GithubChat.py +52 -49
  16. webscout/Provider/GizAI.py +285 -0
  17. webscout/Provider/Glider.py +39 -28
  18. webscout/Provider/Groq.py +48 -20
  19. webscout/Provider/HeckAI.py +18 -36
  20. webscout/Provider/Jadve.py +30 -37
  21. webscout/Provider/LambdaChat.py +36 -59
  22. webscout/Provider/MCPCore.py +18 -21
  23. webscout/Provider/Marcus.py +23 -14
  24. webscout/Provider/Nemotron.py +218 -0
  25. webscout/Provider/Netwrck.py +35 -26
  26. webscout/Provider/OPENAI/__init__.py +1 -1
  27. webscout/Provider/OPENAI/exachat.py +4 -0
  28. webscout/Provider/OPENAI/scirachat.py +3 -4
  29. webscout/Provider/OPENAI/textpollinations.py +20 -22
  30. webscout/Provider/OPENAI/toolbaz.py +1 -0
  31. webscout/Provider/PI.py +22 -13
  32. webscout/Provider/StandardInput.py +42 -30
  33. webscout/Provider/TeachAnything.py +24 -12
  34. webscout/Provider/TextPollinationsAI.py +78 -76
  35. webscout/Provider/TwoAI.py +120 -88
  36. webscout/Provider/TypliAI.py +305 -0
  37. webscout/Provider/Venice.py +24 -22
  38. webscout/Provider/VercelAI.py +31 -12
  39. webscout/Provider/WiseCat.py +1 -1
  40. webscout/Provider/WrDoChat.py +370 -0
  41. webscout/Provider/__init__.py +11 -13
  42. webscout/Provider/ai4chat.py +5 -3
  43. webscout/Provider/akashgpt.py +59 -66
  44. webscout/Provider/asksteve.py +53 -44
  45. webscout/Provider/cerebras.py +77 -31
  46. webscout/Provider/chatglm.py +47 -37
  47. webscout/Provider/elmo.py +38 -32
  48. webscout/Provider/freeaichat.py +57 -43
  49. webscout/Provider/granite.py +24 -21
  50. webscout/Provider/hermes.py +27 -20
  51. webscout/Provider/learnfastai.py +25 -20
  52. webscout/Provider/llmchatco.py +48 -78
  53. webscout/Provider/multichat.py +13 -3
  54. webscout/Provider/scira_chat.py +50 -30
  55. webscout/Provider/scnet.py +27 -21
  56. webscout/Provider/searchchat.py +16 -24
  57. webscout/Provider/sonus.py +37 -39
  58. webscout/Provider/toolbaz.py +24 -46
  59. webscout/Provider/turboseek.py +37 -41
  60. webscout/Provider/typefully.py +30 -22
  61. webscout/Provider/typegpt.py +47 -51
  62. webscout/Provider/uncovr.py +46 -40
  63. webscout/__init__.py +0 -1
  64. webscout/cli.py +256 -0
  65. webscout/conversation.py +305 -448
  66. webscout/exceptions.py +3 -0
  67. webscout/swiftcli/__init__.py +80 -794
  68. webscout/swiftcli/core/__init__.py +7 -0
  69. webscout/swiftcli/core/cli.py +297 -0
  70. webscout/swiftcli/core/context.py +104 -0
  71. webscout/swiftcli/core/group.py +241 -0
  72. webscout/swiftcli/decorators/__init__.py +28 -0
  73. webscout/swiftcli/decorators/command.py +221 -0
  74. webscout/swiftcli/decorators/options.py +220 -0
  75. webscout/swiftcli/decorators/output.py +252 -0
  76. webscout/swiftcli/exceptions.py +21 -0
  77. webscout/swiftcli/plugins/__init__.py +9 -0
  78. webscout/swiftcli/plugins/base.py +135 -0
  79. webscout/swiftcli/plugins/manager.py +262 -0
  80. webscout/swiftcli/utils/__init__.py +59 -0
  81. webscout/swiftcli/utils/formatting.py +252 -0
  82. webscout/swiftcli/utils/parsing.py +267 -0
  83. webscout/version.py +1 -1
  84. {webscout-8.2.4.dist-info → webscout-8.2.6.dist-info}/METADATA +166 -45
  85. {webscout-8.2.4.dist-info → webscout-8.2.6.dist-info}/RECORD +89 -89
  86. {webscout-8.2.4.dist-info → webscout-8.2.6.dist-info}/WHEEL +1 -1
  87. webscout-8.2.6.dist-info/entry_points.txt +3 -0
  88. {webscout-8.2.4.dist-info → webscout-8.2.6.dist-info}/top_level.txt +0 -1
  89. inferno/__init__.py +0 -6
  90. inferno/__main__.py +0 -9
  91. inferno/cli.py +0 -6
  92. inferno/lol.py +0 -589
  93. webscout/LLM.py +0 -442
  94. webscout/Local/__init__.py +0 -12
  95. webscout/Local/__main__.py +0 -9
  96. webscout/Local/api.py +0 -576
  97. webscout/Local/cli.py +0 -516
  98. webscout/Local/config.py +0 -75
  99. webscout/Local/llm.py +0 -287
  100. webscout/Local/model_manager.py +0 -253
  101. webscout/Local/server.py +0 -721
  102. webscout/Local/utils.py +0 -93
  103. webscout/Provider/Chatify.py +0 -175
  104. webscout/Provider/PizzaGPT.py +0 -228
  105. webscout/Provider/askmyai.py +0 -158
  106. webscout/Provider/gaurish.py +0 -244
  107. webscout/Provider/promptrefine.py +0 -193
  108. webscout/Provider/tutorai.py +0 -270
  109. webscout-8.2.4.dist-info/entry_points.txt +0 -5
  110. {webscout-8.2.4.dist-info → webscout-8.2.6.dist-info}/licenses/LICENSE.md +0 -0
@@ -1,10 +1,5 @@
1
- import time
2
- import uuid
3
- import json
4
1
  from typing import Any, Dict, Optional, Generator, Union
5
- from dataclasses import dataclass, asdict
6
- from datetime import date
7
- from webscout.AIutel import Optimizers, Conversation, AwesomePrompts
2
+ from webscout.AIutel import Optimizers, Conversation, AwesomePrompts, sanitize_stream # Import sanitize_stream
8
3
  from webscout.AIbase import Provider
9
4
  from webscout import exceptions
10
5
  from webscout.litagent import LitAgent
@@ -95,6 +90,15 @@ class Netwrck(Provider):
95
90
  if callable(getattr(Optimizers, method)) and not method.startswith("__")
96
91
  )
97
92
 
93
+ @staticmethod
94
+ def _netwrck_extractor(chunk: Union[str, Dict[str, Any]]) -> Optional[str]:
95
+ """Removes surrounding quotes and handles potential escapes."""
96
+ if isinstance(chunk, str):
97
+ text = chunk.strip('"')
98
+ # Handle potential unicode escapes if they appear
99
+ # text = text.encode().decode('unicode_escape') # Uncomment if needed
100
+ return text
101
+ return None
98
102
  def ask(
99
103
  self,
100
104
  prompt: str,
@@ -136,21 +140,18 @@ class Netwrck(Provider):
136
140
  response.raise_for_status() # Check for HTTP errors
137
141
 
138
142
  streaming_text = ""
139
- # Iterate over bytes and decode manually
140
- for line_bytes in response.iter_lines():
141
- if line_bytes:
142
- try:
143
- decoded_line = line_bytes.decode('utf-8').strip('"')
144
- # Handle potential escape sequences if necessary
145
- # decoded_line = decoded_line.encode().decode('unicode_escape') # Uncomment if needed
146
- streaming_text += decoded_line
147
- resp = {"text": decoded_line}
148
- # Yield dict or raw string
149
- yield resp if not raw else decoded_line
150
- except UnicodeDecodeError:
151
- # Handle potential decoding errors if chunks split mid-character
152
- continue
153
-
143
+ # Use sanitize_stream
144
+ processed_stream = sanitize_stream(
145
+ data=response.iter_content(chunk_size=None), # Pass byte iterator
146
+ intro_value=None, # No prefix
147
+ to_json=False, # It's text
148
+ content_extractor=self._netwrck_extractor, # Use the quote stripper
149
+ yield_raw_on_error=True
150
+ )
151
+ for content_chunk in processed_stream:
152
+ if content_chunk and isinstance(content_chunk, str):
153
+ streaming_text += content_chunk
154
+ yield {"text": content_chunk} if not raw else content_chunk
154
155
  # Update history after stream finishes
155
156
  self.last_response = {"text": streaming_text} # Store aggregated text
156
157
  self.conversation.update_chat_history(payload["query"], streaming_text)
@@ -174,11 +175,19 @@ class Netwrck(Provider):
174
175
  )
175
176
  response.raise_for_status() # Check for HTTP errors
176
177
 
177
- # Use response.text which is already decoded
178
- text = response.text.strip('"')
179
- # Handle potential escape sequences if necessary
180
- # text = text.encode().decode('unicode_escape') # Uncomment if needed
181
- self.last_response = {"text": text}
178
+ response_text_raw = response.text # Get raw text
179
+
180
+ # Process the text using sanitize_stream
181
+ processed_stream = sanitize_stream(
182
+ data=response_text_raw,
183
+ intro_value=None,
184
+ to_json=False,
185
+ content_extractor=self._netwrck_extractor
186
+ )
187
+ # Aggregate the single result
188
+ text = "".join(list(processed_stream))
189
+
190
+ self.last_response = {"text": text} # Store processed text
182
191
  self.conversation.update_chat_history(prompt, text)
183
192
 
184
193
  # Return dict or raw string
@@ -25,4 +25,4 @@ from .textpollinations import *
25
25
  from .e2b import *
26
26
  from .multichat import * # Add MultiChatAI
27
27
  from .ai4chat import * # Add AI4Chat
28
- from .mcpcore import *
28
+ from .mcpcore import *
@@ -35,6 +35,8 @@ MODEL_CONFIGS = {
35
35
  "gemini-2.0-flash-thinking-exp-01-21",
36
36
  "gemini-2.5-pro-exp-03-25",
37
37
  "gemini-2.0-pro-exp-02-05",
38
+ "gemini-2.5-flash-preview-04-17",
39
+
38
40
 
39
41
  ],
40
42
  },
@@ -83,6 +85,7 @@ MODEL_CONFIGS = {
83
85
  },
84
86
  }
85
87
 
88
+
86
89
  class Completions(BaseCompletions):
87
90
  def __init__(self, client: 'ExaChat'):
88
91
  self._client = client
@@ -292,6 +295,7 @@ class ExaChat(OpenAICompatibleProvider):
292
295
  "gemini-2.0-flash-thinking-exp-01-21",
293
296
  "gemini-2.5-pro-exp-03-25",
294
297
  "gemini-2.0-pro-exp-02-05",
298
+ "gemini-2.5-flash-preview-04-17",
295
299
 
296
300
  # OpenRouter Models
297
301
  "mistralai/mistral-small-3.1-24b-instruct:free",
@@ -324,15 +324,14 @@ class SciraChat(OpenAICompatibleProvider):
324
324
  """
325
325
 
326
326
  AVAILABLE_MODELS = {
327
- "scira-default": "Grok3",
328
- "scira-grok-3-mini": "Grok3-mini", # thinking model
327
+ "scira-default": "Grok3-mini", # thinking model
328
+ "scira-grok-3": "Grok3",
329
+ "scira-anthropic": "Sonnet 3.7 thinking",
329
330
  "scira-vision" : "Grok2-Vision", # vision model
330
331
  "scira-4.1-mini": "GPT4.1-mini",
331
332
  "scira-qwq": "QWQ-32B",
332
333
  "scira-o4-mini": "o4-mini",
333
334
  "scira-google": "gemini 2.5 flash"
334
-
335
-
336
335
  }
337
336
 
338
337
  def __init__(
@@ -268,28 +268,26 @@ class TextPollinations(OpenAICompatibleProvider):
268
268
  """
269
269
 
270
270
  AVAILABLE_MODELS = [
271
- "openai", # OpenAI GPT-4.1-nano (Azure) - vision capable
272
- "openai-large", # OpenAI GPT-4.1 mini (Azure) - vision capable
273
- "openai-reasoning", # OpenAI o4-mini (Azure) - vision capable, reasoning
274
- "qwen-coder", # Qwen 2.5 Coder 32B (Scaleway)
275
- "llama", # Llama 3.3 70B (Cloudflare)
276
- "llamascout", # Llama 4 Scout 17B (Cloudflare)
277
- "mistral", # Mistral Small 3 (Scaleway) - vision capable
278
- "unity", # Unity Mistral Large (Scaleway) - vision capable, uncensored
279
- "midijourney", # Midijourney (Azure)
280
- "rtist", # Rtist (Azure)
281
- "searchgpt", # SearchGPT (Azure) - vision capable
282
- "evil", # Evil (Scaleway) - vision capable, uncensored
283
- "deepseek-reasoning", # DeepSeek-R1 Distill Qwen 32B (Cloudflare) - reasoning
284
- "deepseek-reasoning-large", # DeepSeek R1 - Llama 70B (Scaleway) - reasoning
285
- "phi", # Phi-4 Instruct (Cloudflare) - vision and audio capable
286
- "llama-vision", # Llama 3.2 11B Vision (Cloudflare) - vision capable
287
- "gemini", # gemini-2.5-flash-preview-04-17 (Azure) - vision and audio capable
288
- "hormoz", # Hormoz 8b (Modal)
289
- "hypnosis-tracy", # Hypnosis Tracy 7B (Azure) - audio capable
290
- "deepseek", # DeepSeek-V3 (DeepSeek)
291
- "sur", # Sur AI Assistant (Mistral) (Scaleway) - vision capable
292
- "openai-audio", # OpenAI GPT-4o-audio-preview (Azure) - vision and audio capable
271
+ "openai",
272
+ "openai-large",
273
+ "qwen-coder",
274
+ "llama",
275
+ "llamascout",
276
+ "mistral",
277
+ "unity",
278
+ "midijourney",
279
+ "rtist",
280
+ "searchgpt",
281
+ "evil",
282
+ "deepseek-reasoning",
283
+ "deepseek-reasoning-large",
284
+ "phi",
285
+ "llama-vision",
286
+ "hormoz",
287
+ "hypnosis-tracy",
288
+ "deepseek",
289
+ "sur",
290
+ "openai-audio",
293
291
  ]
294
292
 
295
293
  def __init__(
@@ -288,6 +288,7 @@ class Toolbaz(OpenAICompatibleProvider):
288
288
  "gemini-2.0-flash-thinking",
289
289
  "gemini-2.0-flash",
290
290
  "gemini-1.5-flash",
291
+ "o3-mini",
291
292
  "gpt-4o-latest",
292
293
  "gpt-4o",
293
294
  "deepseek-r1",
webscout/Provider/PI.py CHANGED
@@ -5,7 +5,7 @@ import json
5
5
  import re
6
6
  import threading
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
  from typing import Dict, Union, Any, Optional
@@ -122,6 +122,13 @@ class PiAI(Provider):
122
122
  if self.is_conversation:
123
123
  self.start_conversation()
124
124
 
125
+ @staticmethod
126
+ def _pi_extractor(chunk: Union[str, Dict[str, Any]]) -> Optional[str]:
127
+ """Extracts text content from PiAI stream JSON objects."""
128
+ if isinstance(chunk, dict) and 'text' in chunk and chunk['text'] is not None:
129
+ return chunk.get("text")
130
+ return None
131
+
125
132
  def start_conversation(self) -> str:
126
133
  """
127
134
  Initializes a new conversation and returns the conversation ID.
@@ -245,17 +252,22 @@ class PiAI(Provider):
245
252
  if line_bytes:
246
253
  line = line_bytes.decode('utf-8')
247
254
  full_raw_data_for_sids += line + "\n" # Accumulate for SID extraction
255
+
248
256
  if line.startswith("data: "):
257
+ json_line_str = line[6:] # Get the JSON part as string
249
258
  try:
250
- parsed_data = json.loads(line[6:])
251
- if 'text' in parsed_data and parsed_data['text'] is not None:
252
- chunk_text = parsed_data['text']
259
+ # Process this single JSON line string with sanitize_stream
260
+ processed_gen = sanitize_stream(
261
+ data=json_line_str,
262
+ to_json=True,
263
+ content_extractor=self._pi_extractor
264
+ )
265
+ chunk_text = next(processed_gen, None) # Get the single extracted text item
266
+ if chunk_text and isinstance(chunk_text, str):
253
267
  streaming_text += chunk_text
254
- # Yield raw JSON object or dict with aggregated text
255
- yield parsed_data if raw else dict(text=streaming_text)
256
- except (json.JSONDecodeError, UnicodeDecodeError):
257
- continue
258
-
268
+ yield {"text": streaming_text} # Always yield dict with aggregated text
269
+ except (StopIteration, json.JSONDecodeError, UnicodeDecodeError):
270
+ continue # Skip if sanitize_stream fails or yields nothing
259
271
  # Extract SIDs after processing the stream
260
272
  sids = re.findall(r'"sid":"(.*?)"', full_raw_data_for_sids)
261
273
  second_sid = sids[1] if len(sids) >= 2 else None
@@ -284,13 +296,10 @@ class PiAI(Provider):
284
296
  else:
285
297
  # For non-stream, collect all responses and return the final one
286
298
  final_text = ""
287
- # Ensure raw=False so process_stream yields dicts
299
+ # process_stream always yields dicts now
288
300
  for res in process_stream():
289
301
  if isinstance(res, dict) and "text" in res:
290
302
  final_text = res["text"] # Keep updating with the latest aggregated text
291
- # Handle raw JSON object case if raw=True was passed
292
- elif raw and isinstance(res, dict) and 'text' in res and res['text'] is not None:
293
- final_text += res['text'] # Append chunks if raw
294
303
 
295
304
  # last_response and history are updated within process_stream
296
305
  # Return the final aggregated response dict or raw text
@@ -1,13 +1,10 @@
1
- from os import system
2
- import requests
3
- import json
1
+ from curl_cffi.requests import Session
4
2
  import uuid
5
3
  import re
6
- from datetime import datetime
7
- from typing import Any, Dict, Optional, Union, Generator
4
+ from typing import Any, Dict, Optional, Union
8
5
  from webscout.AIutel import Optimizers
9
6
  from webscout.AIutel import Conversation
10
- from webscout.AIutel import AwesomePrompts
7
+ from webscout.AIutel import AwesomePrompts, sanitize_stream # Import sanitize_stream
11
8
  from webscout.AIbase import Provider
12
9
  from webscout import exceptions
13
10
  from webscout.litagent import LitAgent
@@ -98,9 +95,9 @@ class StandardInputAI(Provider):
98
95
  "ph_phc_f3wUUyCfmKlKtkc2pfT7OsdcW2mBEVGN2A87yEYbG3c_posthog": '''%7B%22distinct_id%22%3A%220195c7cc-ac8f-79ff-b901-e14a78fc2a67%22%2C%22%24sesid%22%3A%5B1744688627860%2C%220196377f-9f12-77e6-a9ea-0e9669423803%22%2C1744687832850%5D%2C%22%24initial_person_info%22%3A%7B%22r%22%3A%22%24direct%22%2C%22u%22%3A%22https%3A%2F%2Fstandard-input.com%2F%22%7D%7D'''
99
96
  }
100
97
 
101
- self.session = requests.Session()
98
+ self.session = Session() # Use curl_cffi Session
102
99
  self.session.headers.update(self.headers)
103
- self.session.proxies.update(proxies)
100
+ self.session.proxies = proxies # Assign proxies directly
104
101
 
105
102
  self.is_conversation = is_conversation
106
103
  self.max_tokens_to_sample = max_tokens
@@ -153,6 +150,17 @@ class StandardInputAI(Provider):
153
150
 
154
151
  return self.fingerprint
155
152
 
153
+ @staticmethod
154
+ def _standardinput_extractor(chunk: Union[str, Dict[str, Any]]) -> Optional[str]:
155
+ """Extracts content from the StandardInput stream format '0:"..."'."""
156
+ if isinstance(chunk, str):
157
+ match = re.search(r'0:"(.*?)"(?=,|$)', chunk) # Look for 0:"...", possibly followed by comma or end of string
158
+ if match:
159
+ # Decode potential unicode escapes like \u00e9 and handle escaped quotes/backslashes
160
+ content = match.group(1).encode().decode('unicode_escape')
161
+ return content.replace('\\\\', '\\').replace('\\"', '"')
162
+ return None
163
+
156
164
  def ask(
157
165
  self,
158
166
  prompt: str,
@@ -183,45 +191,48 @@ class StandardInputAI(Provider):
183
191
  }
184
192
 
185
193
  try:
186
- response = self.session.post(self.url, cookies=self.cookies, json=payload, stream=True, timeout=self.timeout)
194
+ # Use curl_cffi post with impersonate
195
+ response = self.session.post(
196
+ self.url,
197
+ cookies=self.cookies,
198
+ json=payload,
199
+ stream=True,
200
+ timeout=self.timeout,
201
+ impersonate="chrome120" # Add impersonate
202
+ )
203
+
187
204
  if response.status_code != 200:
188
- # Try to get response content for better error messages
189
205
  try:
190
206
  error_content = response.text
191
207
  except:
192
208
  error_content = "<could not read response content>"
193
209
 
194
210
  if response.status_code in [403, 429]:
195
- print(f"Received status code {response.status_code}, refreshing identity...")
196
211
  self.refresh_identity()
197
- response = self.session.post(self.url, cookies=self.cookies, json=payload, stream=True, timeout=self.timeout)
212
+ response = self.session.post(
213
+ self.url, cookies=self.cookies, json=payload, stream=True,
214
+ timeout=self.timeout, impersonate="chrome120"
215
+ )
198
216
  if not response.ok:
199
217
  raise exceptions.FailedToGenerateResponseError(
200
218
  f"Failed to generate response after identity refresh - ({response.status_code}, {response.reason}) - {error_content}"
201
219
  )
202
- print("Identity refreshed successfully.")
203
220
  else:
204
221
  raise exceptions.FailedToGenerateResponseError(
205
222
  f"Request failed with status code {response.status_code}. Response: {error_content}"
206
223
  )
207
224
 
208
225
  full_response = ""
209
- debug_lines = []
210
-
211
- # Process the streaming response
212
- for i, line in enumerate(response.iter_lines(decode_unicode=True)):
213
- if line:
214
- try:
215
- line_str = line
216
- debug_lines.append(line_str)
217
-
218
- # Extract content from the response
219
- match = re.search(r'0:"(.*?)"', line_str)
220
- if match:
221
- content = match.group(1)
222
- full_response += content
223
- continue
224
- except: pass
226
+ # Use sanitize_stream
227
+ processed_stream = sanitize_stream(
228
+ data=response.iter_content(chunk_size=None), # Pass byte iterator
229
+ intro_value=None, # No simple prefix
230
+ to_json=False, # Content is not JSON
231
+ content_extractor=self._standardinput_extractor # Use the specific extractor
232
+ )
233
+ for content_chunk in processed_stream:
234
+ if content_chunk and isinstance(content_chunk, str):
235
+ full_response += content_chunk
225
236
 
226
237
  self.last_response = {"text": full_response}
227
238
  self.conversation.update_chat_history(prompt, full_response)
@@ -243,7 +254,8 @@ class StandardInputAI(Provider):
243
254
 
244
255
  def get_message(self, response: dict) -> str:
245
256
  assert isinstance(response, dict), "Response should be of dict data-type only"
246
- return response["text"].replace('\\n', '\n').replace('\\n\\n', '\n\n')
257
+ # Extractor handles formatting
258
+ return response.get("text", "").replace('\\n', '\n').replace('\\n\\n', '\n\n')
247
259
 
248
260
  if __name__ == "__main__":
249
261
  print("-" * 100)
@@ -4,7 +4,7 @@ from typing import Union, Any, Dict
4
4
  from webscout.AIbase import Provider # Import Provider base class
5
5
  from webscout import exceptions # Import custom exceptions
6
6
  from webscout.conversation import Conversation
7
- from webscout.optimizers import Optimizers
7
+ from webscout.AIutel import Optimizers, sanitize_stream # Import sanitize_stream
8
8
  from webscout.prompt_manager import AwesomePrompts
9
9
  from webscout.litagent import LitAgent
10
10
 
@@ -131,13 +131,22 @@ class TeachAnything(Provider):
131
131
  )
132
132
  response.raise_for_status() # Check for HTTP errors
133
133
 
134
- # Use response.text which is already decoded
135
- resp_text = response.text
136
- # The response is plain text, wrap it in the expected dict format
137
- self.last_response = {"text": resp_text}
138
- self.conversation.update_chat_history(
139
- prompt, resp_text
134
+ resp_text_raw = response.text # Get raw response text
135
+
136
+ # Process the text using sanitize_stream (even though it's not streaming)
137
+ # This keeps the pattern consistent, though it won't do much here
138
+ processed_stream = sanitize_stream(
139
+ data=resp_text_raw,
140
+ intro_value=None, # No prefix
141
+ to_json=False # It's plain text
140
142
  )
143
+
144
+ # Extract the single result from the generator
145
+ resp_text = "".join(list(processed_stream)) # Aggregate potential chunks (should be one)
146
+
147
+ self.last_response = {"text": resp_text}
148
+ self.conversation.update_chat_history(prompt, resp_text)
149
+
141
150
  # Return dict or raw string based on raw flag
142
151
  return resp_text if raw else self.last_response
143
152
 
@@ -182,17 +191,20 @@ class TeachAnything(Provider):
182
191
  # If stream=False, return the full message directly
183
192
  return self.get_message(response_data)
184
193
 
185
- def get_message(self, response: dict) -> str:
194
+ def get_message(self, response: Union[dict, str]) -> str:
186
195
  """Retrieves message only from response
187
196
 
188
197
  Args:
189
- response (dict): Response generated by `self.ask`
198
+ response (Union[dict, str]): Response generated by `self.ask`
190
199
 
191
200
  Returns:
192
201
  str: Message extracted
193
202
  """
194
- assert isinstance(response, dict), "Response should be of dict data-type only"
195
- return response["text"]
203
+ if isinstance(response, str):
204
+ return response
205
+ elif isinstance(response, dict):
206
+ return response["text"]
207
+ raise ValueError("Response must be either dict or str")
196
208
 
197
209
 
198
210
  if __name__ == '__main__':
@@ -218,4 +230,4 @@ if __name__ == '__main__':
218
230
  except exceptions.FailedToGenerateResponseError as e:
219
231
  print(f"\n[bold red]API Error:[/bold red] {e}")
220
232
  except Exception as e:
221
- print(f"\n[bold red]An unexpected error occurred:[/bold red] {e}")
233
+ print(f"\n[bold red]An unexpected error occurred:[/bold red] {e}")