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.

Files changed (87) hide show
  1. inferno/lol.py +589 -0
  2. webscout/AIutel.py +226 -14
  3. webscout/Bard.py +579 -206
  4. webscout/DWEBS.py +78 -35
  5. webscout/Extra/tempmail/base.py +1 -1
  6. webscout/Provider/AISEARCH/hika_search.py +4 -0
  7. webscout/Provider/AllenAI.py +163 -126
  8. webscout/Provider/ChatGPTClone.py +96 -84
  9. webscout/Provider/Deepinfra.py +95 -67
  10. webscout/Provider/ElectronHub.py +55 -0
  11. webscout/Provider/GPTWeb.py +96 -46
  12. webscout/Provider/Groq.py +194 -91
  13. webscout/Provider/HeckAI.py +89 -47
  14. webscout/Provider/HuggingFaceChat.py +113 -106
  15. webscout/Provider/Hunyuan.py +94 -83
  16. webscout/Provider/Jadve.py +107 -75
  17. webscout/Provider/LambdaChat.py +106 -64
  18. webscout/Provider/Llama3.py +94 -39
  19. webscout/Provider/MCPCore.py +318 -0
  20. webscout/Provider/Marcus.py +85 -36
  21. webscout/Provider/Netwrck.py +76 -43
  22. webscout/Provider/OPENAI/__init__.py +4 -1
  23. webscout/Provider/OPENAI/ai4chat.py +286 -0
  24. webscout/Provider/OPENAI/chatgptclone.py +35 -14
  25. webscout/Provider/OPENAI/deepinfra.py +37 -0
  26. webscout/Provider/OPENAI/groq.py +354 -0
  27. webscout/Provider/OPENAI/heckai.py +6 -2
  28. webscout/Provider/OPENAI/mcpcore.py +376 -0
  29. webscout/Provider/OPENAI/multichat.py +368 -0
  30. webscout/Provider/OPENAI/netwrck.py +3 -1
  31. webscout/Provider/OpenGPT.py +48 -38
  32. webscout/Provider/PI.py +168 -92
  33. webscout/Provider/PizzaGPT.py +66 -36
  34. webscout/Provider/TeachAnything.py +85 -51
  35. webscout/Provider/TextPollinationsAI.py +109 -51
  36. webscout/Provider/TwoAI.py +109 -60
  37. webscout/Provider/Venice.py +93 -56
  38. webscout/Provider/VercelAI.py +2 -2
  39. webscout/Provider/WiseCat.py +65 -28
  40. webscout/Provider/Writecream.py +37 -11
  41. webscout/Provider/WritingMate.py +135 -63
  42. webscout/Provider/__init__.py +3 -21
  43. webscout/Provider/ai4chat.py +6 -7
  44. webscout/Provider/copilot.py +0 -3
  45. webscout/Provider/elmo.py +101 -58
  46. webscout/Provider/granite.py +91 -46
  47. webscout/Provider/hermes.py +87 -47
  48. webscout/Provider/koala.py +1 -1
  49. webscout/Provider/learnfastai.py +104 -50
  50. webscout/Provider/llama3mitril.py +86 -51
  51. webscout/Provider/llmchat.py +88 -46
  52. webscout/Provider/llmchatco.py +74 -49
  53. webscout/Provider/meta.py +41 -37
  54. webscout/Provider/multichat.py +54 -25
  55. webscout/Provider/scnet.py +93 -43
  56. webscout/Provider/searchchat.py +82 -75
  57. webscout/Provider/sonus.py +103 -51
  58. webscout/Provider/toolbaz.py +132 -77
  59. webscout/Provider/turboseek.py +92 -41
  60. webscout/Provider/tutorai.py +82 -64
  61. webscout/Provider/typefully.py +75 -33
  62. webscout/Provider/typegpt.py +96 -35
  63. webscout/Provider/uncovr.py +112 -62
  64. webscout/Provider/x0gpt.py +69 -26
  65. webscout/Provider/yep.py +79 -66
  66. webscout/conversation.py +35 -21
  67. webscout/exceptions.py +20 -0
  68. webscout/prompt_manager.py +56 -42
  69. webscout/version.py +1 -1
  70. webscout/webscout_search.py +65 -47
  71. webscout/webscout_search_async.py +81 -126
  72. webscout/yep_search.py +93 -43
  73. {webscout-8.2.3.dist-info → webscout-8.2.4.dist-info}/METADATA +22 -10
  74. {webscout-8.2.3.dist-info → webscout-8.2.4.dist-info}/RECORD +78 -81
  75. {webscout-8.2.3.dist-info → webscout-8.2.4.dist-info}/WHEEL +1 -1
  76. webscout/Provider/C4ai.py +0 -432
  77. webscout/Provider/ChatGPTES.py +0 -237
  78. webscout/Provider/DeepSeek.py +0 -196
  79. webscout/Provider/Llama.py +0 -200
  80. webscout/Provider/Phind.py +0 -535
  81. webscout/Provider/WebSim.py +0 -228
  82. webscout/Provider/labyrinth.py +0 -340
  83. webscout/Provider/lepton.py +0 -194
  84. webscout/Provider/llamatutor.py +0 -192
  85. {webscout-8.2.3.dist-info → webscout-8.2.4.dist-info}/entry_points.txt +0 -0
  86. {webscout-8.2.3.dist-info → webscout-8.2.4.dist-info/licenses}/LICENSE.md +0 -0
  87. {webscout-8.2.3.dist-info → webscout-8.2.4.dist-info}/top_level.txt +0 -0
