返回提交历史
Modified
README.md
+9
-10
Modified
docs/client.md
+29
-4
Added
g4f/Provider/GeminiPro.py
+86
-0
Modified
g4f/Provider/__init__.py
+1
-0
Modified
g4f/Provider/needs_auth/OpenaiChat.py
+57
-32
Modified
g4f/api/__init__.py
+9
-4
Modified
g4f/client.py
+15
-8
Modified
g4f/image.py
+6
-6
Modified
g4f/requests/__init__.py
+9
-2
Modified
requirements.txt
+1
-1
Modified
setup.py
+1
-1
XFEstudio/gpt4free
Add GeminiPro API provider Set min version for undetected-chromedriver Add api_key to the new client
51264fe2
代码差异
11 个文件
+223
-68
@@ -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
[](/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
@@ -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
[](/docs/client.md)
113
[](/docs/client.md)
116
[](/docs/client.md) [](/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](/)
@@ -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"]
@@ -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
@@ -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
@@ -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)
@@ -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():
@@ -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
@@ -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)
@@ -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
@@ -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