返回提交历史
Modified
g4f/Provider/base_provider.py
+1
-0
Modified
g4f/Provider/needs_auth/Gemini.py
+76
-39
Modified
g4f/client/async_client.py
+4
-0
Modified
g4f/client/client.py
+4
-0
XFEstudio/gpt4free
Add streaming and conversation support to gemini
b7624b75
代码差异
4 个文件
+85
-39
@@ -1,3 +1,4 @@
1
1
from ..providers.base_provider import *
2
2
from ..providers.types import FinishReason, Streaming
3
from ..providers.conversation import BaseConversation
3
4
from .helper import get_cookies, format_prompt
@@ -18,11 +18,11 @@ except ImportError:
18
18
19
19
from ... import debug
20
20
from ...typing import Messages, Cookies, ImageType, AsyncResult, AsyncIterator
21
from ..base_provider import AsyncGeneratorProvider
21
from ..base_provider import AsyncGeneratorProvider, BaseConversation
22
22
from ..helper import format_prompt, get_cookies
23
23
from ...requests.raise_for_status import raise_for_status
24
24
from ...errors import MissingAuthError, MissingRequirementsError
25
from ...image import to_bytes, ImageResponse, ImageDataResponse
25
from ...image import ImageResponse, to_bytes
26
26
from ...webdriver import get_browser, get_driver_cookies
27
27
28
28
REQUEST_HEADERS = {
@@ -32,7 +32,7 @@ REQUEST_HEADERS = {
32
32
'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',
33
33
'x-same-domain': '1',
34
34
}
35
REQUEST_BL_PARAM = "boq_assistant-bard-web-server_20240421.18_p0"
35
REQUEST_BL_PARAM = "boq_assistant-bard-web-server_20240519.16_p0"
36
36
REQUEST_URL = "https://gemini.google.com/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate"
37
37
UPLOAD_IMAGE_URL = "https://content-push.googleapis.com/upload/"
38
38
UPLOAD_IMAGE_HEADERS = {
@@ -57,6 +57,8 @@ class Gemini(AsyncGeneratorProvider):
57
57
image_models = ["gemini"]
58
58
default_vision_model = "gemini"
59
59
_cookies: Cookies = None
60
_snlm0e: str = None
61
_sid: str = None
60
62
61
63
@classmethod
62
64
async def nodriver_login(cls, proxy: str = None) -> AsyncIterator[str]:
@@ -117,42 +119,40 @@ class Gemini(AsyncGeneratorProvider):
117
119
model: str,
118
120
messages: Messages,
119
121
proxy: str = None,
120
api_key: str = None,
121
122
cookies: Cookies = None,
122
123
connector: BaseConnector = None,
123
124
image: ImageType = None,
124
125
image_name: str = None,
125
126
response_format: str = None,
127
return_conversation: bool = False,
128
conversation: Conversation = None,
129
language: str = "en",
126
130
**kwargs
127
131
) -> AsyncResult:
128
prompt = format_prompt(messages)
129
if api_key is not None:
130
if cookies is None:
131
cookies = {}
132
cookies["__Secure-1PSID"] = api_key
132
prompt = format_prompt(messages) if conversation is None else messages[-1]["content"]
133
133
cls._cookies = cookies or cls._cookies or get_cookies(".google.com", False, True)
134
134
base_connector = get_connector(connector, proxy)
135
135
async with ClientSession(
136
136
headers=REQUEST_HEADERS,
137
137
connector=base_connector
138
138
) as session:
139
snlm0e = await cls.fetch_snlm0e(session, cls._cookies) if cls._cookies else None
140
if not snlm0e:
139
if not cls._snlm0e:
140
await cls.fetch_snlm0e(session, cls._cookies) if cls._cookies else None
141
if not cls._snlm0e:
141
142
async for chunk in cls.nodriver_login(proxy):
142
143
yield chunk
143
144
if cls._cookies is None:
144
145
async for chunk in cls.webdriver_login(proxy):
145
146
yield chunk
146
147
if not snlm0e:
147
if not cls._snlm0e:
148
148
if cls._cookies is None or "__Secure-1PSID" not in cls._cookies:
149
149
raise MissingAuthError('Missing "__Secure-1PSID" cookie')
150
snlm0e = await cls.fetch_snlm0e(session, cls._cookies)
151
if not snlm0e:
150
await cls.fetch_snlm0e(session, cls._cookies)
151
if not cls._snlm0e:
152
152
raise RuntimeError("Invalid cookies. SNlM0e not found")
153
153
154
154
image_url = await cls.upload_image(base_connector, to_bytes(image), image_name) if image else None
155
155
156
156
async with ClientSession(
157
157
cookies=cls._cookies,
158
158
headers=REQUEST_HEADERS,
@@ -160,13 +160,17 @@ class Gemini(AsyncGeneratorProvider):
160
160
) as client:
161
161
params = {
162
162
'bl': REQUEST_BL_PARAM,
163
'hl': language,
163
164
'_reqid': random.randint(1111, 9999),
164
'rt': 'c'
165
'rt': 'c',
166
"f.sid": cls._sid,
165
167
}
166
168
data = {
167
'at': snlm0e,
169
'at': cls._snlm0e,
168
170
'f.req': json.dumps([None, json.dumps(cls.build_request(
169
171
prompt,
172
language=language,
173
conversation=conversation,
170
174
image_url=image_url,
171
175
image_name=image_name
172
176
))])
@@ -177,19 +181,33 @@ class Gemini(AsyncGeneratorProvider):
177
181
params=params,
178
182
) as response:
179
183
await raise_for_status(response)
180
response = await response.text()
181
response_part = json.loads(json.loads(response.splitlines()[-5])[0][2])
182
if response_part[4] is None:
183
response_part = json.loads(json.loads(response.splitlines()[-7])[0][2])
184
185
content = response_part[4][0][1][0]
186
image_prompt = None
187
match = re.search(r'\[Imagen of (.*?)\]', content)
188
if match:
189
image_prompt = match.group(1)
190
content = content.replace(match.group(0), '')
191
192
yield content
184
image_prompt = response_part = None
185
last_content_len = 0
186
async for line in response.content:
187
try:
188
try:
189
line = json.loads(line)
190
except ValueError:
191
continue
192
if not isinstance(line, list):
193
continue
194
if len(line[0]) < 3 or not line[0][2]:
195
continue
196
response_part = json.loads(line[0][2])
197
if not response_part[4]:
198
continue
199
if return_conversation:
200
yield Conversation(response_part[1][0], response_part[1][1], response_part[4][0][0])
201
content = response_part[4][0][1][0]
202
except (ValueError, KeyError, TypeError, IndexError) as e:
203
print(f"{cls.__name__}:{e.__class__.__name__}:{e}")
204
continue
205
match = re.search(r'\[Imagen of (.*?)\]', content)
206
if match:
207
image_prompt = match.group(1)
208
content = content.replace(match.group(0), '')
209
yield content[last_content_len:]
210
last_content_len = len(content)
193
211
if image_prompt:
194
212
images = [image[0][3][3] for image in response_part[4][0][12][7][0]]
195
213
if response_format == "b64_json":
@@ -208,9 +226,8 @@ class Gemini(AsyncGeneratorProvider):
208
226
209
227
def build_request(
210
228
prompt: str,
211
conversation_id: str = "",
212
response_id: str = "",
213
choice_id: str = "",
229
language: str,
230
conversation: Conversation = None,
214
231
image_url: str = None,
215
232
image_name: str = None,
216
233
tools: list[list[str]] = []
@@ -218,8 +235,15 @@ class Gemini(AsyncGeneratorProvider):
218
235
image_list = [[[image_url, 1], image_name]] if image_url else []
219
236
return [
220
237
[prompt, 0, None, image_list, None, None, 0],
221
["en"],
222
[conversation_id, response_id, choice_id, None, None, []],
238
[language],
239
[
240
None if conversation is None else conversation.conversation_id,
241
None if conversation is None else conversation.response_id,
242
None if conversation is None else conversation.choice_id,
243
None,
244
None,
245
[]
246
],
223
247
None,
224
248
None,
225
249
None,
@@ -265,7 +289,20 @@ class Gemini(AsyncGeneratorProvider):
265
289
async def fetch_snlm0e(cls, session: ClientSession, cookies: Cookies):
266
290
async with session.get(cls.url, cookies=cookies) as response:
267
291
await raise_for_status(response)
268
text = await response.text()
269
match = re.search(r'SNlM0e\":\"(.*?)\"', text)
292
response_text = await response.text()
293
match = re.search(r'SNlM0e\":\"(.*?)\"', response_text)
270
294
if match:
271
return match.group(1)
295
cls._snlm0e = match.group(1)
296
sid_match = re.search(r'"FdrFJe":"([\d-]+)"', response_text)
297
if sid_match:
298
cls._sid = sid_match.group(1)
299
300
class Conversation(BaseConversation):
301
def __init__(self,
302
conversation_id: str = "",
303
response_id: str = "",
304
choice_id: str = ""
305
) -> None:
306
self.conversation_id = conversation_id
307
self.response_id = response_id
308
self.choice_id = choice_id
@@ -18,6 +18,7 @@ from ..Provider import ProviderUtils
18
18
from ..typing import Union, Messages, AsyncIterator, ImageType
19
19
from ..errors import NoImageResponseError, ProviderNotFoundError
20
20
from ..requests.aiohttp import get_connector
21
from ..providers.conversation import BaseConversation
21
22
from ..image import ImageResponse as ImageProviderResponse, ImageDataResponse
22
23
23
24
try:
@@ -42,6 +43,9 @@ async def iter_response(
42
43
if isinstance(chunk, FinishReason):
43
44
finish_reason = chunk.reason
44
45
break
46
elif isinstance(chunk, BaseConversation):
47
yield chunk
48
continue
45
49
content += str(chunk)
46
50
count += 1
47
51
if max_tokens is not None and count >= max_tokens:
@@ -6,6 +6,7 @@ import string
6
6
7
7
from ..typing import Union, Iterator, Messages, ImageType
8
8
from ..providers.types import BaseProvider, ProviderType, FinishReason
9
from ..providers.conversation import BaseConversation
9
10
from ..image import ImageResponse as ImageProviderResponse
10
11
from ..errors import NoImageResponseError
11
12
from .stubs import ChatCompletion, ChatCompletionChunk, Image, ImagesResponse
@@ -29,6 +30,9 @@ def iter_response(
29
30
if isinstance(chunk, FinishReason):
30
31
finish_reason = chunk.reason
31
32
break
33
elif isinstance(chunk, BaseConversation):
34
yield chunk
35
continue
32
36
content += str(chunk)
33
37
if max_tokens is not None and idx + 1 >= max_tokens:
34
38
finish_reason = "length"