webscout/Provider/PI.py CHANGED
@@ -1,15 +1,16 @@
1
1
  from uuid import uuid4
2
- import cloudscraper
2
+ from curl_cffi.requests import Session
3
+ from curl_cffi import CurlError
3
4
  import json
4
5
  import re
5
6
  import threading
6
- import requests
7
7
  from webscout.AIutel import Optimizers
8
8
  from webscout.AIutel import Conversation
9
9
  from webscout.AIutel import AwesomePrompts
10
10
  from webscout.AIbase import Provider
11
11
  from typing import Dict, Union, Any, Optional
12
12
  from webscout.litagent import LitAgent
13
+ from webscout import exceptions
13
14
 
14
15
  class PiAI(Provider):
15
16
  """
@@ -35,7 +36,7 @@ class PiAI(Provider):
35
36
  def __init__(
36
37
  self,
37
38
  is_conversation: bool = True,
38
- max_tokens: int = 2048,
39
+ max_tokens: int = 2048, # Note: max_tokens is not used by this API
39
40
  timeout: int = 30,
40
41
  intro: str = None,
41
42
  filepath: str = None,
@@ -46,7 +47,7 @@ class PiAI(Provider):
46
47
  voice: bool = False,
47
48
  voice_name: str = "voice3",
48
49
  output_file: str = "PiAI.mp3",
49
- model: str = "inflection_3_pi",
50
+ model: str = "inflection_3_pi", # Note: model is not used by this API
50
51
  ):
51
52
  """
52
53
  Initializes PiAI with voice support.
@@ -64,8 +65,8 @@ class PiAI(Provider):
64
65
  if voice and voice_name and voice_name not in self.AVAILABLE_VOICES:
65
66
  raise ValueError(f"Voice '{voice_name}' not available. Choose from: {list(self.AVAILABLE_VOICES.keys())}")
66
67
 
67
- # Initialize other attributes
68
- self.scraper = cloudscraper.create_scraper()
68
+ # Initialize curl_cffi Session instead of cloudscraper/requests
69
+ self.session = Session()
69
70
  self.primary_url = 'https://pi.ai/api/chat'
70
71
  self.fallback_url = 'https://pi.ai/api/v2/chat'
71
72
  self.url = self.primary_url
@@ -77,9 +78,6 @@ class PiAI(Provider):
77
78
  'DNT': '1',
78
79
  'Origin': 'https://pi.ai',
79
80
  'Referer': 'https://pi.ai/talk',
80
- 'Sec-CH-UA': '"Not)A;Brand";v="99", "Microsoft Edge";v="127", "Chromium";v="127"',
81
- 'Sec-CH-UA-Mobile': '?0',
82
- 'Sec-CH-UA-Platform': '"Windows"',
83
81
  'Sec-Fetch-Dest': 'empty',
84
82
  'Sec-Fetch-Mode': 'cors',
