返回提交历史
Modified
g4f/Provider/hf_space/Janus_Pro_7B.py
+2
-2
Modified
g4f/Provider/needs_auth/DeepSeekAPI.py
+19
-11
Modified
g4f/Provider/template/BackendApi.py
+4
-95
Modified
g4f/api/__init__.py
+1
-1
Modified
g4f/gui/client/static/js/chat.v1.js
+1
-1
Modified
g4f/gui/server/api.py
+4
-15
Modified
g4f/gui/server/backend_api.py
+3
-2
Modified
g4f/image.py
+6
-3
Modified
g4f/providers/response.py
+13
-15
Modified
g4f/requests/__init__.py
+8
-2
Modified
g4f/tools/run_tools.py
+2
-4
XFEstudio/gpt4free
Fix response type of reasoning in UI
797b1783
代码差异
11 个文件
+63
-151
@@ -9,7 +9,7 @@ import urllib.parse
9
9
from ...typing import AsyncResult, Messages, Cookies
10
10
from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin
11
11
from ..helper import format_prompt, format_image_prompt
12
from ...providers.response import JsonConversation, ImageResponse, Notification
12
from ...providers.response import JsonConversation, ImageResponse, DebugResponse
13
13
from ...requests.aiohttp import StreamSession, StreamResponse
14
14
from ...requests.raise_for_status import raise_for_status
15
15
from ...cookies import get_cookies
@@ -105,7 +105,7 @@ class Janus_Pro_7B(AsyncGeneratorProvider, ProviderModelMixin):
105
105
try:
106
106
json_data = json.loads(decoded_line[6:])
107
107
if json_data.get('msg') == 'log':
108
yield Notification(json_data["log"])
108
yield DebugResponse(log=json_data["log"])
109
109
110
110
if json_data.get('msg') == 'process_generating':
111
111
if 'output' in json_data and 'data' in json_data['output']:
@@ -7,24 +7,26 @@ from typing import AsyncIterator
7
7
import asyncio
8
8
9
9
from ..base_provider import AsyncAuthedProvider
10
from ...requests import get_args_from_nodriver
10
from ...providers.helper import get_last_user_message
11
from ... import requests
12
from ...errors import MissingAuthError
13
from ...requests import get_args_from_nodriver, get_nodriver
11
14
from ...providers.response import AuthResult, RequestLogin, Reasoning, JsonConversation, FinishReason
12
15
from ...typing import AsyncResult, Messages
13
16
from ... import debug
14
17
try:
15
18
from curl_cffi import requests
16
19
from dsk.api import DeepSeekAPI, AuthenticationError, DeepSeekPOW
17
20
18
21
class DeepSeekAPIArgs(DeepSeekAPI):
19
22
def __init__(self, args: dict):
20
args.pop("headers")
21
23
self.auth_token = args.pop("api_key")
22
24
if not self.auth_token or not isinstance(self.auth_token, str):
23
25
raise AuthenticationError("Invalid auth token provided")
24
26
self.args = args
25
27
self.pow_solver = DeepSeekPOW()
26
28
27
def _make_request(self, method: str, endpoint: str, json_data: dict, pow_required: bool = False):
29
def _make_request(self, method: str, endpoint: str, json_data: dict, pow_required: bool = False, **kwargs):
28
30
url = f"{self.BASE_URL}{endpoint}"
29
31
headers = self._get_headers()
30
32
if pow_required:
@@ -36,12 +38,15 @@ try:
36
38
method=method,
37
39
url=url,
38
40
json=json_data, **{
39
"headers":headers,
40
"impersonate":'chrome',
41
**self.args,
42
"headers": {**headers, **self.args["headers"]},
41
43
"timeout":None,
42
**self.args
43
}
44
},
45
**kwargs
44
46
)
47
if response.status_code == 403:
48
raise MissingAuthError()
49
response.raise_for_status()
45
50
return response.json()
46
51
except ImportError:
47
52
pass
@@ -55,6 +60,8 @@ class DeepSeekAPI(AsyncAuthedProvider):
55
60
56
61
@classmethod
57
62
async def on_auth_async(cls, proxy: str = None, **kwargs) -> AsyncIterator:
63
if not hasattr(cls, "browser"):
64
cls.browser, cls.stop_browser = await get_nodriver()
58
65
yield RequestLogin(cls.__name__, os.environ.get("G4F_LOGIN_URL") or "")
59
66
async def callback(page):
60
67
while True:
@@ -62,7 +69,7 @@ class DeepSeekAPI(AsyncAuthedProvider):
62
69
cls._access_token = json.loads(await page.evaluate("localStorage.getItem('userToken')") or "{}").get("value")
63
70
if cls._access_token:
64
71
break
65
args = await get_args_from_nodriver(cls.url, proxy, callback=callback)
72
args = await get_args_from_nodriver(cls.url, proxy, callback=callback, browser=cls.browser)
66
73
yield AuthResult(
67
74
api_key=cls._access_token,
68
75
**args
@@ -88,7 +95,7 @@ class DeepSeekAPI(AsyncAuthedProvider):
88
95
is_thinking = 0
89
96
for chunk in api.chat_completion(
90
97
conversation.chat_id,
91
messages[-1]["content"],
98
get_last_user_message(messages),
92
99
thinking_enabled=True
93
100
):
94
101
if chunk['type'] == 'thinking':
@@ -100,6 +107,7 @@ class DeepSeekAPI(AsyncAuthedProvider):
100
107
if is_thinking:
101
108
yield Reasoning(None, f"Thought for {time.time() - is_thinking:.2f}s")
102
109
is_thinking = 0
103
yield chunk['content']
110
if chunk['content']:
111
yield chunk['content']
104
112
if chunk['finish_reason']:
105
113
yield FinishReason(chunk['finish_reason'])
@@ -1,61 +1,15 @@
1
1
from __future__ import annotations
2
2
3
import re
4
3
import json
5
import time
6
from urllib.parse import quote_plus
7
4
8
5
from ...typing import Messages, AsyncResult
9
6
from ...requests import StreamSession
10
7
from ...providers.base_provider import AsyncGeneratorProvider, ProviderModelMixin
11
from ...providers.response import *
12
from ...image import get_image_extension
13
from ...errors import ModelNotSupportedError
14
from ..needs_auth.OpenaiAccount import OpenaiAccount
15
from ..hf.HuggingChat import HuggingChat
8
from ...providers.response import RawResponse
16
9
from ... import debug
17
10
18
11
class BackendApi(AsyncGeneratorProvider, ProviderModelMixin):
19
ssl = False
20
21
models = [
22
*OpenaiAccount.get_models(),
23
*HuggingChat.get_models(),
24
"flux",
25
"flux-pro",
26
"MiniMax-01",
27
"Microsoft Copilot",
28
]
29
30
@classmethod
31
def get_model(cls, model: str):
32
if "MiniMax" in model:
33
model = "MiniMax"
34
elif "Copilot" in model:
35
model = "Copilot"
36
elif "FLUX" in model:
37
model = f"flux-{model.split('-')[-1]}"
38
elif "flux" in model:
39
model = model.split(' ')[-1]
40
elif model in OpenaiAccount.get_models():
41
pass
42
elif model in HuggingChat.get_models():
43
pass
44
else:
45
raise ModelNotSupportedError(f"Model: {model}")
46
return model
47
48
@classmethod
49
def get_provider(cls, model: str):
50
if model.startswith("MiniMax"):
51
return "HailuoAI"
52
elif model == "Copilot":
53
return "CopilotAccount"
54
elif model in OpenaiAccount.get_models():
55
return "OpenaiAccount"
56
elif model in HuggingChat.get_models():
57
return "HuggingChat"
58
return None
12
ssl = None
59
13
60
14
@classmethod
61
15
async def create_async_generator(
@@ -63,61 +17,16 @@ class BackendApi(AsyncGeneratorProvider, ProviderModelMixin):
63
17
model: str,
64
18
messages: Messages,
65
19
api_key: str = None,
66
proxy: str = None,
67
timeout: int = 0,
68
20
**kwargs
69
21
) -> AsyncResult:
70
debug.log(f"{__name__}: {api_key}")
71
22
debug.log(f"{cls.__name__}: {api_key}")
72
23
async with StreamSession(
73
proxy=proxy,
74
24
headers={"Accept": "text/event-stream"},
75
timeout=timeout
76
25
) as session:
77
model = cls.get_model(model)
78
provider = cls.get_provider(model)
79
26
async with session.post(f"{cls.url}/backend-api/v2/conversation", json={
80
27
"model": model,
81
28
"messages": messages,
82
"provider": provider,
83
29
**kwargs
84
30
}, ssl=cls.ssl) as response:
85
31
async for line in response.iter_lines():
86
data = json.loads(line)
87
data_type = data.pop("type")
88
if data_type == "provider":
89
yield ProviderInfo(**data[data_type])
90
provider = data[data_type]["name"]
91
elif data_type == "conversation":
92
yield JsonConversation(**data[data_type][provider] if provider in data[data_type] else data[data_type][""])
93
elif data_type == "conversation_id":
94
pass
95
elif data_type == "message":
96
yield Exception(data)
97
elif data_type == "preview":
98
yield PreviewResponse(data[data_type])
99
elif data_type == "content":
100
def on_image(match):
101
extension = get_image_extension(match.group(3))
102
filename = f"{int(time.time())}_{quote_plus(match.group(1)[:100], '')}{extension}"
103
download_url = f"/download/{filename}?url={cls.url}{match.group(3)}"
104
return f"[](/images/{filename})"
105
yield re.sub(r'\[\!\[(.+?)\]\(([^)]+?)\)\]\(([^)]+?)\)', on_image, data["content"])
106
elif data_type =="synthesize":
107
yield SynthesizeData(**data[data_type])
108
elif data_type == "parameters":
109
yield Parameters(**data[data_type])
110
elif data_type == "usage":
111
yield Usage(**data[data_type])
112
elif data_type == "reasoning":
113
yield Reasoning(**data)
114
elif data_type == "login":
115
pass
116
elif data_type == "title":
117
yield TitleGeneration(data[data_type])
118
elif data_type == "finish":
119
yield FinishReason(data[data_type]["reason"])
120
elif data_type == "log":
121
yield DebugResponse.from_dict(data[data_type])
122
else:
123
yield DebugResponse.from_dict(data)
32
yield RawResponse(**json.loads(line))
@@ -581,7 +581,7 @@ class Api:
581
581
source_url = str(request.query_params).split("url=", 1)
582
582
if len(source_url) > 1:
583
583
source_url = source_url[1]
584
source_url = source_url.replace("%2F", "/").replace("%3A", ":").replace("%3F", "?")
584
source_url = source_url.replace("%2F", "/").replace("%3A", ":").replace("%3F", "?").replace("%3D", "=")
585
585
if source_url.startswith("https://"):
586
586
await copy_images(
587
587
[source_url],
@@ -779,7 +779,7 @@ async function add_message_chunk(message, message_id, provider, scroll, finish_m
779
779
} else if (message.type == "reasoning") {
780
780
if (!reasoning_storage[message_id]) {
781
781
reasoning_storage[message_id] = message;
782
reasoning_storage[message_id].text = "";
782
reasoning_storage[message_id].text = message.token || "";
783
783
} else if (message.status) {
784
784
reasoning_storage[message_id].status = message.status;
785
785
} else if (message.token) {
@@ -187,8 +187,8 @@ class Api:
187
187
elif isinstance(chunk, ImageResponse):
188
188
images = chunk
189
189
if download_images or chunk.get("cookies"):
190
alt = format_image_prompt(kwargs.get("messages"))
191
images = asyncio.run(copy_images(chunk.get_list(), chunk.get("cookies"), proxy, alt))
190
chunk.alt = chunk.alt or format_image_prompt(kwargs.get("messages"))
191
images = asyncio.run(copy_images(chunk.get_list(), chunk.get("cookies"), proxy=proxy, alt=chunk.alt))
192
192
images = ImageResponse(images, chunk.alt)
193
193
yield self._format_json("content", str(images), images=chunk.get_list(), alt=chunk.alt)
194
194
elif isinstance(chunk, SynthesizeData):
@@ -204,11 +204,9 @@ class Api:
204
204
elif isinstance(chunk, Usage):
205
205
yield self._format_json("usage", chunk.get_dict())
206
206
elif isinstance(chunk, Reasoning):
207
yield self._format_json("reasoning", token=chunk.token, status=chunk.status, is_thinking=chunk.is_thinking)
207
yield self._format_json("reasoning", chunk.get_dict())
208
208
elif isinstance(chunk, DebugResponse):
209
yield self._format_json("log", chunk.get_dict())
210
elif isinstance(chunk, Notification):
211
yield self._format_json("notification", chunk.message)
209
yield self._format_json("log", chunk.log)
212
210
else:
213
211
yield self._format_json("content", str(chunk))
214
212
if debug.logs:
@@ -224,15 +222,6 @@ class Api:
224
222
yield self._format_json('error', type(e).__name__, message=get_error_message(e))
225
223
226
224
def _format_json(self, response_type: str, content = None, **kwargs):
227
# Make sure it get be formated as JSON
228
if content is not None and not isinstance(content, (str, dict)):
229
content = str(content)
230
kwargs = {
231
key: value
232
if value is isinstance(value, (str, dict))
233
else str(value)
234
for key, value in kwargs.items()
235
if isinstance(key, str)}
236
225
if content is not None:
237
226
return {
238
227
'type': response_type,
@@ -156,7 +156,7 @@ class Backend_Api(Api):
156
156
157
157
if has_flask_limiter and app.demo:
158
158
@app.route('/backend-api/v2/conversation', methods=['POST'])
159
@limiter.limit("4 per minute") # 1 request in 15 seconds
159
@limiter.limit("2 per minute")
160
160
def _handle_conversation():
161
161
limiter.check()
162
162
return handle_conversation()
@@ -270,7 +270,8 @@ class Backend_Api(Api):
270
270
response = iter_run_tools(ChatCompletion.create, **parameters)
271
271
cache_dir.mkdir(parents=True, exist_ok=True)
272
272
with cache_file.open("w") as f:
273
f.write(response)
273
for chunk in response:
274
f.write(str(chunk))
274
275
else:
275
276
response = iter_run_tools(ChatCompletion.create, **parameters)
276
277
@@ -242,13 +242,15 @@ def ensure_images_dir():
242
242
os.makedirs(images_dir, exist_ok=True)
243
243
244
244
def get_image_extension(image: str) -> str:
245
if match := re.search(r"(\.(?:jpe?g|png|webp))[$?&]", image):
246
return match.group(1)
245
match = re.search(r"\.(?:jpe?g|png|webp)", image)
246
if match:
247
return match.group(0)
247
248
return ".jpg"
248
249
249
250
async def copy_images(
250
251
images: list[str],
251
252
cookies: Optional[Cookies] = None,
253
headers: Optional[dict] = None,
252
254
proxy: Optional[str] = None,
253
255
alt: str = None,
254
256
add_url: bool = True,
@@ -260,7 +262,8 @@ async def copy_images(
260
262
ensure_images_dir()
261
263
async with ClientSession(
262
264
connector=get_connector(proxy=proxy),
263
cookies=cookies
265
cookies=cookies,
266
headers=headers,
264
267
) as session:
265
268
async def copy_image(image: str, target: str = None) -> str:
266
269
if target is None or len(images) > 1:
@@ -88,6 +88,9 @@ class JsonMixin:
88
88
def reset(self):
89
89
self.__dict__ = {}
90
90
91
class RawResponse(ResponseType, JsonMixin):
92
pass
93
91
94
class HiddenResponse(ResponseType):
92
95
def __str__(self) -> str:
93
96
return ""
@@ -113,21 +116,9 @@ class TitleGeneration(HiddenResponse):
113
116
def __init__(self, title: str) -> None:
114
117
self.title = title
115
118
116
class DebugResponse(JsonMixin, HiddenResponse):
117
@classmethod
118
def from_dict(cls, data: dict) -> None:
119
return cls(**data)
120
121
@classmethod
122
def from_str(cls, data: str) -> None:
123
return cls(error=data)
124
125
class Notification(ResponseType):
126
def __init__(self, message: str) -> None:
127
self.message = message
128
129
def __str__(self) -> str:
130
return f"{self.message}\n"
119
class DebugResponse(HiddenResponse):
120
def __init__(self, log: str) -> None:
121
self.log = log
131
122
132
123
class Reasoning(ResponseType):
133
124
def __init__(
@@ -149,6 +140,13 @@ class Reasoning(ResponseType):
149
140
return f"{self.status}\n"
150
141
return ""
151
142
143
def get_dict(self):
144
if self.is_thinking is None:
145
if self.status is None:
146
return {"token": self.token}
147
{"token": self.token, "status": self.status}
148
return {"token": self.token, "status": self.status, "is_thinking": self.is_thinking}
149
152
150
class Sources(ResponseType):
153
151
def __init__(self, sources: list[dict[str, str]]) -> None:
154
152
self.list = []
@@ -28,6 +28,7 @@ try:
28
28
from nodriver import Browser, Tab, util
29
29
has_nodriver = True
30
30
except ImportError:
31
from typing import Type as Browser
31
32
from typing import Type as Tab
32
33
has_nodriver = False
33
34
try:
@@ -85,9 +86,14 @@ async def get_args_from_nodriver(
85
86
timeout: int = 120,
86
87
wait_for: str = None,
87
88
callback: callable = None,
88
cookies: Cookies = None
89
cookies: Cookies = None,
90
browser: Browser = None
89
91
) -> dict:
90
browser, stop_browser = await get_nodriver(proxy=proxy, timeout=timeout)
92
if browser is None:
93
browser, stop_browser = await get_nodriver(proxy=proxy, timeout=timeout)
94
else:
95
def stop_browser():
96
...
91
97
try:
92
98
if debug.logging:
93
99
print(f"Open nodriver with url: {url}")
@@ -157,15 +157,13 @@ def iter_run_tools(
157
157
if "<think>" in chunk:
158
158
chunk = chunk.split("<think>", 1)
159
159
yield chunk[0]
160
yield Reasoning(is_thinking="<think>")
160
yield Reasoning(None, "Is thinking...", is_thinking="<think>")
161
161
yield Reasoning(chunk[1])
162
yield Reasoning(None, "Is thinking...")
163
162
is_thinking = time.time()
164
163
if "</think>" in chunk:
165
164
chunk = chunk.split("</think>", 1)
166
165
yield Reasoning(chunk[0])
167
yield Reasoning(is_thinking="</think>")
168
yield Reasoning(None, f"Finished in {round(time.time()-is_thinking, 2)} seconds")
166
yield Reasoning(None, f"Finished in {round(time.time()-is_thinking, 2)} seconds", is_thinking="</think>")
169
167
yield chunk[1]
170
168
is_thinking = 0
171
169
elif is_thinking: