From aba4b96f23ef38ca75195b3b78a88bb92035b4a9 Mon Sep 17 00:00:00 2001 From: Heiner Lohaus Date: Mon, 12 Feb 2024 11:41:27 +0100 Subject: [PATCH] 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. --- .github/workflows/unittest.yml | 2 +- README.md | 17 ++ docs/cat.jpeg | Bin 0 -> 8672 bytes docs/client.md | 71 +++++ ...reateImagesBing.py => BingCreateImages.py} | 46 +-- g4f/Provider/You.py | 5 + g4f/Provider/__init__.py | 2 +- g4f/Provider/bing/create_images.py | 43 ++- g4f/Provider/create_images.py | 8 +- g4f/Provider/needs_auth/OpenaiChat.py | 85 +++--- g4f/__init__.py | 3 +- g4f/client.py | 267 ++++++++++++++++++ g4f/errors.py | 27 +- g4f/gui/client/js/chat.v1.js | 29 +- 14 files changed, 480 insertions(+), 125 deletions(-) create mode 100644 docs/cat.jpeg create mode 100644 docs/client.md rename g4f/Provider/{CreateImagesBing.py => BingCreateImages.py} (59%) create mode 100644 g4f/client.py diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml index 96e274b1..f49235b6 100644 --- a/.github/workflows/unittest.yml +++ b/.github/workflows/unittest.yml @@ -24,7 +24,7 @@ jobs: run: pip install -r requirements-min.txt - name: Run tests run: python -m etc.unittest - - name: Set up Python 3.11 + - name: Set up Python 3.12 uses: actions/setup-python@v4 with: python-version: "3.12" diff --git a/README.md b/README.md index 6734fe02..dc7ef933 100644 --- a/README.md +++ b/README.md @@ -226,6 +226,23 @@ docker-compose down ## 💡 Usage +### New Client with Image Generation +```python +from g4f.client import Client + +client = Client() +response = client.images.generate( + model="gemini", + prompt="a white siamese cat", + ... +) +image_url = response.data[0].url +``` +Result: +[![Image with cat](/docs/cat.jpeg)](/docs/client.md) + +[to the client API](/docs/client.md) + ### The Web UI To start the web interface, type the following codes in the command line. diff --git a/docs/cat.jpeg b/docs/cat.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..56bbb159831d1931f10507669dbaa32f4a16b722 GIT binary patch literal 8672 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 QDhVGbcm)~v!tZAP2fFjFt^fc4 literal 0 HcmV?d00001 diff --git a/docs/client.md b/docs/client.md new file mode 100644 index 00000000..1201a83d --- /dev/null +++ b/docs/client.md @@ -0,0 +1,71 @@ +### Client API +##### from g4f (beta) + +#### Start +This new client could: + +```python +from g4f.client import Client +``` +replaces this: + +```python +from openai import OpenAI +``` +in your Python Code. + +New client have the same API as OpenAI. + +#### Client + +Create the client with custom providers: + +```python +from g4f.client import Client +from g4f.Provider import BingCreateImages, OpenaiChat, Gemini + +client = Client( + provider=OpenaiChat, + image_provider=Gemini, + proxies=None +) +``` + +#### Examples + +Use the ChatCompletions: + +```python +stream = client.chat.completions.create( + model="gpt-4", + messages=[{"role": "user", "content": "Say this is a test"}], + stream=True, +) +for chunk in stream: + if chunk.choices[0].delta.content is not None: + print(chunk.choices[0].delta.content, end="") +``` + +Or use it for creating a image: +```python +response = client.images.generate( + model="dall-e-3", + prompt="a white siamese cat", + ... +) + +image_url = response.data[0].url +``` + +Also this works with the client: +```python +response = client.images.create_variation( + image=open('cat.jpg') + model="bing", + ... +) + +image_url = response.data[0].url +``` + +[to Home](/docs/client.md) diff --git a/g4f/Provider/CreateImagesBing.py b/g4f/Provider/BingCreateImages.py similarity index 59% rename from g4f/Provider/CreateImagesBing.py rename to g4f/Provider/BingCreateImages.py index 59eafe0c..901b7787 100644 --- a/g4f/Provider/CreateImagesBing.py +++ b/g4f/Provider/BingCreateImages.py @@ -1,60 +1,22 @@ from __future__ import annotations import asyncio -import time import os from typing import Generator from ..cookies import get_cookies -from ..webdriver import WebDriver, get_driver_cookies, get_browser from ..image import ImageResponse from ..errors import MissingRequirementsError, MissingAuthError -from .bing.create_images import BING_URL, create_images, create_session +from .bing.create_images import create_images, create_session, get_cookies_from_browser -BING_URL = "https://www.bing.com" -TIMEOUT_LOGIN = 1200 - -def wait_for_login(driver: WebDriver, timeout: int = TIMEOUT_LOGIN) -> None: - """ - Waits for the user to log in within a given timeout period. - - Args: - driver (WebDriver): Webdriver for browser automation. - timeout (int): Maximum waiting time in seconds. - - Raises: - RuntimeError: If the login process exceeds the timeout. - """ - driver.get(f"{BING_URL}/") - start_time = time.time() - while not driver.get_cookie("_U"): - if time.time() - start_time > timeout: - raise RuntimeError("Timeout error") - time.sleep(0.5) - -def get_cookies_from_browser(proxy: str = None) -> dict[str, str]: - """ - Retrieves cookies from the browser using webdriver. - - Args: - proxy (str, optional): Proxy configuration. - - Returns: - dict[str, str]: Retrieved cookies. - """ - with get_browser(proxy=proxy) as driver: - wait_for_login(driver) - time.sleep(1) - return get_driver_cookies(driver) - -class CreateImagesBing: +class BingCreateImages: """A class for creating images using Bing.""" def __init__(self, cookies: dict[str, str] = {}, proxy: str = None) -> None: self.cookies = cookies self.proxy = proxy - def create_completion(self, prompt: str) -> Generator[ImageResponse, None, None]: + def create(self, prompt: str) -> Generator[ImageResponse, None, None]: """ Generator for creating imagecompletion based on a prompt. @@ -91,4 +53,4 @@ class CreateImagesBing: proxy = self.proxy or os.environ.get("G4F_PROXY") async with create_session(cookies, proxy) as session: images = await create_images(session, prompt, proxy) - return ImageResponse(images, prompt, {"preview": "{image}?w=200&h=200"}) \ No newline at end of file + return ImageResponse(images, prompt, {"preview": "{image}?w=200&h=200"} if len(images) > 1 else {}) \ No newline at end of file diff --git a/g4f/Provider/You.py b/g4f/Provider/You.py index 001f775d..ece1d340 100644 --- a/g4f/Provider/You.py +++ b/g4f/Provider/You.py @@ -58,9 +58,14 @@ class You(AsyncGeneratorProvider): "selectedChatMode": chat_mode, #"chat": json.dumps(chat), } + params = { + "userFiles": upload, + "selectedChatMode": chat_mode, + } async with (client.post if chat_mode == "default" else client.get)( f"{cls.url}/api/streamingSearch", data=data, + params=params, headers=headers, cookies=cookies ) as response: diff --git a/g4f/Provider/__init__.py b/g4f/Provider/__init__.py index 7dbc1504..68b62fd9 100644 --- a/g4f/Provider/__init__.py +++ b/g4f/Provider/__init__.py @@ -53,7 +53,7 @@ from .Vercel import Vercel from .Ylokh import Ylokh from .You import You -from .CreateImagesBing import CreateImagesBing +from .BingCreateImages import BingCreateImages import sys diff --git a/g4f/Provider/bing/create_images.py b/g4f/Provider/bing/create_images.py index de4fd476..7b82dc56 100644 --- a/g4f/Provider/bing/create_images.py +++ b/g4f/Provider/bing/create_images.py @@ -21,8 +21,10 @@ from ..create_images import CreateImagesProvider from ..helper import get_connector from ...base_provider import ProviderType from ...errors import MissingRequirementsError +from ...webdriver import WebDriver, get_driver_cookies, get_browser BING_URL = "https://www.bing.com" +TIMEOUT_LOGIN = 1200 TIMEOUT_IMAGE_CREATION = 300 ERRORS = [ "this prompt is being reviewed", @@ -35,6 +37,39 @@ BAD_IMAGES = [ "https://r.bing.com/rp/TX9QuO3WzcCJz1uaaSwQAz39Kb0.jpg", ] +def wait_for_login(driver: WebDriver, timeout: int = TIMEOUT_LOGIN) -> None: + """ + Waits for the user to log in within a given timeout period. + + Args: + driver (WebDriver): Webdriver for browser automation. + timeout (int): Maximum waiting time in seconds. + + Raises: + RuntimeError: If the login process exceeds the timeout. + """ + driver.get(f"{BING_URL}/") + start_time = time.time() + while not driver.get_cookie("_U"): + if time.time() - start_time > timeout: + raise RuntimeError("Timeout error") + time.sleep(0.5) + +def get_cookies_from_browser(proxy: str = None) -> dict[str, str]: + """ + Retrieves cookies from the browser using webdriver. + + Args: + proxy (str, optional): Proxy configuration. + + Returns: + dict[str, str]: Retrieved cookies. + """ + with get_browser(proxy=proxy) as driver: + wait_for_login(driver) + time.sleep(1) + return get_driver_cookies(driver) + def create_session(cookies: Dict[str, str], proxy: str = None, connector: BaseConnector = None) -> ClientSession: """ Creates a new client session with specified cookies and headers. @@ -141,6 +176,8 @@ def read_images(html_content: str) -> List[str]: """ soup = BeautifulSoup(html_content, "html.parser") tags = soup.find_all("img", class_="mimg") + if not tags: + tags = soup.find_all("img", class_="gir_mmimg") images = [img["src"].split("?w=")[0] for img in tags] if any(im in BAD_IMAGES for im in images): raise RuntimeError("Bad images found") @@ -158,10 +195,10 @@ def patch_provider(provider: ProviderType) -> CreateImagesProvider: Returns: CreateImagesProvider: The patched provider with image creation capabilities. """ - from ..CreateImagesBing import CreateImagesBing - service = CreateImagesBing() + from ..BingCreateImages import BingCreateImages + service = BingCreateImages() return CreateImagesProvider( provider, - service.create_completion, + service.create, service.create_async ) \ No newline at end of file diff --git a/g4f/Provider/create_images.py b/g4f/Provider/create_images.py index 9a9e3f08..2ca92432 100644 --- a/g4f/Provider/create_images.py +++ b/g4f/Provider/create_images.py @@ -7,10 +7,14 @@ from ..typing import CreateResult, Messages from ..base_provider import BaseProvider, ProviderType system_message = """ -You can generate custom images with the DALL-E 3 image generator. +You can generate images, pictures, photos or img with the DALL-E 3 image generator. To generate an image with a prompt, do this: + -Don't use images with data uri. It is important to use a prompt instead. + +Never use own image links. Don't wrap it in backticks. +It is important to use a only a img tag with a prompt. + """ diff --git a/g4f/Provider/needs_auth/OpenaiChat.py b/g4f/Provider/needs_auth/OpenaiChat.py index b1b267fd..9e0edd8a 100644 --- a/g4f/Provider/needs_auth/OpenaiChat.py +++ b/g4f/Provider/needs_auth/OpenaiChat.py @@ -386,50 +386,47 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): ) as response: if not response.ok: raise RuntimeError(f"Response {response.status_code}: {await response.text()}") - try: - last_message: int = 0 - async for line in response.iter_lines(): - if not line.startswith(b"data: "): - continue - elif line.startswith(b"data: [DONE]"): - break - try: - line = json.loads(line[6:]) - except: - continue - if "message" not in line: - continue - if "error" in line and line["error"]: - raise RuntimeError(line["error"]) - if "message_type" not in line["message"]["metadata"]: - continue - try: - image_response = await cls.get_generated_image(session, auth_headers, line) - if image_response: - yield image_response - except Exception as e: - yield e - if line["message"]["author"]["role"] != "assistant": - continue - if line["message"]["content"]["content_type"] != "text": - continue - if line["message"]["metadata"]["message_type"] not in ("next", "continue", "variant"): - continue - conversation_id = line["conversation_id"] - parent_id = line["message"]["id"] - if response_fields: - response_fields = False - yield ResponseFields(conversation_id, parent_id, end_turn) - if "parts" in line["message"]["content"]: - new_message = line["message"]["content"]["parts"][0] - if len(new_message) > last_message: - yield new_message[last_message:] - last_message = len(new_message) - if "finish_details" in line["message"]["metadata"]: - if line["message"]["metadata"]["finish_details"]["type"] == "stop": - end_turn.end() - except Exception as e: - raise e + last_message: int = 0 + async for line in response.iter_lines(): + if not line.startswith(b"data: "): + continue + elif line.startswith(b"data: [DONE]"): + break + try: + line = json.loads(line[6:]) + except: + continue + if "message" not in line: + continue + if "error" in line and line["error"]: + raise RuntimeError(line["error"]) + if "message_type" not in line["message"]["metadata"]: + continue + try: + image_response = await cls.get_generated_image(session, auth_headers, line) + if image_response: + yield image_response + except Exception as e: + yield e + if line["message"]["author"]["role"] != "assistant": + continue + if line["message"]["content"]["content_type"] != "text": + continue + if line["message"]["metadata"]["message_type"] not in ("next", "continue", "variant"): + continue + conversation_id = line["conversation_id"] + parent_id = line["message"]["id"] + if response_fields: + response_fields = False + yield ResponseFields(conversation_id, parent_id, end_turn) + if "parts" in line["message"]["content"]: + new_message = line["message"]["content"]["parts"][0] + if len(new_message) > last_message: + yield new_message[last_message:] + last_message = len(new_message) + if "finish_details" in line["message"]["metadata"]: + if line["message"]["metadata"]["finish_details"]["type"] == "stop": + end_turn.end() if not auto_continue: break action = "continue" diff --git a/g4f/__init__.py b/g4f/__init__.py index 93e4aa86..ec4a1743 100644 --- a/g4f/__init__.py +++ b/g4f/__init__.py @@ -16,7 +16,8 @@ def get_model_and_provider(model : Union[Model, str], stream : bool, ignored : list[str] = None, ignore_working: bool = False, - ignore_stream: bool = False) -> tuple[str, ProviderType]: + ignore_stream: bool = False, + **kwargs) -> tuple[str, ProviderType]: """ Retrieves the model and provider based on input parameters. diff --git a/g4f/client.py b/g4f/client.py new file mode 100644 index 00000000..117db375 --- /dev/null +++ b/g4f/client.py @@ -0,0 +1,267 @@ +from __future__ import annotations + +import re + +from .typing import Union, Generator, AsyncGenerator, Messages, ImageType +from .base_provider import BaseProvider, ProviderType +from .Provider.base_provider import AsyncGeneratorProvider +from .image import ImageResponse as ImageProviderResponse +from .Provider import BingCreateImages, Gemini, OpenaiChat +from .errors import NoImageResponseError +from . import get_model_and_provider + +ImageProvider = Union[BaseProvider, object] +Proxies = Union[dict, str] + +def read_json(text: str) -> dict: + """ + Parses JSON code block from a string. + + Args: + text (str): A string containing a JSON code block. + + Returns: + dict: A dictionary parsed from the JSON code block. + """ + match = re.search(r"```(json|)\n(?P[\S\s]+?)\n```", text) + if match: + return match.group("code") + return text + +def iter_response( + response: iter, + stream: bool, + response_format: dict = None, + max_tokens: int = None, + stop: list = None +) -> Generator: + content = "" + idx = 1 + chunk = None + finish_reason = "stop" + for idx, chunk in enumerate(response): + content += str(chunk) + if max_tokens is not None and idx > max_tokens: + finish_reason = "max_tokens" + break + first = -1 + word = None + if stop is not None: + for word in list(stop): + first = content.find(word) + if first != -1: + content = content[:first] + break + if stream: + if first != -1: + first = chunk.find(word) + if first != -1: + chunk = chunk[:first] + else: + first = 0 + yield ChatCompletionChunk([ChatCompletionDeltaChoice(ChatCompletionDelta(chunk))]) + if first != -1: + break + if not stream: + if response_format is not None and "type" in response_format: + if response_format["type"] == "json_object": + response = read_json(response) + yield ChatCompletion([ChatCompletionChoice(ChatCompletionMessage(response, finish_reason))]) + +async def aiter_response( + response: aiter, + stream: bool, + response_format: dict = None, + max_tokens: int = None, + stop: list = None +) -> AsyncGenerator: + content = "" + try: + idx = 0 + chunk = None + async for chunk in response: + content += str(chunk) + if max_tokens is not None and idx > max_tokens: + break + first = -1 + word = None + if stop is not None: + for word in list(stop): + first = content.find(word) + if first != -1: + content = content[:first] + break + if stream: + if first != -1: + first = chunk.find(word) + if first != -1: + chunk = chunk[:first] + else: + first = 0 + yield ChatCompletionChunk([ChatCompletionDeltaChoice(ChatCompletionDelta(chunk))]) + if first != -1: + break + idx += 1 + except: + ... + if not stream: + if response_format is not None and "type" in response_format: + if response_format["type"] == "json_object": + response = read_json(response) + yield ChatCompletion([ChatCompletionChoice(ChatCompletionMessage(response))]) + +class Model(): + def __getitem__(self, item): + return getattr(self, item) + +class ChatCompletion(Model): + def __init__(self, choices: list): + self.choices = choices + +class ChatCompletionChunk(Model): + def __init__(self, choices: list): + self.choices = choices + +class ChatCompletionChoice(Model): + def __init__(self, message: ChatCompletionMessage): + self.message = message + +class ChatCompletionMessage(Model): + def __init__(self, content: str, finish_reason: str): + self.content = content + self.finish_reason = finish_reason + self.index = 0 + self.logprobs = None + +class ChatCompletionDelta(Model): + def __init__(self, content: str): + self.content = content + +class ChatCompletionDeltaChoice(Model): + def __init__(self, delta: ChatCompletionDelta): + self.delta = delta + +class Client(): + proxies: Proxies = None + chat: Chat + + def __init__( + self, + provider: ProviderType = None, + image_provider: ImageProvider = None, + proxies: Proxies = None, + **kwargs + ) -> None: + self.proxies: Proxies = proxies + self.images = Images(self, image_provider) + self.chat = Chat(self, provider) + + def get_proxy(self) -> Union[str, None]: + if isinstance(self.proxies, str) or self.proxies is None: + return self.proxies + elif "all" in self.proxies: + return self.proxies["all"] + elif "https" in self.proxies: + return self.proxies["https"] + return None + +class Completions(): + def __init__(self, client: Client, provider: ProviderType = None): + self.client: Client = client + self.provider: ProviderType = provider + + def create( + self, + messages: Messages, + model: str, + provider: ProviderType = None, + stream: bool = False, + response_format: dict = None, + max_tokens: int = None, + stop: list = None, + **kwargs + ) -> Union[dict, Generator]: + if max_tokens is not None: + kwargs["max_tokens"] = max_tokens + if stop: + kwargs["stop"] = list(stop) + model, provider = get_model_and_provider( + model, + self.provider if provider is None else provider, + stream, + **kwargs + ) + response = provider.create_completion(model, messages, stream=stream, **kwargs) + if isinstance(provider, type) and issubclass(provider, AsyncGeneratorProvider): + response = iter_response(response, stream, response_format) # max_tokens, stop + else: + response = iter_response(response, stream, response_format, max_tokens, stop) + return response if stream else next(response) + +class Chat(): + completions: Completions + + def __init__(self, client: Client, provider: ProviderType = None): + self.completions = Completions(client, provider) + +class ImageModels(): + gemini = Gemini + openai = OpenaiChat + + def __init__(self, client: Client) -> None: + self.client = client + self.default = BingCreateImages(proxy=self.client.get_proxy()) + + def get(self, name: str) -> ImageProvider: + return getattr(self, name) if hasattr(self, name) else self.default + +class ImagesResponse(Model): + data: list[Image] + + def __init__(self, data: list) -> None: + self.data = data + +class Image(Model): + url: str + + def __init__(self, url: str) -> None: + self.url = url + +class Images(): + def __init__(self, client: Client, provider: ImageProvider = None): + self.client: Client = client + self.provider: ImageProvider = provider + self.models: ImageModels = ImageModels(client) + + def generate(self, prompt, model: str = None, **kwargs): + provider = self.models.get(model) if model else self.provider or self.models.get(model) + if isinstance(provider, BaseProvider) or isinstance(provider, type) and issubclass(provider, BaseProvider): + prompt = f"create a image: {prompt}" + response = provider.create_completion( + "", + [{"role": "user", "content": prompt}], + True, + proxy=self.client.get_proxy() + ) + else: + response = provider.create(prompt) + + for chunk in response: + if isinstance(chunk, ImageProviderResponse): + return ImagesResponse([Image(image)for image in list(chunk.images)]) + raise NoImageResponseError() + + def create_variation(self, image: ImageType, model: str = None, **kwargs): + provider = self.models.get(model) if model else self.provider + if isinstance(provider, BaseProvider): + response = provider.create_completion( + "", + [{"role": "user", "content": "create a image like this"}], + True, + image=image, + proxy=self.client.get_proxy() + ) + for chunk in response: + if isinstance(chunk, ImageProviderResponse): + return ImagesResponse([Image(image)for image in list(chunk.images)]) + raise NoImageResponseError() \ No newline at end of file diff --git a/g4f/errors.py b/g4f/errors.py index 48171a6e..5e821ded 100644 --- a/g4f/errors.py +++ b/g4f/errors.py @@ -1,35 +1,38 @@ class ProviderNotFoundError(Exception): - pass + ... class ProviderNotWorkingError(Exception): - pass + ... class StreamNotSupportedError(Exception): - pass + ... class ModelNotFoundError(Exception): - pass + ... class ModelNotAllowedError(Exception): - pass + ... class RetryProviderError(Exception): - pass + ... class RetryNoProviderError(Exception): - pass + ... class VersionNotFoundError(Exception): - pass + ... class NestAsyncioError(Exception): - pass + ... class ModelNotSupportedError(Exception): - pass + ... class MissingRequirementsError(Exception): - pass + ... class MissingAuthError(Exception): - pass \ No newline at end of file + ... + +class NoImageResponseError(Exception): + ... \ No newline at end of file diff --git a/g4f/gui/client/js/chat.v1.js b/g4f/gui/client/js/chat.v1.js index a243b1de..2042d174 100644 --- a/g4f/gui/client/js/chat.v1.js +++ b/g4f/gui/client/js/chat.v1.js @@ -52,6 +52,12 @@ const handle_ask = async () => { } await add_message(window.conversation_id, "user", message); window.token = message_id(); + + if (imageInput.dataset.src) URL.revokeObjectURL(imageInput.dataset.src); + const input = imageInput && imageInput.files.length > 0 ? imageInput : cameraInput + if (input.files.length > 0) imageInput.dataset.src = URL.createObjectURL(input.files[0]); + else delete imageInput.dataset.src + message_box.innerHTML += `
@@ -64,10 +70,6 @@ const handle_ask = async () => { ? 'Image upload' : '' } - ${cameraInput.dataset.src - ? 'Image capture' - : '' - }
`; @@ -683,24 +685,13 @@ observer.observe(message_input, { attributes: true }); document.getElementById("version_text").innerHTML = text })() for (const el of [imageInput, cameraInput]) { - console.log(el.files); el.addEventListener('click', async () => { el.value = ''; - delete el.dataset.src; - }); - do_load = async () => { - if (el.files.length) { - delete imageInput.dataset.src; - delete cameraInput.dataset.src; - const reader = new FileReader(); - reader.addEventListener('load', (event) => { - el.dataset.src = event.target.result; - }); - reader.readAsDataURL(el.files[0]); + if (imageInput.dataset.src) { + URL.revokeObjectURL(imageInput.dataset.src); + delete imageInput.dataset.src } - } - do_load() - el.addEventListener('change', do_load); + }); } fileInput.addEventListener('click', async (event) => { fileInput.value = '';