webscout 8.3.2__py3-none-any.whl → 8.3.3__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 (94) hide show
  1. webscout/AIutel.py +146 -37
  2. webscout/Bing_search.py +1 -2
  3. webscout/Provider/AISEARCH/__init__.py +1 -0
  4. webscout/Provider/AISEARCH/stellar_search.py +132 -0
  5. webscout/Provider/ExaChat.py +84 -58
  6. webscout/Provider/HeckAI.py +85 -80
  7. webscout/Provider/Jadve.py +56 -50
  8. webscout/Provider/MiniMax.py +207 -0
  9. webscout/Provider/Nemotron.py +41 -13
  10. webscout/Provider/Netwrck.py +34 -51
  11. webscout/Provider/OPENAI/BLACKBOXAI.py +0 -1
  12. webscout/Provider/OPENAI/MiniMax.py +298 -0
  13. webscout/Provider/OPENAI/README.md +30 -29
  14. webscout/Provider/OPENAI/TogetherAI.py +4 -17
  15. webscout/Provider/OPENAI/__init__.py +3 -1
  16. webscout/Provider/OPENAI/autoproxy.py +752 -17
  17. webscout/Provider/OPENAI/base.py +7 -76
  18. webscout/Provider/OPENAI/deepinfra.py +42 -108
  19. webscout/Provider/OPENAI/flowith.py +179 -166
  20. webscout/Provider/OPENAI/friendli.py +233 -0
  21. webscout/Provider/OPENAI/monochat.py +329 -0
  22. webscout/Provider/OPENAI/pydantic_imports.py +1 -172
  23. webscout/Provider/OPENAI/toolbaz.py +1 -0
  24. webscout/Provider/OPENAI/typegpt.py +1 -1
  25. webscout/Provider/OPENAI/utils.py +19 -42
  26. webscout/Provider/OPENAI/x0gpt.py +14 -2
  27. webscout/Provider/OpenGPT.py +54 -32
  28. webscout/Provider/PI.py +58 -84
  29. webscout/Provider/StandardInput.py +32 -13
  30. webscout/Provider/TTI/README.md +9 -9
  31. webscout/Provider/TTI/__init__.py +2 -1
  32. webscout/Provider/TTI/aiarta.py +92 -78
  33. webscout/Provider/TTI/infip.py +212 -0
  34. webscout/Provider/TTI/monochat.py +220 -0
  35. webscout/Provider/TeachAnything.py +11 -3
  36. webscout/Provider/TextPollinationsAI.py +78 -70
  37. webscout/Provider/TogetherAI.py +32 -48
  38. webscout/Provider/Venice.py +37 -46
  39. webscout/Provider/VercelAI.py +27 -24
  40. webscout/Provider/WiseCat.py +35 -35
  41. webscout/Provider/WrDoChat.py +22 -26
  42. webscout/Provider/WritingMate.py +26 -22
  43. webscout/Provider/__init__.py +2 -2
  44. webscout/Provider/granite.py +48 -57
  45. webscout/Provider/koala.py +51 -39
  46. webscout/Provider/learnfastai.py +49 -64
  47. webscout/Provider/llmchat.py +79 -93
  48. webscout/Provider/llmchatco.py +63 -78
  49. webscout/Provider/multichat.py +51 -40
  50. webscout/Provider/oivscode.py +1 -1
  51. webscout/Provider/scira_chat.py +159 -96
  52. webscout/Provider/scnet.py +13 -13
  53. webscout/Provider/searchchat.py +13 -13
  54. webscout/Provider/sonus.py +12 -11
  55. webscout/Provider/toolbaz.py +25 -8
  56. webscout/Provider/turboseek.py +41 -42
  57. webscout/Provider/typefully.py +27 -12
  58. webscout/Provider/typegpt.py +41 -46
  59. webscout/Provider/uncovr.py +55 -90
  60. webscout/Provider/x0gpt.py +33 -17
  61. webscout/Provider/yep.py +79 -96
  62. webscout/auth/__init__.py +12 -1
  63. webscout/auth/providers.py +27 -5
  64. webscout/auth/routes.py +128 -104
  65. webscout/auth/server.py +367 -312
  66. webscout/client.py +121 -116
  67. webscout/litagent/Readme.md +68 -55
  68. webscout/litagent/agent.py +99 -9
  69. webscout/version.py +1 -1
  70. {webscout-8.3.2.dist-info → webscout-8.3.3.dist-info}/METADATA +102 -90
  71. {webscout-8.3.2.dist-info → webscout-8.3.3.dist-info}/RECORD +75 -87
  72. webscout/Provider/TTI/fastflux.py +0 -233
  73. webscout/Provider/Writecream.py +0 -246
  74. webscout/auth/static/favicon.svg +0 -11
  75. webscout/auth/swagger_ui.py +0 -203
  76. webscout/auth/templates/components/authentication.html +0 -237
  77. webscout/auth/templates/components/base.html +0 -103
  78. webscout/auth/templates/components/endpoints.html +0 -750
  79. webscout/auth/templates/components/examples.html +0 -491
  80. webscout/auth/templates/components/footer.html +0 -75
  81. webscout/auth/templates/components/header.html +0 -27
  82. webscout/auth/templates/components/models.html +0 -286
  83. webscout/auth/templates/components/navigation.html +0 -70
  84. webscout/auth/templates/static/api.js +0 -455
  85. webscout/auth/templates/static/icons.js +0 -168
  86. webscout/auth/templates/static/main.js +0 -784
  87. webscout/auth/templates/static/particles.js +0 -201
  88. webscout/auth/templates/static/styles.css +0 -3353
  89. webscout/auth/templates/static/ui.js +0 -374
  90. webscout/auth/templates/swagger_ui.html +0 -170
  91. {webscout-8.3.2.dist-info → webscout-8.3.3.dist-info}/WHEEL +0 -0
  92. {webscout-8.3.2.dist-info → webscout-8.3.3.dist-info}/entry_points.txt +0 -0
  93. {webscout-8.3.2.dist-info → webscout-8.3.3.dist-info}/licenses/LICENSE.md +0 -0
  94. {webscout-8.3.2.dist-info → webscout-8.3.3.dist-info}/top_level.txt +0 -0
@@ -141,7 +141,6 @@ class VercelAI(Provider):
141
141
  raise Exception(
142
142
  f"Optimizer is not one of {self.__available_optimizers}"
143
143
  )
144
-
145
144
  payload = {
146
145
  "id": "guest",
147
146
  "messages": [
@@ -155,7 +154,6 @@ class VercelAI(Provider):
155
154
  ],
156
155
  "selectedChatModelId": self.model
157
156
  }
158
-
159
157
  def for_stream():
160
158
  response = self.session.post(
161
159
  self.api_endpoint, headers=self.headers, json=payload, stream=True, timeout=self.timeout
@@ -163,31 +161,32 @@ class VercelAI(Provider):
163
161
  if not response.ok:
164
162
  error_msg = f"Failed to generate response - ({response.status_code}, {response.reason}) - {response.text}"
165
163
  raise exceptions.FailedToGenerateResponseError(error_msg)
166
-
167
164
  streaming_text = ""
168
- # Use sanitize_stream with the custom extractor
169
165
  processed_stream = sanitize_stream(
170
166
  data=response.iter_content(chunk_size=None), # Pass byte iterator
171
167
  intro_value=None, # No simple prefix
172
168
  to_json=False, # Content is not JSON
173
- content_extractor=self._vercelai_extractor # Use the specific extractor
169
+ content_extractor=self._vercelai_extractor, # Use the specific extractor
170
+ raw=raw
174
171
  )
175
-
176
172
  for content_chunk in processed_stream:
177
- if content_chunk and isinstance(content_chunk, str):
178
- streaming_text += content_chunk
179
- yield content_chunk if raw else dict(text=content_chunk)
180
-
173
+ # Always yield as string, even in raw mode
174
+ if isinstance(content_chunk, bytes):
175
+ content_chunk = content_chunk.decode('utf-8', errors='ignore')
176
+ if raw:
177
+ yield content_chunk
178
+ else:
179
+ if content_chunk and isinstance(content_chunk, str):
180
+ streaming_text += content_chunk
181
+ yield dict(text=content_chunk)
181
182
  self.last_response.update(dict(text=streaming_text))
182
183
  self.conversation.update_chat_history(
183
184
  prompt, self.get_message(self.last_response)
184
185
  )
185
-
186
186
  def for_non_stream():
187
187
  for _ in for_stream():
188
188
  pass
189
189
  return self.last_response
190
-
191
190
  return for_stream() if stream else for_non_stream()
192
191
 
193
192
  def chat(
@@ -196,24 +195,28 @@ class VercelAI(Provider):
196
195
  stream: bool = False,
197
196
  optimizer: str = None,
198
197
  conversationally: bool = False,
198
+ raw: bool = False, # Added raw parameter
199
199
  ) -> str:
200
- """Generate response `str`"""
201
200
  def for_stream():
202
201
  for response in self.ask(
203
- prompt, True, optimizer=optimizer, conversationally=conversationally
202
+ prompt, True, raw=raw, optimizer=optimizer, conversationally=conversationally
204
203
  ):
205
- yield self.get_message(response)
206
-
204
+ if raw:
205
+ yield response
206
+ else:
207
+ yield self.get_message(response)
207
208
  def for_non_stream():
208
- return self.get_message(
209
- self.ask(
210
- prompt,
211
- False,
212
- optimizer=optimizer,
213
- conversationally=conversationally,
214
- )
209
+ result = self.ask(
210
+ prompt,
211
+ False,
212
+ raw=raw,
213
+ optimizer=optimizer,
214
+ conversationally=conversationally,
215
215
  )
216
-
216
+ if raw:
217
+ return result
218
+ else:
219
+ return self.get_message(result)
217
220
  return for_stream() if stream else for_non_stream()
218
221
 
219
222
  def get_message(self, response: dict) -> str:
@@ -106,7 +106,6 @@ class WiseCat(Provider):
106
106
  raise Exception(
107
107
  f"Optimizer is not one of {self.__available_optimizers}"
108
108
  )
109
-
110
109
  payload = {
111
110
  "id": "ephemeral",
112
111
  "messages": [
@@ -121,52 +120,49 @@ class WiseCat(Provider):
121
120
  ],
122
121
  "selectedChatModel": self.model
123
122
  }
124
-
125
123
  def for_stream():
126
- try: # Add try block for CurlError
127
- # Use curl_cffi session post with impersonate
124
+ try:
128
125
  response = self.session.post(
129
126
  self.api_endpoint,
130
127
  headers=self.headers,
131
128
  json=payload,
132
129
  stream=True,
133
130
  timeout=self.timeout,
134
- impersonate="chrome120" # Add impersonate
131
+ impersonate="chrome120"
135
132
  )
136
133
  if not response.ok:
137
134
  error_msg = f"Failed to generate response - ({response.status_code}, {response.reason}) - {response.text}"
138
135
  raise exceptions.FailedToGenerateResponseError(error_msg)
139
-
140
136
  streaming_text = ""
141
- # Use sanitize_stream with the custom extractor
142
137
  processed_stream = sanitize_stream(
143
- data=response.iter_content(chunk_size=None), # Pass byte iterator
144
- intro_value=None, # No simple prefix to remove here
145
- to_json=False, # Content is not JSON
146
- content_extractor=self._wisecat_extractor # Use the specific extractor
138
+ data=response.iter_content(chunk_size=None),
139
+ intro_value=None,
140
+ to_json=False,
141
+ content_extractor=self._wisecat_extractor,
142
+ raw=raw
147
143
  )
148
-
149
144
  for content_chunk in processed_stream:
150
- if content_chunk and isinstance(content_chunk, str):
151
- streaming_text += content_chunk
152
- yield content_chunk if raw else dict(text=content_chunk)
153
-
154
- self.last_response.update(dict(text=streaming_text)) # Use streaming_text here
145
+ # Always yield as string, even in raw mode
146
+ if isinstance(content_chunk, bytes):
147
+ content_chunk = content_chunk.decode('utf-8', errors='ignore')
148
+ if raw:
149
+ yield content_chunk
150
+ else:
151
+ if content_chunk and isinstance(content_chunk, str):
152
+ streaming_text += content_chunk
153
+ yield dict(text=content_chunk)
154
+ self.last_response.update(dict(text=streaming_text))
155
155
  self.conversation.update_chat_history(
156
156
  prompt, self.get_message(self.last_response)
157
157
  )
158
- except CurlError as e: # Catch CurlError
158
+ except CurlError as e:
159
159
  raise exceptions.FailedToGenerateResponseError(f"Request failed (CurlError): {e}")
160
- except Exception as e: # Catch other potential exceptions
160
+ except Exception as e:
161
161
  raise exceptions.FailedToGenerateResponseError(f"An unexpected error occurred ({type(e).__name__}): {e}")
162
-
163
-
164
162
  def for_non_stream():
165
- # This function implicitly uses the updated for_stream
166
163
  for _ in for_stream():
167
164
  pass
168
165
  return self.last_response
169
-
170
166
  return for_stream() if stream else for_non_stream()
171
167
 
172
168
  def chat(
@@ -175,24 +171,28 @@ class WiseCat(Provider):
175
171
  stream: bool = False,
176
172
  optimizer: str = None,
177
173
  conversationally: bool = False,
174
+ raw: bool = False, # Added raw parameter
178
175
  ) -> str:
179
- """Generate response `str`"""
180
176
  def for_stream():
181
177
  for response in self.ask(
182
- prompt, True, optimizer=optimizer, conversationally=conversationally
178
+ prompt, True, raw=raw, optimizer=optimizer, conversationally=conversationally
183
179
  ):
184
- yield self.get_message(response)
185
-
180
+ if raw:
181
+ yield response
182
+ else:
183
+ yield self.get_message(response)
186
184
  def for_non_stream():
187
- return self.get_message(
188
- self.ask(
189
- prompt,
190
- False,
191
- optimizer=optimizer,
192
- conversationally=conversationally,
193
- )
185
+ result = self.ask(
186
+ prompt,
187
+ False,
188
+ raw=raw,
189
+ optimizer=optimizer,
190
+ conversationally=conversationally,
194
191
  )
195
-
192
+ if raw:
193
+ return result
194
+ else:
195
+ return self.get_message(result)
196
196
  return for_stream() if stream else for_non_stream()
197
197
 
198
198
  def get_message(self, response: dict) -> str:
@@ -244,7 +244,6 @@ class WrDoChat(Provider):
244
244
  def for_stream():
245
245
  try:
246
246
  self.headers["referer"] = f"https://oi.wr.do/chat/{chat_id}"
247
-
248
247
  response = self.session.post(
249
248
  self.api_endpoint,
250
249
  json=payload,
@@ -252,31 +251,27 @@ class WrDoChat(Provider):
252
251
  timeout=self.timeout,
253
252
  impersonate="chrome110"
254
253
  )
255
-
256
254
  if response.status_code == 401:
257
255
  raise exceptions.AuthenticationError("Authentication failed. Please check your cookies.")
258
-
259
256
  response.raise_for_status()
260
-
261
257
  streaming_response = ""
262
258
  has_content = False
263
-
264
- # Use sanitize_stream with the custom extractor
265
259
  processed_stream = sanitize_stream(
266
260
  data=response.iter_lines(),
267
261
  intro_value=None, # No intro to remove
268
262
  to_json=False, # Response is not JSON
269
263
  content_extractor=self._wrdo_extractor,
270
- yield_raw_on_error=False
264
+ yield_raw_on_error=False,
265
+ raw=raw
271
266
  )
272
-
273
267
  for content in processed_stream:
268
+ # Always yield as string, even in raw mode
269
+ if isinstance(content, bytes):
270
+ content = content.decode('utf-8', errors='ignore')
274
271
  if content and isinstance(content, str):
275
272
  streaming_response += content
276
273
  has_content = True
277
- yield {"text": content} if not raw else content
278
-
279
- # Only update conversation history if we received content
274
+ yield content if raw else {"text": content}
280
275
  if has_content:
281
276
  self.last_response = {"text": streaming_response}
282
277
  self.conversation.update_chat_history(
@@ -286,12 +281,10 @@ class WrDoChat(Provider):
286
281
  raise exceptions.FailedToGenerateResponseError(
287
282
  "No content received from API"
288
283
  )
289
-
290
284
  except CurlError as e:
291
285
  raise exceptions.FailedToGenerateResponseError(f"Request failed (CurlError): {e}")
292
286
  except Exception as e:
293
287
  raise exceptions.FailedToGenerateResponseError(f"An error occurred: {str(e)}")
294
-
295
288
  def for_non_stream():
296
289
  response_text = ""
297
290
  try:
@@ -303,9 +296,7 @@ class WrDoChat(Provider):
303
296
  except Exception as e:
304
297
  if not response_text:
305
298
  raise exceptions.FailedToGenerateResponseError(f"Failed to get response: {str(e)}")
306
-
307
299
  return response_text if raw else {"text": response_text}
308
-
309
300
  return for_stream() if stream else for_non_stream()
310
301
 
311
302
  def chat(
@@ -314,6 +305,7 @@ class WrDoChat(Provider):
314
305
  stream: bool = False,
315
306
  optimizer: str = None,
316
307
  conversationally: bool = False,
308
+ raw: bool = False, # Added raw parameter
317
309
  ) -> Union[str, Generator[str, None, None]]:
318
310
  """
319
311
  Generate a response to a prompt.
@@ -329,20 +321,24 @@ class WrDoChat(Provider):
329
321
  """
330
322
  def for_stream():
331
323
  for response in self.ask(
332
- prompt, True, optimizer=optimizer, conversationally=conversationally
324
+ prompt, True, raw=raw, optimizer=optimizer, conversationally=conversationally
333
325
  ):
334
- yield self.get_message(response)
335
-
326
+ if raw:
327
+ yield response
328
+ else:
329
+ yield self.get_message(response)
336
330
  def for_non_stream():
337
- return self.get_message(
338
- self.ask(
339
- prompt,
340
- False,
341
- optimizer=optimizer,
342
- conversationally=conversationally,
343
- )
331
+ result = self.ask(
332
+ prompt,
333
+ False,
334
+ raw=raw,
335
+ optimizer=optimizer,
336
+ conversationally=conversationally,
344
337
  )
345
-
338
+ if raw:
339
+ return result
340
+ else:
341
+ return self.get_message(result)
346
342
  return for_stream() if stream else for_non_stream()
347
343
 
348
344
  def get_message(self, response: dict) -> str:
@@ -178,13 +178,20 @@ class WritingMate(Provider):
178
178
  data=response.iter_content(chunk_size=None), # Pass byte iterator
179
179
  intro_value=None, # No simple prefix
180
180
  to_json=False, # Content is not JSON
181
- content_extractor=self._writingmate_extractor # Use the specific extractor
181
+ content_extractor=self._writingmate_extractor, # Use the specific extractor
182
+ raw=raw
182
183
  )
183
184
 
184
185
  for content_chunk in processed_stream:
185
- if content_chunk and isinstance(content_chunk, str):
186
- streaming_text += content_chunk
187
- yield content_chunk if raw else dict(text=content_chunk)
186
+ # Always yield as string, even in raw mode
187
+ if isinstance(content_chunk, bytes):
188
+ content_chunk = content_chunk.decode('utf-8', errors='ignore')
189
+ if raw:
190
+ yield content_chunk
191
+ else:
192
+ if content_chunk and isinstance(content_chunk, str):
193
+ streaming_text += content_chunk
194
+ yield dict(text=content_chunk)
188
195
 
189
196
  self.last_response.update(dict(text=streaming_text))
190
197
  self.conversation.update_chat_history(
@@ -196,12 +203,10 @@ class WritingMate(Provider):
196
203
  raise exceptions.FailedToGenerateResponseError(f"An unexpected error occurred ({type(e).__name__}): {e}")
197
204
 
198
205
  def for_non_stream():
199
- # This function implicitly uses the updated for_stream
200
206
  for _ in for_stream():
201
207
  pass
202
208
  return self.last_response
203
209
 
204
- # Ensure stream defaults to True if not provided, matching original behavior
205
210
  effective_stream = stream if stream is not None else True
206
211
  return for_stream() if effective_stream else for_non_stream()
207
212
 
@@ -210,36 +215,35 @@ class WritingMate(Provider):
210
215
  prompt: str,
211
216
  stream: bool = False, # Default stream to False as per original chat method
212
217
  optimizer: str = None,
213
- conversationally: bool = False
218
+ conversationally: bool = False,
219
+ raw: bool = False, # Added raw parameter
214
220
  ) -> Union[str, Generator[str,None,None]]:
215
221
  if stream:
216
- # yield decoded text chunks
217
222
  def text_stream():
218
- # Call ask with stream=True, raw=False to get dicts
219
- for response_dict in self.ask(
220
- prompt, stream=True, raw=False,
223
+ for response in self.ask(
224
+ prompt, stream=True, raw=raw,
221
225
  optimizer=optimizer, conversationally=conversationally
222
226
  ):
223
- # Extract text from dict
224
- yield self.get_message(response_dict)
227
+ if raw:
228
+ yield response
229
+ else:
230
+ yield self.get_message(response)
225
231
  return text_stream()
226
- else: # non‐stream: return aggregated text
227
- # Call ask with stream=False, raw=False
232
+ else:
228
233
  response_data = self.ask(
229
234
  prompt,
230
235
  stream=False,
231
- raw=False,
236
+ raw=raw,
232
237
  optimizer=optimizer,
233
238
  conversationally=conversationally,
234
239
  )
235
- # Ensure response_data is a dict before passing to get_message
240
+ if raw:
241
+ return response_data
236
242
  if isinstance(response_data, dict):
237
- return self.get_message(response_data)
243
+ return self.get_message(response_data)
238
244
  else:
239
- # Handle unexpected generator case if ask(stream=False) behaves differently
240
- # This part might need adjustment based on actual behavior
241
- full_text = "".join(self.get_message(chunk) for chunk in response_data if isinstance(chunk, dict))
242
- return full_text
245
+ full_text = "".join(self.get_message(chunk) for chunk in response_data if isinstance(chunk, dict))
246
+ return full_text
243
247
 
244
248
 
245
249
  def get_message(self, response: dict) -> str:
@@ -68,7 +68,6 @@ from .ExaAI import ExaAI
68
68
  from .OpenGPT import OpenGPT
69
69
  from .scira_chat import *
70
70
  from .StandardInput import *
71
- from .Writecream import Writecream
72
71
  from .toolbaz import Toolbaz
73
72
  from .scnet import SCNet
74
73
  from .WritingMate import WritingMate
@@ -87,8 +86,10 @@ from .XenAI import XenAI
87
86
  from .deepseek_assistant import DeepSeekAssistant
88
87
  from .GeminiProxy import GeminiProxy
89
88
  from .TogetherAI import TogetherAI
89
+ from .MiniMax import MiniMax
90
90
  __all__ = [
91
91
  'SCNet',
92
+ 'MiniMax',
92
93
  'GeminiProxy',
93
94
  'TogetherAI',
94
95
  'oivscode',
@@ -173,7 +174,6 @@ __all__ = [
173
174
  'AskSteve',
174
175
  'Aitopia',
175
176
  'SearchChatAI',
176
- 'Writecream',
177
177
  'Toolbaz',
178
178
  'MCPCore',
179
179
  'TypliAI',
@@ -83,10 +83,12 @@ class IBMGranite(Provider):
83
83
  self.conversation.history_offset = history_offset
84
84
 
85
85
  @staticmethod
86
- def _granite_extractor(chunk: Union[str, Dict[str, Any]]) -> Optional[str]:
87
- """Extracts content from IBM Granite stream JSON lists [3, "text"]."""
88
- if isinstance(chunk, list) and len(chunk) == 2 and chunk[0] == 3 and isinstance(chunk[1], str):
89
- return chunk[1]
86
+ def _granite_extractor(chunk: Union[str, Dict[str, Any], list]) -> Optional[str]:
87
+ """Extracts content from IBM Granite stream JSON lists [6, "text"] or [3, "text"]."""
88
+ # Accept both [3, str] and [6, str] as content chunks
89
+ if isinstance(chunk, list) and len(chunk) == 2 and isinstance(chunk[1], str):
90
+ if chunk[0] in (3, 6):
91
+ return chunk[1]
90
92
  return None
91
93
 
92
94
  @staticmethod
@@ -157,73 +159,60 @@ class IBMGranite(Provider):
157
159
  payload["thinking"] = True
158
160
 
159
161
  def for_stream():
160
- streaming_text = "" # Initialize outside try block
162
+ streaming_text = ""
161
163
  try:
162
- # Use curl_cffi session post with impersonate
163
164
  response = self.session.post(
164
165
  self.api_endpoint,
165
- # headers are set on the session
166
166
  json=payload,
167
167
  stream=True,
168
168
  timeout=self.timeout,
169
- impersonate="chrome110" # Use a common impersonation profile
169
+ impersonate="chrome110"
170
170
  )
171
- response.raise_for_status() # Check for HTTP errors
172
-
173
- # Use sanitize_stream
171
+ response.raise_for_status()
174
172
  processed_stream = sanitize_stream(
175
- data=response.iter_content(chunk_size=None), # Pass byte iterator
176
- intro_value=None, # No prefix
177
- to_json=True, # Stream sends JSON lines (which are lists)
178
- content_extractor=self._granite_extractor, # Use the specific extractor
179
- yield_raw_on_error=False # Skip non-JSON lines or lines where extractor fails
173
+ data=response.iter_content(chunk_size=None),
174
+ intro_value=None,
175
+ to_json=True,
176
+ content_extractor=self._granite_extractor,
177
+ yield_raw_on_error=False,
178
+ raw=raw
180
179
  )
181
-
182
180
  for content_chunk in processed_stream:
183
- # content_chunk is the string extracted by _granite_extractor
184
- if content_chunk and isinstance(content_chunk, str):
185
- streaming_text += content_chunk
186
- resp = dict(text=content_chunk)
187
- yield resp if not raw else content_chunk
188
-
189
- # Update history after stream finishes
181
+ if raw:
182
+ if content_chunk and isinstance(content_chunk, str):
183
+ streaming_text += content_chunk
184
+ yield content_chunk
185
+ else:
186
+ if content_chunk and isinstance(content_chunk, str):
187
+ streaming_text += content_chunk
188
+ resp = dict(text=content_chunk)
189
+ yield resp
190
190
  self.last_response = dict(text=streaming_text)
191
191
  self.conversation.update_chat_history(prompt, streaming_text)
192
-
193
- except CurlError as e: # Catch CurlError
192
+ except CurlError as e:
194
193
  raise exceptions.ProviderConnectionError(f"Request failed (CurlError): {e}") from e
195
- except json.JSONDecodeError as e: # Keep specific JSON error handling
194
+ except json.JSONDecodeError as e:
196
195
  raise exceptions.InvalidResponseError(f"Failed to decode JSON response: {e}") from e
197
- except Exception as e: # Catch other potential exceptions (like HTTPError)
196
+ except Exception as e:
198
197
  err_text = getattr(e, 'response', None) and getattr(e.response, 'text', '')
199
- # Use specific exception type if available, otherwise generic
200
198
  ex_type = exceptions.FailedToGenerateResponseError if not isinstance(e, exceptions.ProviderConnectionError) else type(e)
201
199
  raise ex_type(f"An unexpected error occurred ({type(e).__name__}): {e} - {err_text}") from e
202
200
 
203
-
204
201
  def for_non_stream():
205
- # Aggregate the stream using the updated for_stream logic
206
202
  full_text = ""
207
203
  try:
208
- # Ensure raw=False so for_stream yields dicts
209
204
  for chunk_data in for_stream():
210
- if isinstance(chunk_data, dict) and "text" in chunk_data:
211
- full_text += chunk_data["text"]
212
- # Handle raw string case if raw=True was passed
213
- elif raw and isinstance(chunk_data, str):
214
- full_text += chunk_data
205
+ if raw:
206
+ if isinstance(chunk_data, str):
207
+ full_text += chunk_data
208
+ else:
209
+ if isinstance(chunk_data, dict) and "text" in chunk_data:
210
+ full_text += chunk_data["text"]
215
211
  except Exception as e:
216
- # If aggregation fails but some text was received, use it. Otherwise, re-raise.
217
- if not full_text:
218
- raise exceptions.FailedToGenerateResponseError(f"Failed to get non-stream response: {str(e)}") from e
219
-
220
- # last_response and history are updated within for_stream
221
- # Return the final aggregated response dict or raw string
212
+ if not full_text:
213
+ raise exceptions.FailedToGenerateResponseError(f"Failed to get non-stream response: {str(e)}") from e
222
214
  return full_text if raw else self.last_response
223
215
 
224
-
225
- # Since the API endpoint suggests streaming, always call the stream generator.
226
- # The non-stream wrapper will handle aggregation if stream=False.
227
216
  return for_stream() if stream else for_non_stream()
228
217
 
229
218
  def chat(
@@ -232,25 +221,27 @@ class IBMGranite(Provider):
232
221
  stream: bool = False,
233
222
  optimizer: str = None,
234
223
  conversationally: bool = False,
224
+ raw: bool = False,
235
225
  ) -> Union[str, Generator[str, None, None]]:
236
226
  """Generate response as a string using chat method"""
237
227
  def for_stream_chat():
238
- # ask() yields dicts or strings when streaming
239
228
  gen = self.ask(
240
- prompt, stream=True, raw=False, # Ensure ask yields dicts
229
+ prompt, stream=True, raw=raw,
241
230
  optimizer=optimizer, conversationally=conversationally
242
231
  )
243
- for response_dict in gen:
244
- yield self.get_message(response_dict) # get_message expects dict
245
-
232
+ for response in gen:
233
+ if raw:
234
+ yield response
235
+ else:
236
+ yield self.get_message(response)
246
237
  def for_non_stream_chat():
247
- # ask() returns dict or str when not streaming
248
238
  response_data = self.ask(
249
- prompt, stream=False, raw=False, # Ensure ask returns dict
239
+ prompt, stream=False, raw=raw,
250
240
  optimizer=optimizer, conversationally=conversationally
251
241
  )
252
- return self.get_message(response_data) # get_message expects dict
253
-
242
+ if raw:
243
+ return response_data if isinstance(response_data, str) else str(response_data)
244
+ return self.get_message(response_data)
254
245
  return for_stream_chat() if stream else for_non_stream_chat()
255
246
 
256
247
  def get_message(self, response: dict) -> str:
@@ -265,6 +256,6 @@ if __name__ == "__main__":
265
256
  ai = IBMGranite(
266
257
  thinking=True,
267
258
  )
268
- response = ai.chat("How many r in strawberry", stream=True)
259
+ response = ai.chat("How many r in strawberry", stream=True, raw=False)
269
260
  for chunk in response:
270
- print(chunk, end="", flush=True)
261
+ print(chunk, end="", flush=True) # Print each chunk without newline