返回提交历史
Modified
.github/workflows/unittest.yml
+1
-1
Modified
README.md
+17
-0
Added
docs/cat.jpeg
+0
-0
Added
docs/client.md
+71
-0
Renamed
g4f/Provider/BingCreateImages.py
+4
-42
Modified
g4f/Provider/You.py
+5
-0
Modified
g4f/Provider/__init__.py
+1
-1
Modified
g4f/Provider/bing/create_images.py
+40
-3
Modified
g4f/Provider/create_images.py
+6
-2
Modified
g4f/Provider/needs_auth/OpenaiChat.py
+41
-44
Modified
g4f/__init__.py
+2
-1
Added
g4f/client.py
+267
-0
Modified
g4f/errors.py
+15
-12
Modified
g4f/gui/client/js/chat.v1.js
+10
-19
XFEstudio/gpt4free
Add new Client API with Docs Use object urls for the preview of image uploads. Fix upload images in You provider Fix create image. It's now a single image. Improve system message for create images.
aba4b96f
代码差异
14 个文件
+480
-125
@@ -24,7 +24,7 @@ jobs:
24
24
run: pip install -r requirements-min.txt
25
25
- name: Run tests
26
26
run: python -m etc.unittest
27
- name: Set up Python 3.11
27
- name: Set up Python 3.12
28
28
uses: actions/setup-python@v4
29
29
with:
30
30
python-version: "3.12"
@@ -226,6 +226,23 @@ docker-compose down
226
226
227
227
## 💡 Usage
228
228
229
### New Client with Image Generation
230
```python
231
from g4f.client import Client
232
233
client = Client()
234
response = client.images.generate(
235
model="gemini",
236
prompt="a white siamese cat",
237
...
238
)
239
image_url = response.data[0].url
240
```
241
Result:
242
[](/docs/client.md)
243
244
[to the client API](/docs/client.md)
245
229
246
### The Web UI
230
247
231
248
To start the web interface, type the following codes in the command line.
二进制文件已变更,无法进行逐行预览。
@@ -0,0 +1,71 @@
1
### Client API
2
##### from g4f (beta)
3
4
#### Start
5
This new client could:
6
7
```python
8
from g4f.client import Client
9
```
10
replaces this:
11
12
```python
13
from openai import OpenAI
14
```
15
in your Python Code.
16
17
New client have the same API as OpenAI.
18
19
#### Client
20
21
Create the client with custom providers:
22
23
```python
24
from g4f.client import Client
25
from g4f.Provider import BingCreateImages, OpenaiChat, Gemini
26
27
client = Client(
28
provider=OpenaiChat,
29
image_provider=Gemini,
30
proxies=None
31
)
32
```
33
34
#### Examples
35
36
Use the ChatCompletions:
37
38
```python
39
stream = client.chat.completions.create(
40
model="gpt-4",
41
messages=[{"role": "user", "content": "Say this is a test"}],
42
stream=True,
43
)
44
for chunk in stream:
45
if chunk.choices[0].delta.content is not None:
46
print(chunk.choices[0].delta.content, end="")
47
```
48
49
Or use it for creating a image:
50
```python
51
response = client.images.generate(
52
model="dall-e-3",
53
prompt="a white siamese cat",
54
...
55
)
56
57
image_url = response.data[0].url
58
```
59
60
Also this works with the client:
61
```python
62
response = client.images.create_variation(
63
image=open('cat.jpg')
64
model="bing",
65
...
66
)
67
68
image_url = response.data[0].url
69
```
70
71
[to Home](/docs/client.md)
@@ -1,60 +1,22 @@
1
1
from __future__ import annotations
2
2
3
3
import asyncio
4
import time
5
4
import os
6
5
from typing import Generator
7
6
8
7
from ..cookies import get_cookies
9
from ..webdriver import WebDriver, get_driver_cookies, get_browser
10
8
from ..image import ImageResponse
11
9
from ..errors import MissingRequirementsError, MissingAuthError
12
from .bing.create_images import BING_URL, create_images, create_session
10
from .bing.create_images import create_images, create_session, get_cookies_from_browser
13
11
14
BING_URL = "https://www.bing.com"
15
TIMEOUT_LOGIN = 1200
16
17
def wait_for_login(driver: WebDriver, timeout: int = TIMEOUT_LOGIN) -> None:
18
"""
19
Waits for the user to log in within a given timeout period.
20
21
Args:
22
driver (WebDriver): Webdriver for browser automation.
23
timeout (int): Maximum waiting time in seconds.
24
25
Raises:
26
RuntimeError: If the login process exceeds the timeout.
27
"""
28
driver.get(f"{BING_URL}/")
29
start_time = time.time()
30
while not driver.get_cookie("_U"):
31
if time.time() - start_time > timeout:
32
raise RuntimeError("Timeout error")
33
time.sleep(0.5)
34
35
def get_cookies_from_browser(proxy: str = None) -> dict[str, str]:
36
"""
37
Retrieves cookies from the browser using webdriver.
38
39
Args:
40
proxy (str, optional): Proxy configuration.
41
42
Returns:
43
dict[str, str]: Retrieved cookies.
44
"""
45
with get_browser(proxy=proxy) as driver:
46
wait_for_login(driver)
47
time.sleep(1)
48
return get_driver_cookies(driver)
49
50
class CreateImagesBing:
12
class BingCreateImages:
51
13
"""A class for creating images using Bing."""
52
14
53
15
def __init__(self, cookies: dict[str, str] = {}, proxy: str = None) -> None:
54
16
self.cookies = cookies
55
17
self.proxy = proxy
56
18
57
def create_completion(self, prompt: str) -> Generator[ImageResponse, None, None]:
19
def create(self, prompt: str) -> Generator[ImageResponse, None, None]:
58
20
"""
59
21
Generator for creating imagecompletion based on a prompt.
60
22
@@ -91,4 +53,4 @@ class CreateImagesBing:
91
53
proxy = self.proxy or os.environ.get("G4F_PROXY")
92
54
async with create_session(cookies, proxy) as session:
93
55
images = await create_images(session, prompt, proxy)
94
return ImageResponse(images, prompt, {"preview": "{image}?w=200&h=200"})
56
return ImageResponse(images, prompt, {"preview": "{image}?w=200&h=200"} if len(images) > 1 else {})
@@ -58,9 +58,14 @@ class You(AsyncGeneratorProvider):
58
58
"selectedChatMode": chat_mode,
59
59
#"chat": json.dumps(chat),
60
60
}
61
params = {
62
"userFiles": upload,
63
"selectedChatMode": chat_mode,
64
}
61
65
async with (client.post if chat_mode == "default" else client.get)(
62
66
f"{cls.url}/api/streamingSearch",
63
67
data=data,
68
params=params,
64
69
headers=headers,
65
70
cookies=cookies
66
71
) as response:
@@ -53,7 +53,7 @@ from .Vercel import Vercel
53
53
from .Ylokh import Ylokh
54
54
from .You import You
55
55
56
from .CreateImagesBing import CreateImagesBing
56
from .BingCreateImages import BingCreateImages
57
57
58
58
import sys
59
59
@@ -21,8 +21,10 @@ from ..create_images import CreateImagesProvider
21
21
from ..helper import get_connector
22
22
from ...base_provider import ProviderType
23
23
from ...errors import MissingRequirementsError
24
from ...webdriver import WebDriver, get_driver_cookies, get_browser
24
25
25
26
BING_URL = "https://www.bing.com"
27
TIMEOUT_LOGIN = 1200
26
28
TIMEOUT_IMAGE_CREATION = 300
27
29
ERRORS = [
28
30
"this prompt is being reviewed",
@@ -35,6 +37,39 @@ BAD_IMAGES = [
35
37
"https://r.bing.com/rp/TX9QuO3WzcCJz1uaaSwQAz39Kb0.jpg",
36
38
]
37
39
40
def wait_for_login(driver: WebDriver, timeout: int = TIMEOUT_LOGIN) -> None:
41
"""
42
Waits for the user to log in within a given timeout period.
43
44
Args:
45
driver (WebDriver): Webdriver for browser automation.
46
timeout (int): Maximum waiting time in seconds.
47
48
Raises:
49
RuntimeError: If the login process exceeds the timeout.
50
"""
51
driver.get(f"{BING_URL}/")
52
start_time = time.time()
53
while not driver.get_cookie("_U"):
54
if time.time() - start_time > timeout:
55
raise RuntimeError("Timeout error")
56
time.sleep(0.5)
57
58
def get_cookies_from_browser(proxy: str = None) -> dict[str, str]:
59
"""
60
Retrieves cookies from the browser using webdriver.
61
62
Args:
63
proxy (str, optional): Proxy configuration.
64
65
Returns:
66
dict[str, str]: Retrieved cookies.
67
"""
68
with get_browser(proxy=proxy) as driver:
69
wait_for_login(driver)
70
time.sleep(1)
71
return get_driver_cookies(driver)
72
38
73
def create_session(cookies: Dict[str, str], proxy: str = None, connector: BaseConnector = None) -> ClientSession:
39
74
"""
40
75
Creates a new client session with specified cookies and headers.
@@ -141,6 +176,8 @@ def read_images(html_content: str) -> List[str]:
141
176
"""
142
177
soup = BeautifulSoup(html_content, "html.parser")
143
178
tags = soup.find_all("img", class_="mimg")
179
if not tags:
180
tags = soup.find_all("img", class_="gir_mmimg")
144
181
images = [img["src"].split("?w=")[0] for img in tags]
145
182
if any(im in BAD_IMAGES for im in images):
146
183
raise RuntimeError("Bad images found")
@@ -158,10 +195,10 @@ def patch_provider(provider: ProviderType) -> CreateImagesProvider:
158
195
Returns:
159
196
CreateImagesProvider: The patched provider with image creation capabilities.
160
197
"""
161
from ..CreateImagesBing import CreateImagesBing
162
service = CreateImagesBing()
198
from ..BingCreateImages import BingCreateImages
199
service = BingCreateImages()
163
200
return CreateImagesProvider(
164
201
provider,
165
service.create_completion,
202
service.create,
166
203
service.create_async
167
204
)
@@ -7,10 +7,14 @@ from ..typing import CreateResult, Messages
7
7
from ..base_provider import BaseProvider, ProviderType
8
8
9
9
system_message = """
10
You can generate custom images with the DALL-E 3 image generator.
10
You can generate images, pictures, photos or img with the DALL-E 3 image generator.
11
11
To generate an image with a prompt, do this:
12
12
13
<img data-prompt=\"keywords for the image\">
13
Don't use images with data uri. It is important to use a prompt instead.
14
15
Never use own image links. Don't wrap it in backticks.
16
It is important to use a only a img tag with a prompt.
17
14
18
<img data-prompt=\"image caption\">
15
19
"""
16
20
@@ -386,50 +386,47 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
386
386
) as response:
387
387
if not response.ok:
388
388
raise RuntimeError(f"Response {response.status_code}: {await response.text()}")
389
try:
390
last_message: int = 0
391
async for line in response.iter_lines():
392
if not line.startswith(b"data: "):
393
continue
394
elif line.startswith(b"data: [DONE]"):
395
break
396
try:
397
line = json.loads(line[6:])
398
except:
399
continue
400
if "message" not in line:
401
continue
402
if "error" in line and line["error"]:
403
raise RuntimeError(line["error"])
404
if "message_type" not in line["message"]["metadata"]:
405
continue
406
try:
407
image_response = await cls.get_generated_image(session, auth_headers, line)
408
if image_response:
409
yield image_response
410
except Exception as e:
411
yield e
412
if line["message"]["author"]["role"] != "assistant":
413
continue
414
if line["message"]["content"]["content_type"] != "text":
415
continue
416
if line["message"]["metadata"]["message_type"] not in ("next", "continue", "variant"):
417
continue
418
conversation_id = line["conversation_id"]
419
parent_id = line["message"]["id"]
420
if response_fields:
421
response_fields = False
422
yield ResponseFields(conversation_id, parent_id, end_turn)
423
if "parts" in line["message"]["content"]:
424
new_message = line["message"]["content"]["parts"][0]
425
if len(new_message) > last_message:
426
yield new_message[last_message:]
427
last_message = len(new_message)
428
if "finish_details" in line["message"]["metadata"]:
429
if line["message"]["metadata"]["finish_details"]["type"] == "stop":
430
end_turn.end()
431
except Exception as e:
432
raise e
389
last_message: int = 0
390
async for line in response.iter_lines():
391
if not line.startswith(b"data: "):
392
continue
393
elif line.startswith(b"data: [DONE]"):
394
break
395
try:
396
line = json.loads(line[6:])
397
except:
398
continue
399
if "message" not in line:
400
continue
401
if "error" in line and line["error"]:
402
raise RuntimeError(line["error"])
403
if "message_type" not in line["message"]["metadata"]:
404
continue
405
try:
406
image_response = await cls.get_generated_image(session, auth_headers, line)
407
if image_response:
408
yield image_response
409
except Exception as e:
410
yield e
411
if line["message"]["author"]["role"] != "assistant":
412
continue
413
if line["message"]["content"]["content_type"] != "text":
414
continue
415
if line["message"]["metadata"]["message_type"] not in ("next", "continue", "variant"):
416
continue
417
conversation_id = line["conversation_id"]
418
parent_id = line["message"]["id"]
419
if response_fields:
420
response_fields = False
421
yield ResponseFields(conversation_id, parent_id, end_turn)
422
if "parts" in line["message"]["content"]:
423
new_message = line["message"]["content"]["parts"][0]
424
if len(new_message) > last_message:
425
yield new_message[last_message:]
426
last_message = len(new_message)
427
if "finish_details" in line["message"]["metadata"]:
428
if line["message"]["metadata"]["finish_details"]["type"] == "stop":
429
end_turn.end()
433
430
if not auto_continue:
434
431
break
435
432
action = "continue"
@@ -16,7 +16,8 @@ def get_model_and_provider(model : Union[Model, str],
16
16
stream : bool,
17
17
ignored : list[str] = None,
18
18
ignore_working: bool = False,
19
ignore_stream: bool = False) -> tuple[str, ProviderType]:
19
ignore_stream: bool = False,
20
**kwargs) -> tuple[str, ProviderType]:
20
21
"""
21
22
Retrieves the model and provider based on input parameters.
22
23
@@ -0,0 +1,267 @@
1
from __future__ import annotations
2
3
import re
4
5
from .typing import Union, Generator, AsyncGenerator, Messages, ImageType
6
from .base_provider import BaseProvider, ProviderType
7
from .Provider.base_provider import AsyncGeneratorProvider
8
from .image import ImageResponse as ImageProviderResponse
9
from .Provider import BingCreateImages, Gemini, OpenaiChat
10
from .errors import NoImageResponseError
11
from . import get_model_and_provider
12
13
ImageProvider = Union[BaseProvider, object]
14
Proxies = Union[dict, str]
15
16
def read_json(text: str) -> dict:
17
"""
18
Parses JSON code block from a string.
19
20
Args:
21
text (str): A string containing a JSON code block.
22
23
Returns:
24
dict: A dictionary parsed from the JSON code block.
25
"""
26
match = re.search(r"```(json|)\n(?P<code>[\S\s]+?)\n```", text)
27
if match:
28
return match.group("code")
29
return text
30
31
def iter_response(
32
response: iter,
33
stream: bool,
34
response_format: dict = None,
35
max_tokens: int = None,
36
stop: list = None
37
) -> Generator:
38
content = ""
39
idx = 1
40
chunk = None
41
finish_reason = "stop"
42
for idx, chunk in enumerate(response):
43
content += str(chunk)
44
if max_tokens is not None and idx > max_tokens:
45
finish_reason = "max_tokens"
46
break
47
first = -1
48
word = None
49
if stop is not None:
50
for word in list(stop):
51
first = content.find(word)
52
if first != -1:
53
content = content[:first]
54
break
55
if stream:
56
if first != -1:
57
first = chunk.find(word)
58
if first != -1:
59
chunk = chunk[:first]
60
else:
61
first = 0
62
yield ChatCompletionChunk([ChatCompletionDeltaChoice(ChatCompletionDelta(chunk))])
63
if first != -1:
64
break
65
if not stream:
66
if response_format is not None and "type" in response_format:
67
if response_format["type"] == "json_object":
68
response = read_json(response)
69
yield ChatCompletion([ChatCompletionChoice(ChatCompletionMessage(response, finish_reason))])
70
71
async def aiter_response(
72
response: aiter,
73
stream: bool,
74
response_format: dict = None,
75
max_tokens: int = None,
76
stop: list = None
77
) -> AsyncGenerator:
78
content = ""
79
try:
80
idx = 0
81
chunk = None
82
async for chunk in response:
83
content += str(chunk)
84
if max_tokens is not None and idx > max_tokens:
85
break
86
first = -1
87
word = None
88
if stop is not None:
89
for word in list(stop):
90
first = content.find(word)
91
if first != -1:
92
content = content[:first]
93
break
94
if stream:
95
if first != -1:
96
first = chunk.find(word)
97
if first != -1:
98
chunk = chunk[:first]
99
else:
100
first = 0
101
yield ChatCompletionChunk([ChatCompletionDeltaChoice(ChatCompletionDelta(chunk))])
102
if first != -1:
103
break
104
idx += 1
105
except:
106
...
107
if not stream:
108
if response_format is not None and "type" in response_format:
109
if response_format["type"] == "json_object":
110
response = read_json(response)
111
yield ChatCompletion([ChatCompletionChoice(ChatCompletionMessage(response))])
112
113
class Model():
114
def __getitem__(self, item):
115
return getattr(self, item)
116
117
class ChatCompletion(Model):
118
def __init__(self, choices: list):
119
self.choices = choices
120
121
class ChatCompletionChunk(Model):
122
def __init__(self, choices: list):
123
self.choices = choices
124
125
class ChatCompletionChoice(Model):
126
def __init__(self, message: ChatCompletionMessage):
127
self.message = message
128
129
class ChatCompletionMessage(Model):
130
def __init__(self, content: str, finish_reason: str):
131
self.content = content
132
self.finish_reason = finish_reason
133
self.index = 0
134
self.logprobs = None
135
136
class ChatCompletionDelta(Model):
137
def __init__(self, content: str):
138
self.content = content
139
140
class ChatCompletionDeltaChoice(Model):
141
def __init__(self, delta: ChatCompletionDelta):
142
self.delta = delta
143
144
class Client():
145
proxies: Proxies = None
146
chat: Chat
147
148
def __init__(
149
self,
150
provider: ProviderType = None,
151
image_provider: ImageProvider = None,
152
proxies: Proxies = None,
153
**kwargs
154
) -> None:
155
self.proxies: Proxies = proxies
156
self.images = Images(self, image_provider)
157
self.chat = Chat(self, provider)
158
159
def get_proxy(self) -> Union[str, None]:
160
if isinstance(self.proxies, str) or self.proxies is None:
161
return self.proxies
162
elif "all" in self.proxies:
163
return self.proxies["all"]
164
elif "https" in self.proxies:
165
return self.proxies["https"]
166
return None
167
168
class Completions():
169
def __init__(self, client: Client, provider: ProviderType = None):
170
self.client: Client = client
171
self.provider: ProviderType = provider
172
173
def create(
174
self,
175
messages: Messages,
176
model: str,
177
provider: ProviderType = None,
178
stream: bool = False,
179
response_format: dict = None,
180
max_tokens: int = None,
181
stop: list = None,
182
**kwargs
183
) -> Union[dict, Generator]:
184
if max_tokens is not None:
185
kwargs["max_tokens"] = max_tokens
186
if stop:
187
kwargs["stop"] = list(stop)
188
model, provider = get_model_and_provider(
189
model,
190
self.provider if provider is None else provider,
191
stream,
192
**kwargs
193
)
194
response = provider.create_completion(model, messages, stream=stream, **kwargs)
195
if isinstance(provider, type) and issubclass(provider, AsyncGeneratorProvider):
196
response = iter_response(response, stream, response_format) # max_tokens, stop
197
else:
198
response = iter_response(response, stream, response_format, max_tokens, stop)
199
return response if stream else next(response)
200
201
class Chat():
202
completions: Completions
203
204
def __init__(self, client: Client, provider: ProviderType = None):
205
self.completions = Completions(client, provider)
206
207
class ImageModels():
208
gemini = Gemini
209
openai = OpenaiChat
210
211
def __init__(self, client: Client) -> None:
212
self.client = client
213
self.default = BingCreateImages(proxy=self.client.get_proxy())
214
215
def get(self, name: str) -> ImageProvider:
216
return getattr(self, name) if hasattr(self, name) else self.default
217
218
class ImagesResponse(Model):
219
data: list[Image]
220
221
def __init__(self, data: list) -> None:
222
self.data = data
223
224
class Image(Model):
225
url: str
226
227
def __init__(self, url: str) -> None:
228
self.url = url
229
230
class Images():
231
def __init__(self, client: Client, provider: ImageProvider = None):
232
self.client: Client = client
233
self.provider: ImageProvider = provider
234
self.models: ImageModels = ImageModels(client)
235
236
def generate(self, prompt, model: str = None, **kwargs):
237
provider = self.models.get(model) if model else self.provider or self.models.get(model)
238
if isinstance(provider, BaseProvider) or isinstance(provider, type) and issubclass(provider, BaseProvider):
239
prompt = f"create a image: {prompt}"
240
response = provider.create_completion(
241
"",
242
[{"role": "user", "content": prompt}],
243
True,
244
proxy=self.client.get_proxy()
245
)
246
else:
247
response = provider.create(prompt)
248
249
for chunk in response:
250
if isinstance(chunk, ImageProviderResponse):
251
return ImagesResponse([Image(image)for image in list(chunk.images)])
252
raise NoImageResponseError()
253
254
def create_variation(self, image: ImageType, model: str = None, **kwargs):
255
provider = self.models.get(model) if model else self.provider
256
if isinstance(provider, BaseProvider):
257
response = provider.create_completion(
258
"",
259
[{"role": "user", "content": "create a image like this"}],
260
True,
261
image=image,
262
proxy=self.client.get_proxy()
263
)
264
for chunk in response:
265
if isinstance(chunk, ImageProviderResponse):
266
return ImagesResponse([Image(image)for image in list(chunk.images)])
267
raise NoImageResponseError()
@@ -1,35 +1,38 @@
1
1
class ProviderNotFoundError(Exception):
2
pass
2
...
3
3
4
4
class ProviderNotWorkingError(Exception):
5
pass
5
...
6
6
7
7
class StreamNotSupportedError(Exception):
8
pass
8
...
9
9
10
10
class ModelNotFoundError(Exception):
11
pass
11
...
12
12
13
13
class ModelNotAllowedError(Exception):
14
pass
14
...
15
15
16
16
class RetryProviderError(Exception):
17
pass
17
...
18
18
19
19
class RetryNoProviderError(Exception):
20
pass
20
...
21
21
22
22
class VersionNotFoundError(Exception):
23
pass
23
...
24
24
25
25
class NestAsyncioError(Exception):
26
pass
26
...
27
27
28
28
class ModelNotSupportedError(Exception):
29
pass
29
...
30
30
31
31
class MissingRequirementsError(Exception):
32
pass
32
...
33
33
34
34
class MissingAuthError(Exception):
35
pass
35
...
36
37
class NoImageResponseError(Exception):
38
...