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

XFEstudio/gpt4free

Add streaming and conversation support to gemini

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

代码差异

4 个文件 +85 -39
Modified g4f/Provider/base_provider.py +1 -0
@@ -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
Modified g4f/Provider/needs_auth/Gemini.py +76 -39
@@ -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
Modified g4f/client/async_client.py +4 -0
@@ -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:
Modified g4f/client/client.py +4 -0
@@ -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"