返回提交历史
Modified
g4f/Provider/__init__.py
+1
-1
Modified
g4f/Provider/bing/create_images.py
+3
-3
Added
g4f/Provider/needs_auth/Gemini.py
+165
-0
Modified
g4f/Provider/needs_auth/OpenaiChat.py
+8
-10
Modified
g4f/Provider/needs_auth/ThebApi.py
+10
-6
Modified
g4f/Provider/needs_auth/__init__.py
+1
-1
Renamed
g4f/Provider/selenium/Bard.py
+1
-0
Modified
g4f/Provider/selenium/__init__.py
+2
-1
Modified
g4f/__init__.py
+7
-6
Modified
g4f/errors.py
+1
-4
Modified
g4f/gui/server/backend.py
+1
-1
Modified
g4f/image.py
+14
-6
XFEstudio/gpt4free
Add Gemini Provider with image upload and generation
c1b992c3
代码差异
12 个文件
+214
-39
@@ -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
@@ -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)
@@ -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()
@@ -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:
@@ -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": {
@@ -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
@@ -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(
@@ -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
@@ -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
@@ -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
@@ -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
}
@@ -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__(