返回提交历史
Modified
g4f/Provider/Pollinations.py
+1
-0
Modified
g4f/Provider/__init__.py
+7
-3
Deleted
g4f/Provider/needs_auth/OrcaRouter.py
+0
-22
Modified
g4f/Provider/needs_auth/__init__.py
+0
-1
Modified
g4f/Provider/template/OpenaiTemplate.py
+0
-2
Modified
g4f/api/__init__.py
+12
-7
Modified
g4f/mcp/pa_provider.py
+6
-6
Modified
g4f/models.py
+3
-1
Modified
g4f/providers/any_provider.py
+3
-3
Modified
g4f/providers/base_provider.py
+18
-3
Modified
g4f/providers/response.py
+4
-1
XFEstudio/gpt4free
Add more default providers
52d4154a
代码差异
11 个文件
+54
-49
@@ -43,6 +43,7 @@ class Pollinations(AsyncGeneratorProvider, ProviderModelMixin):
43
43
backup_url = "https://g4f.space/api/pollinations"
44
44
active_by_default = True
45
45
working = True
46
supports_native_tools = True
46
47
47
48
# API endpoints
48
49
text_api_endpoint = "https://text.pollinations.ai/openai"
@@ -265,9 +265,13 @@ def _resolve_provider(name: str) -> ProviderType:
265
265
266
266
return OpenRouterFree
267
267
elif name == "OrcaRouter":
268
from g4f.Provider.needs_auth.OrcaRouter import OrcaRouter
269
270
return OrcaRouter
268
from ..client.factory import AbstractClientFactory
269
_loaded_providers[name] = AbstractClientFactory.create_provider(
270
None, name.lower()
271
)
272
_loaded_providers[name].__name__ = name
273
_loaded_providers[name].active_by_default = True
274
return _loaded_providers[name]
271
275
elif name == "OpenaiAPI":
272
276
from g4f.Provider.needs_auth.OpenaiAPI import OpenaiAPI
273
277
@@ -1,22 +0,0 @@
1
from __future__ import annotations
2
3
from ..template import OpenaiTemplate
4
5
6
class OrcaRouter(OpenaiTemplate):
7
label = "OrcaRouter"
8
url = "https://www.orcarouter.ai"
9
login_url = "https://www.orcarouter.ai"
10
base_url = "https://api.orcarouter.ai/v1"
11
working = True
12
needs_auth = True
13
default_model = "orcarouter/auto"
14
15
@classmethod
16
def get_headers(
17
cls, stream: bool, api_key: str = None, headers: dict = None
18
) -> dict:
19
return {
20
**super().get_headers(stream, api_key, headers),
21
**({"Authorization": f"Bearer {api_key}"} if api_key else {}),
22
}
@@ -33,7 +33,6 @@ from .OpenaiAPI import OpenaiAPI
33
33
from .OpenaiChat import OpenaiChat
34
34
from .OpenRouter import OpenRouter
35
35
from .OpenRouter import OpenRouterFree
36
from .OrcaRouter import OrcaRouter
37
36
from .PerplexityApi import PerplexityApi
38
37
from .Pi import Pi
39
38
from .Puter import Puter
@@ -187,7 +187,6 @@ class OpenaiTemplate(AsyncGeneratorProvider, ProviderModelMixin, RaiseErrorMixin
187
187
model: str,
188
188
messages: Messages,
189
189
proxy: str = None,
190
timeout: int = 120,
191
190
conversation: JsonConversation = None,
192
191
media: MediaListType = None,
193
192
api_key: str = None,
@@ -230,7 +229,6 @@ class OpenaiTemplate(AsyncGeneratorProvider, ProviderModelMixin, RaiseErrorMixin
230
229
async with StreamSession(
231
230
proxy=proxy,
232
231
headers=cls.get_headers(stream, api_key, headers),
233
timeout=timeout,
234
232
impersonate=impersonate,
235
233
) as session:
236
234
model = cls.get_model(model, api_key=api_key, base_url=base_url)
@@ -798,7 +798,7 @@ class Api:
798
798
}
799
799
800
800
@self.app.post("/v1/chat/completions", responses=responses)
801
@self.app.post("/api/{provider:path}/chat/completions", responses=responses)
801
@self.app.post("/{mode}/{provider:path}/chat/completions", responses=responses)
802
802
@self.app.post(
803
803
"/api/{provider}/{conversation_id}/chat/completions", responses=responses
804
804
)
@@ -807,10 +807,15 @@ class Api:
807
807
credentials: Annotated[
808
808
HTTPAuthorizationCredentials, Depends(Api.security)
809
809
] = None,
810
provider: str = None,
811
conversation_id: str = None,
812
x_user: Annotated[str | None, Header()] = None,
810
mode: str | None = None,
811
provider: str | None = None,
812
conversation_id: str | None = None,
813
x_user: Annotated[str | None, Header()] | None = None,
813
814
):
815
if mode == "raw":
816
config.raw = True
817
elif mode == "custom":
818
provider = f"{mode}:{provider}"
814
819
if provider is not None:
815
820
config.provider = provider
816
821
if config.provider is None:
@@ -972,9 +977,9 @@ class Api:
972
977
config: ResponsesConfig,
973
978
credentials: Annotated[
974
979
HTTPAuthorizationCredentials, Depends(Api.security)
975
] = None,
976
provider: str = None,
977
x_user: Annotated[str | None, Header()] = None,
980
] | None = None,
981
provider: str | None = None,
982
x_user: Annotated[str | None, Header(alias="X-User")] | None = None,
978
983
):
979
984
if provider is not None:
980
985
config.provider = provider
@@ -318,10 +318,12 @@ def _load_workspace_module(
318
318
# Skip .pa.py files — those are providers, not importable modules
319
319
if not candidate.name.endswith(".pa.py"):
320
320
source_path = candidate
321
pkg_init = candidate.parent / "__init__.py"
321
322
break
322
323
if source_path is None:
323
324
for candidate in workspace.rglob(f"{name}/__init__.py"):
324
325
source_path = candidate
326
pkg_init = candidate
325
327
break
326
328
327
329
if source_path is None:
@@ -530,12 +532,10 @@ def _make_safe_globals(
530
532
"""open() restricted to the workspace directory."""
531
533
path = Path(file)
532
534
if not path.is_absolute():
533
parent = file_path.parent if file_path else workspace
534
while parent != parent.parent:
535
path = parent / path.name
536
if path.exists():
537
break
538
parent = parent.parent
535
if file_path is None:
536
path = workspace / path
537
else:
538
path = file_path.parent / path.name
539
539
try:
540
540
resolved = path.resolve()
541
541
ws_resolved = workspace.resolve()
@@ -127,9 +127,11 @@ default = Model(
127
127
"GLM",
128
128
"Pollinations",
129
129
"Qwen",
130
"Together",
131
130
"TeachAnything",
132
131
"OpenaiChat",
132
"OrcaRouter",
133
"OpenCode",
134
"KiloCode",
133
135
]
134
136
),
135
137
)
@@ -82,7 +82,7 @@ LABELS = {
82
82
class AnyModelProviderMixin(ProviderModelMixin):
83
83
"""Mixin to provide model-related methods for providers."""
84
84
85
default_model = "default"
85
default_model = "auto"
86
86
audio_models = audio_models
87
87
image_models = image_models
88
88
vision_models = vision_models
@@ -302,7 +302,7 @@ class AnyModelProviderMixin(ProviderModelMixin):
302
302
groups = {key: [] for key in LABELS.keys()}
303
303
304
304
# Always add default first
305
groups["default"].append("default")
305
groups["default"].append("auto")
306
306
307
307
groups["custom"] = list(RouterConfig.routes.keys())
308
308
@@ -408,7 +408,7 @@ class AnyProvider(AsyncGeneratorProvider, AnyModelProviderMixin):
408
408
**kwargs,
409
409
) -> AsyncResult:
410
410
providers = []
411
if not model or model == cls.default_model:
411
if not model or model == cls.default_model or model == "default":
412
412
model = ""
413
413
has_image = False
414
414
has_audio = False
@@ -107,8 +107,15 @@ async def wait_for(response: AsyncIterator, timeout: int = None) -> AsyncIterato
107
107
if timeout is not None:
108
108
while True:
109
109
try:
110
yield await asyncio.wait_for(response.__anext__(), timeout=timeout)
110
async def wait_for_next():
111
try:
112
return await response.__anext__()
113
except TimeoutError as e:
114
raise TimeoutError(str(e) or "The operation timed out") from e
115
yield await asyncio.wait_for(wait_for_next(), timeout=timeout)
111
116
except TimeoutError as e:
117
if str(e):
118
raise TimeoutError(str(e)) from e
112
119
raise TimeoutError(
113
120
"The operation timed out after {} seconds".format(timeout)
114
121
) from e
@@ -365,8 +372,8 @@ class ProviderModelMixin:
365
372
366
373
@classmethod
367
374
def get_model(cls, model: str, **kwargs) -> str:
368
if not model and cls.default_model is not None:
369
model = cls.default_model
375
if (not model or model == "auto") and cls.default_model is not None:
376
return cls.default_model
370
377
if cls.model_aliases is not None and model in cls.model_aliases:
371
378
alias = cls.model_aliases[model]
372
379
if isinstance(alias, list):
@@ -377,6 +384,14 @@ class ProviderModelMixin:
377
384
return selected_model
378
385
debug.log(f"{cls.__name__}: Using model '{alias}' for alias '{model}'")
379
386
return alias
387
for m in cls.get_models(**kwargs):
388
if not model or model == "auto":
389
if m == "auto" or m.endswith("/free"):
390
cls.default_model = m
391
return m
392
elif m.lower().endswith(model.lower()):
393
debug.log(f"{cls.__name__}: Using model '{m}'")
394
return m
380
395
return model
381
396
382
397
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
import re
4
4
import base64
5
5
import html
6
import json
6
7
from typing import Union, Dict, List, Optional
7
8
from abc import abstractmethod
8
9
from urllib.parse import quote, unquote
@@ -208,7 +209,9 @@ class ObjectMixin:
208
209
class JsonResponse(ResponseType, ObjectMixin):
209
210
def __str__(self) -> str:
210
211
return str(self.get_dict())
211
212
def model_dump_json(self, *args, **kwargs) -> str:
213
"""Return a JSON string representation of the object."""
214
return json.dumps(self.get_dict())
212
215
213
216
class HiddenResponse(ResponseType):
214
217
def __str__(self) -> str: