返回提交历史
Modified
g4f/Provider/ChatGpt.py
+1
-1
Modified
g4f/Provider/Copilot.py
+0
-1
Modified
g4f/Provider/needs_auth/CopilotAccount.py
+45
-3
Modified
g4f/Provider/needs_auth/OpenaiAccount.py
+1
-6
Modified
g4f/Provider/needs_auth/OpenaiChat.py
+3
-29
Modified
g4f/Provider/needs_auth/OpenaiTemplate.py
+6
-5
Modified
g4f/client/__init__.py
+4
-0
Modified
g4f/client/image_models.py
+6
-5
Modified
g4f/gui/client/static/js/chat.v1.js
+16
-10
Modified
g4f/gui/server/api.py
+22
-29
Modified
g4f/gui/server/backend_api.py
+2
-1
Modified
g4f/image.py
+36
-24
Modified
g4f/models.py
+10
-7
Modified
g4f/providers/base_provider.py
+1
-1
Modified
g4f/providers/response.py
+4
-0
Modified
g4f/providers/retry_provider.py
+5
-1
Modified
g4f/requests/curl_cffi.py
+2
-2
Modified
g4f/requests/raise_for_status.py
+4
-2
Modified
requirements-slim.txt
+2
-1
Modified
setup.py
+0
-1
XFEstudio/gpt4free
Add AsyncAuthedProvider in Copilot Add orginal url to downloaded image Support ssl argument in StreamSession Report Provider and Errors in RetryProvider Support ssl argument in OpenaiTemplate Remove model duplication in OpenaiChat Disable ChatGpt provider and remove it from models.py Update slim requirements Support provider names as model name in Image generation Add model qwen-2.5-1m-demo to models.py
9524c3f3
代码差异
20 个文件
+170
-129
@@ -76,7 +76,7 @@ def init_session(user_agent):
76
76
class ChatGpt(AbstractProvider, ProviderModelMixin):
77
77
label = "ChatGpt"
78
78
url = "https://chatgpt.com"
79
working = True
79
working = False
80
80
supports_message_history = True
81
81
supports_system_message = True
82
82
supports_stream = True
@@ -92,7 +92,6 @@ class Copilot(AbstractProvider, ProviderModelMixin):
92
92
cls._access_token, cls._cookies = asyncio.run(get_access_token_and_cookies(cls.url, proxy))
93
93
else:
94
94
raise h
95
yield Parameters(**{"api_key": cls._access_token, "cookies": cls._cookies if isinstance(cls._cookies, dict) else {c.name: c.value for c in cls._cookies}})
96
95
websocket_url = f"{websocket_url}&accessToken={quote(cls._access_token)}"
97
96
headers = {"authorization": f"Bearer {cls._access_token}"}
98
97
@@ -1,8 +1,16 @@
1
1
from __future__ import annotations
2
2
3
from ..Copilot import Copilot
3
import os
4
from typing import AsyncIterator
4
5
5
class CopilotAccount(Copilot):
6
from ..base_provider import AsyncAuthedProvider
7
from ..Copilot import Copilot, readHAR, has_nodriver, get_access_token_and_cookies
8
from ...providers.response import AuthResult, RequestLogin
9
from ...typing import AsyncResult, Messages
10
from ...errors import NoValidHarFileError
11
from ... import debug
12
13
class CopilotAccount(AsyncAuthedProvider, Copilot):
6
14
needs_auth = True
7
15
use_nodriver = True
8
16
parent = "Copilot"
@@ -12,4 +20,38 @@ class CopilotAccount(Copilot):
12
20
image_models = models
13
21
model_aliases = {
14
22
"dall-e-3": default_model
15
}
23
}
24
25
@classmethod
26
async def on_auth_async(cls, proxy: str = None, **kwargs) -> AsyncIterator:
27
if cls._access_token is None:
28
try:
29
cls._access_token, cls._cookies = readHAR(cls.url)
30
except NoValidHarFileError as h:
31
debug.log(f"Copilot: {h}")
32
if has_nodriver:
33
login_url = os.environ.get("G4F_LOGIN_URL")
34
if login_url:
35
yield RequestLogin(cls.label, login_url)
36
cls._access_token, cls._cookies = await get_access_token_and_cookies(cls.url, proxy)
37
else:
38
raise h
39
yield AuthResult(
40
api_key=cls._access_token,
41
cookies=cls._cookies,
42
)
43
44
@classmethod
45
async def create_authed(
46
cls,
47
model: str,
48
messages: Messages,
49
auth_result: AuthResult,
50
**kwargs
51
) -> AsyncResult:
52
Copilot._access_token = getattr(auth_result, "api_key")
53
Copilot._cookies = getattr(auth_result, "cookies")
54
Copilot.needs_auth = cls.needs_auth
55
for chunk in Copilot.create_completion(model, messages, **kwargs):
56
yield chunk
57
auth_result.cookies = Copilot._cookies if isinstance(Copilot._cookies, dict) else {c.name: c.value for c in Copilot._cookies}
@@ -4,9 +4,4 @@ from .OpenaiChat import OpenaiChat
4
4
5
5
class OpenaiAccount(OpenaiChat):
6
6
needs_auth = True
7
parent = "OpenaiChat"
8
default_model = "gpt-4o"
9
default_vision_model = default_model
10
default_image_model = OpenaiChat.default_image_model
11
image_models = [default_model, default_image_model, "gpt-4"]
12
fallback_models = [*OpenaiChat.fallback_models, default_image_model]
7
parent = "OpenaiChat"
@@ -98,8 +98,9 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
98
98
default_model = "auto"
99
99
default_image_model = "dall-e-3"
100
100
image_models = [default_image_model]
101
fallback_models = [default_model, "gpt-4", "gpt-4o", "gpt-4o-mini", "gpt-4o-canmore", "o1", "o1-preview", "o1-mini"] +image_models
102
vision_models = fallback_models
101
text_models = [default_model, "gpt-4", "gpt-4o", "gpt-4o-mini", "gpt-4o-canmore", "o1", "o1-preview", "o1-mini"]
102
vision_models = text_models
103
models = text_models + image_models
103
104
synthesize_content_type = "audio/mpeg"
104
105
105
106
_api_key: str = None
@@ -120,33 +121,6 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
120
121
turnstile_token=RequestConfig.turnstile_token
121
122
)
122
123
123
@classmethod
124
def get_models(cls, proxy: str = None, timeout: int = 180) -> List[str]:
125
if not cls.models:
126
# try:
127
# headers = {
128
# **(cls.get_default_headers() if cls._headers is None else cls._headers),
129
# "accept": "application/json",
130
# }
131
# with Session(
132
# proxy=proxy,
133
# impersonate="chrome",
134
# timeout=timeout,
135
# headers=headers
136
# ) as session:
137
# response = session.get(
138
# f"{cls.url}/backend-anon/models"
139
# if cls._api_key is None else
140
# f"{cls.url}/backend-api/models"
141
# )
142
# raise_for_status(response)
143
# data = response.json()
144
# cls.models = [model.get("slug") for model in data.get("models")]
145
# except Exception as e:
146
# debug.log(f"OpenaiChat: Failed to get models: {type(e).__name__}: {e}")
147
cls.models = cls.fallback_models
148
return cls.models
149
150
124
@classmethod
151
125
async def upload_images(
152
126
cls,
@@ -20,6 +20,7 @@ class OpenaiTemplate(AsyncGeneratorProvider, ProviderModelMixin, RaiseErrorMixin
20
20
default_model = ""
21
21
fallback_models = []
22
22
sort_models = True
23
ssl = None
23
24
24
25
@classmethod
25
26
def get_models(cls, api_key: str = None, api_base: str = None) -> list[str]:
@@ -30,7 +31,7 @@ class OpenaiTemplate(AsyncGeneratorProvider, ProviderModelMixin, RaiseErrorMixin
30
31
api_base = cls.api_base
31
32
if api_key is not None:
32
33
headers["authorization"] = f"Bearer {api_key}"
33
response = requests.get(f"{api_base}/models", headers=headers)
34
response = requests.get(f"{api_base}/models", headers=headers, verify=cls.ssl)
34
35
raise_for_status(response)
35
36
data = response.json()
36
37
data = data.get("data") if isinstance(data, dict) else data
@@ -79,12 +80,12 @@ class OpenaiTemplate(AsyncGeneratorProvider, ProviderModelMixin, RaiseErrorMixin
79
80
api_base = cls.api_base
80
81
81
82
# Proxy for image generation feature
82
if model in cls.image_models:
83
if model and model in cls.image_models:
83
84
data = {
84
85
"prompt": messages[-1]["content"] if prompt is None else prompt,
85
86
"model": model,
86
87
}
87
async with session.post(f"{api_base.rstrip('/')}/images/generations", json=data) as response:
88
async with session.post(f"{api_base.rstrip('/')}/images/generations", json=data, ssl=cls.ssl) as response:
88
89
data = await response.json()
89
90
cls.raise_error(data)
90
91
await raise_for_status(response)
@@ -119,7 +120,7 @@ class OpenaiTemplate(AsyncGeneratorProvider, ProviderModelMixin, RaiseErrorMixin
119
120
)
120
121
if api_endpoint is None:
121
122
api_endpoint = f"{api_base.rstrip('/')}/chat/completions"
122
async with session.post(api_endpoint, json=data) as response:
123
async with session.post(api_endpoint, json=data, ssl=cls.ssl) as response:
123
124
content_type = response.headers.get("content-type", "text/event-stream" if stream else "application/json")
124
125
if content_type.startswith("application/json"):
125
126
data = await response.json()
@@ -180,7 +181,7 @@ class OpenaiTemplate(AsyncGeneratorProvider, ProviderModelMixin, RaiseErrorMixin
180
181
"Content-Type": "application/json",
181
182
**(
182
183
{"Authorization": f"Bearer {api_key}"}
183
if api_key is not None else {}
184
if api_key else {}
184
185
),
185
186
**({} if headers is None else headers)
186
187
}
@@ -70,6 +70,8 @@ def iter_response(
70
70
continue
71
71
elif isinstance(chunk, SynthesizeData) or not chunk:
72
72
continue
73
elif isinstance(chunk, Exception):
74
continue
73
75
74
76
chunk = str(chunk)
75
77
content += chunk
@@ -149,6 +151,8 @@ async def async_iter_response(
149
151
continue
150
152
elif isinstance(chunk, SynthesizeData) or not chunk:
151
153
continue
154
elif isinstance(chunk, Exception):
155
continue
152
156
153
157
chunk = str(chunk)
154
158
content += chunk
@@ -1,14 +1,15 @@
1
1
from __future__ import annotations
2
2
3
3
from ..models import ModelUtils
4
from ..Provider import ProviderUtils
4
5
5
6
class ImageModels():
6
7
def __init__(self, client):
7
8
self.client = client
8
self.models = ModelUtils.convert
9
9
10
10
def get(self, name, default=None):
11
model = self.models.get(name)
12
if model and model.best_provider:
13
return model.best_provider
14
return default
11
if name in ModelUtils.convert:
12
return ModelUtils.convert[name].best_provider
13
if name in ProviderUtils.convert:
14
return ProviderUtils.convert[name]
15
return default
@@ -722,12 +722,13 @@ async function add_message_chunk(message, message_id, provider, scroll, finish_m
722
722
`;
723
723
} else if (message.type == "message") {
724
724
console.error(message.message)
725
await api("log", {...message, provider: provider_storage[message_id]});
725
726
} else if (message.type == "error") {
726
727
content_map.update_timeouts.forEach((timeoutId)=>clearTimeout(timeoutId));
727
728
content_map.update_timeouts = [];
728
error_storage[message_id] = message.error
729
console.error(message.error);
730
content_map.inner.innerHTML += markdown_render(`**An error occured:** ${message.error}`);
729
error_storage[message_id] = message.message
730
console.error(message.message);
731
content_map.inner.innerHTML += markdown_render(`**An error occured:** ${message.message}`);
731
732
let p = document.createElement("p");
732
733
p.innerText = message.error;
733
734
log_storage.appendChild(p);
@@ -865,19 +866,23 @@ const ask_gpt = async (message_id, message_index = -1, regenerate = false, provi
865
866
}
866
867
if (message_storage[message_id]) {
867
868
const message_provider = message_id in provider_storage ? provider_storage[message_id] : null;
868
let usage;
869
let usage = {};
869
870
if (usage_storage[message_id]) {
870
871
usage = usage_storage[message_id];
871
872
delete usage_storage[message_id];
872
873
}
873
// Calculate usage if we have no usage result jet
874
if (document.getElementById("track_usage").checked && !usage && window.GPTTokenizer_cl100k_base) {
874
usage = {
875
model: message_provider?.model,
876
provider: message_provider?.name,
877
...usage
878
}
879
// Calculate usage if we don't have it jet
880
if (document.getElementById("track_usage").checked && !usage.prompt_tokens && window.GPTTokenizer_cl100k_base) {
875
881
const prompt_token_model = model?.startsWith("gpt-3") ? "gpt-3.5-turbo" : "gpt-4"
876
882
const prompt_tokens = GPTTokenizer_cl100k_base?.encodeChat(messages, prompt_token_model).length;
877
883
const completion_tokens = count_tokens(message_provider?.model, message_storage[message_id]);
878
884
usage = {
879
model: message_provider?.model,
880
provider: message_provider?.name,
885
...usage,
881
886
prompt_tokens: prompt_tokens,
882
887
completion_tokens: completion_tokens,
883
888
total_tokens: prompt_tokens + completion_tokens
@@ -1748,9 +1753,10 @@ function update_message(content_map, message_id, content = null, scroll = true)
1748
1753
content = content.substring(0, lastIndex) + '<span class="cursor"></span>' + lastElement;
1749
1754
}
1750
1755
}
1751
content_map.inner.innerHTML = content;
1752
1756
if (error_storage[message_id]) {
1753
content_map.inner.innerHTML += markdown_render(`**An error occured:** ${error_storage[message_id]}`);
1757
content_map.inner.innerHTML = message + markdown_render(`**An error occured:** ${error_storage[message_id]}`);
1758
} else {
1759
content_map.inner.innerHTML = content;
1754
1760
}
1755
1761
content_map.count.innerText = count_words_and_tokens(message_storage[message_id], provider_storage[message_id]?.model);
1756
1762
highlight(content_map.inner);
@@ -12,9 +12,9 @@ from ...image import ImagePreview, ImageResponse, copy_images, ensure_images_dir
12
12
from ...tools.run_tools import iter_run_tools
13
13
from ...Provider import ProviderUtils, __providers__
14
14
from ...providers.base_provider import ProviderModelMixin
15
from ...providers.retry_provider import IterListProvider
15
from ...providers.retry_provider import BaseRetryProvider
16
16
from ...providers.response import BaseConversation, JsonConversation, FinishReason, Usage, Reasoning
17
from ...providers.response import SynthesizeData, TitleGeneration, RequestLogin, Parameters
17
from ...providers.response import SynthesizeData, TitleGeneration, RequestLogin, Parameters, ProviderInfo
18
18
from ... import version, models
19
19
from ... import ChatCompletion, get_model_and_provider
20
20
from ... import debug
@@ -154,41 +154,32 @@ class Api:
154
154
)
155
155
except Exception as e:
156
156
logger.exception(e)
157
yield self._format_json('error', get_error_message(e))
157
yield self._format_json('error', type(e).__name__, message=get_error_message(e))
158
158
return
159
params = {
160
**(provider_handler.get_parameters(as_json=True) if hasattr(provider_handler, "get_parameters") else {}),
161
"model": model,
162
"messages": kwargs.get("messages"),
163
}
164
if isinstance(kwargs.get("conversation"), JsonConversation):
165
params["conversation"] = kwargs.get("conversation").get_dict()
166
else:
167
params["conversation_id"] = conversation_id
168
if kwargs.get("api_key") is not None:
169
params["api_key"] = kwargs["api_key"]
170
yield self._format_json("parameters", params)
171
first = True
159
if not isinstance(provider_handler, BaseRetryProvider):
160
yield self.handle_provider(provider_handler, model)
161
if hasattr(provider_handler, "get_parameters"):
162
yield self._format_json("parameters", provider_handler.get_parameters(as_json=True))
172
163
try:
173
164
result = iter_run_tools(ChatCompletion.create, **{**kwargs, "model": model, "provider": provider_handler})
174
165
for chunk in result:
175
if first:
176
first = False
177
yield self.handle_provider(provider_handler, model)
178
if isinstance(chunk, BaseConversation):
166
if isinstance(chunk, ProviderInfo):
167
yield self.handle_provider(chunk, model)
168
provider = chunk.name
169
elif isinstance(chunk, BaseConversation):
179
170
if provider is not None:
180
171
if provider not in conversations:
181
172
conversations[provider] = {}
182
173
conversations[provider][conversation_id] = chunk
183
174
if isinstance(chunk, JsonConversation):
184
175
yield self._format_json("conversation", {
185
provider.__name__ if isinstance(provider, type) else provider: chunk.get_dict()
176
provider: chunk.get_dict()
186
177
})
187
178
else:
188
179
yield self._format_json("conversation_id", conversation_id)
189
180
elif isinstance(chunk, Exception):
190
181
logger.exception(chunk)
191
yield self._format_json("message", get_error_message(chunk))
182
yield self._format_json('message', get_error_message(chunk), error=type(chunk).__name__)
192
183
elif isinstance(chunk, ImagePreview):
193
184
yield self._format_json("preview", chunk.to_string())
194
185
elif isinstance(chunk, ImageResponse):
@@ -219,9 +210,11 @@ class Api:
219
210
debug.logs = []
220
211
except Exception as e:
221
212
logger.exception(e)
222
yield self._format_json('error', get_error_message(e))
223
if first:
224
yield self.handle_provider(provider_handler, model)
213
if debug.logs:
214
for log in debug.logs:
215
yield self._format_json("log", str(log))
216
debug.logs = []
217
yield self._format_json('error', type(e).__name__, message=get_error_message(e))
225
218
226
219
def _format_json(self, response_type: str, content = None, **kwargs):
227
220
if content is not None:
@@ -235,11 +228,11 @@ class Api:
235
228
}
236
229
237
230
def handle_provider(self, provider_handler, model):
238
if isinstance(provider_handler, IterListProvider) and provider_handler.last_provider is not None:
231
if isinstance(provider_handler, BaseRetryProvider) and provider_handler.last_provider is not None:
239
232
provider_handler = provider_handler.last_provider
240
if not model and hasattr(provider_handler, "last_model") and provider_handler.last_model is not None:
241
model = provider_handler.last_model
242
return self._format_json("provider", {**provider_handler.get_dict(), "model": model})
233
if model:
234
return self._format_json("provider", {**provider_handler.get_dict(), "model": model})
235
return self._format_json("provider", provider_handler.get_dict())
243
236
244
237
def get_error_message(exception: Exception) -> str:
245
238
return f"{type(exception).__name__}: {exception}"
@@ -277,7 +277,8 @@ class Backend_Api(Api):
277
277
return Response(filter_markdown(response, do_filter_markdown), mimetype='text/plain')
278
278
def cast_str():
279
279
for chunk in response:
280
yield str(chunk)
280
if not isinstance(chunk, Exception):
281
yield str(chunk)
281
282
return Response(cast_str(), mimetype='text/plain')
282
283
except Exception as e:
283
284
logger.exception(e)
@@ -242,36 +242,48 @@ def ensure_images_dir():
242
242
async def copy_images(
243
243
images: list[str],
244
244
cookies: Optional[Cookies] = None,
245
proxy: Optional[str] = None
245
proxy: Optional[str] = None,
246
add_url: bool = True,
247
target: str = None,
248
ssl: bool = None
246
249
) -> list[str]:
250
if add_url:
251
add_url = not cookies
247
252
ensure_images_dir()
248
253
async with ClientSession(
249
254
connector=get_connector(proxy=proxy),
250
255
cookies=cookies
251
256
) as session:
252
async def copy_image(image: str) -> str:
253
target = os.path.join(images_dir, f"{int(time.time())}_{str(uuid.uuid4())}")
254
if image.startswith("data:"):
255
with open(target, "wb") as f:
256
f.write(extract_data_uri(image))
257
else:
258
try:
259
async with session.get(image) as response:
260
response.raise_for_status()
261
with open(target, "wb") as f:
262
async for chunk in response.content.iter_chunked(4096):
263
f.write(chunk)
264
except ClientError as e:
265
debug.log(f"copy_images failed: {e.__class__.__name__}: {e}")
266
return image
267
with open(target, "rb") as f:
268
extension = is_accepted_format(f.read(12)).split("/")[-1]
269
extension = "jpg" if extension == "jpeg" else extension
270
new_target = f"{target}.{extension}"
271
os.rename(target, new_target)
272
return f"/images/{os.path.basename(new_target)}"
273
274
return await asyncio.gather(*[copy_image(image) for image in images])
257
async def copy_image(image: str, target: str = None) -> str:
258
if target is None or len(images) > 1:
259
target = os.path.join(images_dir, f"{int(time.time())}_{str(uuid.uuid4())}")
260
try:
261
if image.startswith("data:"):
262
with open(target, "wb") as f:
263
f.write(extract_data_uri(image))
264
else:
265
try:
266
async with session.get(image, ssl=ssl) as response:
267
response.raise_for_status()
268
with open(target, "wb") as f:
269
async for chunk in response.content.iter_chunked(4096):
270
f.write(chunk)
271
except ClientError as e:
272
debug.log(f"copy_images failed: {e.__class__.__name__}: {e}")
273
return image
274
if "." not in target:
275
with open(target, "rb") as f:
276
extension = is_accepted_format(f.read(12)).split("/")[-1]
277
extension = "jpg" if extension == "jpeg" else extension
278
new_target = f"{target}.{extension}"
279
os.rename(target, new_target)
280
target = new_target
281
finally:
282
if "." not in target and os.path.exists(target):
283
os.unlink(target)
284
return f"/images/{os.path.basename(target)}{'?url=' + image if add_url and not image.startswith('data:') else ''}"
285
286
return await asyncio.gather(*[copy_image(image, target) for image in images])
275
287
276
288
class ImageDataResponse():
277
289
def __init__(