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

XFEstudio/gpt4free

Fix response type of reasoning in UI

797b1783
hlohaus <983577+hlohaus@users.noreply.github.com>
提交于

代码差异

11 个文件 +63 -151
Modified g4f/Provider/hf_space/Janus_Pro_7B.py +2 -2
@@ -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']:
Modified g4f/Provider/needs_auth/DeepSeekAPI.py +19 -11
@@ -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'])
Modified g4f/Provider/template/BackendApi.py +4 -95
@@ -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"[![{match.group(1)}]({download_url})](/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))
Modified g4f/api/__init__.py +1 -1
@@ -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],
Modified g4f/gui/client/static/js/chat.v1.js +1 -1
@@ -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) {
Modified g4f/gui/server/api.py +4 -15
@@ -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,
Modified g4f/gui/server/backend_api.py +3 -2
@@ -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
Modified g4f/image.py +6 -3
@@ -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:
Modified g4f/providers/response.py +13 -15
@@ -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 = []
Modified g4f/requests/__init__.py +8 -2
@@ -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}")
Modified g4f/tools/run_tools.py +2 -4
@@ -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: