From feb83c168b0a57ecd8c511aa654209c5f40da30e Mon Sep 17 00:00:00 2001 From: H Lohaus Date: Fri, 26 Jan 2024 07:54:13 +0100 Subject: New minimum requirements (#1515) * New minimum requirements * Add ConversationStyleOptionSets to Bing * Add image.ImageRequest * Improve python version support * Improve unittests --- g4f/Provider/Bing.py | 31 +++++++++------- g4f/Provider/DeepInfra.py | 11 +++--- g4f/Provider/GptForLove.py | 11 +++++- g4f/Provider/HuggingChat.py | 2 +- g4f/Provider/PerplexityLabs.py | 8 ++--- g4f/Provider/Phind.py | 1 - g4f/Provider/Vercel.py | 15 +++++--- g4f/Provider/base_provider.py | 1 + g4f/Provider/bing/conversation.py | 2 ++ g4f/Provider/bing/create_images.py | 24 ++++++++++--- g4f/Provider/bing/upload_image.py | 51 +++++++++++--------------- g4f/Provider/deprecated/ChatgptDuo.py | 4 +-- g4f/Provider/deprecated/GetGpt.py | 30 ++++++++-------- g4f/Provider/helper.py | 68 ++++++++++++++--------------------- g4f/Provider/needs_auth/Bard.py | 12 ++++--- g4f/Provider/needs_auth/OpenaiChat.py | 52 ++++++++++++++++----------- g4f/Provider/selenium/PerplexityAi.py | 12 ++++--- 17 files changed, 185 insertions(+), 150 deletions(-) (limited to 'g4f/Provider') diff --git a/g4f/Provider/Bing.py b/g4f/Provider/Bing.py index 11bb1414..32879fa6 100644 --- a/g4f/Provider/Bing.py +++ b/g4f/Provider/Bing.py @@ -9,7 +9,7 @@ from urllib import parse from aiohttp import ClientSession, ClientTimeout, BaseConnector from ..typing import AsyncResult, Messages, ImageType -from ..image import ImageResponse +from ..image import ImageResponse, ImageRequest from .base_provider import AsyncGeneratorProvider from .helper import get_connector from .bing.upload_image import upload_image @@ -154,6 +154,11 @@ class Defaults: 'SRCHHPGUSR' : f'HV={int(time.time())}', } +class ConversationStyleOptionSets(): + CREATIVE = ["h3imaginative", "clgalileo", "gencontentv3"] + BALANCED = ["galileo"] + PRECISE = ["h3precise", "clgalileo"] + def format_message(msg: dict) -> str: """ Formats a message dictionary into a JSON string with a delimiter. @@ -168,7 +173,7 @@ def create_message( prompt: str, tone: str, context: str = None, - image_response: ImageResponse = None, + image_request: ImageRequest = None, web_search: bool = False, gpt4_turbo: bool = False ) -> str: @@ -179,7 +184,7 @@ def create_message( :param prompt: The user's input prompt. :param tone: The desired tone for the response. :param context: Additional context for the prompt. - :param image_response: The response if an image is involved. + :param image_request: The image request with the url. :param web_search: Flag to enable web search. :param gpt4_turbo: Flag to enable GPT-4 Turbo. :return: A formatted string message for the Bing API. @@ -187,11 +192,11 @@ def create_message( options_sets = Defaults.optionsSets # Append tone-specific options if tone == Tones.creative: - options_sets.append("h3imaginative") + options_sets.extend(ConversationStyleOptionSets.CREATIVE) elif tone == Tones.precise: - options_sets.append("h3precise") + options_sets.extend(ConversationStyleOptionSets.PRECISE) elif tone == Tones.balanced: - options_sets.append("galileo") + options_sets.extend(ConversationStyleOptionSets.BALANCED) else: options_sets.append("harmonyv3") @@ -233,9 +238,9 @@ def create_message( 'type': 4 } - if image_response and image_response.get('imageUrl') and image_response.get('originalImageUrl'): - struct['arguments'][0]['message']['originalImageUrl'] = image_response.get('originalImageUrl') - struct['arguments'][0]['message']['imageUrl'] = image_response.get('imageUrl') + if image_request and image_request.get('imageUrl') and image_request.get('originalImageUrl'): + struct['arguments'][0]['message']['originalImageUrl'] = image_request.get('originalImageUrl') + struct['arguments'][0]['message']['imageUrl'] = image_request.get('imageUrl') struct['arguments'][0]['experienceType'] = None struct['arguments'][0]['attachedFileInfo'] = {"fileName": None, "fileType": None} @@ -282,9 +287,9 @@ async def stream_generate( timeout=ClientTimeout(total=timeout), headers=headers, connector=connector ) as session: conversation = await create_conversation(session) - image_response = await upload_image(session, image, tone) if image else None - if image_response: - yield image_response + image_request = await upload_image(session, image, tone) if image else None + if image_request: + yield image_request try: async with session.ws_connect( @@ -294,7 +299,7 @@ async def stream_generate( ) as wss: await wss.send_str(format_message({'protocol': 'json', 'version': 1})) await wss.receive(timeout=timeout) - await wss.send_str(create_message(conversation, prompt, tone, context, image_response, web_search, gpt4_turbo)) + await wss.send_str(create_message(conversation, prompt, tone, context, image_request, web_search, gpt4_turbo)) response_txt = '' returned_text = '' diff --git a/g4f/Provider/DeepInfra.py b/g4f/Provider/DeepInfra.py index 2f34b679..09b9464e 100644 --- a/g4f/Provider/DeepInfra.py +++ b/g4f/Provider/DeepInfra.py @@ -13,11 +13,12 @@ class DeepInfra(AsyncGeneratorProvider, ProviderModelMixin): supports_message_history = True default_model = 'meta-llama/Llama-2-70b-chat-hf' - @staticmethod - def get_models(): - url = 'https://api.deepinfra.com/models/featured' - models = requests.get(url).json() - return [model['model_name'] for model in models] + @classmethod + def get_models(cls): + if not cls.models: + url = 'https://api.deepinfra.com/models/featured' + cls.models = requests.get(url).json() + return cls.models @classmethod async def create_async_generator( diff --git a/g4f/Provider/GptForLove.py b/g4f/Provider/GptForLove.py index 07e3406f..cc82da21 100644 --- a/g4f/Provider/GptForLove.py +++ b/g4f/Provider/GptForLove.py @@ -1,11 +1,18 @@ from __future__ import annotations from aiohttp import ClientSession -import execjs, os, json +import os +import json +try: + import execjs + has_requirements = True +except ImportError: + has_requirements = False from ..typing import AsyncResult, Messages from .base_provider import AsyncGeneratorProvider from .helper import format_prompt +from ..errors import MissingRequirementsError class GptForLove(AsyncGeneratorProvider): url = "https://ai18.gptforlove.com" @@ -20,6 +27,8 @@ class GptForLove(AsyncGeneratorProvider): proxy: str = None, **kwargs ) -> AsyncResult: + if not has_requirements: + raise MissingRequirementsError('Install "PyExecJS" package') if not model: model = "gpt-3.5-turbo" headers = { diff --git a/g4f/Provider/HuggingChat.py b/g4f/Provider/HuggingChat.py index 79e4ae38..9aa93878 100644 --- a/g4f/Provider/HuggingChat.py +++ b/g4f/Provider/HuggingChat.py @@ -39,7 +39,7 @@ class HuggingChat(AsyncGeneratorProvider, ProviderModelMixin): **kwargs ) -> AsyncResult: if not cookies: - cookies = get_cookies(".huggingface.co") + cookies = get_cookies(".huggingface.co", False) headers = { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36', diff --git a/g4f/Provider/PerplexityLabs.py b/g4f/Provider/PerplexityLabs.py index 5002b39f..a7b98f7c 100644 --- a/g4f/Provider/PerplexityLabs.py +++ b/g4f/Provider/PerplexityLabs.py @@ -14,12 +14,12 @@ WS_URL = "wss://labs-api.perplexity.ai/socket.io/" class PerplexityLabs(AsyncGeneratorProvider, ProviderModelMixin): url = "https://labs.perplexity.ai" working = True + default_model = 'pplx-70b-online' models = [ 'pplx-7b-online', 'pplx-70b-online', 'pplx-7b-chat', 'pplx-70b-chat', 'mistral-7b-instruct', 'codellama-34b-instruct', 'llama-2-70b-chat', 'llava-7b-chat', 'mixtral-8x7b-instruct', 'mistral-medium', 'related' ] - default_model = 'pplx-70b-online' model_aliases = { "mistralai/Mistral-7B-Instruct-v0.1": "mistral-7b-instruct", "meta-llama/Llama-2-70b-chat-hf": "llama-2-70b-chat", @@ -52,8 +52,7 @@ class PerplexityLabs(AsyncGeneratorProvider, ProviderModelMixin): async with ClientSession(headers=headers, connector=get_connector(connector, proxy)) as session: t = format(random.getrandbits(32), '08x') async with session.get( - f"{API_URL}?EIO=4&transport=polling&t={t}", - proxy=proxy + f"{API_URL}?EIO=4&transport=polling&t={t}" ) as response: text = await response.text() @@ -61,8 +60,7 @@ class PerplexityLabs(AsyncGeneratorProvider, ProviderModelMixin): post_data = '40{"jwt":"anonymous-ask-user"}' async with session.post( f'{API_URL}?EIO=4&transport=polling&t={t}&sid={sid}', - data=post_data, - proxy=proxy + data=post_data ) as response: assert await response.text() == 'OK' diff --git a/g4f/Provider/Phind.py b/g4f/Provider/Phind.py index dfc8f992..dbf1e7ae 100644 --- a/g4f/Provider/Phind.py +++ b/g4f/Provider/Phind.py @@ -9,7 +9,6 @@ from ..requests import StreamSession class Phind(AsyncGeneratorProvider): url = "https://www.phind.com" working = True - supports_gpt_4 = True supports_stream = True supports_message_history = True diff --git a/g4f/Provider/Vercel.py b/g4f/Provider/Vercel.py index 466ea3de..8d2137bf 100644 --- a/g4f/Provider/Vercel.py +++ b/g4f/Provider/Vercel.py @@ -1,10 +1,16 @@ from __future__ import annotations -import json, base64, requests, execjs, random, uuid +import json, base64, requests, random, uuid + +try: + import execjs + has_requirements = True +except ImportError: + has_requirements = False from ..typing import Messages, TypedDict, CreateResult, Any from .base_provider import AbstractProvider -from ..debug import logging +from ..errors import MissingRequirementsError class Vercel(AbstractProvider): url = 'https://sdk.vercel.ai' @@ -21,10 +27,11 @@ class Vercel(AbstractProvider): proxy: str = None, **kwargs ) -> CreateResult: - + if not has_requirements: + raise MissingRequirementsError('Install "PyExecJS" package') + if not model: model = "gpt-3.5-turbo" - elif model not in model_info: raise ValueError(f"Vercel does not support {model}") diff --git a/g4f/Provider/base_provider.py b/g4f/Provider/base_provider.py index e1dcd24d..b173db4e 100644 --- a/g4f/Provider/base_provider.py +++ b/g4f/Provider/base_provider.py @@ -1,4 +1,5 @@ from __future__ import annotations + import sys import asyncio from asyncio import AbstractEventLoop diff --git a/g4f/Provider/bing/conversation.py b/g4f/Provider/bing/conversation.py index 36ada3b0..388bdd6b 100644 --- a/g4f/Provider/bing/conversation.py +++ b/g4f/Provider/bing/conversation.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from aiohttp import ClientSession class Conversation: diff --git a/g4f/Provider/bing/create_images.py b/g4f/Provider/bing/create_images.py index af39ef1e..4fa85929 100644 --- a/g4f/Provider/bing/create_images.py +++ b/g4f/Provider/bing/create_images.py @@ -2,21 +2,28 @@ This module provides functionalities for creating and managing images using Bing's service. It includes functions for user login, session creation, image creation, and processing. """ +from __future__ import annotations import asyncio import time import json import os from aiohttp import ClientSession, BaseConnector -from bs4 import BeautifulSoup from urllib.parse import quote from typing import Generator, List, Dict +try: + from bs4 import BeautifulSoup + has_requirements = True +except ImportError: + has_requirements = False + from ..create_images import CreateImagesProvider from ..helper import get_cookies, get_connector from ...webdriver import WebDriver, get_driver_cookies, get_browser from ...base_provider import ProviderType from ...image import ImageResponse +from ...errors import MissingRequirementsError, MissingAccessToken BING_URL = "https://www.bing.com" TIMEOUT_LOGIN = 1200 @@ -97,6 +104,8 @@ async def create_images(session: ClientSession, prompt: str, proxy: str = None, Raises: RuntimeError: If image creation fails or times out. """ + if not has_requirements: + raise MissingRequirementsError('Install "beautifulsoup4" package') url_encoded_prompt = quote(prompt) payload = f"q={url_encoded_prompt}&rt=4&FORM=GENCRE" url = f"{BING_URL}/images/create?q={url_encoded_prompt}&rt=4&FORM=GENCRE" @@ -193,7 +202,11 @@ class CreateImagesBing: Yields: Generator[str, None, None]: The final output as markdown formatted string with images. """ - cookies = self.cookies or get_cookies(".bing.com") + try: + cookies = self.cookies or get_cookies(".bing.com") + except MissingRequirementsError as e: + raise MissingAccessToken(f'Missing "_U" cookie. {e}') + if "_U" not in cookies: login_url = os.environ.get("G4F_LOGIN_URL") if login_url: @@ -211,9 +224,12 @@ class CreateImagesBing: Returns: str: Markdown formatted string with images. """ - cookies = self.cookies or get_cookies(".bing.com") + try: + cookies = self.cookies or get_cookies(".bing.com") + except MissingRequirementsError as e: + raise MissingAccessToken(f'Missing "_U" cookie. {e}') if "_U" not in cookies: - raise RuntimeError('"_U" cookie is missing') + raise MissingAccessToken('Missing "_U" cookie') proxy = os.environ.get("G4F_PROXY") async with create_session(cookies, proxy) as session: images = await create_images(session, prompt, self.proxy) diff --git a/g4f/Provider/bing/upload_image.py b/g4f/Provider/bing/upload_image.py index bb5687a8..f9e11561 100644 --- a/g4f/Provider/bing/upload_image.py +++ b/g4f/Provider/bing/upload_image.py @@ -1,17 +1,14 @@ """ Module to handle image uploading and processing for Bing AI integrations. """ - from __future__ import annotations -import string -import random + import json import math -from aiohttp import ClientSession -from PIL import Image +from aiohttp import ClientSession, FormData from ...typing import ImageType, Tuple -from ...image import to_image, process_image, to_base64, ImageResponse +from ...image import to_image, process_image, to_base64_jpg, ImageRequest, Image IMAGE_CONFIG = { "maxImagePixels": 360000, @@ -24,7 +21,7 @@ async def upload_image( image_data: ImageType, tone: str, proxy: str = None -) -> ImageResponse: +) -> ImageRequest: """ Uploads an image to Bing's AI service and returns the image response. @@ -38,22 +35,22 @@ async def upload_image( RuntimeError: If the image upload fails. Returns: - ImageResponse: The response from the image upload. + ImageRequest: The response from the image upload. """ image = to_image(image_data) new_width, new_height = calculate_new_dimensions(image) - processed_img = process_image(image, new_width, new_height) - img_binary_data = to_base64(processed_img, IMAGE_CONFIG['imageCompressionRate']) + image = process_image(image, new_width, new_height) + img_binary_data = to_base64_jpg(image, IMAGE_CONFIG['imageCompressionRate']) - data, boundary = build_image_upload_payload(img_binary_data, tone) - headers = prepare_headers(session, boundary) + data = build_image_upload_payload(img_binary_data, tone) + headers = prepare_headers(session) async with session.post("https://www.bing.com/images/kblob", data=data, headers=headers, proxy=proxy) as response: if response.status != 200: raise RuntimeError("Failed to upload image.") return parse_image_response(await response.json()) -def calculate_new_dimensions(image: Image.Image) -> Tuple[int, int]: +def calculate_new_dimensions(image: Image) -> Tuple[int, int]: """ Calculates the new dimensions for the image based on the maximum allowed pixels. @@ -70,7 +67,7 @@ def calculate_new_dimensions(image: Image.Image) -> Tuple[int, int]: return int(width * scale_factor), int(height * scale_factor) return width, height -def build_image_upload_payload(image_bin: str, tone: str) -> Tuple[str, str]: +def build_image_upload_payload(image_bin: str, tone: str) -> FormData: """ Builds the payload for image uploading. @@ -81,18 +78,11 @@ def build_image_upload_payload(image_bin: str, tone: str) -> Tuple[str, str]: Returns: Tuple[str, str]: The data and boundary for the payload. """ - boundary = "----WebKitFormBoundary" + ''.join(random.choices(string.ascii_letters + string.digits, k=16)) - data = f"""--{boundary} -Content-Disposition: form-data; name="knowledgeRequest" - -{json.dumps(build_knowledge_request(tone), ensure_ascii=False)} ---{boundary} -Content-Disposition: form-data; name="imageBase64" - -{image_bin} ---{boundary}-- -""" - return data, boundary + data = FormData() + knowledge_request = json.dumps(build_knowledge_request(tone), ensure_ascii=False) + data.add_field('knowledgeRequest', knowledge_request, content_type="application/json") + data.add_field('imageBase64', image_bin) + return data def build_knowledge_request(tone: str) -> dict: """ @@ -119,7 +109,7 @@ def build_knowledge_request(tone: str) -> dict: } } -def prepare_headers(session: ClientSession, boundary: str) -> dict: +def prepare_headers(session: ClientSession) -> dict: """ Prepares the headers for the image upload request. @@ -131,12 +121,11 @@ def prepare_headers(session: ClientSession, boundary: str) -> dict: dict: The headers for the request. """ headers = session.headers.copy() - headers["Content-Type"] = f'multipart/form-data; boundary={boundary}' headers["Referer"] = 'https://www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx' headers["Origin"] = 'https://www.bing.com' return headers -def parse_image_response(response: dict) -> ImageResponse: +def parse_image_response(response: dict) -> ImageRequest: """ Parses the response from the image upload. @@ -147,7 +136,7 @@ def parse_image_response(response: dict) -> ImageResponse: RuntimeError: If parsing the image info fails. Returns: - ImageResponse: The parsed image response. + ImageRequest: The parsed image response. """ if not response.get('blobId'): raise RuntimeError("Failed to parse image info.") @@ -160,4 +149,4 @@ def parse_image_response(response: dict) -> ImageResponse: if IMAGE_CONFIG["enableFaceBlurDebug"] else f"https://www.bing.com/images/blob?bcid={result['bcid']}" ) - return ImageResponse(result["imageUrl"], "", result) \ No newline at end of file + return ImageRequest(result["imageUrl"], "", result) \ No newline at end of file diff --git a/g4f/Provider/deprecated/ChatgptDuo.py b/g4f/Provider/deprecated/ChatgptDuo.py index c2d2de7a..bd9e195d 100644 --- a/g4f/Provider/deprecated/ChatgptDuo.py +++ b/g4f/Provider/deprecated/ChatgptDuo.py @@ -1,7 +1,7 @@ from __future__ import annotations from ...typing import Messages -from curl_cffi.requests import AsyncSession +from ...requests import StreamSession from ..base_provider import AsyncProvider, format_prompt @@ -19,7 +19,7 @@ class ChatgptDuo(AsyncProvider): timeout: int = 120, **kwargs ) -> str: - async with AsyncSession( + async with StreamSession( impersonate="chrome107", proxies={"https": proxy}, timeout=timeout diff --git a/g4f/Provider/deprecated/GetGpt.py b/g4f/Provider/deprecated/GetGpt.py index 69851ee5..dd586569 100644 --- a/g4f/Provider/deprecated/GetGpt.py +++ b/g4f/Provider/deprecated/GetGpt.py @@ -5,10 +5,10 @@ import os import uuid import requests -try: - from Crypto.Cipher import AES -except ImportError: - from Cryptodome.Cipher import AES +# try: +# from Crypto.Cipher import AES +# except ImportError: +# from Cryptodome.Cipher import AES from ...typing import Any, CreateResult from ..base_provider import AbstractProvider @@ -57,19 +57,21 @@ class GetGpt(AbstractProvider): def _encrypt(e: str): - t = os.urandom(8).hex().encode('utf-8') - n = os.urandom(8).hex().encode('utf-8') - r = e.encode('utf-8') + # t = os.urandom(8).hex().encode('utf-8') + # n = os.urandom(8).hex().encode('utf-8') + # r = e.encode('utf-8') - cipher = AES.new(t, AES.MODE_CBC, n) - ciphertext = cipher.encrypt(_pad_data(r)) + # cipher = AES.new(t, AES.MODE_CBC, n) + # ciphertext = cipher.encrypt(_pad_data(r)) - return ciphertext.hex() + t.decode('utf-8') + n.decode('utf-8') + # return ciphertext.hex() + t.decode('utf-8') + n.decode('utf-8') + return def _pad_data(data: bytes) -> bytes: - block_size = AES.block_size - padding_size = block_size - len(data) % block_size - padding = bytes([padding_size] * padding_size) + # block_size = AES.block_size + # padding_size = block_size - len(data) % block_size + # padding = bytes([padding_size] * padding_size) - return data + padding + # return data + padding + return \ No newline at end of file diff --git a/g4f/Provider/helper.py b/g4f/Provider/helper.py index cf204e39..0af61d8d 100644 --- a/g4f/Provider/helper.py +++ b/g4f/Provider/helper.py @@ -1,57 +1,37 @@ from __future__ import annotations -import asyncio import os import random import secrets import string -from asyncio import AbstractEventLoop, BaseEventLoop from aiohttp import BaseConnector -from platformdirs import user_config_dir -from browser_cookie3 import ( - chrome, chromium, opera, opera_gx, - brave, edge, vivaldi, firefox, - _LinuxPasswordManager, BrowserCookieError -) + +try: + from platformdirs import user_config_dir + has_platformdirs = True +except ImportError: + has_platformdirs = False +try: + from browser_cookie3 import ( + chrome, chromium, opera, opera_gx, + brave, edge, vivaldi, firefox, + _LinuxPasswordManager, BrowserCookieError + ) + has_browser_cookie3 = True +except ImportError: + has_browser_cookie3 = False + from ..typing import Dict, Messages, Optional -from ..errors import AiohttpSocksError +from ..errors import AiohttpSocksError, MissingRequirementsError from .. import debug # Global variable to store cookies _cookies: Dict[str, Dict[str, str]] = {} -def get_event_loop() -> AbstractEventLoop: - """ - Get the current asyncio event loop. If the loop is closed or not set, create a new event loop. - If a loop is running, handle nested event loops. Patch the loop if 'nest_asyncio' is installed. - - Returns: - AbstractEventLoop: The current or new event loop. - """ - try: - loop = asyncio.get_event_loop() - if isinstance(loop, BaseEventLoop): - loop._check_closed() - except RuntimeError: - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - try: - asyncio.get_running_loop() - if not hasattr(loop.__class__, "_nest_patched"): - import nest_asyncio - nest_asyncio.apply(loop) - except RuntimeError: - pass - except ImportError: - raise RuntimeError( - 'Use "create_async" instead of "create" function in a running event loop. Or install "nest_asyncio" package.' - ) - return loop - -if os.environ.get('DBUS_SESSION_BUS_ADDRESS') == "/dev/null": +if has_browser_cookie3 and os.environ.get('DBUS_SESSION_BUS_ADDRESS') == "/dev/null": _LinuxPasswordManager.get_password = lambda a, b: b"secret" -def get_cookies(domain_name: str = '') -> Dict[str, str]: +def get_cookies(domain_name: str = '', raise_requirements_error: bool = True) -> Dict[str, str]: """ Load cookies for a given domain from all supported browsers and cache the results. @@ -64,11 +44,11 @@ def get_cookies(domain_name: str = '') -> Dict[str, str]: if domain_name in _cookies: return _cookies[domain_name] - cookies = _load_cookies_from_browsers(domain_name) + cookies = load_cookies_from_browsers(domain_name, raise_requirements_error) _cookies[domain_name] = cookies return cookies -def _load_cookies_from_browsers(domain_name: str) -> Dict[str, str]: +def load_cookies_from_browsers(domain_name: str, raise_requirements_error: bool = True) -> Dict[str, str]: """ Helper function to load cookies from various browsers. @@ -78,6 +58,10 @@ def _load_cookies_from_browsers(domain_name: str) -> Dict[str, str]: Returns: Dict[str, str]: A dictionary of cookie names and values. """ + if not has_browser_cookie3: + if raise_requirements_error: + raise MissingRequirementsError('Install "browser_cookie3" package') + return {} cookies = {} for cookie_fn in [_g4f, chrome, chromium, opera, opera_gx, brave, edge, vivaldi, firefox]: try: @@ -104,6 +88,8 @@ def _g4f(domain_name: str) -> list: Returns: list: List of cookies. """ + if not has_platformdirs: + return [] user_data_dir = user_config_dir("g4f") cookie_file = os.path.join(user_data_dir, "Default", "Cookies") return [] if not os.path.exists(cookie_file) else chrome(cookie_file, domain_name) diff --git a/g4f/Provider/needs_auth/Bard.py b/g4f/Provider/needs_auth/Bard.py index aea67874..09ed1c3c 100644 --- a/g4f/Provider/needs_auth/Bard.py +++ b/g4f/Provider/needs_auth/Bard.py @@ -2,10 +2,14 @@ from __future__ import annotations import time import os -from selenium.webdriver.common.by import By -from selenium.webdriver.support.ui import WebDriverWait -from selenium.webdriver.support import expected_conditions as EC -from selenium.webdriver.common.keys import Keys + +try: + from selenium.webdriver.common.by import By + from selenium.webdriver.support.ui import WebDriverWait + from selenium.webdriver.support import expected_conditions as EC + from selenium.webdriver.common.keys import Keys +except ImportError: + pass from ...typing import CreateResult, Messages from ..base_provider import AbstractProvider diff --git a/g4f/Provider/needs_auth/OpenaiChat.py b/g4f/Provider/needs_auth/OpenaiChat.py index 85866272..b07bd49b 100644 --- a/g4f/Provider/needs_auth/OpenaiChat.py +++ b/g4f/Provider/needs_auth/OpenaiChat.py @@ -1,21 +1,32 @@ from __future__ import annotations + import asyncio import uuid import json import os -from py_arkose_generator.arkose import get_values_for_request -from async_property import async_cached_property -from selenium.webdriver.common.by import By -from selenium.webdriver.support.ui import WebDriverWait -from selenium.webdriver.support import expected_conditions as EC +try: + from py_arkose_generator.arkose import get_values_for_request + from async_property import async_cached_property + has_requirements = True +except ImportError: + async_cached_property = property + has_requirements = False +try: + from selenium.webdriver.common.by import By + from selenium.webdriver.support.ui import WebDriverWait + from selenium.webdriver.support import expected_conditions as EC + has_webdriver = True +except ImportError: + has_webdriver = False from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin from ..helper import format_prompt, get_cookies from ...webdriver import get_browser, get_driver_cookies -from ...typing import AsyncResult, Messages +from ...typing import AsyncResult, Messages, Cookies, ImageType from ...requests import StreamSession -from ...image import to_image, to_bytes, ImageType, ImageResponse +from ...image import to_image, to_bytes, ImageResponse, ImageRequest +from ...errors import MissingRequirementsError, MissingAccessToken class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): @@ -27,12 +38,8 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): supports_gpt_35_turbo = True supports_gpt_4 = True default_model = None - models = ["text-davinci-002-render-sha", "gpt-4", "gpt-4-gizmo"] - model_aliases = { - "gpt-3.5-turbo": "text-davinci-002-render-sha", - } + models = ["gpt-3.5-turbo", "gpt-4", "gpt-4-gizmo"] _cookies: dict = {} - _default_model: str = None @classmethod async def create( @@ -94,7 +101,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): session: StreamSession, headers: dict, image: ImageType - ) -> ImageResponse: + ) -> ImageRequest: """ Upload an image to the service and get the download URL @@ -104,7 +111,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): image: The image to upload, either a PIL Image object or a bytes object Returns: - An ImageResponse object that contains the download URL, file name, and other data + An ImageRequest object that contains the download URL, file name, and other data """ # Convert the image to a PIL Image object and get the extension image = to_image(image) @@ -145,7 +152,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): ) as response: response.raise_for_status() download_url = (await response.json())["download_url"] - return ImageResponse(download_url, image_data["file_name"], image_data) + return ImageRequest(download_url, image_data["file_name"], image_data) @classmethod async def get_default_model(cls, session: StreamSession, headers: dict): @@ -169,7 +176,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): return cls.default_model @classmethod - def create_messages(cls, prompt: str, image_response: ImageResponse = None): + def create_messages(cls, prompt: str, image_response: ImageRequest = None): """ Create a list of messages for the user input @@ -282,7 +289,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): proxy: str = None, timeout: int = 120, access_token: str = None, - cookies: dict = None, + cookies: Cookies = None, auto_continue: bool = False, history_disabled: bool = True, action: str = "next", @@ -317,12 +324,16 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): Raises: RuntimeError: If an error occurs during processing. """ + if not has_requirements: + raise MissingRequirementsError('Install "py-arkose-generator" and "async_property" package') if not parent_id: parent_id = str(uuid.uuid4()) if not cookies: - cookies = cls._cookies or get_cookies("chat.openai.com") + cookies = cls._cookies or get_cookies("chat.openai.com", False) if not access_token and "access_token" in cookies: access_token = cookies["access_token"] + if not access_token and not has_webdriver: + raise MissingAccessToken(f'Missing "access_token"') if not access_token: login_url = os.environ.get("G4F_LOGIN_URL") if login_url: @@ -331,7 +342,6 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): cls._cookies = cookies headers = {"Authorization": f"Bearer {access_token}"} - async with StreamSession( proxies={"https": proxy}, impersonate="chrome110", @@ -346,13 +356,15 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): except Exception as e: yield e end_turn = EndTurn() + model = cls.get_model(model or await cls.get_default_model(session, headers)) + model = "text-davinci-002-render-sha" if model == "gpt-3.5-turbo" else model while not end_turn.is_end: data = { "action": action, "arkose_token": await cls.get_arkose_token(session), "conversation_id": conversation_id, "parent_message_id": parent_id, - "model": cls.get_model(model or await cls.get_default_model(session, headers)), + "model": model, "history_and_training_disabled": history_disabled and not auto_continue, } if action != "continue": diff --git a/g4f/Provider/selenium/PerplexityAi.py b/g4f/Provider/selenium/PerplexityAi.py index 4796f709..8ae6ad2b 100644 --- a/g4f/Provider/selenium/PerplexityAi.py +++ b/g4f/Provider/selenium/PerplexityAi.py @@ -1,10 +1,14 @@ from __future__ import annotations import time -from selenium.webdriver.common.by import By -from selenium.webdriver.support.ui import WebDriverWait -from selenium.webdriver.support import expected_conditions as EC -from selenium.webdriver.common.keys import Keys + +try: + from selenium.webdriver.common.by import By + from selenium.webdriver.support.ui import WebDriverWait + from selenium.webdriver.support import expected_conditions as EC + from selenium.webdriver.common.keys import Keys +except ImportError: + pass from ...typing import CreateResult, Messages from ..base_provider import AbstractProvider -- cgit v1.2.3