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

XFEstudio/gpt4free

Add Gemini Provider with image upload and generation

c1b992c3
Heiner Lohaus <hlohaus@users.noreply.github.com>
提交于

代码差异

12 个文件 +214 -39
Modified g4f/Provider/__init__.py +1 -1
@@ -5,9 +5,9 @@ from .retry_provider import RetryProvider
5 5 from .base_provider import AsyncProvider, AsyncGeneratorProvider
6 6 from .create_images import CreateImagesProvider
7 7 from .deprecated import *
8 from .selenium import *
8 9 from .needs_auth import *
9 10 from .unfinished import *
10 from .selenium import *
11 11
12 12 from .AiAsk import AiAsk
13 13 from .AiChatOnline import AiChatOnline
Modified g4f/Provider/bing/create_images.py +3 -3
@@ -23,7 +23,7 @@ from ..helper import get_cookies, get_connector
23 23 from ...webdriver import WebDriver, get_driver_cookies, get_browser
24 24 from ...base_provider import ProviderType
25 25 from ...image import ImageResponse
26 from ...errors import MissingRequirementsError, MissingAccessToken
26 from ...errors import MissingRequirementsError, MissingAuthError
27 27
28 28 BING_URL = "https://www.bing.com"
29 29 TIMEOUT_LOGIN = 1200
@@ -210,7 +210,7 @@ class CreateImagesBing:
210 210 try:
211 211 self.cookies = get_cookies_from_browser(self.proxy)
212 212 except MissingRequirementsError as e:
213 raise MissingAccessToken(f'Missing "_U" cookie. {e}')
213 raise MissingAuthError(f'Missing "_U" cookie. {e}')
214 214 yield asyncio.run(self.create_async(prompt))
215 215
216 216 async def create_async(self, prompt: str) -> ImageResponse:
@@ -225,7 +225,7 @@ class CreateImagesBing:
225 225 """
226 226 cookies = self.cookies or get_cookies(".bing.com", False)
227 227 if "_U" not in cookies:
228 raise MissingAccessToken('Missing "_U" cookie')
228 raise MissingAuthError('Missing "_U" cookie')
229 229 proxy = os.environ.get("G4F_PROXY")
230 230 async with create_session(cookies, proxy) as session:
231 231 images = await create_images(session, prompt, self.proxy)
Added g4f/Provider/needs_auth/Gemini.py +165 -0
@@ -0,0 +1,165 @@
1 from __future__ import annotations
2
3 import json
4 import random
5 import re
6
7 from aiohttp import ClientSession
8
9 from ...typing import Messages, Cookies, ImageType, AsyncResult
10 from ..base_provider import AsyncGeneratorProvider
11 from ..helper import format_prompt, get_cookies
12 from ...errors import MissingAuthError
13 from ...image import to_bytes, ImageResponse
14
15 REQUEST_HEADERS = {
16 "authority": "gemini.google.com",
17 "origin": "https://gemini.google.com",
18 "referer": "https://gemini.google.com/",
19 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36',
20 'x-same-domain': '1',
21 }
22 REQUEST_BL_PARAM = "boq_assistant-bard-web-server_20240201.08_p8"
23 REQUEST_URL = "https://gemini.google.com/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate"
24 UPLOAD_IMAGE_URL = "https://content-push.googleapis.com/upload/"
25 UPLOAD_IMAGE_HEADERS = {
26 "authority": "content-push.googleapis.com",
27 "accept": "*/*",
28 "accept-language": "en-US,en;q=0.7",
29 "authorization": "Basic c2F2ZXM6cyNMdGhlNmxzd2F2b0RsN3J1d1U=",
30 "content-type": "application/x-www-form-urlencoded;charset=UTF-8",
31 "origin": "https://gemini.google.com",
32 "push-id": "feeds/mcudyrk2a4khkz",
33 "referer": "https://gemini.google.com/",
34 "x-goog-upload-command": "start",
35 "x-goog-upload-header-content-length": "",
36 "x-goog-upload-protocol": "resumable",
37 "x-tenant-id": "bard-storage",
38 }
39
40 class Gemini(AsyncGeneratorProvider):
41 url = "https://gemini.google.com"
42 needs_auth = True
43 working = True
44 supports_stream = False
45
46 @classmethod
47 async def create_async_generator(
48 cls,
49 model: str,
50 messages: Messages,
51 proxy: str = None,
52 cookies: Cookies = None,
53 image: ImageType = None,
54 image_name: str = None,
55 **kwargs
56 ) -> AsyncResult:
57 prompt = format_prompt(messages)
58 if not cookies:
59 cookies = get_cookies(".google.com", False)
60 if "__Secure-1PSID" not in cookies:
61 raise MissingAuthError('Missing "__Secure-1PSID" cookie')
62
63 image_url = await cls.upload_image(to_bytes(image), image_name, proxy) if image else None
64
65 async with ClientSession(
66 cookies=cookies,
67 headers=REQUEST_HEADERS
68 ) as session:
69 async with session.get(cls.url, proxy=proxy) as response:
70 text = await response.text()
71 match = re.search(r'SNlM0e\":\"(.*?)\"', text)
72 if match:
73 snlm0e = match.group(1)
74 else:
75 raise RuntimeError("SNlM0e not found")
76
77 params = {
78 'bl': REQUEST_BL_PARAM,
79 '_reqid': random.randint(1111, 9999),
80 'rt': 'c'
81 }
82 data = {
83 'at': snlm0e,
84 'f.req': json.dumps([None, json.dumps(cls.build_request(
85 prompt,
86 image_url=image_url,
87 image_name=image_name
88 ))])
89 }
90 async with session.post(
91 REQUEST_URL,
92 data=data,
93 params=params,
94 proxy=proxy
95 ) as response:
96 response = await response.text()
97 response_part = json.loads(json.loads(response.splitlines()[-5])[0][2])
98 if response_part[4] is None:
99 response_part = json.loads(json.loads(response.splitlines()[-7])[0][2])
100
101 content = response_part[4][0][1][0]
102 image_prompt = None
103 match = re.search(r'\[Imagen of (.*?)\]', content)
104 if match:
105 image_prompt = match.group(1)
106 content = content.replace(match.group(0), '')
107
108 yield content
109 if image_prompt:
110 images = [image[0][3][3] for image in response_part[4][0][12][7][0]]
111 yield ImageResponse(images, image_prompt)
112
113 def build_request(
114 prompt: str,
115 conversation_id: str = "",
116 response_id: str = "",
117 choice_id: str = "",
118 image_url: str = None,
119 image_name: str = None,
120 tools: list[list[str]] = []
121 ) -> list:
122 image_list = [[[image_url, 1], image_name]] if image_url else []
123 return [
124 [prompt, 0, None, image_list, None, None, 0],
125 ["en"],
126 [conversation_id, response_id, choice_id, None, None, []],
127 None,
128 None,
129 None,
130 [1],
131 0,
132 [],
133 tools,
134 1,
135 0,
136 ]
137
138 async def upload_image(image: bytes, image_name: str = None, proxy: str = None):
139 async with ClientSession(
140 headers=UPLOAD_IMAGE_HEADERS
141 ) as session:
142 async with session.options(UPLOAD_IMAGE_URL, proxy=proxy) as reponse:
143 reponse.raise_for_status()
144
145 headers = {
146 "size": str(len(image)),
147 "x-goog-upload-command": "start"
148 }
149 data = f"File name: {image_name}" if image_name else None
150 async with session.post(
151 UPLOAD_IMAGE_URL, headers=headers, data=data, proxy=proxy
152 ) as response:
153 response.raise_for_status()
154 upload_url = response.headers["X-Goog-Upload-Url"]
155
156 async with session.options(upload_url, headers=headers) as response:
157 response.raise_for_status()
158
159 headers["x-goog-upload-command"] = "upload, finalize"
160 headers["X-Goog-Upload-Offset"] = "0"
161 async with session.post(
162 upload_url, headers=headers, data=image, proxy=proxy
163 ) as response:
164 response.raise_for_status()
165 return await response.text()
Modified g4f/Provider/needs_auth/OpenaiChat.py +8 -10
@@ -25,7 +25,7 @@ from ...webdriver import get_browser, get_driver_cookies
25 25 from ...typing import AsyncResult, Messages, Cookies, ImageType
26 26 from ...requests import StreamSession
27 27 from ...image import to_image, to_bytes, ImageResponse, ImageRequest
28 from ...errors import MissingRequirementsError, MissingAccessToken
28 from ...errors import MissingRequirementsError, MissingAuthError
29 29
30 30
31 31 class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
@@ -99,7 +99,8 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
99 99 cls,
100 100 session: StreamSession,
101 101 headers: dict,
102 image: ImageType
102 image: ImageType,
103 image_name: str = None
103 104 ) -> ImageRequest:
104 105 """
105 106 Upload an image to the service and get the download URL
@@ -118,7 +119,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
118 119 # Convert the image to a bytes object and get the size
119 120 data_bytes = to_bytes(image)
120 121 data = {
121 "file_name": f"{image.width}x{image.height}.{extension}",
122 "file_name": image_name if image_name else f"{image.width}x{image.height}.{extension}",
122 123 "file_size": len(data_bytes),
123 124 "use_case": "multimodal"
124 125 }
@@ -338,7 +339,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
338 339 try:
339 340 access_token, cookies = cls.browse_access_token(proxy)
340 341 except MissingRequirementsError:
341 raise MissingAccessToken(f'Missing "access_token"')
342 raise MissingAuthError(f'Missing "access_token"')
342 343 cls._cookies = cookies
343 344
344 345 headers = {"Authorization": f"Bearer {access_token}"}
@@ -351,7 +352,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
351 352 try:
352 353 image_response = None
353 354 if image:
354 image_response = await cls.upload_image(session, headers, image)
355 image_response = await cls.upload_image(session, headers, image, kwargs.get("image_name"))
355 356 except Exception as e:
356 357 yield e
357 358 end_turn = EndTurn()
@@ -438,21 +439,18 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
438 439 Returns:
439 440 tuple[str, dict]: A tuple containing the access token and cookies.
440 441 """
441 driver = get_browser(proxy=proxy)
442 try:
442 with get_browser(proxy=proxy) as driver:
443 443 driver.get(f"{cls.url}/")
444 444 WebDriverWait(driver, timeout).until(EC.presence_of_element_located((By.ID, "prompt-textarea")))
445 445 access_token = driver.execute_script(
446 446 "let session = await fetch('/api/auth/session');"
447 447 "let data = await session.json();"
448 448 "let accessToken = data['accessToken'];"
449 "let expires = new Date(); expires.setTime(expires.getTime() + 60 * 60 * 24 * 7);"
449 "let expires = new Date(); expires.setTime(expires.getTime() + 60 * 60 * 4);"
450 450 "document.cookie = 'access_token=' + accessToken + ';expires=' + expires.toUTCString() + ';path=/';"
451 451 "return accessToken;"
452 452 )
453 453 return access_token, get_driver_cookies(driver)
454 finally:
455 driver.quit()
456 454
457 455 @classmethod
458 456 async def get_arkose_token(cls, session: StreamSession) -> str:
Modified g4f/Provider/needs_auth/ThebApi.py +10 -6
@@ -3,7 +3,8 @@ from __future__ import annotations
3 3 import requests
4 4
5 5 from ...typing import Any, CreateResult, Messages
6 from ..base_provider import AbstractProvider
6 from ..base_provider import AbstractProvider, ProviderModelMixin
7 from ...errors import MissingAuthError
7 8
8 9 models = {
9 10 "theb-ai": "TheB.AI",
@@ -29,13 +30,16 @@ models = {
29 30 "qwen-7b-chat": "Qwen 7B"
30 31 }
31 32
32 class ThebApi(AbstractProvider):
33 class ThebApi(AbstractProvider, ProviderModelMixin):
33 34 url = "https://theb.ai"
34 35 working = True
35 36 needs_auth = True
37 default_model = "gpt-3.5-turbo"
38 models = list(models)
36 39
37 @staticmethod
40 @classmethod
38 41 def create_completion(
42 cls,
39 43 model: str,
40 44 messages: Messages,
41 45 stream: bool,
@@ -43,8 +47,8 @@ class ThebApi(AbstractProvider):
43 47 proxy: str = None,
44 48 **kwargs
45 49 ) -> CreateResult:
46 if model and model not in models:
47 raise ValueError(f"Model are not supported: {model}")
50 if not auth:
51 raise MissingAuthError("Missing auth")
48 52 headers = {
49 53 'accept': 'application/json',
50 54 'authorization': f'Bearer {auth}',
@@ -54,7 +58,7 @@ class ThebApi(AbstractProvider):
54 58 # models = dict([(m["id"], m["name"]) for m in response])
55 59 # print(json.dumps(models, indent=4))
56 60 data: dict[str, Any] = {
57 "model": model if model else "gpt-3.5-turbo",
61 "model": cls.get_model(model),
58 62 "messages": messages,
59 63 "stream": False,
60 64 "model_params": {
Modified g4f/Provider/needs_auth/__init__.py +1 -1
@@ -1,4 +1,4 @@
1 from .Bard import Bard
1 from .Gemini import Gemini
2 2 from .Raycast import Raycast
3 3 from .Theb import Theb
4 4 from .ThebApi import ThebApi
Renamed g4f/Provider/selenium/Bard.py +1 -0
@@ -20,6 +20,7 @@ class Bard(AbstractProvider):
20 20 url = "https://bard.google.com"
21 21 working = True
22 22 needs_auth = True
23 webdriver = True
23 24
24 25 @classmethod
25 26 def create_completion(
Modified g4f/Provider/selenium/__init__.py +2 -1
@@ -2,4 +2,5 @@ from .AItianhuSpace import AItianhuSpace
2 2 from .MyShell import MyShell
3 3 from .PerplexityAi import PerplexityAi
4 4 from .Phind import Phind
5 from .TalkAi import TalkAi
5 from .TalkAi import TalkAi
6 from .Bard import Bard
Modified g4f/__init__.py +7 -6
@@ -91,7 +91,7 @@ class ChatCompletion:
91 91 auth : Union[str, None] = None,
92 92 ignored : list[str] = None,
93 93 ignore_working: bool = False,
94 ignore_stream_and_auth: bool = False,
94 ignore_stream: bool = False,
95 95 patch_provider: callable = None,
96 96 **kwargs) -> Union[CreateResult, str]:
97 97 """
@@ -105,7 +105,7 @@ class ChatCompletion:
105 105 auth (Union[str, None], optional): Authentication token or credentials, if required.
106 106 ignored (list[str], optional): List of provider names to be ignored.
107 107 ignore_working (bool, optional): If True, ignores the working status of the provider.
108 ignore_stream_and_auth (bool, optional): If True, ignores the stream and authentication requirement checks.
108 ignore_stream (bool, optional): If True, ignores the stream and authentication requirement checks.
109 109 patch_provider (callable, optional): Function to modify the provider.
110 110 **kwargs: Additional keyword arguments.
111 111
@@ -118,10 +118,11 @@ class ChatCompletion:
118 118 ProviderNotWorkingError: If the provider is not operational.
119 119 StreamNotSupportedError: If streaming is requested but not supported by the provider.
120 120 """
121 model, provider = get_model_and_provider(model, provider, stream, ignored, ignore_working, ignore_stream_and_auth)
122
123 if not ignore_stream_and_auth and provider.needs_auth and not auth:
124 raise AuthenticationRequiredError(f'{provider.__name__} requires authentication (use auth=\'cookie or token or jwt ...\' param)')
121 model, provider = get_model_and_provider(
122 model, provider, stream,
123 ignored, ignore_working,
124 ignore_stream or kwargs.get("ignore_stream_and_auth")
125 )
125 126
126 127 if auth:
127 128 kwargs['auth'] = auth
Modified g4f/errors.py +1 -4
@@ -7,9 +7,6 @@ class ProviderNotWorkingError(Exception):
7 7 class StreamNotSupportedError(Exception):
8 8 pass
9 9
10 class AuthenticationRequiredError(Exception):
11 pass
12
13 10 class ModelNotFoundError(Exception):
14 11 pass
15 12
@@ -37,5 +34,5 @@ class MissingRequirementsError(Exception):
37 34 class MissingAiohttpSocksError(MissingRequirementsError):
38 35 pass
39 36
40 class MissingAccessToken(Exception):
37 class MissingAuthError(Exception):
41 38 pass
Modified g4f/gui/server/backend.py +1 -1
@@ -162,7 +162,7 @@ class Backend_Api:
162 162 "provider": provider,
163 163 "messages": messages,
164 164 "stream": True,
165 "ignore_stream_and_auth": True,
165 "ignore_stream": True,
166 166 "patch_provider": patch,
167 167 **kwargs
168 168 }
Modified g4f/image.py +14 -6
@@ -210,20 +210,28 @@ def format_images_markdown(images, alt: str, preview: str = None) -> str:
210 210 end_flag = "<!-- generated images end -->\n"
211 211 return f"\n{start_flag}{images}\n{end_flag}\n"
212 212
213 def to_bytes(image: Image) -> bytes:
213 def to_bytes(image: ImageType) -> bytes:
214 214 """
215 215 Converts the given image to bytes.
216 216
217 217 Args:
218 image (Image.Image): The image to convert.
218 image (ImageType): The image to convert.
219 219
220 220 Returns:
221 221 bytes: The image as bytes.
222 222 """
223 bytes_io = BytesIO()
224 image.save(bytes_io, image.format)
225 image.seek(0)
226 return bytes_io.getvalue()
223 if isinstance(image, bytes):
224 return image
225 elif isinstance(image, str):
226 is_data_uri_an_image(image)
227 return extract_data_uri(image)
228 elif isinstance(image, Image):
229 bytes_io = BytesIO()
230 image.save(bytes_io, image.format)
231 image.seek(0)
232 return bytes_io.getvalue()
233 else:
234 return image.read()
227 235
228 236 class ImageResponse:
229 237 def __init__(