85
83
  'Sec-Fetch-Site': 'same-origin',
@@ -90,9 +88,12 @@ class PiAI(Provider):
90
88
  '__cf_bm': uuid4().hex
91
89
  }
92
90
 
93
- self.session = requests.Session()
91
+ # Update curl_cffi session headers, proxies, and cookies
94
92
  self.session.headers.update(self.headers)
95
- self.session.proxies = proxies
93
+ self.session.proxies = proxies # Assign proxies directly
94
+ # Set cookies on the session object for curl_cffi
95
+ for name, value in self.cookies.items():
96
+ self.session.cookies.set(name, value)
96
97
 
97
98
  self.is_conversation = is_conversation
98
99
  self.max_tokens_to_sample = max_tokens
@@ -125,21 +126,33 @@ class PiAI(Provider):
125
126
  """
126
127
  Initializes a new conversation and returns the conversation ID.
127
128
  """
128
- response = self.scraper.post(
129
- "https://pi.ai/api/chat/start",
130
- headers=self.headers,
131
- cookies=self.cookies,
132
- json={},
133
- timeout=self.timeout
134
- )
135
-
136
- if not response.ok:
137
- raise Exception(f"Failed to start conversation: {response.status_code}")
129
+ try:
130
+ # Use curl_cffi session post with impersonate
131
+ # Cookies are handled by the session
132
+ response = self.session.post(
133
+ "https://pi.ai/api/chat/start",
134
+ # headers are set on the session
135
+ # cookies=self.cookies, # Handled by session
136
+ json={},
137
+ timeout=self.timeout,
138
+ # proxies are set on the session
139
+ impersonate="chrome110" # Use a common impersonation profile
140
+ )
141
+ response.raise_for_status() # Check for HTTP errors
138
142
 
139
- data = response.json()
140
- self.conversation_id = data['conversations'][0]['sid']
143
+ data = response.json()
144
+ # Ensure the expected structure before accessing
145
+ if 'conversations' in data and data['conversations'] and 'sid' in data['conversations'][0]:
146
+ self.conversation_id = data['conversations'][0]['sid']
147
+ return self.conversation_id
148
+ else:
149
+ raise exceptions.FailedToGenerateResponseError(f"Unexpected response structure from start API: {data}")
141
150
 
142
- return self.conversation_id
151
+ except CurlError as e: # Catch CurlError
152
+ raise exceptions.FailedToGenerateResponseError(f"Failed to start conversation (CurlError): {e}") from e
153
+ except Exception as e: # Catch other potential exceptions (like HTTPError, JSONDecodeError)
154
+ err_text = getattr(e, 'response', None) and getattr(e.response, 'text', '')
155
+ raise exceptions.FailedToGenerateResponseError(f"Failed to start conversation ({type(e).__name__}): {e} - {err_text}") from e
143
156
 
144
157
  def ask(
145
158
  self,
@@ -188,65 +201,101 @@ class PiAI(Provider):
188
201
  }
189
202
 
190
203
  def process_stream():
