From ea346971dfc418cb144d633dc75ae704c92f5d92 Mon Sep 17 00:00:00 2001 From: kqlio67 <166700875+kqlio67@users.noreply.github.com> Date: Wed, 20 Nov 2024 08:51:29 +0000 Subject: [PATCH 1/3] New RobocodersAPI provider with providers enhancement and client updates (#2388) * refactor(g4f/Provider/Airforce.py): Enhance Airforce provider with dynamic model fetching * refactor(g4f/Provider/Blackbox.py): Enhance Blackbox AI provider configuration and streamline code * feat(g4f/Provider/RobocodersAPI.py): Add RobocodersAPI new async chat provider * refactor(g4f/client/__init__.py): Improve provider handling in async_generate method * refactor(g4f/models.py): Update provider configurations for multiple models * refactor(g4f/Provider/Blackbox.py): Streamline model configuration and improve response handling --------- Co-authored-by: kqlio67 --- g4f/Provider/Airforce.py | 96 +++++++++---- g4f/Provider/Blackbox.py | 107 ++++++++------- g4f/Provider/RobocodersAPI.py | 90 +++++++++++++ g4f/Provider/__init__.py | 3 +- g4f/Provider/airforce/AirforceChat.py | 179 ------------------------- g4f/Provider/airforce/AirforceImage.py | 81 ----------- g4f/Provider/airforce/__init__.py | 2 - g4f/models.py | 10 +- 8 files changed, 228 insertions(+), 340 deletions(-) create mode 100755 g4f/Provider/RobocodersAPI.py delete mode 100644 g4f/Provider/airforce/AirforceChat.py delete mode 100644 g4f/Provider/airforce/AirforceImage.py delete mode 100644 g4f/Provider/airforce/__init__.py diff --git a/g4f/Provider/Airforce.py b/g4f/Provider/Airforce.py index 6254e160060..54bb543b170 100644 --- a/g4f/Provider/Airforce.py +++ b/g4f/Provider/Airforce.py @@ -4,46 +4,88 @@ import json import re +import requests +from requests.packages.urllib3.exceptions import InsecureRequestWarning +requests.packages.urllib3.disable_warnings(InsecureRequestWarning) + from ..typing import AsyncResult, Messages from .base_provider import AsyncGeneratorProvider, ProviderModelMixin from ..image import ImageResponse from ..requests import StreamSession, raise_for_status -from .airforce.AirforceChat import AirforceChat -from .airforce.AirforceImage import AirforceImage class Airforce(AsyncGeneratorProvider, ProviderModelMixin): - url = "https://api.airforce" - api_endpoint_completions = AirforceChat.api_endpoint - api_endpoint_imagine = AirforceImage.api_endpoint + url = "https://llmplayground.net" + api_endpoint_completions = "https://api.airforce/chat/completions" + api_endpoint_imagine = "https://api.airforce/imagine2" working = True - default_model = "gpt-4o-mini" supports_system_message = True supports_message_history = True - text_models = [ - 'gpt-4-turbo', - default_model, - 'llama-3.1-70b-turbo', - 'llama-3.1-8b-turbo', - ] - image_models = [ - 'flux', - 'flux-realism', - 'flux-anime', - 'flux-3d', - 'flux-disney', - 'flux-pixel', - 'flux-4o', - 'any-dark', - ] + + @classmethod + def fetch_completions_models(cls): + response = requests.get('https://api.airforce/models', verify=False) + response.raise_for_status() + data = response.json() + return [model['id'] for model in data['data']] + + @classmethod + def fetch_imagine_models(cls): + response = requests.get('https://api.airforce/imagine/models', verify=False) + response.raise_for_status() + return response.json() + + completions_models = fetch_completions_models.__func__(None) + imagine_models = fetch_imagine_models.__func__(None) + + default_model = "gpt-4o-mini" + default_image_model = "flux" + additional_models_imagine = ["stable-diffusion-xl-base", "stable-diffusion-xl-lightning", "Flux-1.1-Pro"] + text_models = completions_models + image_models = [*imagine_models, *additional_models_imagine] models = [ *text_models, *image_models, ] - model_aliases = { - "gpt-4o": "chatgpt-4o-latest", + + model_aliases = { + ### completions ### + # openchat + "openchat-3.5": "openchat-3.5-0106", + + # deepseek-ai + "deepseek-coder": "deepseek-coder-6.7b-instruct", + + # NousResearch + "hermes-2-dpo": "Nous-Hermes-2-Mixtral-8x7B-DPO", + "hermes-2-pro": "hermes-2-pro-mistral-7b", + + # teknium + "openhermes-2.5": "openhermes-2.5-mistral-7b", + + # liquid + "lfm-40b": "lfm-40b-moe", + + # DiscoResearch + "german-7b": "discolm-german-7b-v1", + + # meta-llama + "llama-2-7b": "llama-2-7b-chat-int8", + "llama-2-7b": "llama-2-7b-chat-fp16", + "llama-3.1-70b": "llama-3.1-70b-chat", + "llama-3.1-8b": "llama-3.1-8b-chat", "llama-3.1-70b": "llama-3.1-70b-turbo", "llama-3.1-8b": "llama-3.1-8b-turbo", - "gpt-4": "gpt-4-turbo", + + # inferless + "neural-7b": "neural-chat-7b-v3-1", + + # HuggingFaceH4 + "zephyr-7b": "zephyr-7b-beta", + + ### imagine ### + "sdxl": "stable-diffusion-xl-base", + "sdxl": "stable-diffusion-xl-lightning", + "flux-pro": "Flux-1.1-Pro", } @classmethod @@ -53,7 +95,7 @@ def create_async_generator( messages: Messages, proxy: str = None, seed: int = None, - size: str = "1:1", + size: str = "1:1", # "1:1", "16:9", "9:16", "21:9", "9:21", "1:2", "2:1" stream: bool = False, **kwargs ) -> AsyncResult: @@ -168,4 +210,4 @@ def _filter_content(cls, part_response: str) -> str: '', part_response ) - return part_response \ No newline at end of file + return part_response diff --git a/g4f/Provider/Blackbox.py b/g4f/Provider/Blackbox.py index ba58a511b5f..b259b4aada7 100644 --- a/g4f/Provider/Blackbox.py +++ b/g4f/Provider/Blackbox.py @@ -10,7 +10,6 @@ from ..typing import AsyncResult, Messages, ImageType from .base_provider import AsyncGeneratorProvider, ProviderModelMixin from ..image import ImageResponse, to_data_uri -from .helper import get_random_string class Blackbox(AsyncGeneratorProvider, ProviderModelMixin): label = "Blackbox AI" @@ -21,19 +20,19 @@ class Blackbox(AsyncGeneratorProvider, ProviderModelMixin): supports_system_message = True supports_message_history = True _last_validated_value = None - + default_model = 'blackboxai' default_vision_model = default_model - default_image_model = 'generate_image' - image_models = [default_image_model, 'repomap'] - text_models = [default_model, 'gpt-4o', 'gemini-pro', 'claude-sonnet-3.5', 'blackboxai-pro'] - vision_models = [default_model, 'gpt-4o', 'gemini-pro', 'blackboxai-pro'] - model_aliases = { - "claude-3.5-sonnet": "claude-sonnet-3.5", - } + default_image_model = 'Image Generation' + image_models = ['Image Generation', 'repomap'] + vision_models = [default_model, 'gpt-4o', 'gemini-pro', 'gemini-1.5-flash', 'llama-3.1-8b', 'llama-3.1-70b', 'llama-3.1-405b'] + + userSelectedModel = ['gpt-4o', 'gemini-pro', 'claude-sonnet-3.5', 'blackboxai-pro'] + agentMode = { - default_image_model: {'mode': True, 'id': "ImageGenerationLV45LJp", 'name': "Image Generation"}, + 'Image Generation': {'mode': True, 'id': "ImageGenerationLV45LJp", 'name': "Image Generation"}, } + trendingAgentMode = { "gemini-1.5-flash": {'mode': True, 'id': 'Gemini'}, "llama-3.1-8b": {'mode': True, 'id': "llama-3.1-8b"}, @@ -53,6 +52,7 @@ class Blackbox(AsyncGeneratorProvider, ProviderModelMixin): 'React Agent': {'mode': True, 'id': "React Agent"}, 'Xcode Agent': {'mode': True, 'id': "Xcode Agent"}, 'AngularJS Agent': {'mode': True, 'id': "AngularJS Agent"}, + # 'blackboxai-pro': {'mode': True, 'id': "BLACKBOXAI-PRO"}, # 'repomap': {'mode': True, 'id': "repomap"}, @@ -75,8 +75,24 @@ class Blackbox(AsyncGeneratorProvider, ProviderModelMixin): 'Youtube Agent': {'mode': True, 'id': "Youtube Agent"}, 'builder Agent': {'mode': True, 'id': "builder Agent"}, } - model_prefixes = {mode: f"@{value['id']}" for mode, value in trendingAgentMode.items() if mode not in ["gemini-1.5-flash", "llama-3.1-8b", "llama-3.1-70b", "llama-3.1-405b", "repomap"]} - models = [*text_models, default_image_model, *list(trendingAgentMode.keys())] + + additional_prefixes = { + 'gpt-4o': '@gpt-4o', + 'gemini-pro': '@gemini-pro', + 'claude-sonnet-3.5': '@claude-sonnet' + } + + model_prefixes = { + **{mode: f"@{value['id']}" for mode, value in trendingAgentMode.items() + if mode not in ["gemini-1.5-flash", "llama-3.1-8b", "llama-3.1-70b", "llama-3.1-405b", "repomap"]}, + **additional_prefixes + } + + + models = list(dict.fromkeys([default_model, *userSelectedModel, *list(agentMode.keys()), *list(trendingAgentMode.keys())])) + + + model_aliases = { "gemini-flash": "gemini-1.5-flash", "claude-3.5-sonnet": "claude-sonnet-3.5", @@ -85,11 +101,9 @@ class Blackbox(AsyncGeneratorProvider, ProviderModelMixin): @classmethod async def fetch_validated(cls): - # If the key is already stored in memory, return it if cls._last_validated_value: return cls._last_validated_value - # If the key is not found, perform a search async with aiohttp.ClientSession() as session: try: async with session.get(cls.url) as response: @@ -110,13 +124,19 @@ async def fetch_validated(cls): match = key_pattern.search(js_content) if match: validated_value = match.group(1) - cls._last_validated_value = validated_value # Keep in mind + cls._last_validated_value = validated_value return validated_value except Exception as e: print(f"Error fetching validated value: {e}") return cls._last_validated_value + + @staticmethod + def generate_id(length=7): + characters = string.ascii_letters + string.digits + return ''.join(random.choice(characters) for _ in range(length)) + @classmethod def add_prefix_to_messages(cls, messages: Messages, model: str) -> Messages: prefix = cls.model_prefixes.get(model, "") @@ -143,8 +163,7 @@ async def create_async_generator( image_name: str = None, **kwargs ) -> AsyncResult: - model = cls.get_model(model) - message_id = get_random_string(7) + message_id = cls.generate_id() messages = cls.add_prefix_to_messages(messages, model) validated_value = await cls.fetch_validated() @@ -172,7 +191,7 @@ async def create_async_generator( 'sec-fetch-site': 'same-origin', 'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36' } - + data = { "messages": messages, "id": message_id, @@ -193,7 +212,7 @@ async def create_async_generator( "clickedForceWebSearch": False, "visitFromDelta": False, "mobileClient": False, - "userSelectedModel": model if model in cls.text_models else None, + "userSelectedModel": model if model in cls.userSelectedModel else None, "webSearchMode": web_search, "validated": validated_value, } @@ -201,29 +220,27 @@ async def create_async_generator( async with ClientSession(headers=headers) as session: async with session.post(cls.api_endpoint, json=data, proxy=proxy) as response: response.raise_for_status() - is_first = False - async for chunk in response.content.iter_any(): - text_chunk = chunk.decode(errors="ignore") - if model in cls.image_models: - image_matches = re.findall(r'!\[.*?\]\((https?://[^\)]+)\)', text_chunk) - if image_matches: - image_url = image_matches[0] - image_response = ImageResponse(images=[image_url]) - yield image_response - continue - - text_chunk = re.sub(r'Generated by BLACKBOX.AI, try unlimited chat https://www.blackbox.ai', '', text_chunk, flags=re.DOTALL) - json_match = re.search(r'\$~~~\$(.*?)\$~~~\$', text_chunk, re.DOTALL) - if json_match: - search_results = json.loads(json_match.group(1)) - answer = text_chunk.split('$~~~$')[-1].strip() - formatted_response = f"{answer}\n\n**Source:**" - for i, result in enumerate(search_results, 1): - formatted_response += f"\n{i}. {result['title']}: {result['link']}" - yield formatted_response - elif text_chunk: - if is_first: - is_first = False - yield text_chunk.lstrip() - else: - yield text_chunk \ No newline at end of file + response_text = await response.text() + + if model in cls.image_models: + image_matches = re.findall(r'!\[.*?\]\((https?://[^\)]+)\)', response_text) + if image_matches: + image_url = image_matches[0] + image_response = ImageResponse(images=[image_url], alt="Generated Image") + yield image_response + return + + response_text = re.sub(r'Generated by BLACKBOX.AI, try unlimited chat https://www.blackbox.ai', '', response_text, flags=re.DOTALL) + + json_match = re.search(r'\$~~~\$(.*?)\$~~~\$', response_text, re.DOTALL) + if json_match: + search_results = json.loads(json_match.group(1)) + answer = response_text.split('$~~~$')[-1].strip() + + formatted_response = f"{answer}\n\n**Source:**" + for i, result in enumerate(search_results, 1): + formatted_response += f"\n{i}. {result['title']}: {result['link']}" + + yield formatted_response + else: + yield response_text.strip() diff --git a/g4f/Provider/RobocodersAPI.py b/g4f/Provider/RobocodersAPI.py new file mode 100755 index 00000000000..aa82902bb34 --- /dev/null +++ b/g4f/Provider/RobocodersAPI.py @@ -0,0 +1,90 @@ +from __future__ import annotations + +import json +import aiohttp +from ..typing import AsyncResult, Messages +from .base_provider import AsyncGeneratorProvider, ProviderModelMixin +from .helper import format_prompt + +class RobocodersAPI(AsyncGeneratorProvider, ProviderModelMixin): + label = "API Robocoders AI" + url = "https://api.robocoders.ai/docs" + api_endpoint = "https://api.robocoders.ai/chat" + working = True + supports_message_history = True + default_model = 'GeneralCodingAgent' + agent = [default_model, "RepoAgent", "FrontEndAgent"] + models = [*agent] + + @classmethod + async def create_async_generator( + cls, + model: str, + messages: Messages, + proxy: str = None, + **kwargs + ) -> AsyncResult: + async with aiohttp.ClientSession() as session: + access_token = await cls._get_access_token(session) + if not access_token: + raise Exception("Failed to get access token") + + session_id = await cls._create_session(session, access_token) + if not session_id: + raise Exception("Failed to create session") + + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {access_token}" + } + + prompt = format_prompt(messages) + + data = { + "sid": session_id, + "prompt": prompt, + "agent": model + } + + async with session.post(cls.api_endpoint, headers=headers, json=data, proxy=proxy) as response: + if response.status != 200: + raise Exception(f"Error: {response.status}") + + async for line in response.content: + if line: + try: + response_data = json.loads(line) + message = response_data.get('message', '') + if message: + yield message + except json.JSONDecodeError: + pass + + @staticmethod + async def _get_access_token(session: aiohttp.ClientSession) -> str: + url_auth = 'https://api.robocoders.ai/auth' + headers_auth = { + 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', + 'accept-language': 'en-US,en;q=0.9', + 'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36', + } + + async with session.get(url_auth, headers=headers_auth) as response: + if response.status == 200: + text = await response.text() + return text.split('id="token">')[1].split('')[0].strip() + return None + + @staticmethod + async def _create_session(session: aiohttp.ClientSession, access_token: str) -> str: + url_create_session = 'https://api.robocoders.ai/create-session' + headers_create_session = { + 'Authorization': f'Bearer {access_token}' + } + + async with session.get(url_create_session, headers=headers_create_session) as response: + if response.status == 200: + data = await response.json() + return data.get('sid') + return None + diff --git a/g4f/Provider/__init__.py b/g4f/Provider/__init__.py index faf9979e82d..2083c0ff22c 100644 --- a/g4f/Provider/__init__.py +++ b/g4f/Provider/__init__.py @@ -35,11 +35,12 @@ from .Prodia import Prodia from .Reka import Reka from .ReplicateHome import ReplicateHome +from .RobocodersAPI import RobocodersAPI from .RubiksAI import RubiksAI from .TeachAnything import TeachAnything from .Upstage import Upstage from .You import You -from .Mhystical import Mhystical +from .Mhystical import Mhystical import sys diff --git a/g4f/Provider/airforce/AirforceChat.py b/g4f/Provider/airforce/AirforceChat.py deleted file mode 100644 index 1efe002614f..00000000000 --- a/g4f/Provider/airforce/AirforceChat.py +++ /dev/null @@ -1,179 +0,0 @@ -from __future__ import annotations -import re -import json -import requests -from aiohttp import ClientSession -from typing import List -import logging - -from ...typing import AsyncResult, Messages -from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin -from ..helper import format_prompt - -# Helper function to clean the response -def clean_response(text: str) -> str: - """Clean response from unwanted patterns.""" - patterns = [ - r"One message exceeds the \d+chars per message limit\..+https:\/\/discord\.com\/invite\/\S+", - r"Rate limit \(\d+\/minute\) exceeded\. Join our discord for more: .+https:\/\/discord\.com\/invite\/\S+", - r"Rate limit \(\d+\/hour\) exceeded\. Join our discord for more: https:\/\/discord\.com\/invite\/\S+", - r"", # zephyr-7b-beta - r"\[ERROR\] '\w{8}-\w{4}-\w{4}-\w{4}-\w{12}'", # Matches [ERROR] 'UUID' - ] - for pattern in patterns: - text = re.sub(pattern, '', text) - - # Remove the <|im_end|> token if present - text = text.replace("<|im_end|>", "").strip() - - return text - -def split_message(message: str, max_length: int = 1000) -> List[str]: - """Splits the message into chunks of a given length (max_length)""" - # Split the message into smaller chunks to avoid exceeding the limit - chunks = [] - while len(message) > max_length: - # Find the last space or punctuation before max_length to avoid cutting words - split_point = message.rfind(' ', 0, max_length) - if split_point == -1: # No space found, split at max_length - split_point = max_length - chunks.append(message[:split_point]) - message = message[split_point:].strip() - if message: - chunks.append(message) # Append the remaining part of the message - return chunks - -class AirforceChat(AsyncGeneratorProvider, ProviderModelMixin): - label = "AirForce Chat" - api_endpoint = "https://api.airforce/chat/completions" - supports_stream = True - supports_system_message = True - supports_message_history = True - - default_model = 'llama-3.1-70b-chat' - - @classmethod - def get_models(cls) -> list: - if not cls.models: - try: - response = requests.get('https://api.airforce/models', verify=False) - data = response.json() - cls.models = [model['id'] for model in data['data']] - except Exception as e: - logging.exception(e) - cls.models = [cls.default_model] - - model_aliases = { - # openchat - "openchat-3.5": "openchat-3.5-0106", - - # deepseek-ai - "deepseek-coder": "deepseek-coder-6.7b-instruct", - - # NousResearch - "hermes-2-dpo": "Nous-Hermes-2-Mixtral-8x7B-DPO", - "hermes-2-pro": "hermes-2-pro-mistral-7b", - - # teknium - "openhermes-2.5": "openhermes-2.5-mistral-7b", - - # liquid - "lfm-40b": "lfm-40b-moe", - - # DiscoResearch - "german-7b": "discolm-german-7b-v1", - - # meta-llama - "llama-2-7b": "llama-2-7b-chat-int8", - "llama-2-7b": "llama-2-7b-chat-fp16", - "llama-3.1-70b": "llama-3.1-70b-chat", - "llama-3.1-8b": "llama-3.1-8b-chat", - "llama-3.1-70b": "llama-3.1-70b-turbo", - "llama-3.1-8b": "llama-3.1-8b-turbo", - - # inferless - "neural-7b": "neural-chat-7b-v3-1", - - # HuggingFaceH4 - "zephyr-7b": "zephyr-7b-beta", - - # llmplayground.net - #"any-uncensored": "any-uncensored", - } - - @classmethod - async def create_async_generator( - cls, - model: str, - messages: Messages, - stream: bool = False, - proxy: str = None, - max_tokens: str = 4096, - temperature: str = 1, - top_p: str = 1, - **kwargs - ) -> AsyncResult: - model = cls.get_model(model) - - headers = { - 'accept': '*/*', - 'accept-language': 'en-US,en;q=0.9', - 'authorization': 'Bearer missing api key', - 'cache-control': 'no-cache', - 'content-type': 'application/json', - 'origin': 'https://llmplayground.net', - 'pragma': 'no-cache', - 'priority': 'u=1, i', - 'referer': 'https://llmplayground.net/', - 'sec-ch-ua': '"Not?A_Brand";v="99", "Chromium";v="130"', - 'sec-ch-ua-mobile': '?0', - 'sec-ch-ua-platform': '"Linux"', - 'sec-fetch-dest': 'empty', - 'sec-fetch-mode': 'cors', - 'sec-fetch-site': 'cross-site', - 'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36' - } - - # Format the messages for the API - formatted_messages = format_prompt(messages) - message_chunks = split_message(formatted_messages) - - full_response = "" - for chunk in message_chunks: - data = { - "messages": [{"role": "user", "content": chunk}], - "model": model, - "max_tokens": max_tokens, - "temperature": temperature, - "top_p": top_p, - "stream": stream - } - - async with ClientSession(headers=headers) as session: - async with session.post(cls.api_endpoint, json=data, proxy=proxy) as response: - response.raise_for_status() - - text = "" - if stream: - async for line in response.content: - line = line.decode('utf-8').strip() - if line.startswith('data: '): - json_str = line[6:] - try: - if json_str and json_str != "[DONE]": - chunk = json.loads(json_str) - if 'choices' in chunk and chunk['choices']: - content = chunk['choices'][0].get('delta', {}).get('content', '') - text += content - except json.JSONDecodeError as e: - print(f"Error decoding JSON: {json_str}, Error: {e}") - elif line == "[DONE]": - break - full_response += clean_response(text) - else: - response_json = await response.json() - text = response_json["choices"][0]["message"]["content"] - full_response += clean_response(text) - - # Return the complete response after all chunks - yield full_response diff --git a/g4f/Provider/airforce/AirforceImage.py b/g4f/Provider/airforce/AirforceImage.py deleted file mode 100644 index a5bd113ffc2..00000000000 --- a/g4f/Provider/airforce/AirforceImage.py +++ /dev/null @@ -1,81 +0,0 @@ -from __future__ import annotations - -from aiohttp import ClientSession -from urllib.parse import urlencode -import random -import requests -import logging - -from ...typing import AsyncResult, Messages -from ...image import ImageResponse -from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin - -class AirforceImage(AsyncGeneratorProvider, ProviderModelMixin): - label = "Airforce Image" - url = "https://api.airforce" - api_endpoint = "https://api.airforce/imagine2" - working = False - - default_model = 'flux' - additional_models = ["stable-diffusion-xl-base", "stable-diffusion-xl-lightning", "Flux-1.1-Pro"] - model_aliases = { - "sdxl": "stable-diffusion-xl-base", - "sdxl": "stable-diffusion-xl-lightning", - "flux-pro": "Flux-1.1-Pro", - } - - @classmethod - def get_models(cls) -> list: - if not cls.models: - try: - response = requests.get('https://api.airforce/imagine/models', verify=False) - response.raise_for_status() - cls.models = [*response.json(), *cls.additional_models] - except Exception as e: - logging.exception(e) - cls.models = [cls.default_model] - return cls.models - - @classmethod - async def create_async_generator( - cls, - model: str, - messages: Messages, - size: str = '1:1', # "1:1", "16:9", "9:16", "21:9", "9:21", "1:2", "2:1" - proxy: str = None, - **kwargs - ) -> AsyncResult: - model = cls.get_model(model) - - headers = { - 'accept': 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8', - 'accept-language': 'en-US,en;q=0.9', - 'cache-control': 'no-cache', - 'dnt': '1', - 'pragma': 'no-cache', - 'priority': 'u=1, i', - 'referer': 'https://llmplayground.net/', - 'sec-ch-ua': '"Not?A_Brand";v="99", "Chromium";v="130"', - 'sec-ch-ua-mobile': '?0', - 'sec-ch-ua-platform': '"Linux"', - 'sec-fetch-dest': 'image', - 'sec-fetch-mode': 'no-cors', - 'sec-fetch-site': 'cross-site', - 'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36' - } - - async with ClientSession(headers=headers) as session: - seed = random.randint(0, 58463) - params = { - 'model': model, - 'prompt': messages[-1]["content"], - 'size': size, - 'seed': seed - } - full_url = f"{cls.api_endpoint}?{urlencode(params)}" - - async with session.get(full_url, headers=headers, proxy=proxy) as response: - if response.status == 200 and response.headers.get('content-type', '').startswith('image'): - yield ImageResponse(images=[full_url], alt="Generated Image") - else: - raise Exception(f"Error: status {response.status}, content type {response.headers.get('content-type')}") diff --git a/g4f/Provider/airforce/__init__.py b/g4f/Provider/airforce/__init__.py deleted file mode 100644 index 5ffa6d310a5..00000000000 --- a/g4f/Provider/airforce/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .AirforceChat import AirforceChat -from .AirforceImage import AirforceImage diff --git a/g4f/models.py b/g4f/models.py index 8825242f901..655aca9c5df 100644 --- a/g4f/models.py +++ b/g4f/models.py @@ -211,7 +211,7 @@ def __all__() -> list[str]: gemini_pro = Model( name = 'gemini-pro', base_provider = 'Google DeepMind', - best_provider = IterListProvider([GeminiPro, Blackbox, AIChatFree, Liaobots]) + best_provider = IterListProvider([Blackbox, AIChatFree, GeminiPro, Liaobots]) ) gemini_flash = Model( @@ -347,7 +347,7 @@ def __all__() -> list[str]: wizardlm_2_8x22b = Model( name = 'wizardlm-2-8x22b', base_provider = 'WizardLM', - best_provider = IterListProvider([DeepInfraChat]) + best_provider = DeepInfraChat ) ### Yorickvp ### @@ -389,7 +389,7 @@ def __all__() -> list[str]: sonar_online = Model( name = 'sonar-online', base_provider = 'Perplexity AI', - best_provider = IterListProvider([PerplexityLabs]) + best_provider = PerplexityLabs ) sonar_chat = Model( @@ -450,7 +450,7 @@ def __all__() -> list[str]: sdxl = Model( name = 'sdxl', base_provider = 'Stability AI', - best_provider = IterListProvider([ReplicateHome]) + best_provider = ReplicateHome ) @@ -671,4 +671,4 @@ class ModelUtils: 'any-dark': any_dark, } -_all_models = list(ModelUtils.convert.keys()) \ No newline at end of file +_all_models = list(ModelUtils.convert.keys()) From dba41cda5647dc912e581d4bf81c09bb25257aab Mon Sep 17 00:00:00 2001 From: H Lohaus Date: Wed, 20 Nov 2024 09:52:38 +0100 Subject: [PATCH 2/3] Fix image generation in OpenaiChat (#2390) * Fix image generation in OpenaiChat * Add PollinationsAI provider with image and text generation --- g4f/Provider/PollinationsAI.py | 69 +++++++++++++++++++++++++++ g4f/Provider/__init__.py | 1 + g4f/Provider/needs_auth/OpenaiChat.py | 65 +++++-------------------- g4f/providers/base_provider.py | 1 + 4 files changed, 84 insertions(+), 52 deletions(-) create mode 100644 g4f/Provider/PollinationsAI.py diff --git a/g4f/Provider/PollinationsAI.py b/g4f/Provider/PollinationsAI.py new file mode 100644 index 00000000000..57597bf17b0 --- /dev/null +++ b/g4f/Provider/PollinationsAI.py @@ -0,0 +1,69 @@ +from __future__ import annotations + +from urllib.parse import quote +import random +import requests +from sys import maxsize +from aiohttp import ClientSession + +from ..typing import AsyncResult, Messages +from ..image import ImageResponse +from ..requests.raise_for_status import raise_for_status +from ..requests.aiohttp import get_connector +from .needs_auth.OpenaiAPI import OpenaiAPI +from .helper import format_prompt + +class PollinationsAI(OpenaiAPI): + label = "Pollinations.AI" + url = "https://pollinations.ai" + working = True + supports_stream = True + default_model = "openai" + + @classmethod + def get_models(cls): + if not cls.image_models: + url = "https://image.pollinations.ai/models" + response = requests.get(url) + raise_for_status(response) + cls.image_models = response.json() + if not cls.models: + url = "https://text.pollinations.ai/models" + response = requests.get(url) + raise_for_status(response) + cls.models = [model.get("name") for model in response.json()] + cls.models.extend(cls.image_models) + return cls.models + + @classmethod + async def create_async_generator( + cls, + model: str, + messages: Messages, + api_base: str = "https://text.pollinations.ai/openai", + api_key: str = None, + proxy: str = None, + seed: str = None, + **kwargs + ) -> AsyncResult: + if model: + model = cls.get_model(model) + if model in cls.image_models: + prompt = messages[-1]["content"] + if seed is None: + seed = random.randint(0, maxsize) + image = f"https://image.pollinations.ai/prompt/{quote(prompt)}?width=1024&height=1024&seed={int(seed)}&nofeed=true&nologo=true&model={quote(model)}" + yield ImageResponse(image, prompt) + return + if api_key is None: + async with ClientSession(connector=get_connector(proxy=proxy)) as session: + prompt = format_prompt(messages) + async with session.get(f"https://text.pollinations.ai/{quote(prompt)}?model={quote(model)}") as response: + await raise_for_status(response) + async for line in response.content.iter_any(): + yield line.decode(errors="ignore") + else: + async for chunk in super().create_async_generator( + model, messages, api_base=api_base, proxy=proxy, **kwargs + ): + yield chunk \ No newline at end of file diff --git a/g4f/Provider/__init__.py b/g4f/Provider/__init__.py index 2083c0ff22c..378f09c8e5b 100644 --- a/g4f/Provider/__init__.py +++ b/g4f/Provider/__init__.py @@ -32,6 +32,7 @@ from .PerplexityLabs import PerplexityLabs from .Pi import Pi from .Pizzagpt import Pizzagpt +from .PollinationsAI import PollinationsAI from .Prodia import Prodia from .Reka import Reka from .ReplicateHome import ReplicateHome diff --git a/g4f/Provider/needs_auth/OpenaiChat.py b/g4f/Provider/needs_auth/OpenaiChat.py index 15a87f38b29..97515ec4063 100644 --- a/g4f/Provider/needs_auth/OpenaiChat.py +++ b/g4f/Provider/needs_auth/OpenaiChat.py @@ -65,6 +65,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): default_vision_model = "gpt-4o" fallback_models = ["auto", "gpt-4", "gpt-4o", "gpt-4o-mini", "gpt-4o-canmore", "o1-preview", "o1-mini"] vision_models = fallback_models + image_models = fallback_models _api_key: str = None _headers: dict = None @@ -330,7 +331,7 @@ async def create_async_generator( api_key: str = None, cookies: Cookies = None, auto_continue: bool = False, - history_disabled: bool = True, + history_disabled: bool = False, action: str = "next", conversation_id: str = None, conversation: Conversation = None, @@ -425,12 +426,6 @@ async def create_async_generator( f"Arkose: {'False' if not need_arkose else RequestConfig.arkose_token[:12]+'...'}", f"Proofofwork: {'False' if proofofwork is None else proofofwork[:12]+'...'}", )] - ws = None - if need_arkose: - async with session.post(f"{cls.url}/backend-api/register-websocket", headers=cls._headers) as response: - wss_url = (await response.json()).get("wss_url") - if wss_url: - ws = await session.ws_connect(wss_url) data = { "action": action, "messages": None, @@ -474,7 +469,7 @@ async def create_async_generator( await asyncio.sleep(5) continue await raise_for_status(response) - async for chunk in cls.iter_messages_chunk(response.iter_lines(), session, conversation, ws): + async for chunk in cls.iter_messages_chunk(response.iter_lines(), session, conversation): if return_conversation: history_disabled = False return_conversation = False @@ -489,44 +484,16 @@ async def create_async_generator( if history_disabled and auto_continue: await cls.delete_conversation(session, cls._headers, conversation.conversation_id) - @staticmethod - async def iter_messages_ws(ws: ClientWebSocketResponse, conversation_id: str, is_curl: bool) -> AsyncIterator: - while True: - if is_curl: - message = json.loads(ws.recv()[0]) - else: - message = await ws.receive_json() - if message["conversation_id"] == conversation_id: - yield base64.b64decode(message["body"]) - @classmethod async def iter_messages_chunk( cls, messages: AsyncIterator, session: StreamSession, fields: Conversation, - ws = None ) -> AsyncIterator: async for message in messages: - if message.startswith(b'{"wss_url":'): - message = json.loads(message) - ws = await session.ws_connect(message["wss_url"]) if ws is None else ws - try: - async for chunk in cls.iter_messages_chunk( - cls.iter_messages_ws(ws, message["conversation_id"], hasattr(ws, "recv")), - session, fields - ): - yield chunk - finally: - await ws.aclose() if hasattr(ws, "aclose") else await ws.close() - break async for chunk in cls.iter_messages_line(session, message, fields): - if fields.finish_reason is not None: - break - else: - yield chunk - if fields.finish_reason is not None: - break + yield chunk @classmethod async def iter_messages_line(cls, session: StreamSession, line: bytes, fields: Conversation) -> AsyncIterator: @@ -542,9 +509,9 @@ async def iter_messages_line(cls, session: StreamSession, line: bytes, fields: C return if isinstance(line, dict) and "v" in line: v = line.get("v") - if isinstance(v, str): + if isinstance(v, str) and fields.is_recipient: yield v - elif isinstance(v, list): + elif isinstance(v, list) and fields.is_recipient: for m in v: if m.get("p") == "/message/content/parts/0": yield m.get("v") @@ -556,25 +523,20 @@ async def iter_messages_line(cls, session: StreamSession, line: bytes, fields: C fields.conversation_id = v.get("conversation_id") debug.log(f"OpenaiChat: New conversation: {fields.conversation_id}") m = v.get("message", {}) - if m.get("author", {}).get("role") == "assistant": - fields.message_id = v.get("message", {}).get("id") + fields.is_recipient = m.get("recipient") == "all" + if fields.is_recipient: c = m.get("content", {}) if c.get("content_type") == "multimodal_text": generated_images = [] for element in c.get("parts"): - if isinstance(element, str): - debug.log(f"No image or text: {line}") - elif element.get("content_type") == "image_asset_pointer": + if isinstance(element, dict) and element.get("content_type") == "image_asset_pointer": generated_images.append( cls.get_generated_image(session, cls._headers, element) ) - elif element.get("content_type") == "text": - for part in element.get("parts", []): - yield part for image_response in await asyncio.gather(*generated_images): yield image_response - else: - debug.log(f"OpenaiChat: {line}") + if m.get("author", {}).get("role") == "assistant": + fields.message_id = v.get("message", {}).get("id") return if "error" in line and line.get("error"): raise RuntimeError(line.get("error")) @@ -652,7 +614,7 @@ def _create_request_args(cls, cookies: Cookies = None, headers: dict = None, use cls._headers = cls.get_default_headers() if headers is None else headers if user_agent is not None: cls._headers["user-agent"] = user_agent - cls._cookies = {} if cookies is None else {k: v for k, v in cookies.items() if k != "access_token"} + cls._cookies = {} if cookies is None else cookies cls._update_cookie_header() @classmethod @@ -671,8 +633,6 @@ def _set_api_key(cls, api_key: str): @classmethod def _update_cookie_header(cls): cls._headers["cookie"] = format_cookies(cls._cookies) - if "oai-did" in cls._cookies: - cls._headers["oai-device-id"] = cls._cookies["oai-did"] class Conversation(BaseConversation): """ @@ -682,6 +642,7 @@ def __init__(self, conversation_id: str = None, message_id: str = None, finish_r self.conversation_id = conversation_id self.message_id = message_id self.finish_reason = finish_reason + self.is_recipient = False class Response(): """ diff --git a/g4f/providers/base_provider.py b/g4f/providers/base_provider.py index 128fb5a012b..9fa17fc3948 100644 --- a/g4f/providers/base_provider.py +++ b/g4f/providers/base_provider.py @@ -290,6 +290,7 @@ class ProviderModelMixin: default_model: str = None models: list[str] = [] model_aliases: dict[str, str] = {} + image_models: list = None @classmethod def get_models(cls) -> list[str]: From c959d9b469c9a23e2657a0d51cb6e735b71082a9 Mon Sep 17 00:00:00 2001 From: H Lohaus Date: Wed, 20 Nov 2024 10:12:54 +0100 Subject: [PATCH 3/3] Update api.py --- g4f/gui/server/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/g4f/gui/server/api.py b/g4f/gui/server/api.py index 2d871ff34b6..29fc34e25ff 100644 --- a/g4f/gui/server/api.py +++ b/g4f/gui/server/api.py @@ -48,7 +48,7 @@ def get_provider_models(provider: str, api_key: str = None) -> list[dict]: "model": model, "default": model == provider.default_model, "vision": getattr(provider, "default_vision_model", None) == model or model in getattr(provider, "vision_models", []), - "image": model in getattr(provider, "image_models", []), + "image": False if provider.image_models is None else model in provider.image_models, } for model in models ]