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

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
Heiner Lohaus <hlohaus@users.noreply.github.com>
提交于

代码差异

14 个文件 +480 -125
Modified .github/workflows/unittest.yml +1 -1
@@ -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"
Modified README.md +17 -0
@@ -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 [![Image with cat](/docs/cat.jpeg)](/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.
Added docs/cat.jpeg +0 -0
二进制文件已变更,无法进行逐行预览。
Added docs/client.md +71 -0
@@ -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)
Renamed g4f/Provider/BingCreateImages.py +4 -42
@@ -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 {})
Modified g4f/Provider/You.py +5 -0
@@ -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:
Modified g4f/Provider/__init__.py +1 -1
@@ -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
Modified g4f/Provider/bing/create_images.py +40 -3
@@ -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 )
Modified g4f/Provider/create_images.py +6 -2
@@ -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
Modified g4f/Provider/needs_auth/OpenaiChat.py +41 -44
@@ -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"
Modified g4f/__init__.py +2 -1
@@ -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
Added g4f/client.py +267 -0
@@ -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()
Modified g4f/errors.py +15 -12
@@ -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 ...
Modified g4f/gui/client/js/chat.v1.js +10 -19