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

XFEstudio/gpt4free

Add GeminiPro API provider Set min version for undetected-chromedriver Add api_key to the new client

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

代码差异

11 个文件 +223 -68
Modified README.md +9 -10
@@ -103,7 +103,7 @@ or set the api base in your client to: [http://localhost:1337/v1](http://localho
103 103 1. [Download and install Python](https://www.python.org/downloads/) (Version 3.10+ is recommended).
104 104 2. [Install Google Chrome](https://www.google.com/chrome/) for providers with webdriver
105 105
106 ##### Install using pypi:
106 ##### Install using PyPI package:
107 107
108 108 ```
109 109 pip install -U g4f[all]
@@ -113,12 +113,12 @@ Or use partial requirements.
113 113
114 114 See: [/docs/requirements](/docs/requirements.md)
115 115
116 ##### Install from source:
116 ##### Install from source using git:
117 117
118 118 See: [/docs/git](/docs/git.md)
119 119
120 120
121 ##### Install using Docker
121 ##### Install using Docker for Developers:
122 122
123 123 See: [/docs/docker](/docs/docker.md)
124 124
@@ -126,7 +126,6 @@ See: [/docs/git](/docs/git.md)
126 126 ## 💡 Usage
127 127
128 128 #### Text Generation
129 **with Python**
130 129
131 130 ```python
132 131 from g4f.client import Client
@@ -134,14 +133,13 @@ from g4f.client import Client
134 133 client = Client()
135 134 response = client.chat.completions.create(
136 135 model="gpt-3.5-turbo",
137 messages=[{"role": "user", "content": "Say this is a test"}],
136 messages=[{"role": "user", "content": "Hello"}],
138 137 ...
139 138 )
140 139 print(response.choices[0].message.content)
141 140 ```
142 141
143 142 #### Image Generation
144 **with Python**
145 143
146 144 ```python
147 145 from g4f.client import Client
@@ -154,14 +152,15 @@ response = client.images.generate(
154 152 )
155 153 image_url = response.data[0].url
156 154 ```
157 Result:
155
156 **Result:**
158 157
159 158 [![Image with cat](/docs/cat.jpeg)](/docs/client.md)
160 159
161 **See also for Python:**
160 **See also:**
162 161
163 - [Documentation for new Client](/docs/client.md)
164 - [Documentation for leagcy API](/docs/leagcy.md)
162 - Documentation for the new Client: [/docs/client](/docs/client.md)
163 - Documentation for the leagcy API: [docs/leagcy](/docs/leagcy.md)
165 164
166 165
167 166 #### Web UI
Modified docs/client.md +29 -4
@@ -37,12 +37,16 @@ client = Client(
37 37 )
38 38 ```
39 39
40 You also have the option to define a proxy in the client for all outgoing requests:
40 ## Configuration
41
42 You can set an "api_key" for your provider in client.
43 And you also have the option to define a proxy for all outgoing requests:
41 44
42 45 ```python
43 46 from g4f.client import Client
44 47
45 48 client = Client(
49 api_key="...",
46 50 proxies="http://user:pass@host",
47 51 ...
48 52 )
@@ -74,7 +78,7 @@ stream = client.chat.completions.create(
74 78 )
75 79 for chunk in stream:
76 80 if chunk.choices[0].delta.content:
77 print(chunk.choices[0].delta.content, end="")
81 print(chunk.choices[0].delta.content or "", end="")
78 82 ```
79 83
80 84 **Image Generation:**
@@ -109,7 +113,28 @@ image_url = response.data[0].url
109 113
110 114 Original / Variant:
111 115
112 [![Original Image](/docs/cat.jpeg)](/docs/client.md)
113 [![Variant Image](/docs/cat.webp)](/docs/client.md)
116 [![Original Image](/docs/cat.jpeg)](/docs/client.md) [![Variant Image](/docs/cat.webp)](/docs/client.md)
117
118 #### Advanced example using GeminiProVision
119
120 ```python
121 from g4f.client import Client
122 from g4f.Provider.GeminiPro import GeminiPro
123
124 client = Client(
125 api_key="...",
126 provider=GeminiPro
127 )
128 response = client.chat.completions.create(
129 model="gemini-pro-vision",
130 messages=[{"role": "user", "content": "What are on this image?"}],
131 image=open("docs/cat.jpeg", "rb")
132 )
133 print(response.choices[0].message.content)
134 ```
135 **Question:** What are on this image?
136 ```
137 A cat is sitting on a window sill looking at a bird outside the window.
138 ```
114 139
115 140 [Return to Home](/)
Added g4f/Provider/GeminiPro.py +86 -0
@@ -0,0 +1,86 @@
1 from __future__ import annotations
2
3 import base64
4 import json
5 from aiohttp import ClientSession
6
7 from ..typing import AsyncResult, Messages, ImageType
8 from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
9 from ..image import to_bytes, is_accepted_format
10
11
12 class GeminiPro(AsyncGeneratorProvider, ProviderModelMixin):
13 url = "https://ai.google.dev"
14 working = True
15 supports_message_history = True
16 default_model = "gemini-pro"
17 models = ["gemini-pro", "gemini-pro-vision"]
18
19 @classmethod
20 async def create_async_generator(
21 cls,
22 model: str,
23 messages: Messages,
24 stream: bool = False,
25 proxy: str = None,
26 api_key: str = None,
27 image: ImageType = None,
28 **kwargs
29 ) -> AsyncResult:
30 model = "gemini-pro-vision" if not model and image else model
31 model = cls.get_model(model)
32 api_key = api_key if api_key else kwargs.get("access_token")
33 headers = {
34 "Content-Type": "application/json",
35 }
36 async with ClientSession(headers=headers) as session:
37 method = "streamGenerateContent" if stream else "generateContent"
38 url = f"https://generativelanguage.googleapis.com/v1beta/models/{model}:{method}"
39 contents = [
40 {
41 "role": "model" if message["role"] == "assistant" else message["role"],
42 "parts": [{"text": message["content"]}]
43 }
44 for message in messages
45 ]
46 if image:
47 image = to_bytes(image)
48 contents[-1]["parts"].append({
49 "inline_data": {
50 "mime_type": is_accepted_format(image),
51 "data": base64.b64encode(image).decode()
52 }
53 })
54 data = {
55 "contents": contents,
56 # "generationConfig": {
57 # "stopSequences": kwargs.get("stop"),
58 # "temperature": kwargs.get("temperature"),
59 # "maxOutputTokens": kwargs.get("max_tokens"),
60 # "topP": kwargs.get("top_p"),
61 # "topK": kwargs.get("top_k"),
62 # }
63 }
64 async with session.post(url, params={"key": api_key}, json=data, proxy=proxy) as response:
65 if not response.ok:
66 data = await response.json()
67 raise RuntimeError(data[0]["error"]["message"])
68 if stream:
69 lines = []
70 async for chunk in response.content:
71 if chunk == b"[{\n":
72 lines = [b"{\n"]
73 elif chunk == b",\r\n" or chunk == b"]":
74 try:
75 data = b"".join(lines)
76 data = json.loads(data)
77 yield data["candidates"][0]["content"]["parts"][0]["text"]
78 except:
79 data = data.decode() if isinstance(data, bytes) else data
80 raise RuntimeError(f"Read text failed. data: {data}")
81 lines = []
82 else:
83 lines.append(chunk)
84 else:
85 data = await response.json()
86 yield data["candidates"][0]["content"]["parts"][0]["text"]
Modified g4f/Provider/__init__.py +1 -0
@@ -34,6 +34,7 @@ from .FakeGpt import FakeGpt
34 34 from .FreeChatgpt import FreeChatgpt
35 35 from .FreeGpt import FreeGpt
36 36 from .GeekGpt import GeekGpt
37 from .GeminiPro import GeminiPro
37 38 from .GeminiProChat import GeminiProChat
38 39 from .Gpt6 import Gpt6
39 40 from .GPTalk import GPTalk
Modified g4f/Provider/needs_auth/OpenaiChat.py +57 -32
@@ -23,10 +23,11 @@ from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin
23 23 from ..helper import format_prompt, get_cookies
24 24 from ...webdriver import get_browser, get_driver_cookies
25 25 from ...typing import AsyncResult, Messages, Cookies, ImageType
26 from ...requests import StreamSession
26 from ...requests import get_args_from_browser
27 from ...requests.aiohttp import StreamSession
27 28 from ...image import to_image, to_bytes, ImageResponse, ImageRequest
28 29 from ...errors import MissingRequirementsError, MissingAuthError
29
30 from ... import debug
30 31
31 32 class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
32 33 """A class for creating and managing conversations with OpenAI chat service"""
@@ -39,7 +40,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
39 40 default_model = None
40 41 models = ["gpt-3.5-turbo", "gpt-4", "gpt-4-gizmo"]
41 42 model_aliases = {"text-davinci-002-render-sha": "gpt-3.5-turbo"}
42 _cookies: dict = {}
43 _args: dict = None
43 44
44 45 @classmethod
45 46 async def create(
@@ -169,11 +170,12 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
169 170 """
170 171 if not cls.default_model:
171 172 async with session.get(f"{cls.url}/backend-api/models", headers=headers) as response:
173 response.raise_for_status()
172 174 data = await response.json()
173 175 if "categories" in data:
174 176 cls.default_model = data["categories"][-1]["default_model"]
175 else:
176 raise RuntimeError(f"Response: {data}")
177 return cls.default_model
178 raise RuntimeError(f"Response: {data}")
177 179 return cls.default_model
178 180
179 181 @classmethod
@@ -249,8 +251,10 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
249 251 first_part = line["message"]["content"]["parts"][0]
250 252 if "asset_pointer" not in first_part or "metadata" not in first_part:
251 253 return
252 file_id = first_part["asset_pointer"].split("file-service://", 1)[1]
254 if first_part["metadata"] is None:
255 return
253 256 prompt = first_part["metadata"]["dalle"]["prompt"]
257 file_id = first_part["asset_pointer"].split("file-service://", 1)[1]
254 258 try:
255 259 async with session.get(f"{cls.url}/backend-api/files/{file_id}/download", headers=headers) as response:
256 260 response.raise_for_status()
@@ -289,7 +293,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
289 293 messages: Messages,
290 294 proxy: str = None,
291 295 timeout: int = 120,
292 access_token: str = None,
296 api_key: str = None,
293 297 cookies: Cookies = None,
294 298 auto_continue: bool = False,
295 299 history_disabled: bool = True,
@@ -308,7 +312,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
308 312 messages (Messages): The list of previous messages.
309 313 proxy (str): Proxy to use for requests.
310 314 timeout (int): Timeout for requests.
311 access_token (str): Access token for authentication.
315 api_key (str): Access token for authentication.
312 316 cookies (dict): Cookies to use for authentication.
313 317 auto_continue (bool): Flag to automatically continue the conversation.
314 318 history_disabled (bool): Flag to disable history and training.
@@ -329,35 +333,47 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
329 333 raise MissingRequirementsError('Install "py-arkose-generator" and "async_property" package')
330 334 if not parent_id:
331 335 parent_id = str(uuid.uuid4())
332 if not cookies:
333 cookies = cls._cookies or get_cookies("chat.openai.com", False)
334 if not access_token and "access_token" in cookies:
335 access_token = cookies["access_token"]
336 if not access_token:
337 login_url = os.environ.get("G4F_LOGIN_URL")
338 if login_url:
339 yield f"Please login: [ChatGPT]({login_url})\n\n"
340 try:
341 access_token, cookies = cls.browse_access_token(proxy)
342 except MissingRequirementsError:
343 raise MissingAuthError(f'Missing "access_token"')
344 cls._cookies = cookies
345
346 auth_headers = {"Authorization": f"Bearer {access_token}"}
336 if cls._args is None and cookies is None:
337 cookies = get_cookies("chat.openai.com", False)
338 api_key = kwargs["access_token"] if "access_token" in kwargs else api_key
339 if api_key is None:
340 api_key = cookies["access_token"] if "access_token" in cookies else api_key
341 if cls._args is None:
342 cls._args = {
343 "headers": {"Cookie": "; ".join(f"{k}={v}" for k, v in cookies.items() if k != "access_token")},
344 "cookies": {} if cookies is None else cookies
345 }
346 if api_key is not None:
347 cls._args["headers"]["Authorization"] = f"Bearer {api_key}"
347 348 async with StreamSession(
348 349 proxies={"https": proxy},
349 impersonate="chrome110",
350 impersonate="chrome",
350 351 timeout=timeout,
351 headers={"Cookie": "; ".join(f"{k}={v}" for k, v in cookies.items())}
352 headers=cls._args["headers"]
352 353 ) as session:
354 if api_key is not None:
355 try:
356 cls.default_model = await cls.get_default_model(session, cls._args["headers"])
357 except Exception as e:
358 if debug.logging:
359 print(f"{e.__class__.__name__}: {e}")
360 if cls.default_model is None:
361 login_url = os.environ.get("G4F_LOGIN_URL")
362 if login_url:
363 yield f"Please login: [ChatGPT]({login_url})\n\n"
364 try:
365 cls._args = cls.browse_access_token(proxy)
366 except MissingRequirementsError:
367 raise MissingAuthError(f'Missing or invalid "access_token". Add a new "api_key" please')
368 cls.default_model = await cls.get_default_model(session, cls._args["headers"])
353 369 try:
354 370 image_response = None
355 371 if image:
356 image_response = await cls.upload_image(session, auth_headers, image, kwargs.get("image_name"))
372 image_response = await cls.upload_image(session, cls._args["headers"], image, kwargs.get("image_name"))
357 373 except Exception as e:
358 374 yield e
359 375 end_turn = EndTurn()
360 model = cls.get_model(model or await cls.get_default_model(session, auth_headers))
376 model = cls.get_model(model)
361 377 model = "text-davinci-002-render-sha" if model == "gpt-3.5-turbo" else model
362 378 while not end_turn.is_end:
363 379 arkose_token = await cls.get_arkose_token(session)
@@ -375,13 +391,19 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
375 391 if action != "continue":
376 392 prompt = format_prompt(messages) if not conversation_id else messages[-1]["content"]
377 393 data["messages"] = cls.create_messages(prompt, image_response)
394
395 # Update cookies before next request
396 for c in session.cookie_jar if hasattr(session, "cookie_jar") else session.cookies.jar:
397 cls._args["cookies"][c.name if hasattr(c, "name") else c.key] = c.value
398 cls._args["headers"]["Cookie"] = "; ".join(f"{k}={v}" for k, v in cls._args["cookies"].items())
399
378 400 async with session.post(
379 401 f"{cls.url}/backend-api/conversation",
380 402 json=data,
381 403 headers={
382 404 "Accept": "text/event-stream",
383 405 "OpenAI-Sentinel-Arkose-Token": arkose_token,
384 **auth_headers
406 **cls._args["headers"]
385 407 }
386 408 ) as response:
387 409 if not response.ok:
@@ -403,8 +425,8 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
403 425 if "message_type" not in line["message"]["metadata"]:
404 426 continue
405 427 try:
406 image_response = await cls.get_generated_image(session, auth_headers, line)
407 if image_response:
428 image_response = await cls.get_generated_image(session, cls._args["headers"], line)
429 if image_response is not None:
408 430 yield image_response
409 431 except Exception as e:
410 432 yield e
@@ -432,7 +454,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
432 454 action = "continue"
433 455 await asyncio.sleep(5)
434 456 if history_disabled and auto_continue:
435 await cls.delete_conversation(session, auth_headers, conversation_id)
457 await cls.delete_conversation(session, cls._args["headers"], conversation_id)
436 458
437 459 @classmethod
438 460 def browse_access_token(cls, proxy: str = None, timeout: int = 1200) -> tuple[str, dict]:
@@ -457,7 +479,10 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
457 479 "document.cookie = 'access_token=' + accessToken + ';expires=' + expires.toUTCString() + ';path=/';"
458 480 "return accessToken;"
459 481 )
460 return access_token, get_driver_cookies(driver)
482 args = get_args_from_browser(f"{cls.url}/", driver, do_bypass_cloudflare=False)
483 args["headers"]["Authorization"] = f"Bearer {access_token}"
484 args["headers"]["Cookie"] = "; ".join(f"{k}={v}" for k, v in args["cookies"].items() if k != "access_token")
485 return args
461 486 finally:
462 487 driver.close()
463 488
Modified g4f/api/__init__.py +9 -4
@@ -21,7 +21,7 @@ class ChatCompletionsConfig(BaseModel):
21 21 temperature: Union[float, None]
22 22 max_tokens: int = None
23 23 stop: Union[list[str], str, None]
24 access_token: Union[str, None]
24 api_key: Union[str, None]
25 25
26 26 class Api:
27 27 def __init__(self, engine: g4f, debug: bool = True, sentry: bool = False,
@@ -82,10 +82,10 @@ class Api:
82 82 async def chat_completions(config: ChatCompletionsConfig = None, request: Request = None, provider: str = None):
83 83 try:
84 84 config.provider = provider if config.provider is None else config.provider
85 if config.access_token is None and request is not None:
85 if config.api_key is None and request is not None:
86 86 auth_header = request.headers.get("Authorization")
87 87 if auth_header is not None:
88 config.access_token = auth_header.split(None, 1)[-1]
88 config.api_key = auth_header.split(None, 1)[-1]
89 89
90 90 response = self.client.chat.completions.create(
91 91 **dict(config),
@@ -124,4 +124,9 @@ def format_exception(e: Exception, config: ChatCompletionsConfig) -> str:
124 124 "error": {"message": f"ChatCompletionsError: {e.__class__.__name__}: {e}"},
125 125 "model": last_provider.get("model") if last_provider else config.model,
126 126 "provider": last_provider.get("name") if last_provider else config.provider
127 })
127 })
128
129 def run_api(host: str = '0.0.0.0', port: int = 1337, debug: bool = False, use_colors=True) -> None:
130 print(f'Starting server... [g4f v-{g4f.version.utils.current_version}]')
131 app = Api(engine=g4f, debug=debug)
132 uvicorn.run(app=app, host=host, port=port, use_colors=use_colors)
Modified g4f/client.py +15 -8
@@ -86,20 +86,19 @@ def iter_append_model_and_provider(response: IterResponse) -> IterResponse:
86 86 yield chunk
87 87
88 88 class Client():
89 proxies: Proxies = None
90 chat: Chat
91 images: Images
92 89
93 90 def __init__(
94 91 self,
92 api_key: str = None,
93 proxies: Proxies = None,
95 94 provider: ProviderType = None,
96 95 image_provider: ImageProvider = None,
97 proxies: Proxies = None,
98 96 **kwargs
99 97 ) -> None:
100 self.chat = Chat(self, provider)
101 self.images = Images(self, image_provider)
98 self.api_key: str = api_key
102 99 self.proxies: Proxies = proxies
100 self.chat: Chat = Chat(self, provider)
101 self.images: Images = Images(self, image_provider)
103 102
104 103 def get_proxy(self) -> Union[str, None]:
105 104 if isinstance(self.proxies, str):
@@ -125,6 +124,7 @@ class Completions():
125 124 response_format: dict = None,
126 125 max_tokens: int = None,
127 126 stop: Union[list[str], str] = None,
127 api_key: str = None,
128 128 **kwargs
129 129 ) -> Union[ChatCompletion, Generator[ChatCompletionChunk]]:
130 130 if max_tokens is not None:
@@ -137,9 +137,16 @@ class Completions():
137 137 stream,
138 138 **kwargs
139 139 )
140 response = provider.create_completion(model, messages, stream=stream, proxy=self.client.get_proxy(), **kwargs)
141 140 stop = [stop] if isinstance(stop, str) else stop
142 response = iter_append_model_and_provider(iter_response(response, stream, response_format, max_tokens, stop))
141 response = provider.create_completion(
142 model, messages, stream,
143 proxy=self.client.get_proxy(),
144 stop=stop,
145 api_key=self.client.api_key if api_key is None else api_key,
146 **kwargs
147 )
148 response = iter_response(response, stream, response_format, max_tokens, stop)
149 response = iter_append_model_and_provider(response)
143 150 return response if stream else next(response)
144 151
145 152 class Chat():
Modified g4f/image.py +6 -6
@@ -97,17 +97,17 @@ def is_accepted_format(binary_data: bytes) -> bool:
97 97 ValueError: If the image format is not allowed.
98 98 """
99 99 if binary_data.startswith(b'\xFF\xD8\xFF'):
100 pass # It's a JPEG image
100 return "image/jpeg"
101 101 elif binary_data.startswith(b'\x89PNG\r\n\x1a\n'):
102 pass # It's a PNG image
102 return "image/png"
103 103 elif binary_data.startswith(b'GIF87a') or binary_data.startswith(b'GIF89a'):
104 pass # It's a GIF image
104 return "image/gif"
105 105 elif binary_data.startswith(b'\x89JFIF') or binary_data.startswith(b'JFIF\x00'):
106 pass # It's a JPEG image
106 return "image/jpeg"
107 107 elif binary_data.startswith(b'\xFF\xD8'):
108 pass # It's a JPEG image
108 return "image/jpeg"
109 109 elif binary_data.startswith(b'RIFF') and binary_data[8:12] == b'WEBP':
110 pass # It's a WebP image
110 return "image/webp"
111 111 else:
112 112 raise ValueError("Invalid image format (from magic code).")
113 113
Modified g4f/requests/__init__.py +9 -2
@@ -15,7 +15,13 @@ from ..webdriver import WebDriver, WebDriverSession, bypass_cloudflare, get_driv
15 15 from ..errors import MissingRequirementsError
16 16 from .defaults import DEFAULT_HEADERS
17 17
18 def get_args_from_browser(url: str, webdriver: WebDriver = None, proxy: str = None, timeout: int = 120) -> dict:
18 def get_args_from_browser(
19 url: str,
20 webdriver: WebDriver = None,
21 proxy: str = None,
22 timeout: int = 120,
23 do_bypass_cloudflare: bool = True
24 ) -> dict:
19 25 """
20 26 Create a Session object using a WebDriver to handle cookies and headers.
21 27
@@ -29,7 +35,8 @@ def get_args_from_browser(url: str, webdriver: WebDriver = None, proxy: str = No
29 35 Session: A Session object configured with cookies and headers from the WebDriver.
30 36 """
31 37 with WebDriverSession(webdriver, "", proxy=proxy, virtual_display=False) as driver:
32 bypass_cloudflare(driver, url, timeout)
38 if do_bypass_cloudflare:
39 bypass_cloudflare(driver, url, timeout)
33 40 cookies = get_driver_cookies(driver)
34 41 user_agent = driver.execute_script("return navigator.userAgent")
35 42 parse = urlparse(url)
Modified requirements.txt +1 -1
@@ -16,7 +16,7 @@ uvicorn
16 16 flask
17 17 py-arkose-generator
18 18 async-property
19 undetected-chromedriver
19 undetected-chromedriver>=3.5.5
20 20 brotli
21 21 beautifulsoup4
22 22 setuptools
Modified setup.py +1 -1
@@ -25,7 +25,7 @@ EXTRA_REQUIRE = {
25 25 "beautifulsoup4", # internet.search and bing.create_images
26 26 "brotli", # openai
27 27 "platformdirs", # webdriver
28 "undetected-chromedriver", # webdriver
28 "undetected-chromedriver>=3.5.5", # webdriver
29 29 "setuptools", # webdriver
30 30 "aiohttp_socks", # proxy
31 31 "pillow", # image