返回提交历史
Modified
g4f/Provider/Copilot.py
+5
-2
Modified
g4f/Provider/local/Ollama.py
+21
-17
Modified
g4f/Provider/needs_auth/Azure.py
+2
-3
Modified
g4f/Provider/needs_auth/GeminiPro.py
+5
-181
Modified
g4f/Provider/needs_auth/Groq.py
+1
-22
Modified
g4f/Provider/needs_auth/Nvidia.py
+1
-2
Modified
g4f/Provider/needs_auth/OpenRouter.py
+1
-0
Modified
g4f/Provider/template/OpenaiTemplate.py
+11
-3
Modified
g4f/gui/server/api.py
+2
-0
Modified
g4f/providers/response.py
+3
-0
XFEstudio/gpt4free
Refactor providers to use OpenaiTemplate and add backup URLs; enhance response handling in API
798a951b
代码差异
10 个文件
+52
-230
@@ -191,7 +191,7 @@ class Copilot(AsyncAuthedProvider, ProviderModelMixin):
191
191
response.raise_for_status()
192
192
debug.log(f"Copilot: Update cookies: [{', '.join(key for key in response.cookies)}]")
193
193
auth_result.cookies.update({key: value for key, value in response.cookies.items()})
194
if not cls.needs_auth and cls.anon_cookie_name not in auth_result.cookies:
194
if not getattr(auth_result, "access_token", None) and not cls.needs_auth and cls.anon_cookie_name not in auth_result.cookies:
195
195
raise MissingAuthError(f"Missing cookie: {cls.anon_cookie_name}")
196
196
conversation = Conversation(response.json().get("currentConversationId"))
197
197
debug.log(f"Copilot: Created conversation: {conversation.conversation_id}")
@@ -360,14 +360,17 @@ async def get_access_token_and_cookies(url: str, proxy: str = None, needs_auth:
360
360
debug.log(f"Got access token: {access_token[:10]}..., useridentitytype: {useridentitytype}")
361
361
break
362
362
if not needs_auth:
363
debug.log("No access token found, but authentication not required.")
363
364
break
364
365
if not needs_auth:
365
366
textarea = await page.select("textarea")
366
367
if textarea is not None:
368
debug.log("Filling textarea to generate anon cookie.")
367
369
await textarea.send_keys("Hello")
368
370
await asyncio.sleep(1)
369
371
button = await page.select("[data-testid=\"submit-button\"]")
370
372
if button:
373
debug.log("Clicking submit button to generate anon cookie.")
371
374
await button.click()
372
375
turnstile = await page.select('#cf-turnstile')
373
376
if turnstile:
@@ -375,7 +378,7 @@ async def get_access_token_and_cookies(url: str, proxy: str = None, needs_auth:
375
378
await asyncio.sleep(3)
376
379
await click_trunstile(page)
377
380
cookies = {}
378
while Copilot.anon_cookie_name not in cookies:
381
while not access_token and Copilot.anon_cookie_name not in cookies:
379
382
await asyncio.sleep(2)
380
383
cookies = {c.name: c.value for c in await page.send(nodriver.cdp.network.get_cookies([url]))}
381
384
if not needs_auth and Copilot.anon_cookie_name in cookies:
@@ -4,17 +4,17 @@ import json
4
4
import requests
5
5
import os
6
6
7
from ..needs_auth.OpenaiAPI import OpenaiAPI
7
from ..template import OpenaiTemplate
8
8
from ...requests import StreamSession, raise_for_status
9
9
from ...providers.response import Usage, Reasoning
10
10
from ...tools.run_tools import AuthManager
11
11
from ...typing import AsyncResult, Messages
12
12
13
class Ollama(OpenaiAPI):
13
class Ollama(OpenaiTemplate):
14
14
label = "Ollama 🦙"
15
15
url = "https://ollama.com"
16
16
login_url = "https://ollama.com/settings/keys"
17
api_endpoint = "https://ollama.com/api/chat"
17
backup_url = "https://g4f.dev/api/ollama"
18
18
needs_auth = False
19
19
working = True
20
20
active_by_default = True
@@ -30,11 +30,10 @@ class Ollama(OpenaiAPI):
30
30
cls.models = []
31
31
if not api_key:
32
32
api_key = AuthManager.load_api_key(cls)
33
if api_key:
34
models = requests.get("https://ollama.com/api/tags", {"headers": {"Authorization": f"Bearer {api_key}"}}).json()["models"]
35
if models:
36
cls.live += 1
37
cls.models = [model["name"] for model in models]
33
models = requests.get("https://ollama.com/api/tags").json()["models"]
34
if models:
35
cls.live += 1
36
cls.models = [model["name"] for model in models]
38
37
if base_url is None:
39
38
host = os.getenv("OLLAMA_HOST", "127.0.0.1")
40
39
port = os.getenv("OLLAMA_PORT", "11434")
@@ -48,7 +47,7 @@ class Ollama(OpenaiAPI):
48
47
if cls.live == 0 and models:
49
48
cls.live += 1
50
49
cls.local_models = [model["name"] for model in models]
51
cls.models = cls.models + cls.local_models
50
cls.models = cls.models.copy() + cls.local_models
52
51
cls.default_model = next(iter(cls.models), None)
53
52
return cls.models
54
53
@@ -66,14 +65,9 @@ class Ollama(OpenaiAPI):
66
65
host = os.getenv("OLLAMA_HOST", "localhost")
67
66
port = os.getenv("OLLAMA_PORT", "11434")
68
67
base_url: str = f"http://{host}:{port}/v1"
69
if model in cls.local_models or not api_key:
70
async for chunk in super().create_async_generator(
71
model, messages, base_url=base_url, proxy=proxy, **kwargs
72
):
73
yield chunk
74
else:
68
if model in cls.local_models:
75
69
async with StreamSession(headers={"Authorization": f"Bearer {api_key}"}, proxy=proxy) as session:
76
async with session.post(cls.api_endpoint, json={
70
async with session.post(f"{base_url}/api/chat", json={
77
71
"model": model,
78
72
"messages": messages,
79
73
}) as response:
@@ -92,4 +86,14 @@ class Ollama(OpenaiAPI):
92
86
prompt_tokens=last_data.get("prompt_eval_count", 0),
93
87
completion_tokens=last_data.get("eval_count", 0),
94
88
total_tokens=last_data.get("prompt_eval_count", 0) + last_data.get("eval_count", 0),
95
)
89
)
90
else:
91
async for chunk in super().create_async_generator(
92
model,
93
messages,
94
api_key=api_key,
95
base_url=cls.backup_url,
96
proxy=proxy,
97
**kwargs
98
):
99
yield chunk
@@ -14,10 +14,9 @@ from ..helper import format_media_prompt
14
14
class Azure(OpenaiTemplate):
15
15
label = "Azure ☁️"
16
16
url = "https://ai.azure.com"
17
base_url = "https://host.g4f.dev/api/Azure"
17
base_url = "https://g4f.dev/api/azure"
18
backup_url = "https://g4f.dev/api/azure"
18
19
working = True
19
needs_auth = True
20
models_needs_auth = True
21
20
active_by_default = False
22
21
login_url = "https://discord.gg/qXA4Wf4Fsm"
23
22
routes: dict[str, str] = {}
@@ -1,33 +1,15 @@
1
1
from __future__ import annotations
2
2
3
import base64
4
import json
5
import requests
6
from typing import Optional
7
from aiohttp import ClientSession, BaseConnector
3
from ..template import OpenaiTemplate
8
4
9
from ...typing import AsyncResult, Messages, MediaListType
10
from ...image import to_bytes, is_data_an_media
11
from ...errors import MissingAuthError, ModelNotFoundError
12
from ...requests import raise_for_status, iter_lines
13
from ...providers.response import Usage, FinishReason
14
from ...image.copy_images import save_response_media
15
from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin
16
from ..helper import get_connector, to_string, format_media_prompt, get_system_prompt
17
from ... import debug
18
19
class GeminiPro(AsyncGeneratorProvider, ProviderModelMixin):
5
class GeminiPro(OpenaiTemplate):
20
6
label = "Google Gemini API"
21
7
url = "https://ai.google.dev"
22
8
login_url = "https://aistudio.google.com/u/0/apikey"
23
base_url = "https://generativelanguage.googleapis.com/v1beta"
9
base_url = "https://generativelanguage.googleapis.com/v1beta/openai"
10
backup_url = "https://g4f.dev/custom/srv_mjnryskw9fe0567fa267"
24
11
active_by_default = True
25
26
12
working = True
27
supports_message_history = True
28
supports_system_message = True
29
needs_auth = True
30
31
13
default_model = "gemini-2.5-flash"
32
14
default_vision_model = default_model
33
15
fallback_models = [
@@ -39,162 +21,4 @@ class GeminiPro(AsyncGeneratorProvider, ProviderModelMixin):
39
21
"gemma-3-4b-it",
40
22
"gemma-3n-e2b-it",
41
23
"gemma-3n-e4b-it",
42
]
43
44
@classmethod
45
def get_models(cls, api_key: str = None, base_url: str = base_url, **kwargs) -> list[str]:
46
if not api_key:
47
return cls.fallback_models
48
if not cls.models:
49
try:
50
url = f"{cls.base_url if not base_url else base_url}/models"
51
response = requests.get(url, params={"key": api_key})
52
raise_for_status(response)
53
data = response.json()
54
cls.models = [
55
model.get("name").split("/").pop()
56
for model in data.get("models")
57
if "generateContent" in model.get("supportedGenerationMethods")
58
]
59
cls.models.sort()
60
cls.live += 1
61
except Exception as e:
62
debug.error(e)
63
if api_key is not None:
64
raise MissingAuthError("Invalid API key")
65
return cls.fallback_models
66
return cls.models
67
68
@classmethod
69
async def create_async_generator(
70
cls,
71
model: str,
72
messages: Messages,
73
stream: bool = False,
74
proxy: str = None,
75
api_key: str = None,
76
base_url: str = base_url,
77
use_auth_header: bool = False,
78
media: MediaListType = None,
79
tools: Optional[list] = None,
80
connector: BaseConnector = None,
81
**kwargs
82
) -> AsyncResult:
83
if not api_key:
84
raise MissingAuthError('Add a "api_key"')
85
86
try:
87
model = cls.get_model(model, api_key=api_key, base_url=base_url)
88
except ModelNotFoundError:
89
pass
90
91
headers = params = None
92
if use_auth_header:
93
headers = {"Authorization": f"Bearer {api_key}"}
94
else:
95
params = {"key": api_key}
96
97
method = "streamGenerateContent" if stream else "generateContent"
98
url = f"{base_url.rstrip('/')}/models/{model}:{method}"
99
async with ClientSession(headers=headers, connector=get_connector(connector, proxy)) as session:
100
contents = [
101
{
102
"role": "model" if message["role"] == "assistant" else "user",
103
"parts": [{"text": to_string(message["content"])}]
104
}
105
for message in messages
106
if message["role"] not in ["system", "developer"]
107
]
108
if media is not None:
109
if not contents:
110
contents.append({"role": "user", "parts": []})
111
for media_data, filename in media:
112
media_data = to_bytes(media_data)
113
contents[-1]["parts"].append({
114
"inline_data": {
115
"mime_type": is_data_an_media(media_data, filename),
116
"data": base64.b64encode(media_data).decode()
117
}
118
})
119
responseModalities = {"responseModalities": ["AUDIO"]} if "tts" in model else {}
120
data = {
121
"contents": contents,
122
"generationConfig": {
123
"stopSequences": kwargs.get("stop"),
124
"temperature": kwargs.get("temperature"),
125
"maxOutputTokens": kwargs.get("max_tokens"),
126
"topP": kwargs.get("top_p"),
127
"topK": kwargs.get("top_k"),
128
**responseModalities,
129
},
130
"tools": [{
131
"function_declarations": [{
132
"name": tool["function"]["name"],
133
"description": tool["function"]["description"],
134
"parameters": {
135
"type": "object",
136
"properties": {key: {
137
"type": value["type"],
138
"description": value["title"]
139
} for key, value in tool["function"]["parameters"]["properties"].items()}
140
},
141
} for tool in tools]
142
}] if tools else None
143
}
144
system_prompt = get_system_prompt(messages)
145
if system_prompt:
146
data["system_instruction"] = {"parts": {"text": system_prompt}}
147
async with session.post(url, params=params, json=data) as response:
148
if not response.ok:
149
data = await response.json()
150
data = data[0] if isinstance(data, list) else data
151
raise RuntimeError(f"Response {response.status}: {data['error']['message']}")
152
if stream:
153
lines = []
154
buffer = b""
155
async for chunk in iter_lines(response.content.iter_any()):
156
buffer += chunk
157
if chunk == b"[{":
158
lines = [b"{"]
159
elif chunk == b"," or chunk == b"]":
160
try:
161
data = json.loads(b"".join(lines))
162
content = data["candidates"][0]["content"]
163
if "parts" in content and content["parts"]:
164
if "text" in content["parts"][0]:
165
yield content["parts"][0]["text"]
166
elif "inlineData" in content["parts"][0]:
167
async for media in save_response_media(
168
content["parts"][0]["inlineData"], format_media_prompt(messages)
169
):
170
yield media
171
if "finishReason" in data["candidates"][0]:
172
yield FinishReason(data["candidates"][0]["finishReason"].lower())
173
usage = data.get("usageMetadata")
174
if usage:
175
yield Usage(
176
prompt_tokens=usage.get("promptTokenCount"),
177
completion_tokens=usage.get("candidatesTokenCount"),
178
total_tokens=usage.get("totalTokenCount")
179
)
180
except Exception as e:
181
raise RuntimeError(f"Read chunk failed") from e
182
lines = []
183
else:
184
lines.append(chunk)
185
else:
186
data = await response.json()
187
candidate = data["candidates"][0]
188
if "content" in candidate:
189
content = candidate["content"]
190
if "parts" in content and content["parts"]:
191
for part in content["parts"]:
192
if "text" in part:
193
yield part["text"]
194
elif "inlineData" in part:
195
async for media in save_response_media(
196
part["inlineData"], format_media_prompt(messages)
197
):
198
yield media
199
if "finishReason" in candidate:
200
yield FinishReason(candidate["finishReason"].lower())
24
]
@@ -7,31 +7,10 @@ class Groq(OpenaiTemplate):
7
7
url = "https://console.groq.com/playground"
8
8
login_url = "https://console.groq.com/keys"
9
9
base_url = "https://api.groq.com/openai/v1"
10
backup_url = "https://g4f.dev/api/groq"
10
11
working = True
11
needs_auth = True
12
models_needs_auth = True
13
12
active_by_default = True
14
13
default_model = DEFAULT_MODEL
15
fallback_models = [
16
"distil-whisper-large-v3-en",
17
"gemma2-9b-it",
18
"gemma-7b-it",
19
"llama3-groq-70b-8192-tool-use-preview",
20
"llama3-groq-8b-8192-tool-use-preview",
21
"llama-3.1-70b-versatile",
22
"llama-3.1-8b-instant",
23
"llama-3.2-1b-preview",
24
"llama-3.2-3b-preview",
25
"llama-3.2-11b-vision-preview",
26
"llama-3.2-90b-vision-preview",
27
"llama-guard-3-8b",
28
"llava-v1.5-7b-4096-preview",
29
"llama3-70b-8192",
30
"llama3-8b-8192",
31
"mixtral-8x7b-32768",
32
"whisper-large-v3",
33
"whisper-large-v3-turbo",
34
]
35
14
model_aliases = {
36
15
"mixtral-8x7b": "mixtral-8x7b-32768",
37
16
"llama2-70b": "llama2-70b-4096",
@@ -6,11 +6,10 @@ from ...config import DEFAULT_MODEL
6
6
class Nvidia(OpenaiTemplate):
7
7
label = "Nvidia"
8
8
base_url = "https://integrate.api.nvidia.com/v1"
9
backup_url = "https://g4f.dev/api/nvidia"
9
10
login_url = "https://google.com"
10
11
url = "https://build.nvidia.com"
11
12
working = True
12
13
active_by_default = True
13
needs_auth = True
14
models_needs_auth = True
15
14
default_model = DEFAULT_MODEL
16
15
add_user = False
@@ -13,6 +13,7 @@ class OpenRouter(OpenaiTemplate):
13
13
14
14
class OpenRouterFree(OpenRouter):
15
15
label = "OpenRouter (free)"
16
backup_url = "https://g4f.dev/api/openrouter"
16
17
max_tokens = 4096
17
18
active_by_default = True
18
19
@@ -16,6 +16,7 @@ from ... import debug
16
16
17
17
class OpenaiTemplate(AsyncGeneratorProvider, ProviderModelMixin, RaiseErrorMixin):
18
18
base_url = ""
19
backup_url = None
19
20
api_key = None
20
21
api_endpoint = None
21
22
supports_message_history = True
@@ -30,16 +31,22 @@ class OpenaiTemplate(AsyncGeneratorProvider, ProviderModelMixin, RaiseErrorMixin
30
31
use_image_size = False
31
32
max_tokens: int = None
32
33
34
@classmethod
35
def is_provider_api_key(cls, api_key: str) -> bool:
36
if cls.backup_url is None:
37
return True
38
return api_key and not api_key.startswith("g4f-") and not api_key.startswith("gfs-")
39
33
40
@classmethod
34
41
def get_models(cls, api_key: str = None, base_url: str = None, timeout: int = None) -> list[str]:
35
42
if not cls.models:
36
43
try:
37
if base_url is None:
38
base_url = cls.base_url
39
44
if api_key is None and cls.api_key is not None:
40
45
api_key = cls.api_key
41
46
if not api_key:
42
47
api_key = AuthManager.load_api_key(cls)
48
if base_url is None:
49
base_url = cls.base_url if cls.is_provider_api_key(api_key) else cls.backup_url
43
50
if cls.models_needs_auth and not api_key:
44
51
raise MissingAuthError('Add a "api_key"')
45
52
response = requests.get(f"{base_url}/models", headers=cls.get_headers(False, api_key), verify=cls.ssl, timeout=timeout)
@@ -102,7 +109,7 @@ class OpenaiTemplate(AsyncGeneratorProvider, ProviderModelMixin, RaiseErrorMixin
102
109
) as session:
103
110
model = cls.get_model(model, api_key=api_key, base_url=base_url)
104
111
if base_url is None:
105
base_url = cls.base_url
112
base_url = cls.base_url if cls.is_provider_api_key(api_key) else cls.backup_url
106
113
107
114
# Proxy for image generation feature
108
115
if model and model in cls.image_models:
@@ -165,6 +172,7 @@ class OpenaiTemplate(AsyncGeneratorProvider, ProviderModelMixin, RaiseErrorMixin
165
172
}
166
173
167
174
async def read_response(response: StreamResponse, stream: bool, prompt: str, provider_info: dict, download_media: bool) -> AsyncResult:
175
yield HeadersResponse.from_dict({key: value for key, value in response.headers.items() if key.lower().startswith("x-")})
168
176
content_type = response.headers.get("content-type", "text/event-stream" if stream else "application/json")
169
177
if content_type.startswith("application/json"):
170
178
data = await response.json()
@@ -287,6 +287,8 @@ class Api:
287
287
yield self._format_json("response", chunk.get_dict())
288
288
elif isinstance(chunk, PlainTextResponse):
289
289
yield self._format_json("response", chunk.text)
290
elif isinstance(chunk, HeadersResponse):
291
yield self._format_json("headers", chunk.get_dict())
290
292
else:
291
293
yield self._format_json("content", str(chunk))
292
294
except MissingAuthError as e:
@@ -178,6 +178,9 @@ class HiddenResponse(ResponseType):
178
178
def __str__(self) -> str:
179
179
"""Hidden responses return an empty string."""
180
180
return ""
181
182
class HeadersResponse(HiddenResponse, ObjectMixin):
183
pass
181
184
182
185
class JsonRequest(HiddenResponse, ObjectMixin):
183
186
pass