webscout 8.2.6__py3-none-any.whl → 8.2.8__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 (150) hide show
  1. webscout/AIauto.py +1 -1
  2. webscout/AIutel.py +298 -239
  3. webscout/Extra/Act.md +309 -0
  4. webscout/Extra/GitToolkit/gitapi/README.md +110 -0
  5. webscout/Extra/YTToolkit/README.md +375 -0
  6. webscout/Extra/YTToolkit/ytapi/README.md +44 -0
  7. webscout/Extra/YTToolkit/ytapi/extras.py +92 -19
  8. webscout/Extra/autocoder/autocoder.py +309 -114
  9. webscout/Extra/autocoder/autocoder_utiles.py +15 -15
  10. webscout/Extra/gguf.md +430 -0
  11. webscout/Extra/tempmail/README.md +488 -0
  12. webscout/Extra/weather.md +281 -0
  13. webscout/Litlogger/Readme.md +175 -0
  14. webscout/Provider/AISEARCH/DeepFind.py +41 -37
  15. webscout/Provider/AISEARCH/README.md +279 -0
  16. webscout/Provider/AISEARCH/__init__.py +0 -1
  17. webscout/Provider/AISEARCH/genspark_search.py +228 -86
  18. webscout/Provider/AISEARCH/hika_search.py +11 -11
  19. webscout/Provider/AISEARCH/scira_search.py +324 -322
  20. webscout/Provider/AllenAI.py +7 -14
  21. webscout/Provider/Blackboxai.py +518 -74
  22. webscout/Provider/Cloudflare.py +0 -1
  23. webscout/Provider/Deepinfra.py +23 -21
  24. webscout/Provider/Flowith.py +217 -0
  25. webscout/Provider/FreeGemini.py +250 -0
  26. webscout/Provider/GizAI.py +15 -5
  27. webscout/Provider/Glider.py +11 -8
  28. webscout/Provider/HeckAI.py +80 -52
  29. webscout/Provider/Koboldai.py +7 -4
  30. webscout/Provider/LambdaChat.py +2 -2
  31. webscout/Provider/Marcus.py +10 -18
  32. webscout/Provider/OPENAI/BLACKBOXAI.py +735 -0
  33. webscout/Provider/OPENAI/Cloudflare.py +378 -0
  34. webscout/Provider/OPENAI/FreeGemini.py +282 -0
  35. webscout/Provider/OPENAI/NEMOTRON.py +244 -0
  36. webscout/Provider/OPENAI/README.md +1253 -0
  37. webscout/Provider/OPENAI/__init__.py +8 -0
  38. webscout/Provider/OPENAI/ai4chat.py +293 -286
  39. webscout/Provider/OPENAI/api.py +810 -0
  40. webscout/Provider/OPENAI/base.py +217 -14
  41. webscout/Provider/OPENAI/c4ai.py +373 -367
  42. webscout/Provider/OPENAI/chatgpt.py +7 -0
  43. webscout/Provider/OPENAI/chatgptclone.py +7 -0
  44. webscout/Provider/OPENAI/chatsandbox.py +172 -0
  45. webscout/Provider/OPENAI/deepinfra.py +30 -20
  46. webscout/Provider/OPENAI/e2b.py +6 -0
  47. webscout/Provider/OPENAI/exaai.py +7 -0
  48. webscout/Provider/OPENAI/exachat.py +6 -0
  49. webscout/Provider/OPENAI/flowith.py +162 -0
  50. webscout/Provider/OPENAI/freeaichat.py +359 -352
  51. webscout/Provider/OPENAI/glider.py +323 -316
  52. webscout/Provider/OPENAI/groq.py +361 -354
  53. webscout/Provider/OPENAI/heckai.py +30 -64
  54. webscout/Provider/OPENAI/llmchatco.py +8 -0
  55. webscout/Provider/OPENAI/mcpcore.py +7 -0
  56. webscout/Provider/OPENAI/multichat.py +8 -0
  57. webscout/Provider/OPENAI/netwrck.py +356 -350
  58. webscout/Provider/OPENAI/opkfc.py +8 -0
  59. webscout/Provider/OPENAI/scirachat.py +471 -462
  60. webscout/Provider/OPENAI/sonus.py +9 -0
  61. webscout/Provider/OPENAI/standardinput.py +9 -1
  62. webscout/Provider/OPENAI/textpollinations.py +339 -329
  63. webscout/Provider/OPENAI/toolbaz.py +7 -0
  64. webscout/Provider/OPENAI/typefully.py +355 -0
  65. webscout/Provider/OPENAI/typegpt.py +358 -346
  66. webscout/Provider/OPENAI/uncovrAI.py +7 -0
  67. webscout/Provider/OPENAI/utils.py +103 -7
  68. webscout/Provider/OPENAI/venice.py +12 -0
  69. webscout/Provider/OPENAI/wisecat.py +19 -19
  70. webscout/Provider/OPENAI/writecream.py +7 -0
  71. webscout/Provider/OPENAI/x0gpt.py +7 -0
  72. webscout/Provider/OPENAI/yep.py +50 -21
  73. webscout/Provider/OpenGPT.py +1 -1
  74. webscout/Provider/TTI/AiForce/README.md +159 -0
  75. webscout/Provider/TTI/FreeAIPlayground/README.md +99 -0
  76. webscout/Provider/TTI/ImgSys/README.md +174 -0
  77. webscout/Provider/TTI/MagicStudio/README.md +101 -0
  78. webscout/Provider/TTI/Nexra/README.md +155 -0
  79. webscout/Provider/TTI/PollinationsAI/README.md +146 -0
  80. webscout/Provider/TTI/README.md +128 -0
  81. webscout/Provider/TTI/aiarta/README.md +134 -0
  82. webscout/Provider/TTI/artbit/README.md +100 -0
  83. webscout/Provider/TTI/fastflux/README.md +129 -0
  84. webscout/Provider/TTI/huggingface/README.md +114 -0
  85. webscout/Provider/TTI/piclumen/README.md +161 -0
  86. webscout/Provider/TTI/pixelmuse/README.md +79 -0
  87. webscout/Provider/TTI/talkai/README.md +139 -0
  88. webscout/Provider/TTS/README.md +192 -0
  89. webscout/Provider/TTS/__init__.py +2 -1
  90. webscout/Provider/TTS/speechma.py +500 -100
  91. webscout/Provider/TTS/sthir.py +94 -0
  92. webscout/Provider/TeachAnything.py +3 -7
  93. webscout/Provider/TextPollinationsAI.py +4 -2
  94. webscout/Provider/{aimathgpt.py → UNFINISHED/ChatHub.py} +88 -68
  95. webscout/Provider/UNFINISHED/liner_api_request.py +263 -0
  96. webscout/Provider/UNFINISHED/oivscode.py +351 -0
  97. webscout/Provider/UNFINISHED/test_lmarena.py +119 -0
  98. webscout/Provider/Writecream.py +11 -2
  99. webscout/Provider/__init__.py +8 -14
  100. webscout/Provider/ai4chat.py +4 -58
  101. webscout/Provider/asksteve.py +17 -9
  102. webscout/Provider/cerebras.py +3 -1
  103. webscout/Provider/koala.py +170 -268
  104. webscout/Provider/llmchat.py +3 -0
  105. webscout/Provider/lmarena.py +198 -0
  106. webscout/Provider/meta.py +7 -4
  107. webscout/Provider/samurai.py +223 -0
  108. webscout/Provider/scira_chat.py +4 -2
  109. webscout/Provider/typefully.py +23 -151
  110. webscout/__init__.py +4 -2
  111. webscout/cli.py +3 -28
  112. webscout/conversation.py +35 -35
  113. webscout/litagent/Readme.md +276 -0
  114. webscout/scout/README.md +402 -0
  115. webscout/swiftcli/Readme.md +323 -0
  116. webscout/version.py +1 -1
  117. webscout/webscout_search.py +2 -182
  118. webscout/webscout_search_async.py +1 -179
  119. webscout/zeroart/README.md +89 -0
  120. webscout/zeroart/__init__.py +134 -54
  121. webscout/zeroart/base.py +19 -13
  122. webscout/zeroart/effects.py +101 -99
  123. webscout/zeroart/fonts.py +1239 -816
  124. {webscout-8.2.6.dist-info → webscout-8.2.8.dist-info}/METADATA +116 -74
  125. {webscout-8.2.6.dist-info → webscout-8.2.8.dist-info}/RECORD +130 -103
  126. {webscout-8.2.6.dist-info → webscout-8.2.8.dist-info}/WHEEL +1 -1
  127. webscout-8.2.8.dist-info/entry_points.txt +3 -0
  128. webscout-8.2.8.dist-info/top_level.txt +1 -0
  129. webscout/Provider/AISEARCH/ISou.py +0 -256
  130. webscout/Provider/ElectronHub.py +0 -773
  131. webscout/Provider/Free2GPT.py +0 -241
  132. webscout/Provider/GPTWeb.py +0 -249
  133. webscout/Provider/bagoodex.py +0 -145
  134. webscout/Provider/geminiprorealtime.py +0 -160
  135. webscout/scout/core.py +0 -881
  136. webscout-8.2.6.dist-info/entry_points.txt +0 -3
  137. webscout-8.2.6.dist-info/top_level.txt +0 -2
  138. webstoken/__init__.py +0 -30
  139. webstoken/classifier.py +0 -189
  140. webstoken/keywords.py +0 -216
  141. webstoken/language.py +0 -128
  142. webstoken/ner.py +0 -164
  143. webstoken/normalizer.py +0 -35
  144. webstoken/processor.py +0 -77
  145. webstoken/sentiment.py +0 -206
  146. webstoken/stemmer.py +0 -73
  147. webstoken/tagger.py +0 -60
  148. webstoken/tokenizer.py +0 -158
  149. /webscout/Provider/{Youchat.py → UNFINISHED/Youchat.py} +0 -0
  150. {webscout-8.2.6.dist-info → webscout-8.2.8.dist-info}/licenses/LICENSE.md +0 -0
