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

XFEstudio/gpt4free

feat(g4f/Provider/Blackbox.py): enhance async response handling and cleanup

4e630d29
kqlio67 <kqlio67@users.noreply.github.com>
提交于

代码差异

1 个文件 +221 -99
Modified g4f/Provider/Blackbox.py +221 -99
@@ -1,19 +1,27 @@
1 1 from __future__ import annotations
2 2
3 import re
3 import asyncio
4 import aiohttp
4 5 import random
5 6 import string
6 7 import json
7 from aiohttp import ClientSession
8 import uuid
9 import re
10 from typing import Optional, AsyncGenerator, Union
11
12 from aiohttp import ClientSession, ClientResponseError
8 13
9 from ..typing import AsyncResult, Messages, ImageType
10 from ..image import ImageResponse, to_data_uri
14 from ..typing import AsyncResult, Messages
11 15 from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
16 from ..image import ImageResponse
17
12 18
13 19 class Blackbox(AsyncGeneratorProvider, ProviderModelMixin):
20 label = "Blackbox AI"
14 21 url = "https://www.blackbox.ai"
15 22 api_endpoint = "https://www.blackbox.ai/api/chat"
16 23 working = True
24 supports_gpt_4 = True
17 25 supports_stream = True
18 26 supports_system_message = True
19 27 supports_message_history = True
@@ -23,6 +31,7 @@ class Blackbox(AsyncGeneratorProvider, ProviderModelMixin):
23 31 models = [
24 32 default_model,
25 33 'blackboxai-pro',
34 *image_models,
26 35 "llama-3.1-8b",
27 36 'llama-3.1-70b',
28 37 'llama-3.1-405b',
@@ -43,7 +52,6 @@ class Blackbox(AsyncGeneratorProvider, ProviderModelMixin):
43 52 'ReactAgent',
44 53 'XcodeAgent',
45 54 'AngularJSAgent',
46 *image_models,
47 55 ]
48 56
49 57 agentMode = {
@@ -71,13 +79,13 @@ class Blackbox(AsyncGeneratorProvider, ProviderModelMixin):
71 79 'XcodeAgent': {'mode': True, 'id': "Xcode Agent"},
72 80 'AngularJSAgent': {'mode': True, 'id': "AngularJS Agent"},
73 81 }
74
82
75 83 userSelectedModel = {
76 84 "gpt-4o": "gpt-4o",
77 85 "gemini-pro": "gemini-pro",
78 86 'claude-sonnet-3.5': "claude-sonnet-3.5",
79 87 }
80
88
81 89 model_prefixes = {
82 90 'gpt-4o': '@GPT-4o',
83 91 'gemini-pro': '@Gemini-PRO',
@@ -98,14 +106,14 @@ class Blackbox(AsyncGeneratorProvider, ProviderModelMixin):
98 106 'blackboxai-pro': '@BLACKBOXAI-PRO',
99 107 'ImageGeneration': '@Image Generation',
100 108 }
101
109
102 110 model_referers = {
103 "blackboxai": f"{url}/?model=blackboxai",
104 "gpt-4o": f"{url}/?model=gpt-4o",
105 "gemini-pro": f"{url}/?model=gemini-pro",
106 "claude-sonnet-3.5": f"{url}/?model=claude-sonnet-3.5"
111 "blackboxai": "/?model=blackboxai",
112 "gpt-4o": "/?model=gpt-4o",
113 "gemini-pro": "/?model=gemini-pro",
114 "claude-sonnet-3.5": "/?model=claude-sonnet-3.5"
107 115 }
108
116
109 117 model_aliases = {
110 118 "gemini-flash": "gemini-1.5-flash",
111 119 "claude-3.5-sonnet": "claude-sonnet-3.5",
@@ -116,69 +124,131 @@ class Blackbox(AsyncGeneratorProvider, ProviderModelMixin):
116 124 def get_model(cls, model: str) -> str:
117 125 if model in cls.models:
118 126 return model
119 elif model in cls.userSelectedModel:
120 return model
121 127 elif model in cls.model_aliases:
122 128 return cls.model_aliases[model]
123 129 else:
124 130 return cls.default_model
125 131
132 @staticmethod
133 def generate_random_string(length: int = 7) -> str:
134 characters = string.ascii_letters + string.digits
135 return ''.join(random.choices(characters, k=length))
136
137 @staticmethod
138 def generate_next_action() -> str:
139 return uuid.uuid4().hex
140
141 @staticmethod
142 def generate_next_router_state_tree() -> str:
143 router_state = [
144 "",
145 {
146 "children": [
147 "(chat)",
148 {
149 "children": [
150 "__PAGE__",
151 {}
152 ]
153 }
154 ]
155 },
156 None,
157 None,
158 True
159 ]
160 return json.dumps(router_state)
161
162 @staticmethod
163 def clean_response(text: str) -> str:
164 pattern = r'^\$\@\$v=undefined-rv1\$\@\$'
165 cleaned_text = re.sub(pattern, '', text)
166 return cleaned_text
167
126 168 @classmethod
127 169 async def create_async_generator(
128 170 cls,
129 171 model: str,
130 172 messages: Messages,
131 proxy: str = None,
132 image: ImageType = None,
133 image_name: str = None,
134 webSearchMode: bool = False,
173 proxy: Optional[str] = None,
174 web_search_mode: bool = False,
135 175 **kwargs
136 ) -> AsyncResult:
176 ) -> AsyncGenerator[Union[str, ImageResponse], None]:
177 """
178 Creates an asynchronous generator for streaming responses from Blackbox AI.
179
180 Parameters:
181 model (str): Model to use for generating responses.
182 messages (Messages): Message history.
183 proxy (Optional[str]): Proxy URL, if needed.
184 web_search_mode (bool): Enables or disables web search mode.
185 **kwargs: Additional keyword arguments.
186
187 Yields:
188 Union[str, ImageResponse]: Segments of the generated response or ImageResponse objects.
189 """
137 190 model = cls.get_model(model)
138
139 headers = {
140 "accept": "*/*",
141 "accept-language": "en-US,en;q=0.9",
142 "cache-control": "no-cache",
143 "content-type": "application/json",
144 "origin": cls.url,
145 "pragma": "no-cache",
146 "priority": "u=1, i",
147 "referer": cls.model_referers.get(model, cls.url),
148 "sec-ch-ua": '"Chromium";v="129", "Not=A?Brand";v="8"',
149 "sec-ch-ua-mobile": "?0",
150 "sec-ch-ua-platform": '"Linux"',
151 "sec-fetch-dest": "empty",
152 "sec-fetch-mode": "cors",
153 "sec-fetch-site": "same-origin",
154 "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36",
155 }
156 191
157 if model in cls.model_prefixes:
158 prefix = cls.model_prefixes[model]
159 if not messages[0]['content'].startswith(prefix):
160 messages[0]['content'] = f"{prefix} {messages[0]['content']}"
192 chat_id = cls.generate_random_string()
193 next_action = cls.generate_next_action()
194 next_router_state_tree = cls.generate_next_router_state_tree()
195
196 agent_mode = cls.agentMode.get(model, {})
197 trending_agent_mode = cls.trendingAgentMode.get(model, {})
198
199 prefix = cls.model_prefixes.get(model, "")
161 200
162 random_id = ''.join(random.choices(string.ascii_letters + string.digits, k=7))
163 messages[-1]['id'] = random_id
164 messages[-1]['role'] = 'user'
165
166 if image is not None:
167 messages[-1]['data'] = {
168 'fileText': '',
169 'imageBase64': to_data_uri(image),
170 'title': image_name
171 }
172 messages[-1]['content'] = 'FILE:BB\n$#$\n\n$#$\n' + messages[-1]['content']
201 formatted_prompt = ""
202 for message in messages:
203 role = message.get('role', '').capitalize()
204 content = message.get('content', '')
205 if role and content:
206 formatted_prompt += f"{role}: {content}\n"
173 207
174 data = {
175 "messages": messages,
176 "id": random_id,
208 if prefix:
209 formatted_prompt = f"{prefix} {formatted_prompt}".strip()
210
211 referer_path = cls.model_referers.get(model, f"/?model={model}")
212 referer_url = f"{cls.url}{referer_path}"
213
214 common_headers = {
215 'accept': '*/*',
216 'accept-language': 'en-US,en;q=0.9',
217 'cache-control': 'no-cache',
218 'origin': cls.url,
219 'pragma': 'no-cache',
220 'priority': 'u=1, i',
221 'sec-ch-ua': '"Chromium";v="129", "Not=A?Brand";v="8"',
222 'sec-ch-ua-mobile': '?0',
223 'sec-ch-ua-platform': '"Linux"',
224 'sec-fetch-dest': 'empty',
225 'sec-fetch-mode': 'cors',
226 'sec-fetch-site': 'same-origin',
227 'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) '
228 'AppleWebKit/537.36 (KHTML, like Gecko) '
229 'Chrome/129.0.0.0 Safari/537.36'
230 }
231
232 headers_api_chat = {
233 'Content-Type': 'application/json',
234 'Referer': referer_url
235 }
236 headers_api_chat_combined = {**common_headers, **headers_api_chat}
237
238 payload_api_chat = {
239 "messages": [
240 {
241 "id": chat_id,
242 "content": formatted_prompt,
243 "role": "user"
244 }
245 ],
246 "id": chat_id,
177 247 "previewToken": None,
178 248 "userId": None,
179 249 "codeModelMode": True,
180 "agentMode": {},
181 "trendingAgentMode": {},
250 "agentMode": agent_mode,
251 "trendingAgentMode": trending_agent_mode,
182 252 "isMicMode": False,
183 253 "userSystemPrompt": None,
184 254 "maxTokens": 1024,
@@ -191,47 +261,99 @@ class Blackbox(AsyncGeneratorProvider, ProviderModelMixin):
191 261 "clickedForceWebSearch": False,
192 262 "visitFromDelta": False,
193 263 "mobileClient": False,
194 "userSelectedModel": None,
195 "webSearchMode": webSearchMode,
264 "webSearchMode": web_search_mode,
265 "userSelectedModel": cls.userSelectedModel.get(model, model)
196 266 }
197 267
198 if model in cls.agentMode:
199 data["agentMode"] = cls.agentMode[model]
200 elif model in cls.trendingAgentMode:
201 data["trendingAgentMode"] = cls.trendingAgentMode[model]
202 elif model in cls.userSelectedModel:
203 data["userSelectedModel"] = cls.userSelectedModel[model]
204
205 async with ClientSession(headers=headers) as session:
206 async with session.post(cls.api_endpoint, json=data, proxy=proxy) as response:
207 response.raise_for_status()
208 if model == 'ImageGeneration':
209 response_text = await response.text()
210 url_match = re.search(r'https://storage\.googleapis\.com/[^\s\)]+', response_text)
211 if url_match:
212 image_url = url_match.group(0)
213 yield ImageResponse(image_url, alt=messages[-1]['content'])
214 else:
215 raise Exception("Image URL not found in the response")
216 else:
217 full_response = ""
218 search_results_json = ""
219 async for chunk, _ in response.content.iter_chunks():
220 if chunk:
221 decoded_chunk = chunk.decode()
222 decoded_chunk = re.sub(r'\$@\$v=[^$]+\$@\$', '', decoded_chunk)
223 if decoded_chunk.strip():
224 if '$~~~$' in decoded_chunk:
225 search_results_json += decoded_chunk
226 else:
227 full_response += decoded_chunk
228 yield decoded_chunk
229
230 if data["webSearchMode"] and search_results_json:
231 match = re.search(r'\$~~~\$(.*?)\$~~~\$', search_results_json, re.DOTALL)
268 headers_chat = {
269 'Accept': 'text/x-component',
270 'Content-Type': 'text/plain;charset=UTF-8',
271 'Referer': f'{cls.url}/chat/{chat_id}?model={model}',
272 'next-action': next_action,
273 'next-router-state-tree': next_router_state_tree,
274 'next-url': '/'
275 }
276 headers_chat_combined = {**common_headers, **headers_chat}
277
278 data_chat = '[]'
279
280 async with ClientSession(headers=common_headers) as session:
281 try:
282 async with session.post(
283 cls.api_endpoint,
284 headers=headers_api_chat_combined,
285 json=payload_api_chat,
286 proxy=proxy
287 ) as response_api_chat:
288 response_api_chat.raise_for_status()
289 text = await response_api_chat.text()
290 cleaned_response = cls.clean_response(text)
291
292 if model in cls.image_models:
293 match = re.search(r'!\[.*?\]\((https?://[^\)]+)\)', cleaned_response)
232 294 if match:
233 search_results = json.loads(match.group(1))
234 formatted_results = "\n\n**Sources:**\n"
235 for i, result in enumerate(search_results[:5], 1):
236 formatted_results += f"{i}. [{result['title']}]({result['link']})\n"
237 yield formatted_results
295 image_url = match.group(1)
296 image_response = ImageResponse(images=image_url, alt="Generated Image")
297 yield image_response
298 else:
299 yield cleaned_response
300 else:
301 if web_search_mode:
302 match = re.search(r'\$~~~\$(.*?)\$~~~\$', cleaned_response, re.DOTALL)
303 if match:
304 source_part = match.group(1).strip()
305 answer_part = cleaned_response[match.end():].strip()
306 try:
307 sources = json.loads(source_part)
308 source_formatted = "**Source:**\n"
309 for item in sources:
310 title = item.get('title', 'No Title')
311 link = item.get('link', '#')
312 position = item.get('position', '')
313 source_formatted += f"{position}. [{title}]({link})\n"
314 final_response = f"{answer_part}\n\n{source_formatted}"
315 except json.JSONDecodeError:
316 final_response = f"{answer_part}\n\nSource information is unavailable."
317 else:
318 final_response = cleaned_response
319 else:
320 if '$~~~$' in cleaned_response:
321 final_response = cleaned_response.split('$~~~$')[0].strip()
322 else:
323 final_response = cleaned_response
324
325 yield final_response
326 except ClientResponseError as e:
327 error_text = f"Error {e.status}: {e.message}"
328 try:
329 error_response = await e.response.text()
330 cleaned_error = cls.clean_response(error_response)
331 error_text += f" - {cleaned_error}"
332 except Exception:
333 pass
334 yield error_text
335 except Exception as e:
336 yield f"Unexpected error during /api/chat request: {str(e)}"
337
338 chat_url = f'{cls.url}/chat/{chat_id}?model={model}'
339
340 try:
341 async with session.post(
342 chat_url,
343 headers=headers_chat_combined,
344 data=data_chat,
345 proxy=proxy
346 ) as response_chat:
347 response_chat.raise_for_status()
348 pass
349 except ClientResponseError as e:
350 error_text = f"Error {e.status}: {e.message}"
351 try:
352 error_response = await e.response.text()
353 cleaned_error = cls.clean_response(error_response)
354 error_text += f" - {cleaned_error}"
355 except Exception:
356 pass
357 yield error_text
358 except Exception as e:
359 yield f"Unexpected error during /chat/{chat_id} request: {str(e)}"