XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet
公开
关注 0 Fork 0 Star 0
返回提交历史

XFEstudio/gpt4free

feat: introduce AnyProvider & LM Arena, overhaul model/provider logic (#2925)

* feat: introduce AnyProvider & LM Arena, overhaul model/provider logic - **Provider additions & removals** - Added `Provider/LMArenaProvider.py` with full async stream implementation and vision model support - Registered `LMArenaProvider` in `Provider/__init__.py`; removed old `hf_space/LMArenaProvider.py` - Created `providers/any_provider.py`; registers `AnyProvider` dynamically in `Provider` - **Provider framework enhancements** - `providers/base_provider.py` - Added `video_models` and `audio_models` attributes - `providers/retry_provider.py` - Introduced `is_content()` helper; now treats `AudioResponse` as stream content - **Cloudflare provider refactor** - `Provider/Cloudflare.py` - Re‑implemented `get_models()` with `read_models()` helper, `fallback_models`, robust nodriver/curl handling and model‑name cleaning - **Other provider tweaks** - `Provider/Copilot.py` – removed `"reasoning"` alias and initial `setOptions` WS message - `Provider/PollinationsAI.py` & `PollinationsImage.py` - Converted `audio_models` from list to dict, adjusted usage checks and labels - `Provider/hf/__init__.py` – applies `model_aliases` remap before dispatch - `Provider/hf_space/DeepseekAI_JanusPro7b.py` – now merges media before upload - `needs_auth/Gemini.py` – dropped obsolete Gemini model entries - `needs_auth/GigaChat.py` – added lowercase `"gigachat"` alias - **API & client updates** - Replaced `ProviderUtils` with new `Provider` map usage throughout API and GUI server - Integrated `AnyProvider` as default fallback in `g4f/client` sync & async flows - API endpoints now return counts of providers per model and filter by `x_ignored` header - **GUI improvements** - Updated JS labels with emoji icons, provider ignore logic, model count display - **Model registry** - Renamed base model `"GigaChat:latest"` ➜ `"gigachat"` in `models.py` - **Miscellaneous** - Added audio/video flags to GUI provider list - Tightened error propagation in `retry_provider.raise_exceptions` * Fix unittests * fix: handle None conversation when accessing provider-specific data - Modified `AnyProvider` class in `g4f/providers/any_provider.py` - Updated logic to check if `conversation` is not None before accessing `provider.__name__` attribute - Wrapped `getattr(conversation, provider.__name__, None)` block in an additional `if conversation is not None` condition - Changed `setattr(conversation, provider.__name__, chunk)` to use `chunk.get_dict()` instead of the object directly - Ensured consistent use of `JsonConversation` when modifying or assigning `conversation` data * ``` feat: add provider string conversion & update IterListProvider call - In g4f/client/__init__.py, within both Completions and AsyncCompletions, added a check to convert the provider from a string using convert_to_provider(provider) when applicable. - In g4f/providers/any_provider.py, removed the second argument (False) from the IterListProvider constructor call in the async for loop. ``` --------- Co-authored-by: hlohaus <983577+hlohaus@users.noreply.github.com>

0a070bdf
H Lohaus <hlohaus@users.noreply.github.com>
提交于

代码差异

24 个文件 +668 -340
Modified etc/tool/commit.py +3 -4
@@ -22,18 +22,17 @@ import os
22 22 import argparse
23 23 import tempfile
24 24 import time
25 from typing import Optional, Dict, Any, List, Tuple
25 from typing import Optional, Any, List
26 26
27 27 from g4f.client import Client
28 28 from g4f.models import ModelUtils
29 import g4f.Provider
30 29
31 30 from g4f import debug
32 31 debug.logging = True
33 32
34 33 # Constants
35 DEFAULT_MODEL = "claude-3.7-sonnet"
36 FALLBACK_MODELS = ["claude-3.5-sonnet", "o1", "o3-mini", "gpt-4o"]
34 DEFAULT_MODEL = "o1"
35 FALLBACK_MODELS = ["o1", "o3-mini", "gpt-4o"]
37 36 MAX_DIFF_SIZE = None # Set to None to disable truncation, or a number for character limit
38 37 MAX_RETRIES = 3
39 38 RETRY_DELAY = 2 # Seconds
Modified etc/unittest/client.py +2 -1
@@ -3,7 +3,8 @@ from __future__ import annotations
3 3 import unittest
4 4
5 5 from g4f.errors import ModelNotFoundError
6 from g4f.client import Client, AsyncClient, ChatCompletion, ChatCompletionChunk, get_model_and_provider
6 from g4f.client import Client, AsyncClient, ChatCompletion, ChatCompletionChunk
7 from g4f.client.service import get_model_and_provider
7 8 from g4f.Provider.Copilot import Copilot
8 9 from g4f.models import gpt_4o
9 10 from .mocks import AsyncGeneratorProviderMock, ModelProviderMock, YieldProviderMock
Modified etc/unittest/models.py +1 -1
@@ -17,7 +17,7 @@ class TestProviderHasModel(unittest.TestCase):
17 17 model_name = provider.model_aliases[model.name]
18 18 else:
19 19 model_name = model.name
20 self.provider_has_model(provider, model_name)
20 self.provider_has_model(provider, model_name)
21 21
22 22 def provider_has_model(self, provider: Type[BaseProvider], model: str):
23 23 if provider.__name__ not in self.cache:
Modified g4f/Provider/Cloudflare.py +37 -16
@@ -8,13 +8,14 @@ from .base_provider import AsyncGeneratorProvider, ProviderModelMixin, AuthFileM
8 8 from ..requests import Session, StreamSession, get_args_from_nodriver, raise_for_status, merge_cookies
9 9 from ..requests import DEFAULT_HEADERS, has_nodriver, has_curl_cffi
10 10 from ..providers.response import FinishReason, Usage
11 from ..errors import ResponseStatusError, ModelNotFoundError
11 from ..errors import ResponseStatusError, ModelNotFoundError, MissingRequirementsError
12 from .. import debug
12 13 from .helper import render_messages
13 14
14 15 class Cloudflare(AsyncGeneratorProvider, ProviderModelMixin, AuthFileMixin):
15 16 label = "Cloudflare AI"
16 17 url = "https://playground.ai.cloudflare.com"
17 working = True
18 working = has_curl_cffi
18 19 use_nodriver = True
19 20 api_endpoint = "https://playground.ai.cloudflare.com/api/inference"
20 21 models_url = "https://playground.ai.cloudflare.com/api/models"
@@ -38,29 +39,49 @@ class Cloudflare(AsyncGeneratorProvider, ProviderModelMixin, AuthFileMixin):
38 39 "qwen-1.5-7b": "@cf/qwen/qwen1.5-7b-chat-awq",
39 40 "qwen-2.5-coder": "@cf/qwen/qwen2.5-coder-32b-instruct",
40 41 }
42 fallback_models = list(model_aliases.keys())
41 43 _args: dict = None
42 44
43 45 @classmethod
44 46 def get_models(cls) -> str:
47 def read_models():
48 with Session(**cls._args) as session:
49 response = session.get(cls.models_url)
50 cls._args["cookies"] = merge_cookies(cls._args["cookies"], response)
51 raise_for_status(response)
52 json_data = response.json()
53 def clean_name(name: str) -> str:
54 return name.split("/")[-1].replace(
55 "-instruct", "").replace(
56 "-17b-16e", "").replace(
57 "-chat", "").replace(
58 "-fp8", "").replace(
59 "-fast", "").replace(
60 "-int8", "").replace(
61 "-awq", "").replace(
62 "-qvq", "").replace(
63 "-r1", "")
64 model_map = {clean_name(model.get("name")): model.get("name") for model in json_data.get("models")}
65 cls.models = list(model_map.keys())
66 cls.model_aliases = {**cls.model_aliases, **model_map}
45 67 if not cls.models:
46 if cls._args is None:
68 try:
69 if cls._args is None:
70 cls._args = {"headers": DEFAULT_HEADERS, "cookies": {}}
71 read_models()
72 except ResponseStatusError as f:
47 73 if has_nodriver:
48 74 get_running_loop(check_nested=True)
49 75 args = get_args_from_nodriver(cls.url)
50 cls._args = asyncio.run(args)
51 elif not has_curl_cffi:
52 return cls.models
76 try:
77 cls._args = asyncio.run(args)
78 read_models()
79 except RuntimeError as e:
80 cls.models = cls.fallback_models
81 debug.log(f"Nodriver is not available: {type(e).__name__}: {e}")
53 82 else:
54 cls._args = {"headers": DEFAULT_HEADERS, "cookies": {}}
55 with Session(**cls._args) as session:
56 response = session.get(cls.models_url)
57 cls._args["cookies"] = merge_cookies(cls._args["cookies"], response)
58 try:
59 raise_for_status(response)
60 except ResponseStatusError:
61 return cls.models
62 json_data = response.json()
63 cls.models = [model.get("name") for model in json_data.get("models")]
83 cls.models = cls.fallback_models
84 debug.log(f"Nodriver is not installed: {type(f).__name__}: {f}")
64 85 return cls.models
65 86
66 87 @classmethod
Modified g4f/Provider/Copilot.py +0 -2
@@ -48,7 +48,6 @@ class Copilot(AsyncGeneratorProvider, ProviderModelMixin):
48 48 "gpt-4": default_model,
49 49 "gpt-4o": default_model,
50 50 "o1": "Think Deeper",
51 "reasoning": "Think Deeper",
52 51 "dall-e-3": default_model
53 52 }
54 53
@@ -144,7 +143,6 @@ class Copilot(AsyncGeneratorProvider, ProviderModelMixin):
144 143 uploaded_images.append({"type":"image", "url": media})
145 144
146 145 wss = await session.ws_connect(cls.websocket_url, timeout=3)
147 await wss.send(json.dumps({"event":"setOptions","supportedCards":["weather","local","image","sports","video","ads","finance"],"ads":{"supportedTypes":["multimedia","product","tourActivity","propertyPromotion","text"]}}));
148 146 await wss.send(json.dumps({
149 147 "event": "send",
150 148 "conversationId": conversation_id,
Added g4f/Provider/LMArenaProvider.py +371 -0
@@ -0,0 +1,371 @@
1 from __future__ import annotations
2
3 import json
4 import uuid
5 import requests
6
7 from ..typing import AsyncResult, Messages, MediaListType
8 from ..requests import StreamSession, FormData, raise_for_status
9 from ..providers.response import FinishReason, JsonConversation
10 from ..tools.media import merge_media
11 from ..image import to_bytes, is_accepted_format
12 from .base_provider import AsyncGeneratorProvider, ProviderModelMixin, AuthFileMixin
13 from .helper import get_last_user_message
14 from .. import debug
15
16 class LMArenaProvider(AsyncGeneratorProvider, ProviderModelMixin, AuthFileMixin):
17 label = "LM Arena"
18 url = "https://lmarena.ai"
19 api_endpoint = "/queue/join?"
20 working = True
21
22 default_model = "gpt-4o"
23 model_aliases = {default_model: "chatgpt-4o-latest-20250326"}
24 models = [
25 default_model,
26 "o3-2025-04-16",
27 "o4-mini-2025-04-16",
28 "gpt-4.1-2025-04-14",
29 "gemini-2.5-pro-exp-03-25",
30 "llama-4-maverick-03-26-experimental",
31 "grok-3-preview-02-24",
32 "claude-3-7-sonnet-20250219",
33 "claude-3-7-sonnet-20250219-thinking-32k",
34 "deepseek-v3-0324",
35 "llama-4-maverick-17b-128e-instruct",
36 "gpt-4.1-mini-2025-04-14",
37 "gpt-4.1-nano-2025-04-14",
38 "gemini-2.0-flash-thinking-exp-01-21",
39 "gemini-2.0-flash-001",
40 "gemini-2.0-flash-lite-preview-02-05",
41 "gemma-3-27b-it",
42 "gemma-3-12b-it",
43 "gemma-3-4b-it",
44 "deepseek-r1",
45 "claude-3-5-sonnet-20241022",
46 "o3-mini",
47 "llama-3.3-70b-instruct",
48 "gpt-4o-mini-2024-07-18",
49 "gpt-4o-2024-11-20",
50 "gpt-4o-2024-08-06",
51 "gpt-4o-2024-05-13",
52 "command-a-03-2025",
53 "qwq-32b",
54 "p2l-router-7b",
55 "claude-3-5-haiku-20241022",
56 "claude-3-5-sonnet-20240620",
57 "doubao-1.5-pro-32k-250115",
58 "doubao-1.5-vision-pro-32k-250115",
59 "mistral-small-24b-instruct-2501",
60 "phi-4",
61 "amazon-nova-pro-v1.0",
62 "amazon-nova-lite-v1.0",
63 "amazon-nova-micro-v1.0",
64 "cobalt-exp-beta-v3",
65 "cobalt-exp-beta-v4",
66 "qwen-max-2025-01-25",
67 "qwen-plus-0125-exp",
68 "qwen2.5-vl-32b-instruct",
69 "qwen2.5-vl-72b-instruct",
70 "gemini-1.5-pro-002",
71 "gemini-1.5-flash-002",
72 "gemini-1.5-flash-8b-001",
73 "gemini-1.5-pro-001",
74 "gemini-1.5-flash-001",
75 "llama-3.1-405b-instruct-bf16",
76 "llama-3.3-nemotron-49b-super-v1",
77 "llama-3.1-nemotron-ultra-253b-v1",
78 "llama-3.1-nemotron-70b-instruct",
79 "llama-3.1-70b-instruct",
80 "llama-3.1-8b-instruct",
81 "hunyuan-standard-2025-02-10",
82 "hunyuan-large-2025-02-10",
83 "hunyuan-standard-vision-2024-12-31",
84 "hunyuan-turbo-0110",
85 "hunyuan-turbos-20250226",
86 "mistral-large-2411",
87 "pixtral-large-2411",
88 "mistral-large-2407",
89 "llama-3.1-nemotron-51b-instruct",
90 "granite-3.1-8b-instruct",
91 "granite-3.1-2b-instruct",
92 "step-2-16k-exp-202412",
93 "step-2-16k-202502",
94 "step-1o-vision-32k-highres",
95 "yi-lightning",
96 "glm-4-plus",
97 "glm-4-plus-0111",
98 "jamba-1.5-large",
99 "jamba-1.5-mini",
100 "gemma-2-27b-it",
101 "gemma-2-9b-it",
102 "gemma-2-2b-it",
103 "eureka-chatbot",
104 "claude-3-haiku-20240307",
105 "claude-3-sonnet-20240229",
106 "claude-3-opus-20240229",
107 "nemotron-4-340b",
108 "llama-3-70b-instruct",
109 "llama-3-8b-instruct",
110 "qwen2.5-plus-1127",
111 "qwen2.5-coder-32b-instruct",
112 "qwen2.5-72b-instruct",
113 "qwen-max-0919",
114 "qwen-vl-max-1119",
115 "qwen-vl-max-0809",
116 "llama-3.1-tulu-3-70b",
117 "olmo-2-0325-32b-instruct",
118 "gpt-3.5-turbo-0125",
119 "reka-core-20240904",
120 "reka-flash-20240904",
121 "c4ai-aya-expanse-32b",
122 "c4ai-aya-expanse-8b",
123 "c4ai-aya-vision-32b",
124 "command-r-plus-08-2024",
125 "command-r-08-2024",
126 "codestral-2405",
127 "mixtral-8x22b-instruct-v0.1",
128 "mixtral-8x7b-instruct-v0.1",
129 "pixtral-12b-2409",
130 "ministral-8b-2410"
131 ]
132 vision_models = [
133 "o3-2025-04-16",
134 "o4-mini-2025-04-16",
135 "gpt-4.1-2025-04-14",
136 "gemini-2.5-pro-exp-03-25",
137 "claude-3-7-sonnet-20250219",
138 "claude-3-7-sonnet-20250219-thinking-32k",
139 "llama-4-maverick-17b-128e-instruct",
140 "gpt-4.1-mini-2025-04-14",
141 "gpt-4.1-nano-2025-04-14",
142 "gemini-2.0-flash-thinking-exp-01-21",
143 "gemini-2.0-flash-001",
144 "gemini-2.0-flash-lite-preview-02-05",
145 "claude-3-5-sonnet-20241022",
146 "gpt-4o-mini-2024-07-18",
147 "gpt-4o-2024-11-20",
148 "gpt-4o-2024-08-06",
149 "gpt-4o-2024-05-13",
150 "claude-3-5-sonnet-20240620",
151 "doubao-1.5-vision-pro-32k-250115",
152 "amazon-nova-pro-v1.0",
153 "amazon-nova-lite-v1.0",
154 "qwen2.5-vl-32b-instruct",
155 "qwen2.5-vl-72b-instruct",
156 "gemini-1.5-pro-002",
157 "gemini-1.5-flash-002",
158 "gemini-1.5-flash-8b-001",
159 "gemini-1.5-pro-001",
160 "gemini-1.5-flash-001",
161 "hunyuan-standard-vision-2024-12-31",
162 "pixtral-large-2411",
163 "step-1o-vision-32k-highres",
164 "claude-3-haiku-20240307",
165 "claude-3-sonnet-20240229",
166 "claude-3-opus-20240229",
167 "qwen-vl-max-1119",
168 "qwen-vl-max-0809",
169 "reka-core-20240904",
170 "reka-flash-20240904",
171 "c4ai-aya-vision-32b",
172 "pixtral-12b-2409"
173 ]
174
175 _args: dict = None
176
177 @classmethod
178 def get_models(cls) -> list[str]:
179 if not cls.models:
180 url = "https://storage.googleapis.com/public-arena-no-cors/p2l-explorer/data/overall/arena.json"
181 data = requests.get(url).json()
182 cls.models = [model[0] for model in data["leaderboard"]]
183 return cls.models
184
185 @classmethod
186 def _build_payloads(cls, model_id: str, session_hash: str, text: str, files: list, max_tokens: int, temperature: float, top_p: float):
187 first_payload = {
188 "data": [
189 None,
190 model_id,
191 {"text": text, "files": files},
192 {
193 "text_models": [model_id],
194 "all_text_models": [model_id],
195 "vision_models": [],
196 "all_vision_models": [],
197 "image_gen_models": [],
198 "all_image_gen_models": [],
199 "search_models": [],
200 "all_search_models": [],
201 "models": [model_id],
202 "all_models": [model_id],
203 "arena_type": "text-arena"
204 }
205 ],
206 "event_data": None,
207 "fn_index": 117,
208 "trigger_id": 159,
209 "session_hash": session_hash
210 }
211
212 second_payload = {
213 "data": [],
214 "event_data": None,
215 "fn_index": 118,
216 "trigger_id": 159,
217 "session_hash": session_hash
218 }
219
220 third_payload = {
221 "data": [None, temperature, top_p, max_tokens],
222 "event_data": None,
223 "fn_index": 119,
224 "trigger_id": 159,
225 "session_hash": session_hash
226 }
227
228 return first_payload, second_payload, third_payload
229
230 @classmethod
231 def _build_second_payloads(cls, model_id: str, session_hash: str, text: str, max_tokens: int, temperature: float, top_p: float):
232 first_payload = {
233 "data":[None,model_id,text,{
234 "text_models":[model_id],
235 "all_text_models":[model_id],
236 "vision_models":[],
237 "image_gen_models":[],
238 "all_image_gen_models":[],
239 "search_models":[],
240 "all_search_models":[],
241 "models":[model_id],
242 "all_models":[model_id],
243 "arena_type":"text-arena"}],
244 "event_data": None,
245 "fn_index": 120,
246 "trigger_id": 157,
247 "session_hash": session_hash
248 }
249
250 second_payload = {
251 "data": [],
252 "event_data": None,
253 "fn_index": 121,
254 "trigger_id": 157,
255 "session_hash": session_hash
256 }
257
258 third_payload = {
259 "data": [None, temperature, top_p, max_tokens],
260 "event_data": None,
261 "fn_index": 122,
262 "trigger_id": 157,
263 "session_hash": session_hash
264 }
265
266 return first_payload, second_payload, third_payload
267
268 @classmethod
269 async def create_async_generator(
270 cls, model: str, messages: Messages,
271 media: MediaListType = None,
272 conversation: JsonConversation = None,
273 return_conversation: bool = False,
274 max_tokens: int = 2048,
275 temperature: float = 0.7,
276 top_p: float = 1,
277 proxy: str = None,
278 **kwargs
279 ) -> AsyncResult:
280 if not model:
281 model = cls.default_model
282 if model in cls.model_aliases:
283 model = cls.model_aliases[model]
284 prompt = get_last_user_message(messages)
285 new_conversation = False
286 if conversation is None:
287 conversation = JsonConversation(session_hash=str(uuid.uuid4()).replace("-", ""))
288 new_conversation = True
289 async with StreamSession(impersonate="chrome") as session:
290 if new_conversation:
291 media = list(merge_media(media, messages))
292 if media:
293 data = FormData()
294 for i in range(len(media)):
295 media[i] = (to_bytes(media[i][0]), media[i][1])
296 for image, image_name in media:
297 data.add_field(f"files", image, filename=image_name)
298 async with session.post(f"{cls.url}/upload", params={"upload_id": conversation.session_hash}, data=data) as response:
299 await raise_for_status(response)
300 image_files = await response.json()
301 media = [{
302 "path": image_file,
303 "url": f"{cls.url}/file={image_file}",
304 "orig_name": media[i][1],
305 "size": len(media[i][0]),
306 "mime_type": is_accepted_format(media[i][0]),
307 "meta": {
308 "_type": "gradio.FileData"
309 }
310 } for i, image_file in enumerate(image_files)]
311 first_payload, second_payload, third_payload = cls._build_payloads(model, conversation.session_hash, prompt, media, max_tokens, temperature, top_p)
312 else:
313 first_payload, second_payload, third_payload = cls._build_second_payloads(model, conversation.session_hash, prompt, max_tokens, temperature, top_p)
314
315 headers = {
316 "Content-Type": "application/json",
317 "Accept": "application/json",
318 }
319
320 # POST 1
321 async with session.post(f"{cls.url}{cls.api_endpoint}", json=first_payload, proxy=proxy, headers=headers) as response:
322 await raise_for_status(response)
323
324 # POST 2
325 async with session.post(f"{cls.url}{cls.api_endpoint}", json=second_payload, proxy=proxy, headers=headers) as response:
326 await raise_for_status(response)
327
328 # POST 3
329 async with session.post(f"{cls.url}{cls.api_endpoint}", json=third_payload, proxy=proxy, headers=headers) as response:
330 await raise_for_status(response)
331
332 # Long stream GET
333 async def sse_stream():
334 stream_url = f"{cls.url}/queue/data?session_hash={conversation.session_hash}"
335 async with session.get(stream_url, headers={"Accept": "text/event-stream"}, proxy=proxy) as response:
336 await raise_for_status(response)
337 text_position = 0
338 count = 0
339 async for line in response.iter_lines():
340 if line.startswith(b"data: "):
341 try:
342 msg = json.loads(line[6:])
343 except Exception as e:
344 raise RuntimeError(f"Failed to decode JSON from stream: {line}", e)
345 if msg.get("msg") == "process_generating":
346 data = msg["output"]["data"][1]
347 if data:
348 data = data[0]
349 if len(data) > 2:
350 if isinstance(data[2], list):
351 data[2] = data[2][-1]
352 content = data[2][text_position:]
353 if content.endswith("▌"):
354 content = content[:-2]
355 if content:
356 count += 1
357 yield count, content
358 text_position += len(content)
359 elif msg.get("msg") == "close_stream":
360 break
361 elif msg.get("msg") not in ("process_completed", "process_starts", "estimation"):
362 debug.log(f"Unexpected message: {msg}")
363 count = 0
364 async for count, chunk in sse_stream():
365 yield chunk
366 if count == 0:
367 raise RuntimeError("No response from server.")
368 if return_conversation:
369 yield conversation
370 if count == max_tokens:
371 yield FinishReason("length")
Modified g4f/Provider/PollinationsAI.py +4 -7
@@ -47,9 +47,9 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
47 47 default_image_model = "flux"
48 48 default_vision_model = default_model
49 49 default_audio_model = "openai-audio"
50 text_models = [default_model]
50 text_models = [default_model, "evil"]
51 51 image_models = [default_image_model]
52 audio_models = [default_audio_model]
52 audio_models = {default_audio_model: []}
53 53 extra_image_models = ["flux-pro", "flux-dev", "flux-schnell", "midjourney", "dall-e-3", "turbo"]
54 54 vision_models = [default_vision_model, "gpt-4o-mini", "openai", "openai-large", "searchgpt"]
55 55 _models_loaded = False
@@ -66,9 +66,6 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
66 66 "llama-3.1-8b": "llamalight",
67 67 "llama-3.3-70b": "llama-scaleway",
68 68 "phi-4": "phi",
69 "gemini-2.0": "gemini",
70 "gemini-2.0-flash": "gemini",
71 "gemini-2.0-flash-thinking": "gemini-thinking",
72 69 "deepseek-r1": "deepseek-reasoning-large",
73 70 "deepseek-r1": "deepseek-reasoning",
74 71 "deepseek-v3": "deepseek",
@@ -332,7 +329,7 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
332 329 result = json.loads(line[6:])
333 330 if "error" in result:
334 331 raise ResponseError(result["error"].get("message", result["error"]))
335 if "usage" in result:
332 if result.get("usage") is not None:
336 333 yield Usage(**result["usage"])
337 334 choices = result.get("choices", [{}])
338 335 choice = choices.pop() if choices else {}
@@ -354,7 +351,7 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
354 351 yield ToolCalls(message["tool_calls"])
355 352 else:
356 353 raise ResponseError(result)
357 if "usage" in result:
354 if result.get("usage") is not None:
358 355 yield Usage(**result["usage"])
359 356 finish_reason = choice.get("finish_reason")
360 357 if finish_reason:
Modified g4f/Provider/PollinationsImage.py +2 -1
@@ -8,10 +8,11 @@ from .PollinationsAI import PollinationsAI
8 8
9 9 class PollinationsImage(PollinationsAI):
10 10 label = "PollinationsImage"
11 parent = PollinationsAI.__name__
11 12 default_model = "flux"
12 13 default_vision_model = None
13 14 default_image_model = default_model
14 audio_models = None
15 audio_models = {}
15 16 image_models = [default_image_model] # Default models
16 17 _models_loaded = False # Add a checkbox for synchronization
17 18
Modified g4f/Provider/__init__.py +1 -0
@@ -56,6 +56,7 @@ try:
56 56 from .Jmuz import Jmuz
57 57 from .LambdaChat import LambdaChat
58 58 from .Liaobots import Liaobots
59 from .LMArenaProvider import LMArenaProvider
59 60 from .OIVSCode import OIVSCode
60 61 except ImportError as e:
61 62 debug.error("Providers not loaded (F-L):", e)
Modified g4f/Provider/hf/__init__.py +2 -0
@@ -37,6 +37,8 @@ class HuggingFace(AsyncGeneratorProvider, ProviderModelMixin):
37 37 messages: Messages,
38 38 **kwargs
39 39 ) -> AsyncResult:
40 if model in cls.model_aliases:
41 model = cls.model_aliases[model]
40 42 if "tools" not in kwargs and "media" not in kwargs and random.random() >= 0.5:
41 43 try:
42 44 is_started = False
Modified g4f/Provider/hf_space/DeepseekAI_JanusPro7b.py +3 -1
@@ -13,6 +13,7 @@ from ..helper import format_prompt, format_image_prompt
13 13 from ...providers.response import JsonConversation, ImageResponse, Reasoning
14 14 from ...requests.aiohttp import StreamSession, StreamResponse, FormData
15 15 from ...requests.raise_for_status import raise_for_status
16 from ...tools.media import merge_media
16 17 from ...image import to_bytes, is_accepted_format
17 18 from ...cookies import get_cookies
18 19 from ...errors import ResponseError
@@ -99,7 +100,8 @@ class DeepseekAI_JanusPro7b(AsyncGeneratorProvider, ProviderModelMixin):
99 100 if return_conversation:
100 101 yield conversation
101 102
102 if media is not None:
103 media = list(merge_media(media, messages))
104 if media:
103 105 data = FormData()
104 106 for i in range(len(media)):
105 107 media[i] = (to_bytes(media[i][0]), media[i][1])
Deleted g4f/Provider/hf_space/LMArenaProvider.py +0 -253
@@ -1,253 +0,0 @@
1 from __future__ import annotations
2
3 import json
4 import uuid
5 import asyncio
6
7 from ...typing import AsyncResult, Messages
8 from ...requests import StreamSession, raise_for_status
9 from ...providers.response import FinishReason
10 from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin, AuthFileMixin
11 from ..helper import format_prompt
12 from ... import debug
13
14 class LMArenaProvider(AsyncGeneratorProvider, ProviderModelMixin, AuthFileMixin):
15 label = "LM Arena"
16 url = "https://lmarena.ai"
17 api_endpoint = "/queue/join?"
18
19 working = True
20
21 default_model = "chatgpt-4o-latest-20250326"
22 model_aliases = {"gpt-4o": default_model}
23 models = [
24 default_model,
25 "gpt-4.1-2025-04-14",
26 "gemini-2.5-pro-exp-03-25",
27 "llama-4-maverick-03-26-experimental",
28 "grok-3-preview-02-24",
29 "claude-3-7-sonnet-20250219",
30 "claude-3-7-sonnet-20250219-thinking-32k",
31 "deepseek-v3-0324",
32 "llama-4-maverick-17b-128e-instruct",
33 "gpt-4.1-mini-2025-04-14",
34 "gpt-4.1-nano-2025-04-14",
35 "gemini-2.0-flash-thinking-exp-01-21",
36 "gemini-2.0-flash-001",
37 "gemini-2.0-flash-lite-preview-02-05",
38 "gemma-3-27b-it",
39 "gemma-3-12b-it",
40 "gemma-3-4b-it",
41 "deepseek-r1",
42 "claude-3-5-sonnet-20241022",
43 "o3-mini",
44 "llama-3.3-70b-instruct",
45 "gpt-4o-mini-2024-07-18",
46 "gpt-4o-2024-11-20",
47 "gpt-4o-2024-08-06",
48 "gpt-4o-2024-05-13",
49 "command-a-03-2025",
50 "qwq-32b",
51 "p2l-router-7b",
52 "claude-3-5-haiku-20241022",
53 "claude-3-5-sonnet-20240620",
54 "doubao-1.5-pro-32k-250115",
55 "doubao-1.5-vision-pro-32k-250115",
56 "mistral-small-24b-instruct-2501",
57 "phi-4",
58 "amazon-nova-pro-v1.0",
59 "amazon-nova-lite-v1.0",
60 "amazon-nova-micro-v1.0",
61 "cobalt-exp-beta-v3",
62 "cobalt-exp-beta-v4",
63 "qwen-max-2025-01-25",
64 "qwen-plus-0125-exp",
65 "qwen2.5-vl-32b-instruct",
66 "qwen2.5-vl-72b-instruct",
67 "gemini-1.5-pro-002",
68 "gemini-1.5-flash-002",
69 "gemini-1.5-flash-8b-001",
70 "gemini-1.5-pro-001",
71 "gemini-1.5-flash-001",
72 "llama-3.1-405b-instruct-bf16",
73 "llama-3.3-nemotron-49b-super-v1",
74 "llama-3.1-nemotron-ultra-253b-v1",
75 "llama-3.1-nemotron-70b-instruct",
76 "llama-3.1-70b-instruct",
77 "llama-3.1-8b-instruct",
78 "hunyuan-standard-2025-02-10",
79 "hunyuan-large-2025-02-10",
80 "hunyuan-standard-vision-2024-12-31",
81 "hunyuan-turbo-0110",
82 "hunyuan-turbos-20250226",
83 "mistral-large-2411",
84 "pixtral-large-2411",
85 "mistral-large-2407",
86 "llama-3.1-nemotron-51b-instruct",
87 "granite-3.1-8b-instruct",
88 "granite-3.1-2b-instruct",
89 "step-2-16k-exp-202412",
90 "step-2-16k-202502",
91 "step-1o-vision-32k-highres",
92 "yi-lightning",
93 "glm-4-plus",
94 "glm-4-plus-0111",
95 "jamba-1.5-large",
96 "jamba-1.5-mini",
97 "gemma-2-27b-it",
98 "gemma-2-9b-it",
99 "gemma-2-2b-it",
100 "eureka-chatbot",
101 "claude-3-haiku-20240307",
102 "claude-3-sonnet-20240229",
103 "claude-3-opus-20240229",
104 "nemotron-4-340b",
105 "llama-3-70b-instruct",
106 "llama-3-8b-instruct",
107 "qwen2.5-plus-1127",
108 "qwen2.5-coder-32b-instruct",
109 "qwen2.5-72b-instruct",
110 "qwen-max-0919",
111 "qwen-vl-max-1119",
112 "qwen-vl-max-0809",
113 "llama-3.1-tulu-3-70b",
114 "olmo-2-0325-32b-instruct",
115 "gpt-3.5-turbo-0125",
116 "reka-core-20240904",
117 "reka-flash-20240904",
118 "c4ai-aya-expanse-32b",
119 "c4ai-aya-expanse-8b",
120 "c4ai-aya-vision-32b",
121 "command-r-plus-08-2024",
122 "command-r-08-2024",
123 "codestral-2405",
124 "mixtral-8x22b-instruct-v0.1",
125 "mixtral-8x7b-instruct-v0.1",
126 "pixtral-12b-2409",
127 "ministral-8b-2410"]
128
129 _args: dict = None
130
131 @staticmethod
132 def _random_session_hash():
133 return str(uuid.uuid4())
134
135 @classmethod
136 def _build_payloads(cls, model_id: str, session_hash: str, messages: Messages, max_tokens: int, temperature: float, top_p: float):
137 first_payload = {
138 "data": [
139 None,
140 model_id,
141 {"text": format_prompt(messages), "files": []},
142 {
143 "text_models": [model_id],
144 "all_text_models": [model_id],
145 "vision_models": [],
146 "all_vision_models": [],
147 "image_gen_models": [],
148 "all_image_gen_models": [],
149 "search_models": [],
150 "all_search_models": [],
151 "models": [model_id],
152 "all_models": [model_id],
153 "arena_type": "text-arena"
154 }
155 ],
156 "event_data": None,
157 "fn_index": 117,
158 "trigger_id": 159,
159 "session_hash": session_hash
160 }
161
162 second_payload = {
163 "data": [],
164 "event_data": None,
165 "fn_index": 118,
166 "trigger_id": 159,
167 "session_hash": session_hash
168 }
169
170 third_payload = {
171 "data": [None, temperature, top_p, max_tokens],
172 "event_data": None,
173 "fn_index": 119,
174 "trigger_id": 159,
175 "session_hash": session_hash
176 }
177
178 return first_payload, second_payload, third_payload
179
180 @classmethod
181 async def create_async_generator(
182 cls, model: str, messages: Messages,
183 max_tokens: int = 2048,
184 temperature: float = 0.7,
185 top_p: float = 1,
186 proxy: str = None,
187 **kwargs
188 ) -> AsyncResult:
189 if not model:
190 model = cls.default_model
191 if model in cls.model_aliases:
192 model = cls.model_aliases[model]
193 session_hash = cls._random_session_hash()
194 headers = {
195 "Content-Type": "application/json",
196 "Accept": "application/json"
197 }
198 async with StreamSession(impersonate="chrome", headers=headers) as session:
199 first_payload, second_payload, third_payload = cls._build_payloads(model, session_hash, messages, max_tokens, temperature, top_p)
200 # Long stream GET
201 async def long_stream():
202 # POST 1
203 async with session.post(f"{cls.url}{cls.api_endpoint}", json=first_payload, proxy=proxy) as response:
204 await raise_for_status(response)
205
206 # POST 2
207 async with session.post(f"{cls.url}{cls.api_endpoint}", json=second_payload, proxy=proxy) as response:
208 await raise_for_status(response)
209
210 # POST 3
211 async with session.post(f"{cls.url}{cls.api_endpoint}", json=third_payload, proxy=proxy) as response:
212 await raise_for_status(response)
213
214 stream_url = f"{cls.url}/queue/data?session_hash={session_hash}"
215 async with session.get(stream_url, headers={"Accept": "text/event-stream"}, proxy=proxy) as response:
216 await raise_for_status(response)
217 text_position = 0
218 count = 0
219 async for line in response.iter_lines():
220 if line.startswith(b"data: "):
221 try:
222 msg = json.loads(line[6:])
223 except Exception as e:
224 raise RuntimeError(f"Failed to decode JSON from stream: {line}", e)
225 if msg.get("msg") == "process_generating":
226 data = msg["output"]["data"][1]
227 if data:
228 data = data[0]
229 if len(data) > 2:
230 if isinstance(data[2], list):
231 data[2] = data[2][-1]
232 content = data[2][text_position:]
233 if content.endswith("▌"):
234 content = content[:-2]
235 if content:
236 count += 1
237 yield count, content
238 text_position += len(content)
239 elif msg.get("msg") == "close_stream":
240 break
241 elif msg.get("msg") not in ("process_completed", "process_starts", "estimation"):
242 debug.log(f"Unexpected message: {msg}")
243 count = 0
244 async for count, chunk in long_stream():
245 yield chunk
246 if count == 0:
247 await asyncio.sleep(10)
248 async for count, chunk in long_stream():
249 yield chunk
250 if count == 0:
251 raise RuntimeError("No response from server.")
252 if count == max_tokens:
253 yield FinishReason("length")
Modified g4f/Provider/hf_space/__init__.py +0 -2
Modified g4f/Provider/needs_auth/Gemini.py +0 -4
Modified g4f/Provider/needs_auth/GigaChat.py +1 -0
Modified g4f/api/__init__.py +11 -9
Modified g4f/client/__init__.py +14 -17
Modified g4f/gui/client/static/js/chat.v1.js +7 -2
Modified g4f/gui/server/api.py +11 -9
Modified g4f/gui/server/backend_api.py +2 -1
Modified g4f/models.py +2 -2
Added g4f/providers/any_provider.py +181 -0
Modified g4f/providers/base_provider.py +2 -0
Modified g4f/providers/retry_provider.py +11 -8