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

XFEstudio/gpt4free

Major Update for Bing - Supports latest bundle version and image analysis

Here it is, a much-needed update to this service which offers numerous functionalities that the old code was unable to deliver to us. As you may know, ChatGPT Plus subscribers now have the opportunity to request image analysis directly from GPT within the chat bar. Bing has also integrated this feature into its chatbot. With this new code, you can now provide an image using a data URI, with all the following supported extensions: jpg, jpeg, png, and gif! **What is a data URI and how can I provide an image to Bing?** Just to clarify, a data URI is a method for encoding data directly into a URI (Uniform Resource Identifier). It is typically used for embedding small data objects like images, text, or other resources within web pages or documents. Data URIs are widely used in web applications. To provide an image from your desktop and retrieve it as a data URI, you can use this code: [GitHub link](https://gist.github.com/jsocol/1089733). Now, here is a code snippet you can use to provide images to Bing: ```python import g4f provider = g4f.Provider.Bing user_message = [{"role": "user", "content": "Hi, describe this image."}] response = g4f.ChatCompletion.create( model = g4f.models.gpt_4, provider = g4f.provider, # Corrected the provider value messages = user_message, stream = True, image = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/4RiSRXhpZgAASUkqAAg..." # Insert your full data URI image here ) for message in response: print(message, flush=True, end='') ``` If you don't want to analyze the image, just do not specify the image parameter. Regarding the implementation, the image is preprocessed within the Bing.py code, which can be resource-intensive for a server-side implementation. When using the Bing chatbot in your web browser, the image is preprocessed on your computer before being sent to the server. This preprocessing includes tasks like image rotation and compression. Although this implementation works, it would be more efficient to delegate image preprocessing to the client as it happens in reality. I will try to provide a JavaScript code for that at a later time. As you saw, I did mention in the title that it is in Beta. The way the code is written, Bing can sometimes mess up its answers. Indeed, Bing does not really stream its responses as the other providers do. Bing sends its answers like this on each iteration: "Hi," "Hi, this," "Hi, this is," "Hi, this is Bing." Instead of sending each segment one at a time, it already adds them on each iteration. So, to simulate a normal streaming response, other contributors made the code wait for the next iteration to retrieve the newer segments and yield them. However, this method ignores something that Bing does. Bing processes its responses in a markdown detector, which searches for links while the AI answers. If it finds a link, it saves it and waits until the AI finishes its answer to put all the found links at the very end of the answer. So if the AI is writing a link, but then on the next iteration, it finishes writing this link, it will then be deleted from the answer and appear later at the very end. Example: "Here is your link reference [" "Here is your link reference [^" "Here is your link reference [^1" "Here is your link reference [^1^" And then the response would get stuck there because the markdown detector would have deleted this link reference in the next response and waited until the AI is finished to put it at the very end. For this reason, I am working on an update to anticipate the markdown detector. So please, if you guys notice any bugs with this new implementation, I would greatly appreciate it if you could report them on the issue tab of this repo. Thanks in advance, and I hope that all these explanations were clear to you!

c400d020
Luneye <73485421+Luneye@users.noreply.github.com>
提交于

代码差异

1 个文件 +222 -42
Modified g4f/Provider/Bing.py +222 -42
@@ -1,10 +1,16 @@
1 1 from __future__ import annotations
2 2
3 import string
3 4 import random
4 5 import json
5 6 import os
7 import re
8 import io
9 import base64
10 import numpy as np
6 11 import uuid
7 12 import urllib.parse
13 from PIL import Image
8 14 from aiohttp import ClientSession, ClientTimeout
9 15 from ..typing import AsyncResult, Messages
10 16 from .base_provider import AsyncGeneratorProvider
@@ -35,6 +41,7 @@ class Bing(AsyncGeneratorProvider):
35 41 proxy: str = None,
36 42 cookies: dict = None,
37 43 tone: str = Tones.creative,
44 image: str = None,
38 45 **kwargs
39 46 ) -> AsyncResult:
40 47 if len(messages) < 2:
@@ -46,7 +53,7 @@ class Bing(AsyncGeneratorProvider):
46 53
47 54 if not cookies or "SRCHD" not in cookies:
48 55 cookies = default_cookies
49 return stream_generate(prompt, tone, context, proxy, cookies)
56 return stream_generate(prompt, tone, image, context, proxy, cookies)
50 57
51 58 def create_context(messages: Messages):
52 59 context = "".join(f"[{message['role']}](#message)\n{message['content']}\n\n" for message in messages)
@@ -54,14 +61,14 @@ def create_context(messages: Messages):
54 61 return context
55 62
56 63 class Conversation():
57 def __init__(self, conversationId: str, clientId: str, conversationSignature: str) -> None:
64 def __init__(self, conversationId: str, clientId: str, conversationSignature: str, imageInfo: dict=None) -> None:
58 65 self.conversationId = conversationId
59 66 self.clientId = clientId
60 67 self.conversationSignature = conversationSignature
68 self.imageInfo = imageInfo
61 69
62 async def create_conversation(session: ClientSession, proxy: str = None) -> Conversation:
63 url = 'https://www.bing.com/turing/conversation/create?bundleVersion=1.1150.3'
64
70 async def create_conversation(session: ClientSession, tone: str, image: str = None, proxy: str = None) -> Conversation:
71 url = 'https://www.bing.com/turing/conversation/create?bundleVersion=1.1199.4'
65 72 async with await session.get(url, proxy=proxy) as response:
66 73 data = await response.json()
67 74
@@ -71,8 +78,65 @@ async def create_conversation(session: ClientSession, proxy: str = None) -> Conv
71 78
72 79 if not conversationId or not clientId or not conversationSignature:
73 80 raise Exception('Failed to create conversation.')
74
75 return Conversation(conversationId, clientId, conversationSignature)
81 conversation = Conversation(conversationId, clientId, conversationSignature, None)
82 if isinstance(image,str):
83 try:
84 config = {
85 "visualSearch": {
86 "maxImagePixels": 360000,
87 "imageCompressionRate": 0.7,
88 "enableFaceBlurDebug": 0,
89 }
90 }
91 is_data_uri_an_image(image)
92 img_binary_data = extract_data_uri(image)
93 is_accepted_format(img_binary_data)
94 img = Image.open(io.BytesIO(img_binary_data))
95 width, height = img.size
96 max_image_pixels = config['visualSearch']['maxImagePixels']
97 compression_rate = config['visualSearch']['imageCompressionRate']
98
99 if max_image_pixels / (width * height) < 1:
100 new_width = int(width * np.sqrt(max_image_pixels / (width * height)))
101 new_height = int(height * np.sqrt(max_image_pixels / (width * height)))
102 else:
103 new_width = width
104 new_height = height
105 try:
106 orientation = get_orientation(img)
107 except Exception:
108 orientation = None
109 new_img = process_image(orientation, img, new_width, new_height)
110 new_img_binary_data = compress_image_to_base64(new_img, compression_rate)
111 data, boundary = build_image_upload_api_payload(new_img_binary_data, conversation, tone)
112 headers = session.headers.copy()
113 headers["content-type"] = 'multipart/form-data; boundary=' + boundary
114 headers["referer"] = 'https://www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx'
115 headers["origin"] = 'https://www.bing.com'
116 async with await session.post("https://www.bing.com/images/kblob", data=data, headers=headers, proxy=proxy) as image_upload_response:
117 if image_upload_response.status == 200:
118 image_info = await image_upload_response.json()
119 result = {}
120 if image_info.get('blobId'):
121 result['bcid'] = image_info.get('blobId', "")
122 result['blurredBcid'] = image_info.get('processedBlobId', "")
123 if result['blurredBcid'] != "":
124 result["imageUrl"] = "https://www.bing.com/images/blob?bcid=" + result['blurredBcid']
125 elif result['bcid'] != "":
126 result["imageUrl"] = "https://www.bing.com/images/blob?bcid=" + result['bcid']
127 if config['visualSearch']["enableFaceBlurDebug"]:
128 result['originalImageUrl'] = "https://www.bing.com/images/blob?bcid=" + result['blurredBcid']
129 else:
130 result['originalImageUrl'] = "https://www.bing.com/images/blob?bcid=" + result['bcid']
131 conversation.imageInfo = result
132 else:
133 raise Exception("Failed to parse image info.")
134 else:
135 raise Exception("Failed to upload image.")
136
137 except Exception as e:
138 print(f"An error happened while trying to send image: {str(e)}")
139 return conversation
76 140
77 141 async def list_conversations(session: ClientSession) -> list:
78 142 url = "https://www.bing.com/turing/conversation/chats"
@@ -98,37 +162,47 @@ class Defaults:
98 162 ip_address = f"13.{random.randint(104, 107)}.{random.randint(0, 255)}.{random.randint(0, 255)}"
99 163
100 164 allowedMessageTypes = [
165 "ActionRequest",
101 166 "Chat",
167 "Context",
102 168 "Disengaged",
169 "Progress",
103 170 "AdsQuery",
104 171 "SemanticSerp",
105 172 "GenerateContentQuery",
106 173 "SearchQuery",
107 "ActionRequest",
108 "Context",
109 "Progress",
110 "AdsQuery",
111 "SemanticSerp",
174 # The following message types should not be added so that it does not flood with
175 # useless messages (such as "Analyzing images" or "Searching the web") while it's retrieving the AI response
176 # "InternalSearchQuery",
177 # "InternalSearchResult",
178 # Not entirely certain about these two, but these parameters may be used for real-time markdown rendering.
179 # Keeping them could potentially complicate the retrieval of the messages because link references written while
180 # the AI is responding would then be moved to the very end of its message.
181 # "RenderCardRequest",
182 # "RenderContentRequest"
112 183 ]
113 184
114 185 sliceIds = [
115 "winmuid3tf",
116 "osbsdusgreccf",
117 "ttstmout",
118 "crchatrev",
119 "winlongmsgtf",
120 "ctrlworkpay",
121 "norespwtf",
122 "tempcacheread",
123 "temptacache",
124 "505scss0",
125 "508jbcars0",
126 "515enbotdets0",
127 "5082tsports",
128 "515vaoprvs",
129 "424dagslnv1s0",
130 "kcimgattcf",
131 "427startpms0",
186 "wrapuxslimt5",
187 "wrapalgo",
188 "wraptopalgo",
189 "st14",
190 "arankr1_1_9_9",
191 "0731ziv2s0",
192 "voiceall",
193 "1015onstblg",
194 "vsspec",
195 "cacdiscf",
196 "909ajcopus0",
197 "scpbfmob",
198 "rwt1",
199 "cacmuidarb",
200 "sappdlpt",
201 "917fluxv14",
202 "delaygc",
203 "remsaconn3p",
204 "splitcss3p",
205 "sydconfigoptt"
132 206 ]
133 207
134 208 location = {
@@ -173,27 +247,128 @@ class Defaults:
173 247 }
174 248
175 249 optionsSets = [
176 'saharasugg',
177 'enablenewsfc',
178 'clgalileo',
179 'gencontentv3',
180 250 "nlu_direct_response_filter",
181 251 "deepleo",
182 252 "disable_emoji_spoken_text",
183 253 "responsible_ai_policy_235",
184 254 "enablemm",
185 "h3precise"
186 "dtappid",
187 "cricinfo",
188 "cricinfov2",
189 255 "dv3sugg",
190 "nojbfedge"
256 "iyxapbing",
257 "iycapbing",
258 "h3imaginative",
259 "clgalileo",
260 "gencontentv3",
261 "fluxv14",
262 "eredirecturl"
191 263 ]
192 264
193 265 def format_message(msg: dict) -> str:
194 266 return json.dumps(msg, ensure_ascii=False) + Defaults.delimiter
195 267
268 def build_image_upload_api_payload(image_bin: str, conversation: Conversation, tone: str):
269 payload = {
270 'invokedSkills': ["ImageById"],
271 'subscriptionId': "Bing.Chat.Multimodal",
272 'invokedSkillsRequestData': {
273 'enableFaceBlur': True
274 },
275 'convoData': {
276 'convoid': "",
277 'convotone': tone
278 }
279 }
280 knowledge_request = {
281 'imageInfo': {},
282 'knowledgeRequest': payload
283 }
284 boundary="----WebKitFormBoundary" + ''.join(random.choices(string.ascii_letters + string.digits, k=16))
285 data = '--' + boundary + '\r\nContent-Disposition: form-data; name="knowledgeRequest"\r\n\r\n' + json.dumps(knowledge_request,ensure_ascii=False) + "\r\n--" + boundary + '\r\nContent-Disposition: form-data; name="imageBase64"\r\n\r\n' + image_bin + "\r\n--" + boundary + "--\r\n"
286 return data, boundary
287
288 def is_data_uri_an_image(data_uri):
289 try:
290 # Check if the data URI starts with 'data:image' and contains an image format (e.g., jpeg, png, gif)
291 if not re.match(r'data:image/(\w+);base64,', data_uri):
292 raise ValueError("Invalid data URI image.")
293 # Extract the image format from the data URI
294 image_format = re.match(r'data:image/(\w+);base64,', data_uri).group(1)
295 # Check if the image format is one of the allowed formats (jpg, jpeg, png, gif)
296 if image_format.lower() not in ['jpeg', 'jpg', 'png', 'gif']:
297 raise ValueError("Invalid image format (from mime file type).")
298 except Exception as e:
299 raise e
300
301 def is_accepted_format(binary_data):
302 try:
303 check = False
304 if binary_data.startswith(b'\xFF\xD8\xFF'):
305 check = True # It's a JPEG image
306 elif binary_data.startswith(b'\x89PNG\r\n\x1a\n'):
307 check = True # It's a PNG image
308 elif binary_data.startswith(b'GIF87a') or binary_data.startswith(b'GIF89a'):
309 check = True # It's a GIF image
310 elif binary_data.startswith(b'\x89JFIF') or binary_data.startswith(b'JFIF\x00'):
311 check = True # It's a JPEG image
312 elif binary_data.startswith(b'\xFF\xD8'):
313 check = True # It's a JPEG image
314 elif binary_data.startswith(b'RIFF') and binary_data[8:12] == b'WEBP':
315 check = True # It's a WebP image
316 # else we raise ValueError
317 if not check:
318 raise ValueError("Invalid image format (from magic code).")
319 except Exception as e:
320 raise e
321
322 def extract_data_uri(data_uri):
323 try:
324 data = data_uri.split(",")[1]
325 data = base64.b64decode(data)
326 return data
327 except Exception as e:
328 raise e
329
330 def get_orientation(data: bytes):
331 try:
332 if data[0:2] != b'\xFF\xD8':
333 raise Exception('NotJpeg')
334 with Image.open(data) as img:
335 exif_data = img._getexif()
336 if exif_data is not None:
337 orientation = exif_data.get(274) # 274 corresponds to the orientation tag in EXIF
338 if orientation is not None:
339 return orientation
340 except Exception:
341 pass
342
343 def process_image(orientation, img, new_width, new_height):
344 try:
345 # Initialize the canvas
346 new_img = Image.new("RGB", (new_width, new_height), color="#FFFFFF")
347 if orientation:
348 if orientation > 4:
349 img = img.transpose(Image.FLIP_LEFT_RIGHT)
350 if orientation == 3 or orientation == 4:
351 img = img.transpose(Image.ROTATE_180)
352 if orientation == 5 or orientation == 6:
353 img = img.transpose(Image.ROTATE_270)
354 if orientation == 7 or orientation == 8:
355 img = img.transpose(Image.ROTATE_90)
356 new_img.paste(img, (0, 0))
357 return new_img
358 except Exception as e:
359 raise e
360
361 def compress_image_to_base64(img, compression_rate):
362 try:
363 output_buffer = io.BytesIO()
364 img.save(output_buffer, format="JPEG", quality=int(compression_rate * 100))
365 base64_image = base64.b64encode(output_buffer.getvalue()).decode('utf-8')
366 return base64_image
367 except Exception as e:
368 raise e
369
196 370 def create_message(conversation: Conversation, prompt: str, tone: str, context: str=None) -> str:
371
197 372 request_id = str(uuid.uuid4())
198 373 struct = {
199 374 'arguments': [
@@ -213,6 +388,7 @@ def create_message(conversation: Conversation, prompt: str, tone: str, context:
213 388 'requestId': request_id,
214 389 'messageId': request_id,
215 390 },
391 "scenario": "SERP",
216 392 'tone': tone,
217 393 'spokenTextMode': 'None',
218 394 'conversationId': conversation.conversationId,
@@ -225,7 +401,11 @@ def create_message(conversation: Conversation, prompt: str, tone: str, context:
225 401 'target': 'chat',
226 402 'type': 4
227 403 }
228
404 if conversation.imageInfo != None and "imageUrl" in conversation.imageInfo and "originalImageUrl" in conversation.imageInfo:
405 struct['arguments'][0]['message']['originalImageUrl'] = conversation.imageInfo['originalImageUrl']
406 struct['arguments'][0]['message']['imageUrl'] = conversation.imageInfo['imageUrl']
407 struct['arguments'][0]['experienceType'] = None
408 struct['arguments'][0]['attachedFileInfo'] = {"fileName": None, "fileType": None}
229 409 if context:
230 410 struct['arguments'][0]['previousMessages'] = [{
231 411 "author": "user",
@@ -239,6 +419,7 @@ def create_message(conversation: Conversation, prompt: str, tone: str, context:
239 419 async def stream_generate(
240 420 prompt: str,
241 421 tone: str,
422 image: str = None,
242 423 context: str = None,
243 424 proxy: str = None,
244 425 cookies: dict = None
@@ -248,7 +429,7 @@ async def stream_generate(
248 429 cookies=cookies,
249 430 headers=Defaults.headers,
250 431 ) as session:
251 conversation = await create_conversation(session, proxy)
432 conversation = await create_conversation(session, tone, image, proxy)
252 433 try:
253 434 async with session.ws_connect(
254 435 f'wss://sydney.bing.com/sydney/ChatHub',
@@ -264,7 +445,6 @@ async def stream_generate(
264 445 response_txt = ''
265 446 returned_text = ''
266 447 final = False
267
268 448 while not final:
269 449 msg = await wss.receive(timeout=900)
270 450 objects = msg.data.split(Defaults.delimiter)
@@ -299,4 +479,4 @@ async def stream_generate(
299 479 raise Exception(f"{result['value']}: {result['message']}")
300 480 return
301 481 finally:
302 await delete_conversation(session, conversation, proxy)
482 await delete_conversation(session, conversation, proxy)