191
- # Try primary URL first
192
- response = self.scraper.post(
193
- self.url,
194
- headers=self.headers,
195
- cookies=self.cookies,
196
- json=data,
197
- stream=True,
198
- timeout=self.timeout
199
- )
200
-
201
- # If primary URL fails, try fallback URL
202
- if not response.ok and self.url == self.primary_url:
203
- self.url = self.fallback_url
204
- response = self.scraper.post(
205
- self.url,
206
- headers=self.headers,
207
- cookies=self.cookies,
204
+ try: # Add outer try block for error handling
205
+ # Try primary URL first
206
+ current_url = self.url
207
+ response = self.session.post(
208
+ current_url,
209
+ # headers are set on the session
210
+ # cookies are handled by the session
208
211
  json=data,
209
212
  stream=True,
210
- timeout=self.timeout
213
+ timeout=self.timeout,
214
+ # proxies are set on the session
215
+ impersonate="chrome110" # Use a common impersonation profile
211
216
  )
212
217
 
213
- if not response.ok:
214
- raise Exception(f"API request failed: {response.status_code}")
215
-
216
- output_str = response.content.decode('utf-8')
217
- sids = re.findall(r'"sid":"(.*?)"', output_str)
218
- second_sid = sids[1] if len(sids) >= 2 else None
219
-
220
- if voice and voice_name and second_sid:
221
- threading.Thread(
222
- target=self.download_audio_threaded,
223
- args=(voice_name, second_sid, output_file)
224
- ).start()
225
-
226
- streaming_text = ""
227
- for line in response.iter_lines(decode_unicode=True):
228
- if line.startswith("data: "):
229
- try:
230
- parsed_data = json.loads(line[6:])
231
- if 'text' in parsed_data:
232
- streaming_text += parsed_data['text']
233
- resp = dict(text=streaming_text)
234
- self.last_response.update(resp)
235
- yield parsed_data if raw else resp
236
- except json.JSONDecodeError:
237
- continue
238
-
239
- self.conversation.update_chat_history(
240
- prompt, self.get_message(self.last_response)
241
- )
218
+ # If primary URL fails, try fallback URL
219
+ if not response.ok and current_url == self.primary_url:
220
+ current_url = self.fallback_url
221
+ response = self.session.post(
222
+ current_url,
223
+ # headers are set on the session
224
+ # cookies are handled by the session
225
+ json=data,
226
+ stream=True,
227
+ timeout=self.timeout,
228
+ # proxies are set on the session
229
+ impersonate="chrome110" # Use a common impersonation profile
230
+ )
231
+
232
+ response.raise_for_status() # Check for HTTP errors after potential fallback
233
+
234
+ # --- Process response content ---
235
+ # Note: curl_cffi's response.content might behave differently for streams.
236
+ # It's often better to iterate directly.
237
+ # output_str = response.content.decode('utf-8') # Avoid reading full content at once for streams
238
+
239
+ sids = []
240
+ streaming_text = ""
241
+ full_raw_data_for_sids = "" # Accumulate raw data to find SIDs later
242
+
243
+ # Iterate over bytes and decode manually
244
+ for line_bytes in response.iter_lines():
245
+ if line_bytes:
246
+ line = line_bytes.decode('utf-8')
247
+ full_raw_data_for_sids += line + "\n" # Accumulate for SID extraction
248
+ if line.startswith("data: "):
249
+ 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']
253
+ 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
+
259
+ # Extract SIDs after processing the stream
260
+ sids = re.findall(r'"sid":"(.*?)"', full_raw_data_for_sids)
261
+ second_sid = sids[1] if len(sids) >= 2 else None
262
+
263
+ if voice and voice_name and second_sid:
264
+ threading.Thread(
265
+ target=self.download_audio_threaded,
266
+ args=(voice_name, second_sid, output_file)
267
+ ).start()
268
+
269
+ # Update history and last response after stream finishes
270
+ self.last_response = dict(text=streaming_text)
271
+ self.conversation.update_chat_history(
272
+ prompt, streaming_text
273
+ )
274
+
275
+ except CurlError as e: # Catch CurlError
276
+ raise exceptions.FailedToGenerateResponseError(f"API request failed (CurlError): {e}") from e
277
+ except Exception as e: # Catch other potential exceptions (like HTTPError)
278
+ err_text = getattr(e, 'response', None) and getattr(e.response, 'text', '')
279
+ raise exceptions.FailedToGenerateResponseError(f"API request failed ({type(e).__name__}): {e} - {err_text}") from e
280
+
242
281
 
243
282
  if stream:
244
283
  return process_stream()
245
284
  else:
246
285
  # For non-stream, collect all responses and return the final one
286
+ final_text = ""
287
+ # Ensure raw=False so process_stream yields dicts
247
288
  for res in process_stream():
248
- pass
249
- return self.last_response
289
+ if isinstance(res, dict) and "text" in res:
290
+ 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
+
295
+ # last_response and history are updated within process_stream
296
+ # Return the final aggregated response dict or raw text
297
+ return final_text if raw else self.last_response
298
+
250
299
 
251
300
  def chat(
252
301
  self,
@@ -280,28 +329,35 @@ class PiAI(Provider):
280
329
 
281
330
  if stream:
282
331
  def stream_generator():
283
- for response in self.ask(
332
+ # ask() yields dicts or raw JSON objects when streaming
333
+ gen = self.ask(
284
334
  prompt,
285
335
  stream=True,
336
+ raw=False, # Ensure ask yields dicts for get_message
286
337
  optimizer=optimizer,
287
338
  conversationally=conversationally,
288
339
  voice=voice,
289
340
  voice_name=voice_name,
290
341
  output_file=output_file
291
- ):
292
- yield self.get_message(response).encode('utf-8').decode('utf-8')
342
+ )
343
+ for response_dict in gen:
344
+ # get_message expects dict
345
+ yield self.get_message(response_dict)
293
346
  return stream_generator()
294
347
  else:
295
- response = self.ask(
348
+ # ask() returns dict or raw text when not streaming
349
+ response_data = self.ask(
296
350
  prompt,
297
351
  stream=False,
352
+ raw=False, # Ensure ask returns dict for get_message
298
353
  optimizer=optimizer,
299
354
  conversationally=conversationally,
300
355
  voice=voice,
301
356
  voice_name=voice_name,
302
357
  output_file=output_file
303
358
  )
304
- return self.get_message(response)
359
+ # get_message expects dict
360
+ return self.get_message(response_data)
305
361
 
306
362
  def get_message(self, response: dict) -> str:
307
363
  """Retrieves message only from response"""
@@ -317,28 +373,48 @@ class PiAI(Provider):
317
373
  }
318
374
 
319
375
  try:
320
- audio_response = self.scraper.get(
376
+ # Use curl_cffi session get with impersonate
377
+ audio_response = self.session.get(
321
378
  'https://pi.ai/api/chat/voice',
322
379
  params=params,
323
- cookies=self.cookies,
324
- headers=self.headers,
325
- timeout=self.timeout
380
+ # cookies are handled by the session
381
+ # headers are set on the session
382
+ timeout=self.timeout,
383
+ # proxies are set on the session
384
+ impersonate="chrome110" # Use a common impersonation profile
326
385
  )
327
-
328
- if not audio_response.ok:
329
- return
330
-
331
- audio_response.raise_for_status()
386
+ audio_response.raise_for_status() # Check for HTTP errors
332
387
 
333
388
  with open(output_file, "wb") as file:
334
389
  file.write(audio_response.content)
335
390
 
336
- except requests.exceptions.RequestException:
391
+ except CurlError: # Catch CurlError
392
+ # Optionally log the error
393
+ pass
394
+ except Exception: # Catch other potential exceptions
395
+ # Optionally log the error
337
396
  pass
338
397
 
339
398
  if __name__ == '__main__':
399
+ # Ensure curl_cffi is installed
340
400
  from rich import print
341
- ai = PiAI()
342
- response = ai.chat(input(">>> "), stream=True)
343
- for chunk in response:
344
- print(chunk, end="", flush=True)
401
+ try: # Add try-except block for testing
402
+ ai = PiAI(timeout=60)
403
+ print("[bold blue]Testing Chat (Stream):[/bold blue]")
404
+ response = ai.chat(input(">>> "), stream=True)
405
+ full_response = ""
406
+ for chunk in response:
407
+ print(chunk, end="", flush=True)
408
+ full_response += chunk
409
+ print("\n[bold green]Stream Test Complete.[/bold green]")
410
+
411
+ # Optional: Test non-stream
412
+ # print("\n[bold blue]Testing Chat (Non-Stream):[/bold blue]")
413
+ # response_non_stream = ai.chat("Hello again", stream=False)
414
+ # print(response_non_stream)
415
+ # print("[bold green]Non-Stream Test Complete.[/bold green]")
416
+
417
+ except exceptions.FailedToGenerateResponseError as e:
418
+ print(f"\n[bold red]API Error:[/bold red] {e}")
419
+ except Exception as e:
420
+ print(f"\n[bold red]An unexpected error occurred:[/bold red] {e}")
@@ -1,4 +1,5 @@
1
- import requests
1
+ from curl_cffi.requests import Session
2
+ from curl_cffi import CurlError
2
3
  import json
3
4
  import re
4
5
  from typing import Any, Dict, Optional, Union, Generator
@@ -17,7 +18,7 @@ class PIZZAGPT(Provider):
17
18
  def __init__(
18
19
  self,
19
20
  is_conversation: bool = True,
20
- max_tokens: int = 600,
21
+ max_tokens: int = 600, # Note: max_tokens is not used by this API
21
22
  timeout: int = 30,
22
23
  intro: str = None,
23
24
  filepath: str = None,
@@ -31,7 +32,8 @@ class PIZZAGPT(Provider):
31
32
  if model not in self.AVAILABLE_MODELS:
32
33
  raise ValueError(f"Invalid model: {model}. Choose from: {self.AVAILABLE_MODELS}")
33
34
 
34
- self.session = requests.Session()
35
+ # Initialize curl_cffi Session
36
+ self.session = Session()
35
37
  self.is_conversation = is_conversation
36
38
  self.max_tokens_to_sample = max_tokens
37
39
  self.api_endpoint = "https://www.pizzagpt.it/api/chatx-completion"
@@ -48,8 +50,6 @@ class PIZZAGPT(Provider):
48
50
  "referer": "https://www.pizzagpt.it/en",
49
51
  "user-agent": Lit().random(),
50
52
  "x-secret": "Marinara",
51
- "sec-ch-ua": '"Chromium";v="134", "Not:A-Brand";v="24"',
52
- "sec-ch-ua-platform": '"Windows"'
53
53
  }
54
54
 
55
55
  self.__available_optimizers = (
@@ -57,7 +57,10 @@ class PIZZAGPT(Provider):
57
57
  if callable(getattr(Optimizers, method)) and not method.startswith("__")
58
58
  )
59
59
 
60
+ # Update curl_cffi session headers and proxies
60
61
  self.session.headers.update(self.headers)
62
+ self.session.proxies = proxies # Assign proxies directly
63
+
61
64
  Conversation.intro = (
62
65
  AwesomePrompts().get_act(
63
66
  act, raise_not_found=True, default=None, case_insensitive=True
@@ -70,7 +73,6 @@ class PIZZAGPT(Provider):
70
73
  is_conversation, self.max_tokens_to_sample, filepath, update_file
71
74
  )
72
75
  self.conversation.history_offset = history_offset
73
- self.session.proxies = proxies
74
76
 
75
77
  def _extract_content(self, text: str) -> Dict[str, Any]:
76
78
  """
@@ -104,8 +106,8 @@ class PIZZAGPT(Provider):
104
106
  def ask(
105
107
  self,
106
108
  prompt: str,
107
- stream: bool = False,
108
- raw: bool = False,
109
+ stream: bool = False, # Note: API does not support streaming
110
+ raw: bool = False, # Keep raw param for interface consistency
109
111
  optimizer: str = None,
110
112
  conversationally: bool = False,
111
113
  web_search: bool = False,
@@ -116,9 +118,7 @@ class PIZZAGPT(Provider):
116
118
  conversation_prompt = self.conversation.gen_complete_prompt(prompt)
117
119
  if optimizer:
118
120
  if optimizer in self.__available_optimizers:
119
- conversation_prompt = getattr(Optimizers, optimizer)(
120
- conversation_prompt if conversationally else prompt
121
- )
121
+ conversation_prompt = getattr(Optimizers, optimizer)(conversation_prompt if conversationally else prompt)
122
122
  else:
123
123
  raise Exception(f"Optimizer is not one of {self.__available_optimizers}")
124
124
 
@@ -129,16 +129,17 @@ class PIZZAGPT(Provider):
129
129
  }
130
130
 
131
131
  try:
132
+ # Use curl_cffi session post with impersonate
132
133
  response = self.session.post(
133
134
  self.api_endpoint,
135
+ # headers are set on the session
134
136
  json=payload,
135
- timeout=self.timeout
137
+ timeout=self.timeout,
138
+ # proxies are set on the session
139
+ impersonate="chrome110" # Use a common impersonation profile
136
140
  )
137
141
 
138
- if not response.ok:
139
- raise exceptions.FailedToGenerateResponseError(
140
- f"Failed to generate response - ({response.status_code}, {response.reason})"
141
- )
142
+ response.raise_for_status() # Check for HTTP errors
142
143
 
143
144
  response_text = response.text
144
145
  if not response_text:
@@ -147,52 +148,81 @@ class PIZZAGPT(Provider):
147
148
  try:
148
149
  resp = self._extract_content(response_text)
149
150
 
150
- self.last_response.update(dict(text=resp['content']))
151
+ self.last_response = {"text": resp['content']} # Store only text in last_response
151
152
  self.conversation.update_chat_history(
152
153
  prompt, self.get_message(self.last_response)
153
154
  )
154
- return self.last_response
155
+ # Return the full extracted data (content + citations) or raw text
156
+ return response_text if raw else resp
155
157
 
156
158
  except Exception as e:
157
159
  raise exceptions.FailedToGenerateResponseError(f"Failed to parse response: {str(e)}")
158
160
 
159
- except requests.exceptions.RequestException as e:
160
- raise exceptions.FailedToGenerateResponseError(f"Request failed: {str(e)}")
161
+ except CurlError as e: # Catch CurlError
162
+ raise exceptions.FailedToGenerateResponseError(f"Request failed (CurlError): {str(e)}") from e
163
+ except Exception as e: # Catch other potential exceptions (like HTTPError)
164
+ err_text = getattr(e, 'response', None) and getattr(e.response, 'text', '')
165
+ raise exceptions.FailedToGenerateResponseError(f"An unexpected error occurred ({type(e).__name__}): {e} - {err_text}") from e
161
166
 
162
167
  def chat(
163
168
  self,
164
169
  prompt: str,
165
- stream: bool = False,
170
+ stream: bool = False, # Keep stream param for interface consistency
166
171
  optimizer: str = None,
167
172
  conversationally: bool = False,
168
173
  web_search: bool = False,
174
+ # Add raw parameter for consistency
175
+ raw: bool = False
169
176
  ) -> str:
170
177
  """
171
178
  Chat with PizzaGPT with optional web search capability.
172
179
  """
173
- try:
174
- response = self.ask(
175
- prompt,
176
- optimizer=optimizer,
177
- conversationally=conversationally,
178
- web_search=web_search
179
- )
180
- return self.get_message(response)
181
- except Exception as e:
182
- raise
180
+ # API doesn't stream, call ask directly
181
+ response_data = self.ask(
182
+ prompt,
183
+ stream=False, # Call ask in non-stream mode
184
+ raw=raw, # Pass raw flag to ask
185
+ optimizer=optimizer,
186
+ conversationally=conversationally,
187
+ web_search=web_search
188
+ )
189
+ # If raw=True, ask returns string, otherwise dict
190
+ return response_data if raw else self.get_message(response_data)
191
+
183
192
 
184
193
  def get_message(self, response: dict) -> str:
185
194
  """Extract message from response dictionary."""
186
- assert isinstance(response, dict), "Response should be of dict data-type only"
187
- return response.get("text", "")
195
+ # Handle case where raw response (string) might be passed mistakenly
196
+ if isinstance(response, str):
197
+ # Attempt to parse if it looks like the expected structure, otherwise return as is
198
+ try:
199
+ extracted = self._extract_content(response)
200
+ return extracted.get("content", "")
201
+ except:
202
+ return response # Return raw string if parsing fails
203
+ elif isinstance(response, dict):
204
+ # If it's already the extracted dict from ask(raw=False)
205
+ if "content" in response:
206
+ return response.get("content", "")
207
+ # If it's the last_response format
208
+ elif "text" in response:
209
+ return response.get("text", "")
210
+ return "" # Default empty string
188
211
 
189
212
  if __name__ == "__main__":
213
+ # Ensure curl_cffi is installed
190
214
  from rich import print
191
215
 
192
216
  # Example usage with web search enabled
193
- ai = PIZZAGPT()
217
+ ai = PIZZAGPT(timeout=60)
194
218
  try:
195
- response = ai.chat("hi")
219
+ print("[bold blue]Testing Chat (Web Search Disabled):[/bold blue]")
220
+ response = ai.chat("hi", web_search=False)
196
221
  print(response)
222
+
223
+ # print("\n[bold blue]Testing Chat (Web Search Enabled):[/bold blue]")
224
+ # response_web = ai.chat("What's the weather in Rome?", web_search=True)
225
+ # print(response_web)
226
+
197
227
  except Exception as e:
198
- print(f"Error: {str(e)}")
228
+ print(f"[bold red]Error:[/bold red] {str(e)}")