@@ -1,354 +1,361 @@
1
- import requests
2
- import json
3
- import time
4
- import uuid
5
- from typing import List, Dict, Optional, Union, Generator, Any
6
-
7
- # Import curl_cffi for improved request handling
8
- from curl_cffi.requests import Session
9
- from curl_cffi import CurlError
10
-
11
- # Import base classes and utility structures
12
- from .base import OpenAICompatibleProvider, BaseChat, BaseCompletions
13
- from .utils import (
14
- ChatCompletionChunk, ChatCompletion, Choice, ChoiceDelta,
15
- ChatCompletionMessage, CompletionUsage
16
- )
17
-
18
- # Attempt to import LitAgent, fallback if not available
19
- try:
20
- from webscout.litagent import LitAgent
21
- except ImportError:
22
- pass
23
-
24
- # --- Groq Client ---
25
-
26
- class Completions(BaseCompletions):
27
- def __init__(self, client: 'Groq'):
28
- self._client = client
29
-
30
- def create(
31
- self,
32
- *,
33
- model: str,
34
- messages: List[Dict[str, str]],
35
- max_tokens: Optional[int] = 2049,
36
- stream: bool = False,
37
- temperature: Optional[float] = None,
38
- top_p: Optional[float] = None,
39
- **kwargs: Any
40
- ) -> Union[ChatCompletion, Generator[ChatCompletionChunk, None, None]]:
41
- """
42
- Creates a model response for the given chat conversation.
43
- Mimics openai.chat.completions.create
44
- """
45
- payload = {
46
- "model": model,
47
- "messages": messages,
48
- "max_tokens": max_tokens,
49
- "stream": stream,
50
- }
51
- if temperature is not None:
52
- payload["temperature"] = temperature
53
- if top_p is not None:
54
- payload["top_p"] = top_p
55
-
56
- # Add frequency_penalty and presence_penalty if provided
57
- if "frequency_penalty" in kwargs:
58
- payload["frequency_penalty"] = kwargs.pop("frequency_penalty")
59
- if "presence_penalty" in kwargs:
60
- payload["presence_penalty"] = kwargs.pop("presence_penalty")
61
-
62
- # Add any tools if provided
63
- if "tools" in kwargs and kwargs["tools"]:
64
- payload["tools"] = kwargs.pop("tools")
65
-
66
- payload.update(kwargs)
67
-
68
- request_id = f"chatcmpl-{uuid.uuid4()}"
69
- created_time = int(time.time())
70
-
71
- if stream:
72
- return self._create_stream(request_id, created_time, model, payload)
73
- else:
74
- return self._create_non_stream(request_id, created_time, model, payload)
75
-
76
- def _create_stream(
77
- self, request_id: str, created_time: int, model: str, payload: Dict[str, Any]
78
- ) -> Generator[ChatCompletionChunk, None, None]:
79
- try:
80
- response = self._client.session.post(
81
- self._client.base_url,
82
- json=payload,
83
- stream=True,
84
- timeout=self._client.timeout,
85
- impersonate="chrome110" # Use impersonate for better compatibility
86
- )
87
-
88
- if response.status_code != 200:
89
- raise IOError(f"Groq request failed with status code {response.status_code}: {response.text}")
90
-
91
- # Track token usage across chunks
92
- prompt_tokens = 0
93
- completion_tokens = 0
94
- total_tokens = 0
95
-
96
- for line in response.iter_lines(decode_unicode=True):
97
- if line:
98
- if line.startswith("data: "):
99
- json_str = line[6:]
100
- if json_str == "[DONE]":
101
- break
102
-
103
- try:
104
- data = json.loads(json_str)
105
- choice_data = data.get('choices', [{}])[0]
106
- delta_data = choice_data.get('delta', {})
107
- finish_reason = choice_data.get('finish_reason')
108
-
109
- # Update token counts if available
110
- usage_data = data.get('usage', {})
111
- if usage_data:
112
- prompt_tokens = usage_data.get('prompt_tokens', prompt_tokens)
113
- completion_tokens = usage_data.get('completion_tokens', completion_tokens)
114
- total_tokens = usage_data.get('total_tokens', total_tokens)
115
-
116
- # Create the delta object
117
- delta = ChoiceDelta(
118
- content=delta_data.get('content'),
119
- role=delta_data.get('role'),
120
- tool_calls=delta_data.get('tool_calls')
121
- )
122
-
123
- # Create the choice object
124
- choice = Choice(
125
- index=choice_data.get('index', 0),
126
- delta=delta,
127
- finish_reason=finish_reason,
128
- logprobs=choice_data.get('logprobs')
129
- )
130
-
131
- # Create the chunk object
132
- chunk = ChatCompletionChunk(
133
- id=request_id,
134
- choices=[choice],
135
- created=created_time,
136
- model=model,
137
- system_fingerprint=data.get('system_fingerprint')
138
- )
139
-
140
- # Convert to dict for proper formatting
141
- chunk_dict = chunk.to_dict()
142
-
143
- # Add usage information to match OpenAI format
144
- usage_dict = {
145
- "prompt_tokens": prompt_tokens or 10,
146
- "completion_tokens": completion_tokens or (len(delta_data.get('content', '')) if delta_data.get('content') else 0),
147
- "total_tokens": total_tokens or (10 + (len(delta_data.get('content', '')) if delta_data.get('content') else 0)),
148
- "estimated_cost": None
149
- }
150
-
151
- # Update completion_tokens and total_tokens as we receive more content
152
- if delta_data.get('content'):
153
- completion_tokens += 1
154
- total_tokens = prompt_tokens + completion_tokens
155
- usage_dict["completion_tokens"] = completion_tokens
156
- usage_dict["total_tokens"] = total_tokens
157
-
158
- chunk_dict["usage"] = usage_dict
159
-
160
- yield chunk
161
- except json.JSONDecodeError:
162
- print(f"Warning: Could not decode JSON line: {json_str}")
163
- continue
164
- except CurlError as e:
165
- print(f"Error during Groq stream request: {e}")
166
- raise IOError(f"Groq request failed: {e}") from e
167
- except Exception as e:
168
- print(f"Error processing Groq stream: {e}")
169
- raise
170
-
171
- def _create_non_stream(
172
- self, request_id: str, created_time: int, model: str, payload: Dict[str, Any]
173
- ) -> ChatCompletion:
174
- try:
175
- response = self._client.session.post(
176
- self._client.base_url,
177
- json=payload,
178
- timeout=self._client.timeout,
179
- impersonate="chrome110" # Use impersonate for better compatibility
180
- )
181
-
182
- if response.status_code != 200:
183
- raise IOError(f"Groq request failed with status code {response.status_code}: {response.text}")
184
-
185
- data = response.json()
186
-
187
- choices_data = data.get('choices', [])
188
- usage_data = data.get('usage', {})
189
-
190
- choices = []
191
- for choice_d in choices_data:
192
- message_d = choice_d.get('message', {})
193
-
194
- # Handle tool calls if present
195
- tool_calls = message_d.get('tool_calls')
196
-
197
- message = ChatCompletionMessage(
198
- role=message_d.get('role', 'assistant'),
199
- content=message_d.get('content', ''),
200
- tool_calls=tool_calls
201
- )
202
- choice = Choice(
203
- index=choice_d.get('index', 0),
204
- message=message,
205
- finish_reason=choice_d.get('finish_reason', 'stop')
206
- )
207
- choices.append(choice)
208
-
209
- usage = CompletionUsage(
210
- prompt_tokens=usage_data.get('prompt_tokens', 0),
211
- completion_tokens=usage_data.get('completion_tokens', 0),
212
- total_tokens=usage_data.get('total_tokens', 0)
213
- )
214
-
215
- completion = ChatCompletion(
216
- id=request_id,
217
- choices=choices,
218
- created=created_time,
219
- model=data.get('model', model),
220
- usage=usage,
221
- )
222
- return completion
223
-
224
- except CurlError as e:
225
- print(f"Error during Groq non-stream request: {e}")
226
- raise IOError(f"Groq request failed: {e}") from e
227
- except Exception as e:
228
- print(f"Error processing Groq response: {e}")
229
- raise
230
-
231
- class Chat(BaseChat):
232
- def __init__(self, client: 'Groq'):
233
- self.completions = Completions(client)
234
-
235
- class Groq(OpenAICompatibleProvider):
236
- AVAILABLE_MODELS = [
237
- "distil-whisper-large-v3-en",
238
- "gemma2-9b-it",
239
- "llama-3.3-70b-versatile",
240
- "llama-3.1-8b-instant",
241
- "llama-guard-3-8b",
242
- "llama3-70b-8192",
243
- "llama3-8b-8192",
244
- "whisper-large-v3",
245
- "whisper-large-v3-turbo",
246
- "meta-llama/llama-4-scout-17b-16e-instruct",
247
- "meta-llama/llama-4-maverick-17b-128e-instruct",
248
- "playai-tts",
249
- "playai-tts-arabic",
250
- "qwen-qwq-32b",
251
- "mistral-saba-24b",
252
- "qwen-2.5-coder-32b",
253
- "qwen-2.5-32b",
254
- "deepseek-r1-distill-qwen-32b",
255
- "deepseek-r1-distill-llama-70b",
256
- "llama-3.3-70b-specdec",
257
- "llama-3.2-1b-preview",
258
- "llama-3.2-3b-preview",
259
- "llama-3.2-11b-vision-preview",
260
- "llama-3.2-90b-vision-preview",
261
- "mixtral-8x7b-32768"
262
- ]
263
-
264
- def __init__(self, api_key: str = None, timeout: Optional[int] = 30, browser: str = "chrome"):
265
- self.timeout = timeout
266
- self.base_url = "https://api.groq.com/openai/v1/chat/completions"
267
- self.api_key = api_key
268
-
269
- # Initialize curl_cffi Session
270
- self.session = Session()
271
-
272
- # Set up headers with API key if provided
273
- self.headers = {
274
- "Content-Type": "application/json",
275
- }
276
-
277
- if api_key:
278
- self.headers["Authorization"] = f"Bearer {api_key}"
279
-
280
- # Try to use LitAgent for browser fingerprinting
281
- try:
282
- agent = LitAgent()
283
- fingerprint = agent.generate_fingerprint(browser)
284
-
285
- self.headers.update({
286
- "Accept": fingerprint["accept"],
287
- "Accept-Encoding": "gzip, deflate, br, zstd",
288
- "Accept-Language": fingerprint["accept_language"],
289
- "Cache-Control": "no-cache",
290
- "Connection": "keep-alive",
291
- "Origin": "https://console.groq.com",
292
- "Pragma": "no-cache",
293
- "Referer": "https://console.groq.com/",
294
- "Sec-Fetch-Dest": "empty",
295
- "Sec-Fetch-Mode": "cors",
296
- "Sec-Fetch-Site": "same-site",
297
- "Sec-CH-UA": fingerprint["sec_ch_ua"] or '"Not)A;Brand";v="99", "Microsoft Edge";v="127", "Chromium";v="127"',
298
- "Sec-CH-UA-Mobile": "?0",
299
- "Sec-CH-UA-Platform": f'"{fingerprint["platform"]}"',
300
- "User-Agent": fingerprint["user_agent"],
301
- })
302
- except (NameError, Exception):
303
- # Fallback to basic headers if LitAgent is not available
304
- self.headers.update({
305
- "Accept": "application/json",
306
- "Accept-Encoding": "gzip, deflate, br",
307
- "Accept-Language": "en-US,en;q=0.9",
308
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
309
- })
310
-
311
- # Update session headers
312
- self.session.headers.update(self.headers)
313
-
314
- # Initialize chat interface
315
- self.chat = Chat(self)
316
-
317
- @classmethod
318
- def get_models(cls, api_key: str = None):
319
- """Fetch available models from Groq API.
320
-
321
- Args:
322
- api_key (str, optional): Groq API key. If not provided, returns default models.
323
-
324
- Returns:
325
- list: List of available model IDs
326
- """
327
- if not api_key:
328
- return cls.AVAILABLE_MODELS
329
-
330
- try:
331
- # Use a temporary curl_cffi session for this class method
332
- temp_session = Session()
333
- headers = {
334
- "Content-Type": "application/json",
335
- "Authorization": f"Bearer {api_key}",
336
- }
337
-
338
- response = temp_session.get(
339
- "https://api.groq.com/openai/v1/models",
340
- headers=headers,
341
- impersonate="chrome110" # Use impersonate for fetching
342
- )
343
-
344
- if response.status_code != 200:
345
- return cls.AVAILABLE_MODELS
346
-
347
- data = response.json()
348
- if "data" in data and isinstance(data["data"], list):
349
- return [model["id"] for model in data["data"]]
350
- return cls.AVAILABLE_MODELS
351
-
352
- except (CurlError, Exception):
353
- # Fallback to default models list if fetching fails
354
- return cls.AVAILABLE_MODELS
1
+ import requests
2
+ import json
3
+ import time
4
+ import uuid
5
+ from typing import List, Dict, Optional, Union, Generator, Any
6
+
7
+ # Import curl_cffi for improved request handling
8
+ from curl_cffi.requests import Session
9
+ from curl_cffi import CurlError
10
+
11
+ # Import base classes and utility structures
12
+ from .base import OpenAICompatibleProvider, BaseChat, BaseCompletions
13
+ from .utils import (
14
+ ChatCompletionChunk, ChatCompletion, Choice, ChoiceDelta,
15
+ ChatCompletionMessage, CompletionUsage
16
+ )
17
+
18
+ # Attempt to import LitAgent, fallback if not available
19
+ try:
20
+ from webscout.litagent import LitAgent
21
+ except ImportError:
22
+ pass
23
+
24
+ # --- Groq Client ---
25
+
26
+ class Completions(BaseCompletions):
27
+ def __init__(self, client: 'Groq'):
28
+ self._client = client
29
+
30
+ def create(
31
+ self,
32
+ *,
33
+ model: str,
34
+ messages: List[Dict[str, str]],
35
+ max_tokens: Optional[int] = 2049,
36
+ stream: bool = False,
37
+ temperature: Optional[float] = None,
38
+ top_p: Optional[float] = None,
39
+ **kwargs: Any
40
+ ) -> Union[ChatCompletion, Generator[ChatCompletionChunk, None, None]]:
41
+ """
42
+ Creates a model response for the given chat conversation.
43
+ Mimics openai.chat.completions.create
44
+ """
45
+ payload = {
46
+ "model": model,
47
+ "messages": messages,
48
+ "max_tokens": max_tokens,
49
+ "stream": stream,
50
+ }
51
+ if temperature is not None:
52
+ payload["temperature"] = temperature
53
+ if top_p is not None:
54
+ payload["top_p"] = top_p
55
+
56
+ # Add frequency_penalty and presence_penalty if provided
57
+ if "frequency_penalty" in kwargs:
58
+ payload["frequency_penalty"] = kwargs.pop("frequency_penalty")
59
+ if "presence_penalty" in kwargs:
60
+ payload["presence_penalty"] = kwargs.pop("presence_penalty")
61
+
62
+ # Add any tools if provided
63
+ if "tools" in kwargs and kwargs["tools"]:
64
+ payload["tools"] = kwargs.pop("tools")
65
+
66
+ payload.update(kwargs)
67
+
68
+ request_id = f"chatcmpl-{uuid.uuid4()}"
69
+ created_time = int(time.time())
70
+
71
+ if stream:
72
+ return self._create_stream(request_id, created_time, model, payload)
73
+ else:
74
+ return self._create_non_stream(request_id, created_time, model, payload)
75
+
76
+ def _create_stream(
77
+ self, request_id: str, created_time: int, model: str, payload: Dict[str, Any]
78
+ ) -> Generator[ChatCompletionChunk, None, None]:
79
+ try:
80
+ response = self._client.session.post(
81
+ self._client.base_url,
82
+ json=payload,
83
+ stream=True,
84
+ timeout=self._client.timeout,
85
+ impersonate="chrome110" # Use impersonate for better compatibility
86
+ )
87
+
88
+ if response.status_code != 200:
89
+ raise IOError(f"Groq request failed with status code {response.status_code}: {response.text}")
90
+
91
+ # Track token usage across chunks
92
+ prompt_tokens = 0
93
+ completion_tokens = 0
94
+ total_tokens = 0
95
+
96
+ for line in response.iter_lines(decode_unicode=True):
97
+ if line:
98
+ if line.startswith("data: "):
99
+ json_str = line[6:]
100
+ if json_str == "[DONE]":
101
+ break
102
+
103
+ try:
104
+ data = json.loads(json_str)
105
+ choice_data = data.get('choices', [{}])[0]
106
+ delta_data = choice_data.get('delta', {})
107
+ finish_reason = choice_data.get('finish_reason')
108
+
109
+ # Update token counts if available
110
+ usage_data = data.get('usage', {})
111
+ if usage_data:
112
+ prompt_tokens = usage_data.get('prompt_tokens', prompt_tokens)
113
+ completion_tokens = usage_data.get('completion_tokens', completion_tokens)
114
+ total_tokens = usage_data.get('total_tokens', total_tokens)
115
+
116
+ # Create the delta object
117
+ delta = ChoiceDelta(
118
+ content=delta_data.get('content'),
119
+ role=delta_data.get('role'),
120
+ tool_calls=delta_data.get('tool_calls')
121
+ )
122
+
123
+ # Create the choice object
124
+ choice = Choice(
125
+ index=choice_data.get('index', 0),
126
+ delta=delta,
127
+ finish_reason=finish_reason,
128
+ logprobs=choice_data.get('logprobs')
129
+ )
130
+
131
+ # Create the chunk object
132
+ chunk = ChatCompletionChunk(
133
+ id=request_id,
134
+ choices=[choice],
135
+ created=created_time,
136
+ model=model,
137
+ system_fingerprint=data.get('system_fingerprint')
138
+ )
139
+
140
+ # Convert to dict for proper formatting
141
+ chunk_dict = chunk.to_dict()
142
+
143
+ # Add usage information to match OpenAI format
144
+ usage_dict = {
145
+ "prompt_tokens": prompt_tokens or 10,
146
+ "completion_tokens": completion_tokens or (len(delta_data.get('content', '')) if delta_data.get('content') else 0),
147
+ "total_tokens": total_tokens or (10 + (len(delta_data.get('content', '')) if delta_data.get('content') else 0)),
148
+ "estimated_cost": None
149
+ }
150
+
151
+ # Update completion_tokens and total_tokens as we receive more content
152
+ if delta_data.get('content'):
153
+ completion_tokens += 1
154
+ total_tokens = prompt_tokens + completion_tokens
155
+ usage_dict["completion_tokens"] = completion_tokens
156
+ usage_dict["total_tokens"] = total_tokens
157
+
158
+ chunk_dict["usage"] = usage_dict
159
+
160
+ yield chunk
161
+ except json.JSONDecodeError:
162
+ print(f"Warning: Could not decode JSON line: {json_str}")
163
+ continue
164
+ except CurlError as e:
165
+ print(f"Error during Groq stream request: {e}")
166
+ raise IOError(f"Groq request failed: {e}") from e
167
+ except Exception as e:
168
+ print(f"Error processing Groq stream: {e}")
169
+ raise
170
+
171
+ def _create_non_stream(
172
+ self, request_id: str, created_time: int, model: str, payload: Dict[str, Any]
173
+ ) -> ChatCompletion:
174
+ try:
175
+ response = self._client.session.post(
176
+ self._client.base_url,
177
+ json=payload,
178
+ timeout=self._client.timeout,
179
+ impersonate="chrome110" # Use impersonate for better compatibility
180
+ )
181
+
182
+ if response.status_code != 200:
183
+ raise IOError(f"Groq request failed with status code {response.status_code}: {response.text}")
184
+
185
+ data = response.json()
186
+
187
+ choices_data = data.get('choices', [])
188
+ usage_data = data.get('usage', {})
189
+
190
+ choices = []
191
+ for choice_d in choices_data:
192
+ message_d = choice_d.get('message', {})
193
+
194
+ # Handle tool calls if present
195
+ tool_calls = message_d.get('tool_calls')
196
+
197
+ message = ChatCompletionMessage(
198
+ role=message_d.get('role', 'assistant'),
199
+ content=message_d.get('content', ''),
200
+ tool_calls=tool_calls
201
+ )
202
+ choice = Choice(
203
+ index=choice_d.get('index', 0),
204
+ message=message,
205
+ finish_reason=choice_d.get('finish_reason', 'stop')
206
+ )
207
+ choices.append(choice)
208
+
209
+ usage = CompletionUsage(
210
+ prompt_tokens=usage_data.get('prompt_tokens', 0),
211
+ completion_tokens=usage_data.get('completion_tokens', 0),
212
+ total_tokens=usage_data.get('total_tokens', 0)
213
+ )
214
+
215
+ completion = ChatCompletion(
216
+ id=request_id,
217
+ choices=choices,
218
+ created=created_time,
219
+ model=data.get('model', model),
220
+ usage=usage,
221
+ )
222
+ return completion
223
+
224
+ except CurlError as e:
225
+ print(f"Error during Groq non-stream request: {e}")
226
+ raise IOError(f"Groq request failed: {e}") from e
227
+ except Exception as e:
228
+ print(f"Error processing Groq response: {e}")
229
+ raise
230
+
231
+ class Chat(BaseChat):
232
+ def __init__(self, client: 'Groq'):
233
+ self.completions = Completions(client)
234
+
235
+ class Groq(OpenAICompatibleProvider):
236
+ AVAILABLE_MODELS = [
237
+ "distil-whisper-large-v3-en",
238
+ "gemma2-9b-it",
239
+ "llama-3.3-70b-versatile",
240
+ "llama-3.1-8b-instant",
241
+ "llama-guard-3-8b",
242
+ "llama3-70b-8192",
243
+ "llama3-8b-8192",
244
+ "whisper-large-v3",
245
+ "whisper-large-v3-turbo",
246
+ "meta-llama/llama-4-scout-17b-16e-instruct",
247
+ "meta-llama/llama-4-maverick-17b-128e-instruct",
248
+ "playai-tts",
249
+ "playai-tts-arabic",
250
+ "qwen-qwq-32b",
251
+ "mistral-saba-24b",
252
+ "qwen-2.5-coder-32b",
253
+ "qwen-2.5-32b",
254
+ "deepseek-r1-distill-qwen-32b",
255
+ "deepseek-r1-distill-llama-70b",
256
+ "llama-3.3-70b-specdec",
257
+ "llama-3.2-1b-preview",
258
+ "llama-3.2-3b-preview",
259
+ "llama-3.2-11b-vision-preview",
260
+ "llama-3.2-90b-vision-preview",
261
+ "mixtral-8x7b-32768"
262
+ ]
263
+
264
+ def __init__(self, api_key: str = None, timeout: Optional[int] = 30, browser: str = "chrome"):
265
+ self.timeout = timeout
266
+ self.base_url = "https://api.groq.com/openai/v1/chat/completions"
267
+ self.api_key = api_key
268
+
269
+ # Initialize curl_cffi Session
270
+ self.session = Session()
271
+
272
+ # Set up headers with API key if provided
273
+ self.headers = {
274
+ "Content-Type": "application/json",
275
+ }
276
+
277
+ if api_key:
278
+ self.headers["Authorization"] = f"Bearer {api_key}"
279
+
280
+ # Try to use LitAgent for browser fingerprinting
281
+ try:
282
+ agent = LitAgent()
283
+ fingerprint = agent.generate_fingerprint(browser)
284
+
285
+ self.headers.update({
286
+ "Accept": fingerprint["accept"],
287
+ "Accept-Encoding": "gzip, deflate, br, zstd",
288
+ "Accept-Language": fingerprint["accept_language"],
289
+ "Cache-Control": "no-cache",
290
+ "Connection": "keep-alive",
291
+ "Origin": "https://console.groq.com",
292
+ "Pragma": "no-cache",
293
+ "Referer": "https://console.groq.com/",
294
+ "Sec-Fetch-Dest": "empty",
295
+ "Sec-Fetch-Mode": "cors",
296
+ "Sec-Fetch-Site": "same-site",
297
+ "Sec-CH-UA": fingerprint["sec_ch_ua"] or '"Not)A;Brand";v="99", "Microsoft Edge";v="127", "Chromium";v="127"',
298
+ "Sec-CH-UA-Mobile": "?0",
299
+ "Sec-CH-UA-Platform": f'"{fingerprint["platform"]}"',
300
+ "User-Agent": fingerprint["user_agent"],
301
+ })
302
+ except (NameError, Exception):
303
+ # Fallback to basic headers if LitAgent is not available
304
+ self.headers.update({
305
+ "Accept": "application/json",
306
+ "Accept-Encoding": "gzip, deflate, br",
307
+ "Accept-Language": "en-US,en;q=0.9",
308
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
309
+ })
310
+
311
+ # Update session headers
312
+ self.session.headers.update(self.headers)
313
+
314
+ # Initialize chat interface
315
+ self.chat = Chat(self)
316
+
317
+ @classmethod
318
+ def get_models(cls, api_key: str = None):
319
+ """Fetch available models from Groq API.
320
+
321
+ Args:
322
+ api_key (str, optional): Groq API key. If not provided, returns default models.
323
+
324
+ Returns:
325
+ list: List of available model IDs
326
+ """
327
+ if not api_key:
328
+ return cls.AVAILABLE_MODELS
329
+
330
+ try:
331
+ # Use a temporary curl_cffi session for this class method
332
+ temp_session = Session()
333
+ headers = {
334
+ "Content-Type": "application/json",
335
+ "Authorization": f"Bearer {api_key}",
336
+ }
337
+
338
+ response = temp_session.get(
339
+ "https://api.groq.com/openai/v1/models",
340
+ headers=headers,
341
+ impersonate="chrome110" # Use impersonate for fetching
342
+ )
343
+
344
+ if response.status_code != 200:
345
+ return cls.AVAILABLE_MODELS
346
+
347
+ data = response.json()
348
+ if "data" in data and isinstance(data["data"], list):
349
+ return [model["id"] for model in data["data"]]
350
+ return cls.AVAILABLE_MODELS
351
+
352
+ except (CurlError, Exception):
353
+ # Fallback to default models list if fetching fails
354
+ return cls.AVAILABLE_MODELS
355
+
356
+ @property
357
+ def models(self):
358
+ class _ModelList:
359
+ def list(inner_self):
360
+ return type(self).AVAILABLE_MODELS
361
+ return _ModelList()