webscout 8.3__py3-none-any.whl → 8.3.2__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 (120) hide show
  1. webscout/AIauto.py +4 -4
  2. webscout/AIbase.py +61 -1
  3. webscout/AIutel.py +46 -53
  4. webscout/Bing_search.py +418 -0
  5. webscout/Extra/YTToolkit/ytapi/patterns.py +45 -45
  6. webscout/Extra/YTToolkit/ytapi/stream.py +1 -1
  7. webscout/Extra/YTToolkit/ytapi/video.py +10 -10
  8. webscout/Extra/autocoder/autocoder_utiles.py +1 -1
  9. webscout/Extra/gguf.py +706 -177
  10. webscout/Litlogger/formats.py +9 -0
  11. webscout/Litlogger/handlers.py +18 -0
  12. webscout/Litlogger/logger.py +43 -1
  13. webscout/Provider/AISEARCH/genspark_search.py +7 -7
  14. webscout/Provider/AISEARCH/scira_search.py +3 -2
  15. webscout/Provider/GeminiProxy.py +140 -0
  16. webscout/Provider/LambdaChat.py +7 -1
  17. webscout/Provider/MCPCore.py +78 -75
  18. webscout/Provider/OPENAI/BLACKBOXAI.py +1046 -1017
  19. webscout/Provider/OPENAI/GeminiProxy.py +328 -0
  20. webscout/Provider/OPENAI/Qwen3.py +303 -303
  21. webscout/Provider/OPENAI/README.md +5 -0
  22. webscout/Provider/OPENAI/README_AUTOPROXY.md +238 -0
  23. webscout/Provider/OPENAI/TogetherAI.py +355 -0
  24. webscout/Provider/OPENAI/__init__.py +16 -1
  25. webscout/Provider/OPENAI/autoproxy.py +332 -0
  26. webscout/Provider/OPENAI/base.py +101 -14
  27. webscout/Provider/OPENAI/chatgpt.py +15 -2
  28. webscout/Provider/OPENAI/chatgptclone.py +14 -3
  29. webscout/Provider/OPENAI/deepinfra.py +339 -328
  30. webscout/Provider/OPENAI/e2b.py +295 -74
  31. webscout/Provider/OPENAI/mcpcore.py +109 -70
  32. webscout/Provider/OPENAI/opkfc.py +18 -6
  33. webscout/Provider/OPENAI/scirachat.py +59 -50
  34. webscout/Provider/OPENAI/toolbaz.py +2 -10
  35. webscout/Provider/OPENAI/writecream.py +166 -166
  36. webscout/Provider/OPENAI/x0gpt.py +367 -367
  37. webscout/Provider/OPENAI/xenai.py +514 -0
  38. webscout/Provider/OPENAI/yep.py +389 -383
  39. webscout/Provider/STT/__init__.py +3 -0
  40. webscout/Provider/STT/base.py +281 -0
  41. webscout/Provider/STT/elevenlabs.py +265 -0
  42. webscout/Provider/TTI/__init__.py +4 -1
  43. webscout/Provider/TTI/aiarta.py +399 -365
  44. webscout/Provider/TTI/base.py +74 -2
  45. webscout/Provider/TTI/bing.py +231 -0
  46. webscout/Provider/TTI/fastflux.py +63 -30
  47. webscout/Provider/TTI/gpt1image.py +149 -0
  48. webscout/Provider/TTI/imagen.py +196 -0
  49. webscout/Provider/TTI/magicstudio.py +60 -29
  50. webscout/Provider/TTI/piclumen.py +43 -32
  51. webscout/Provider/TTI/pixelmuse.py +232 -225
  52. webscout/Provider/TTI/pollinations.py +43 -32
  53. webscout/Provider/TTI/together.py +287 -0
  54. webscout/Provider/TTI/utils.py +2 -1
  55. webscout/Provider/TTS/README.md +1 -0
  56. webscout/Provider/TTS/__init__.py +2 -1
  57. webscout/Provider/TTS/freetts.py +140 -0
  58. webscout/Provider/TTS/speechma.py +45 -39
  59. webscout/Provider/TogetherAI.py +366 -0
  60. webscout/Provider/UNFINISHED/ChutesAI.py +314 -0
  61. webscout/Provider/UNFINISHED/fetch_together_models.py +95 -0
  62. webscout/Provider/XenAI.py +324 -0
  63. webscout/Provider/__init__.py +8 -0
  64. webscout/Provider/deepseek_assistant.py +378 -0
  65. webscout/Provider/scira_chat.py +3 -2
  66. webscout/Provider/toolbaz.py +0 -1
  67. webscout/auth/__init__.py +44 -0
  68. webscout/auth/api_key_manager.py +189 -0
  69. webscout/auth/auth_system.py +100 -0
  70. webscout/auth/config.py +76 -0
  71. webscout/auth/database.py +400 -0
  72. webscout/auth/exceptions.py +67 -0
  73. webscout/auth/middleware.py +248 -0
  74. webscout/auth/models.py +130 -0
  75. webscout/auth/providers.py +257 -0
  76. webscout/auth/rate_limiter.py +254 -0
  77. webscout/auth/request_models.py +127 -0
  78. webscout/auth/request_processing.py +226 -0
  79. webscout/auth/routes.py +526 -0
  80. webscout/auth/schemas.py +103 -0
  81. webscout/auth/server.py +312 -0
  82. webscout/auth/static/favicon.svg +11 -0
  83. webscout/auth/swagger_ui.py +203 -0
  84. webscout/auth/templates/components/authentication.html +237 -0
  85. webscout/auth/templates/components/base.html +103 -0
  86. webscout/auth/templates/components/endpoints.html +750 -0
  87. webscout/auth/templates/components/examples.html +491 -0
  88. webscout/auth/templates/components/footer.html +75 -0
  89. webscout/auth/templates/components/header.html +27 -0
  90. webscout/auth/templates/components/models.html +286 -0
  91. webscout/auth/templates/components/navigation.html +70 -0
  92. webscout/auth/templates/static/api.js +455 -0
  93. webscout/auth/templates/static/icons.js +168 -0
  94. webscout/auth/templates/static/main.js +784 -0
  95. webscout/auth/templates/static/particles.js +201 -0
  96. webscout/auth/templates/static/styles.css +3353 -0
  97. webscout/auth/templates/static/ui.js +374 -0
  98. webscout/auth/templates/swagger_ui.html +170 -0
  99. webscout/client.py +49 -3
  100. webscout/litagent/Readme.md +12 -3
  101. webscout/litagent/agent.py +99 -62
  102. webscout/scout/core/scout.py +104 -26
  103. webscout/scout/element.py +139 -18
  104. webscout/swiftcli/core/cli.py +14 -3
  105. webscout/swiftcli/decorators/output.py +59 -9
  106. webscout/update_checker.py +31 -49
  107. webscout/version.py +1 -1
  108. webscout/webscout_search.py +4 -12
  109. webscout/webscout_search_async.py +3 -10
  110. webscout/yep_search.py +2 -11
  111. {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/METADATA +41 -11
  112. {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/RECORD +116 -68
  113. {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/entry_points.txt +1 -1
  114. webscout/Provider/HF_space/__init__.py +0 -0
  115. webscout/Provider/HF_space/qwen_qwen2.py +0 -206
  116. webscout/Provider/OPENAI/api.py +0 -1035
  117. webscout/Provider/TTI/artbit.py +0 -0
  118. {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/WHEEL +0 -0
  119. {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/licenses/LICENSE.md +0 -0
  120. {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/top_level.txt +0 -0
@@ -1,304 +1,304 @@
1
- import requests
2
- import json
3
- import time
4
- import uuid
5
- from typing import List, Dict, Optional, Union, Generator, Any
6
-
7
- from webscout.Provider.OPENAI.base import OpenAICompatibleProvider, BaseChat, BaseCompletions
8
- from webscout.Provider.OPENAI.utils import (
9
- ChatCompletionChunk, ChatCompletion, Choice, ChoiceDelta,
10
- ChatCompletionMessage, CompletionUsage,
11
- get_last_user_message, get_system_prompt,
12
- count_tokens
13
- )
14
-
15
- class Completions(BaseCompletions):
16
- def __init__(self, client: 'Qwen3'):
17
- self._client = client
18
-
19
- def create(
20
- self,
21
- *,
22
- model: str,
23
- messages: List[Dict[str, str]],
24
- max_tokens: Optional[int] = 2048,
25
- stream: bool = False,
26
- temperature: Optional[float] = None,
27
- top_p: Optional[float] = None,
28
- timeout: Optional[int] = None,
29
- proxies: Optional[dict] = None,
30
- **kwargs: Any
31
- ) -> Union[ChatCompletion, Generator[ChatCompletionChunk, None, None]]:
32
- payload = {
33
- "data": [
34
- get_last_user_message(messages),
35
- {
36
- "thinking_budget": kwargs.get("thinking_budget", 38),
37
- "model": self._client.get_model(model),
38
- "sys_prompt": get_system_prompt(messages)
39
- },
40
- None, None
41
- ],
42
- "event_data": None,
43
- "fn_index": 13,
44
- "trigger_id": 31,
45
- "session_hash": str(uuid.uuid4()).replace('-', '')
46
- }
47
-
48
- request_id = f"chatcmpl-{uuid.uuid4()}"
49
- created_time = int(time.time())
50
-
51
- if stream:
52
- return self._create_stream(request_id, created_time, model, payload, timeout=timeout, proxies=proxies)
53
- else:
54
- return self._create_non_stream(request_id, created_time, model, payload, timeout=timeout, proxies=proxies)
55
-
56
- def _create_stream(
57
- self, request_id: str, created_time: int, model: str, payload: Dict[str, Any],
58
- timeout: Optional[int] = None, proxies: Optional[dict] = None
59
- ) -> Generator[ChatCompletionChunk, None, None]:
60
- original_proxies = self._client.session.proxies.copy()
61
- if proxies is not None:
62
- self._client.session.proxies = proxies
63
- else:
64
- self._client.session.proxies = {}
65
- try:
66
- session = self._client.session
67
- headers = self._client.headers
68
- # Step 1: Join the queue
69
- join_resp = session.post(self._client.api_endpoint, headers=headers, json=payload, timeout=timeout if timeout is not None else self._client.timeout)
70
- join_resp.raise_for_status()
71
- event_id = join_resp.json().get('event_id')
72
- session_hash = payload["session_hash"]
73
-
74
- # Step 2: Stream data
75
- params = {'session_hash': session_hash}
76
- stream_resp = session.get(self._client.url + "/gradio_api/queue/data", headers=self._client.stream_headers, params=params, stream=True, timeout=timeout if timeout is not None else self._client.timeout)
77
- stream_resp.raise_for_status()
78
-
79
- # --- New logic to yield all content, tool reasoning, and status, similar to Reasoning class ---
80
- is_thinking_tag_open = False # True if <think> has been yielded and not yet </think>
81
-
82
- for line in stream_resp.iter_lines():
83
- if line:
84
- decoded_line = line.decode('utf-8')
85
- if decoded_line.startswith('data: '):
86
- try:
87
- json_data = json.loads(decoded_line[6:])
88
- if json_data.get('msg') == 'process_generating':
89
- if 'output' in json_data and 'data' in json_data['output'] and len(json_data['output']['data']) > 5:
90
- updates_list = json_data['output']['data'][5] # This is a list of operations
91
- for op_details in updates_list:
92
- action = op_details[0]
93
- path = op_details[1]
94
- value = op_details[2]
95
-
96
- content_to_yield = None
97
- is_current_op_tool = False
98
- is_current_op_text = False
99
-
100
- # Case 1: Adding a new content block (tool or text object)
101
- if action == "add" and isinstance(value, dict) and "type" in value:
102
- if len(path) == 4 and path[0] == "value" and path[2] == "content":
103
- block_type = value.get("type")
104
- content_to_yield = value.get("content")
105
- if block_type == "tool":
106
- is_current_op_tool = True
107
- elif block_type == "text":
108
- is_current_op_text = True
109
-
110
- # Case 2: Appending content string to an existing block
111
- elif action == "append" and isinstance(value, str):
112
- if len(path) == 5 and path[0] == "value" and path[2] == "content" and path[4] == "content":
113
- block_index = path[3] # 0 for tool's content, 1 for text's content
114
- content_to_yield = value
115
- if block_index == 0: # Appending to tool's content
116
- is_current_op_tool = True
117
- elif block_index == 1: # Appending to text's content
118
- is_current_op_text = True
119
-
120
- # Case 3: Tool status update (e.g., "End of Thought")
121
- elif action == "replace" and len(path) == 6 and \
122
- path[0] == "value" and path[2] == "content" and \
123
- path[3] == 0 and path[4] == "options" and path[5] == "status": # path[3]==0 ensures it's the tool block
124
- if value == "done": # Tool block processing is complete
125
- if is_thinking_tag_open:
126
- delta = ChoiceDelta(content="</think>\n\n", role="assistant")
127
- yield ChatCompletionChunk(id=request_id, choices=[Choice(index=0, delta=delta)], created=created_time, model=model)
128
- is_thinking_tag_open = False
129
- continue # This operation itself doesn't yield visible content
130
-
131
- # Yielding logic
132
- if is_current_op_tool and content_to_yield:
133
- if not is_thinking_tag_open:
134
- delta = ChoiceDelta(content="<think>", role="assistant")
135
- yield ChatCompletionChunk(id=request_id, choices=[Choice(index=0, delta=delta)], created=created_time, model=model)
136
- is_thinking_tag_open = True
137
-
138
- delta = ChoiceDelta(content=content_to_yield, role="assistant")
139
- yield ChatCompletionChunk(id=request_id, choices=[Choice(index=0, delta=delta)], created=created_time, model=model)
140
-
141
- elif is_current_op_text and content_to_yield:
142
- if is_thinking_tag_open: # If text starts, close any open thinking tag
143
- delta = ChoiceDelta(content="</think>", role="assistant")
144
- yield ChatCompletionChunk(id=request_id, choices=[Choice(index=0, delta=delta)], created=created_time, model=model)
145
- is_thinking_tag_open = False
146
-
147
- delta = ChoiceDelta(content=content_to_yield, role="assistant")
148
- yield ChatCompletionChunk(id=request_id, choices=[Choice(index=0, delta=delta)], created=created_time, model=model)
149
-
150
- if json_data.get('msg') == 'process_completed':
151
- if is_thinking_tag_open: # Ensure </think> is yielded if process completes mid-thought
152
- delta = ChoiceDelta(content="</think>", role="assistant")
153
- yield ChatCompletionChunk(id=request_id, choices=[Choice(index=0, delta=delta)], created=created_time, model=model)
154
- is_thinking_tag_open = False
155
- break
156
- except json.JSONDecodeError:
157
- continue
158
- except Exception as e:
159
- # Log or handle other potential exceptions
160
- continue
161
-
162
- # After the loop, ensure the tag is closed if the stream broke for reasons other than 'process_completed'
163
- if is_thinking_tag_open:
164
- delta = ChoiceDelta(content="</think>", role="assistant")
165
- yield ChatCompletionChunk(id=request_id, choices=[Choice(index=0, delta=delta)], created=created_time, model=model)
166
- finally:
167
- self._client.session.proxies = original_proxies
168
-
169
- def _create_non_stream(
170
- self, request_id: str, created_time: int, model: str, payload: Dict[str, Any],
171
- timeout: Optional[int] = None, proxies: Optional[dict] = None
172
- ) -> ChatCompletion:
173
- original_proxies = self._client.session.proxies.copy()
174
- if proxies is not None:
175
- self._client.session.proxies = proxies
176
- else:
177
- self._client.session.proxies = {}
178
- try:
179
- # For non-streaming, just call the join endpoint and parse the result
180
- session = self._client.session
181
- headers = self._client.headers
182
- resp = session.post(self._client.api_endpoint, headers=headers, json=payload, timeout=timeout if timeout is not None else self._client.timeout)
183
- resp.raise_for_status()
184
- data = resp.json()
185
- # Return the full content as a single message, including all tool and text reasoning if present
186
- output = ""
187
- if 'output' in data and 'data' in data['output'] and len(data['output']['data']) > 5:
188
- updates = data['output']['data'][5]
189
- parts = []
190
- for update in updates:
191
- if isinstance(update, list) and len(update) > 2 and isinstance(update[2], str):
192
- parts.append(update[2])
193
- elif isinstance(update, list) and isinstance(update[1], list) and len(update[1]) > 4:
194
- if update[1][4] == "content":
195
- parts.append(update[2])
196
- elif update[1][4] == "options" and update[2] != "done":
197
- parts.append(str(update[2]))
198
- elif isinstance(update, dict):
199
- if update.get('type') == 'tool':
200
- parts.append(update.get('content', ''))
201
- elif update.get('type') == 'text':
202
- parts.append(update.get('content', ''))
203
- output = "\n".join([str(p) for p in parts if p])
204
- else:
205
- output = data.get('output', {}).get('data', ["", "", "", "", "", [["", "", ""]]])[5][0][2]
206
- message = ChatCompletionMessage(role="assistant", content=output)
207
- choice = Choice(index=0, message=message, finish_reason="stop")
208
- # Use count_tokens to compute usage
209
- prompt_tokens = count_tokens([m.get('content', '') for m in payload['data'] if isinstance(m, dict) and 'content' in m or isinstance(m, str)])
210
- completion_tokens = count_tokens(output)
211
- usage = CompletionUsage(
212
- prompt_tokens=prompt_tokens,
213
- completion_tokens=completion_tokens,
214
- total_tokens=prompt_tokens + completion_tokens
215
- )
216
- completion = ChatCompletion(
217
- id=request_id,
218
- choices=[choice],
219
- created=created_time,
220
- model=model,
221
- usage=usage,
222
- )
223
- return completion
224
- finally:
225
- self._client.session.proxies = original_proxies
226
-
227
- class Chat(BaseChat):
228
- def __init__(self, client: 'Qwen3'):
229
- self.completions = Completions(client)
230
-
231
- class Qwen3(OpenAICompatibleProvider):
232
- url = "https://qwen-qwen3-demo.hf.space"
233
- api_endpoint = "https://qwen-qwen3-demo.hf.space/gradio_api/queue/join?__theme=system"
234
- AVAILABLE_MODELS = [
235
- "qwen3-235b-a22b",
236
- "qwen3-32b",
237
- "qwen3-30b-a3b",
238
- "qwen3-14b",
239
- "qwen3-8b",
240
- "qwen3-4b",
241
- "qwen3-1.7b",
242
- "qwen3-0.6b",
243
- ]
244
- MODEL_ALIASES = {
245
- "qwen-3-235b": "qwen3-235b-a22b",
246
- "qwen-3-30b": "qwen3-30b-a3b",
247
- "qwen-3-32b": "qwen3-32b",
248
- "qwen-3-14b": "qwen3-14b",
249
- "qwen-3-4b": "qwen3-4b",
250
- "qwen-3-1.7b": "qwen3-1.7b",
251
- "qwen-3-0.6b": "qwen3-0.6b"
252
- }
253
-
254
- def __init__(self):
255
- self.timeout = 30
256
- self.session = requests.Session()
257
- self.session.proxies = {}
258
- self.headers = {
259
- 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:136.0) Gecko/20100101 Firefox/136.0',
260
- 'Accept': '*/*',
261
- 'Accept-Language': 'en-US,en;q=0.5',
262
- 'Accept-Encoding': 'gzip, deflate, br, zstd',
263
- 'Referer': f'{self.url}/?__theme=system',
264
- 'content-type': 'application/json',
265
- 'Origin': self.url,
266
- 'Connection': 'keep-alive',
267
- 'Sec-Fetch-Dest': 'empty',
268
- 'Sec-Fetch-Mode': 'cors',
269
- 'Sec-Fetch-Site': 'same-origin',
270
- 'Pragma': 'no-cache',
271
- 'Cache-Control': 'no-cache',
272
- }
273
- self.stream_headers = {
274
- 'Accept': 'text/event-stream',
275
- 'Accept-Language': 'en-US,en;q=0.5',
276
- 'Referer': f'{self.url}/?__theme=system',
277
- 'User-Agent': self.headers['User-Agent'],
278
- }
279
- self.session.headers.update(self.headers)
280
- self.chat = Chat(self)
281
-
282
- def get_model(self, model):
283
- return self.MODEL_ALIASES.get(model, model)
284
-
285
- @property
286
- def models(self):
287
- class _ModelList:
288
- def list(inner_self):
289
- return type(self).AVAILABLE_MODELS
290
- return _ModelList()
291
-
292
- if __name__ == "__main__":
293
- client = Qwen3()
294
- from rich import print
295
- resp = client.chat.completions.create(
296
- model="qwen3-14b",
297
- messages=[
298
- {"role": "system", "content": "You are a helpful assistant."},
299
- {"role": "user", "content": "Hello "}
300
- ],
301
- stream=True
302
- )
303
- for chunk in resp:
1
+ import requests
2
+ import json
3
+ import time
4
+ import uuid
5
+ from typing import List, Dict, Optional, Union, Generator, Any
6
+
7
+ from webscout.Provider.OPENAI.base import OpenAICompatibleProvider, BaseChat, BaseCompletions
8
+ from webscout.Provider.OPENAI.utils import (
9
+ ChatCompletionChunk, ChatCompletion, Choice, ChoiceDelta,
10
+ ChatCompletionMessage, CompletionUsage,
11
+ get_last_user_message, get_system_prompt,
12
+ count_tokens
13
+ )
14
+
15
+ class Completions(BaseCompletions):
16
+ def __init__(self, client: 'Qwen3'):
17
+ self._client = client
18
+
19
+ def create(
20
+ self,
21
+ *,
22
+ model: str,
23
+ messages: List[Dict[str, str]],
24
+ max_tokens: Optional[int] = 2048,
25
+ stream: bool = False,
26
+ temperature: Optional[float] = None,
27
+ top_p: Optional[float] = None,
28
+ timeout: Optional[int] = None,
29
+ proxies: Optional[dict] = None,
30
+ **kwargs: Any
31
+ ) -> Union[ChatCompletion, Generator[ChatCompletionChunk, None, None]]:
32
+ payload = {
33
+ "data": [
34
+ get_last_user_message(messages),
35
+ {
36
+ "thinking_budget": kwargs.get("thinking_budget", 38),
37
+ "model": self._client.get_model(model),
38
+ "sys_prompt": get_system_prompt(messages)
39
+ },
40
+ None, None
41
+ ],
42
+ "event_data": None,
43
+ "fn_index": 13,
44
+ "trigger_id": 31,
45
+ "session_hash": str(uuid.uuid4()).replace('-', '')
46
+ }
47
+
48
+ request_id = f"chatcmpl-{uuid.uuid4()}"
49
+ created_time = int(time.time())
50
+
51
+ if stream:
52
+ return self._create_stream(request_id, created_time, model, payload, timeout=timeout, proxies=proxies)
53
+ else:
54
+ return self._create_non_stream(request_id, created_time, model, payload, timeout=timeout, proxies=proxies)
55
+
56
+ def _create_stream(
57
+ self, request_id: str, created_time: int, model: str, payload: Dict[str, Any],
58
+ timeout: Optional[int] = None, proxies: Optional[dict] = None
59
+ ) -> Generator[ChatCompletionChunk, None, None]:
60
+ original_proxies = self._client.session.proxies.copy()
61
+ if proxies is not None:
62
+ self._client.session.proxies = proxies
63
+ else:
64
+ self._client.session.proxies = {}
65
+ try:
66
+ session = self._client.session
67
+ headers = self._client.headers
68
+ # Step 1: Join the queue
69
+ join_resp = session.post(self._client.api_endpoint, headers=headers, json=payload, timeout=timeout if timeout is not None else self._client.timeout)
70
+ join_resp.raise_for_status()
71
+ event_id = join_resp.json().get('event_id')
72
+ session_hash = payload["session_hash"]
73
+
74
+ # Step 2: Stream data
75
+ params = {'session_hash': session_hash}
76
+ stream_resp = session.get(self._client.url + "/gradio_api/queue/data", headers=self._client.stream_headers, params=params, stream=True, timeout=timeout if timeout is not None else self._client.timeout)
77
+ stream_resp.raise_for_status()
78
+
79
+ # --- New logic to yield all content, tool reasoning, and status, similar to Reasoning class ---
80
+ is_thinking_tag_open = False # True if <think> has been yielded and not yet </think>
81
+
82
+ for line in stream_resp.iter_lines():
83
+ if line:
84
+ decoded_line = line.decode('utf-8')
85
+ if decoded_line.startswith('data: '):
86
+ try:
87
+ json_data = json.loads(decoded_line[6:])
88
+ if json_data.get('msg') == 'process_generating':
89
+ if 'output' in json_data and 'data' in json_data['output'] and len(json_data['output']['data']) > 5:
90
+ updates_list = json_data['output']['data'][5] # This is a list of operations
91
+ for op_details in updates_list:
92
+ action = op_details[0]
93
+ path = op_details[1]
94
+ value = op_details[2]
95
+
96
+ content_to_yield = None
97
+ is_current_op_tool = False
98
+ is_current_op_text = False
99
+
100
+ # Case 1: Adding a new content block (tool or text object)
101
+ if action == "add" and isinstance(value, dict) and "type" in value:
102
+ if len(path) == 4 and path[0] == "value" and path[2] == "content":
103
+ block_type = value.get("type")
104
+ content_to_yield = value.get("content")
105
+ if block_type == "tool":
106
+ is_current_op_tool = True
107
+ elif block_type == "text":
108
+ is_current_op_text = True
109
+
110
+ # Case 2: Appending content string to an existing block
111
+ elif action == "append" and isinstance(value, str):
112
+ if len(path) == 5 and path[0] == "value" and path[2] == "content" and path[4] == "content":
113
+ block_index = path[3] # 0 for tool's content, 1 for text's content
114
+ content_to_yield = value
115
+ if block_index == 0: # Appending to tool's content
116
+ is_current_op_tool = True
117
+ elif block_index == 1: # Appending to text's content
118
+ is_current_op_text = True
119
+
120
+ # Case 3: Tool status update (e.g., "End of Thought")
121
+ elif action == "replace" and len(path) == 6 and \
122
+ path[0] == "value" and path[2] == "content" and \
123
+ path[3] == 0 and path[4] == "options" and path[5] == "status": # path[3]==0 ensures it's the tool block
124
+ if value == "done": # Tool block processing is complete
125
+ if is_thinking_tag_open:
126
+ delta = ChoiceDelta(content="</think>\n\n", role="assistant")
127
+ yield ChatCompletionChunk(id=request_id, choices=[Choice(index=0, delta=delta)], created=created_time, model=model)
128
+ is_thinking_tag_open = False
129
+ continue # This operation itself doesn't yield visible content
130
+
131
+ # Yielding logic
132
+ if is_current_op_tool and content_to_yield:
133
+ if not is_thinking_tag_open:
134
+ delta = ChoiceDelta(content="<think>", role="assistant")
135
+ yield ChatCompletionChunk(id=request_id, choices=[Choice(index=0, delta=delta)], created=created_time, model=model)
136
+ is_thinking_tag_open = True
137
+
138
+ delta = ChoiceDelta(content=content_to_yield, role="assistant")
139
+ yield ChatCompletionChunk(id=request_id, choices=[Choice(index=0, delta=delta)], created=created_time, model=model)
140
+
141
+ elif is_current_op_text and content_to_yield:
142
+ if is_thinking_tag_open: # If text starts, close any open thinking tag
143
+ delta = ChoiceDelta(content="</think>", role="assistant")
144
+ yield ChatCompletionChunk(id=request_id, choices=[Choice(index=0, delta=delta)], created=created_time, model=model)
145
+ is_thinking_tag_open = False
146
+
147
+ delta = ChoiceDelta(content=content_to_yield, role="assistant")
148
+ yield ChatCompletionChunk(id=request_id, choices=[Choice(index=0, delta=delta)], created=created_time, model=model)
149
+
150
+ if json_data.get('msg') == 'process_completed':
151
+ if is_thinking_tag_open: # Ensure </think> is yielded if process completes mid-thought
152
+ delta = ChoiceDelta(content="</think>", role="assistant")
153
+ yield ChatCompletionChunk(id=request_id, choices=[Choice(index=0, delta=delta)], created=created_time, model=model)
154
+ is_thinking_tag_open = False
155
+ break
156
+ except json.JSONDecodeError:
157
+ continue
158
+ except Exception as e:
159
+ # Log or handle other potential exceptions
160
+ continue
161
+
162
+ # After the loop, ensure the tag is closed if the stream broke for reasons other than 'process_completed'
163
+ if is_thinking_tag_open:
164
+ delta = ChoiceDelta(content="</think>", role="assistant")
165
+ yield ChatCompletionChunk(id=request_id, choices=[Choice(index=0, delta=delta)], created=created_time, model=model)
166
+ finally:
167
+ self._client.session.proxies = original_proxies
168
+
169
+ def _create_non_stream(
170
+ self, request_id: str, created_time: int, model: str, payload: Dict[str, Any],
171
+ timeout: Optional[int] = None, proxies: Optional[dict] = None
172
+ ) -> ChatCompletion:
173
+ original_proxies = self._client.session.proxies.copy()
174
+ if proxies is not None:
175
+ self._client.session.proxies = proxies
176
+ else:
177
+ self._client.session.proxies = {}
178
+ try:
179
+ # For non-streaming, just call the join endpoint and parse the result
180
+ session = self._client.session
181
+ headers = self._client.headers
182
+ resp = session.post(self._client.api_endpoint, headers=headers, json=payload, timeout=timeout if timeout is not None else self._client.timeout)
183
+ resp.raise_for_status()
184
+ data = resp.json()
185
+ # Return the full content as a single message, including all tool and text reasoning if present
186
+ output = ""
187
+ if 'output' in data and 'data' in data['output'] and len(data['output']['data']) > 5:
188
+ updates = data['output']['data'][5]
189
+ parts = []
190
+ for update in updates:
191
+ if isinstance(update, list) and len(update) > 2 and isinstance(update[2], str):
192
+ parts.append(update[2])
193
+ elif isinstance(update, list) and isinstance(update[1], list) and len(update[1]) > 4:
194
+ if update[1][4] == "content":
195
+ parts.append(update[2])
196
+ elif update[1][4] == "options" and update[2] != "done":
197
+ parts.append(str(update[2]))
198
+ elif isinstance(update, dict):
199
+ if update.get('type') == 'tool':
200
+ parts.append(update.get('content', ''))
201
+ elif update.get('type') == 'text':
202
+ parts.append(update.get('content', ''))
203
+ output = "\n".join([str(p) for p in parts if p])
204
+ else:
205
+ output = data.get('output', {}).get('data', ["", "", "", "", "", [["", "", ""]]])[5][0][2]
206
+ message = ChatCompletionMessage(role="assistant", content=output)
207
+ choice = Choice(index=0, message=message, finish_reason="stop")
208
+ # Use count_tokens to compute usage
209
+ prompt_tokens = count_tokens([m.get('content', '') for m in payload['data'] if isinstance(m, dict) and 'content' in m or isinstance(m, str)])
210
+ completion_tokens = count_tokens(output)
211
+ usage = CompletionUsage(
212
+ prompt_tokens=prompt_tokens,
213
+ completion_tokens=completion_tokens,
214
+ total_tokens=prompt_tokens + completion_tokens
215
+ )
216
+ completion = ChatCompletion(
217
+ id=request_id,
218
+ choices=[choice],
219
+ created=created_time,
220
+ model=model,
221
+ usage=usage,
222
+ )
223
+ return completion
224
+ finally:
225
+ self._client.session.proxies = original_proxies
226
+
227
+ class Chat(BaseChat):
228
+ def __init__(self, client: 'Qwen3'):
229
+ self.completions = Completions(client)
230
+
231
+ class Qwen3(OpenAICompatibleProvider):
232
+ url = "https://qwen-qwen3-demo.hf.space"
233
+ api_endpoint = "https://qwen-qwen3-demo.hf.space/gradio_api/queue/join?__theme=system"
234
+ AVAILABLE_MODELS = [
235
+ "qwen3-235b-a22b",
236
+ "qwen3-32b",
237
+ "qwen3-30b-a3b",
238
+ "qwen3-14b",
239
+ "qwen3-8b",
240
+ "qwen3-4b",
241
+ "qwen3-1.7b",
242
+ "qwen3-0.6b",
243
+ ]
244
+ MODEL_ALIASES = {
245
+ "qwen-3-235b": "qwen3-235b-a22b",
246
+ "qwen-3-30b": "qwen3-30b-a3b",
247
+ "qwen-3-32b": "qwen3-32b",
248
+ "qwen-3-14b": "qwen3-14b",
249
+ "qwen-3-4b": "qwen3-4b",
250
+ "qwen-3-1.7b": "qwen3-1.7b",
251
+ "qwen-3-0.6b": "qwen3-0.6b"
252
+ }
253
+
254
+ def __init__(self):
255
+ self.timeout = 30
256
+ self.session = requests.Session()
257
+ self.session.proxies = {}
258
+ self.headers = {
259
+ 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:136.0) Gecko/20100101 Firefox/136.0',
260
+ 'Accept': '*/*',
261
+ 'Accept-Language': 'en-US,en;q=0.5',
262
+ 'Accept-Encoding': 'gzip, deflate, br, zstd',
263
+ 'Referer': f'{self.url}/?__theme=system',
264
+ 'content-type': 'application/json',
265
+ 'Origin': self.url,
266
+ 'Connection': 'keep-alive',
267
+ 'Sec-Fetch-Dest': 'empty',
268
+ 'Sec-Fetch-Mode': 'cors',
269
+ 'Sec-Fetch-Site': 'same-origin',
270
+ 'Pragma': 'no-cache',
271
+ 'Cache-Control': 'no-cache',
272
+ }
273
+ self.stream_headers = {
274
+ 'Accept': 'text/event-stream',
275
+ 'Accept-Language': 'en-US,en;q=0.5',
276
+ 'Referer': f'{self.url}/?__theme=system',
277
+ 'User-Agent': self.headers['User-Agent'],
278
+ }
279
+ self.session.headers.update(self.headers)
280
+ self.chat = Chat(self)
281
+
282
+ def get_model(self, model):
283
+ return self.MODEL_ALIASES.get(model, model)
284
+
285
+ @property
286
+ def models(self):
287
+ class _ModelList:
288
+ def list(inner_self):
289
+ return type(self).AVAILABLE_MODELS
290
+ return _ModelList()
291
+
292
+ if __name__ == "__main__":
293
+ client = Qwen3()
294
+ from rich import print
295
+ resp = client.chat.completions.create(
296
+ model="qwen3-14b",
297
+ messages=[
298
+ {"role": "system", "content": "You are a helpful assistant."},
299
+ {"role": "user", "content": "Hello "}
300
+ ],
301
+ stream=True
302
+ )
303
+ for chunk in resp:
304
304
  print(chunk, end="", flush=True)
@@ -65,6 +65,11 @@ Currently, the following providers are implemented with OpenAI-compatible interf
65
65
  - TwoAI
66
66
  - oivscode
67
67
  - Qwen3
68
+ - TogetherAI
69
+ - PiAI
70
+ - FalconH1
71
+ - XenAI
72
+ - GeminiProxy
68
73
  ---
69
74
 
70
75