From f1ef23285ae6e63d5fb28f5e271fec0e40ad65f2 Mon Sep 17 00:00:00 2001 From: Heiner Lohaus Date: Mon, 18 Nov 2024 15:41:45 +0100 Subject: [PATCH 1/3] Fix load model list i AirforceChat provider Add Microsoft Copilot provider Show image support in the model list of the gui --- README.md | 2 +- g4f/Provider/Blackbox.py | 65 +++++++++----------- g4f/Provider/Copilot.py | 87 +++++++++++++++++++++++++++ g4f/Provider/DeepInfraChat.py | 2 - g4f/Provider/__init__.py | 1 + g4f/Provider/airforce/AirforceChat.py | 10 +-- g4f/Provider/needs_auth/OpenaiChat.py | 16 ++++- g4f/gui/client/index.html | 1 + g4f/gui/client/static/js/chat.v1.js | 5 +- g4f/gui/server/api.py | 21 ++++--- g4f/requests/__init__.py | 2 +- 11 files changed, 157 insertions(+), 55 deletions(-) create mode 100644 g4f/Provider/Copilot.py diff --git a/README.md b/README.md index 671fd908cd3..cfaaadc05dd 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ > Stats: [![Downloads](https://static.pepy.tech/badge/g4f)](https://pepy.tech/project/g4f) [![Downloads](https://static.pepy.tech/badge/g4f/month)](https://pepy.tech/project/g4f) ```sh -pip install -U g4f +pip install -U g4f[all] ``` ```sh diff --git a/g4f/Provider/Blackbox.py b/g4f/Provider/Blackbox.py index 75abb1836df..97466c04fdb 100644 --- a/g4f/Provider/Blackbox.py +++ b/g4f/Provider/Blackbox.py @@ -10,6 +10,7 @@ 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" @@ -22,11 +23,13 @@ class Blackbox(AsyncGeneratorProvider, ProviderModelMixin): _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'] agentMode = { - 'Image Generation': {'mode': True, 'id': "ImageGenerationLV45LJp", 'name': "Image Generation"}, + default_image_model: {'mode': True, 'id': "ImageGenerationLV45LJp", 'name': "Image Generation"}, } trendingAgentMode = { "gemini-1.5-flash": {'mode': True, 'id': 'Gemini'}, @@ -111,11 +114,6 @@ async def fetch_validated(cls): 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,12 +141,12 @@ async def create_async_generator( **kwargs ) -> AsyncResult: model = cls.get_model(model) - message_id = cls.generate_id() - messages_with_prefix = cls.add_prefix_to_messages(messages, model) + message_id = get_random_string(7) + messages = cls.add_prefix_to_messages(messages, model) validated_value = await cls.fetch_validated() if image is not None: - messages_with_prefix[-1]['data'] = { + messages[-1]['data'] = { 'fileText': '', 'imageBase64': to_data_uri(image), 'title': image_name @@ -171,9 +169,9 @@ 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_with_prefix, + "messages": messages, "id": message_id, "previewToken": None, "userId": None, @@ -200,27 +198,24 @@ 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() - 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() + 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 + else: + yield text_chunk.strip() diff --git a/g4f/Provider/Copilot.py b/g4f/Provider/Copilot.py new file mode 100644 index 00000000000..ddfed4a87af --- /dev/null +++ b/g4f/Provider/Copilot.py @@ -0,0 +1,87 @@ +from __future__ import annotations + +import json +from http.cookiejar import CookieJar +try: + from curl_cffi.requests import Session, CurlWsFlag + has_curl_cffi = True +except ImportError: + has_curl_cffi = False + +from .base_provider import AbstractProvider, BaseConversation +from .helper import format_prompt +from ..typing import CreateResult, Messages +from ..errors import MissingRequirementsError +from ..requests.raise_for_status import raise_for_status +from .. import debug + +class Conversation(BaseConversation): + conversation_id: str + cookie_jar: CookieJar + + def __init__(self, conversation_id: str, cookie_jar: CookieJar): + self.conversation_id = conversation_id + self.cookie_jar = cookie_jar + +class Copilot(AbstractProvider): + label = "Microsoft Copilot" + url = "https://copilot.microsoft.com" + working = True + supports_stream = True + + websocket_url = "wss://copilot.microsoft.com/c/api/chat?api-version=2" + conversation_url = f"{url}/c/api/conversations" + + @classmethod + def create_completion( + cls, + model: str, + messages: Messages, + stream: bool = False, + proxy: str = None, + timeout: int = 900, + conversation: Conversation = None, + return_conversation: bool = False, + **kwargs + ) -> CreateResult: + if not has_curl_cffi: + raise MissingRequirementsError('Install or update "curl_cffi" package | pip install -U nodriver') + + cookies = conversation.cookie_jar if conversation is not None else None + with Session(timeout=timeout, proxy=proxy, impersonate="chrome", cookies=cookies) as session: + response = session.get(f"{cls.url}/") + raise_for_status(response) + if conversation is None: + response = session.post(cls.conversation_url) + raise_for_status(response) + conversation_id = response.json().get("id") + if return_conversation: + yield Conversation(conversation_id, session.cookies.jar) + prompt = format_prompt(messages) + if debug.logging: + print(f"Copilot: Created conversation: {conversation_id}") + else: + conversation_id = conversation.conversation_id + prompt = messages[-1]["content"] + if debug.logging: + print(f"Copilot: Use conversation: {conversation_id}") + + wss = session.ws_connect(cls.websocket_url) + wss.send(json.dumps({ + "event": "send", + "conversationId": conversation_id, + "content": [{ + "type": "text", + "text": prompt, + }], + "mode": "chat" + }).encode(), CurlWsFlag.TEXT) + while True: + try: + msg = json.loads(wss.recv()[0]) + except: + break + if msg.get("event") == "appendText": + yield msg.get("text") + elif msg.get("event") in ["done", "partCompleted"]: + break \ No newline at end of file diff --git a/g4f/Provider/DeepInfraChat.py b/g4f/Provider/DeepInfraChat.py index 5c668599da4..d8cb072ac1e 100644 --- a/g4f/Provider/DeepInfraChat.py +++ b/g4f/Provider/DeepInfraChat.py @@ -4,10 +4,8 @@ import json from ..typing import AsyncResult, Messages, ImageType -from ..image import to_data_uri from .base_provider import AsyncGeneratorProvider, ProviderModelMixin - class DeepInfraChat(AsyncGeneratorProvider, ProviderModelMixin): url = "https://deepinfra.com/chat" api_endpoint = "https://api.deepinfra.com/v1/openai/chat/completions" diff --git a/g4f/Provider/__init__.py b/g4f/Provider/__init__.py index 8a162baf640..faf9979e82d 100644 --- a/g4f/Provider/__init__.py +++ b/g4f/Provider/__init__.py @@ -19,6 +19,7 @@ from .ChatGpt import ChatGpt from .ChatGptEs import ChatGptEs from .Cloudflare import Cloudflare +from .Copilot import Copilot from .DarkAI import DarkAI from .DDG import DDG from .DeepInfraChat import DeepInfraChat diff --git a/g4f/Provider/airforce/AirforceChat.py b/g4f/Provider/airforce/AirforceChat.py index cec911a3cbb..e94dd0a8794 100644 --- a/g4f/Provider/airforce/AirforceChat.py +++ b/g4f/Provider/airforce/AirforceChat.py @@ -50,11 +50,13 @@ class AirforceChat(AsyncGeneratorProvider, ProviderModelMixin): supports_message_history = True default_model = 'llama-3.1-70b-chat' - response = requests.get('https://api.airforce/models') - data = response.json() - text_models = [model['id'] for model in data['data']] - models = [*text_models] + @classmethod + def get_models(cls) -> list: + if not cls.models: + response = requests.get('https://api.airforce/models') + data = response.json() + cls.models = [model['id'] for model in data['data']] model_aliases = { # openchat diff --git a/g4f/Provider/needs_auth/OpenaiChat.py b/g4f/Provider/needs_auth/OpenaiChat.py index 43444699f0b..13e15f1d84b 100644 --- a/g4f/Provider/needs_auth/OpenaiChat.py +++ b/g4f/Provider/needs_auth/OpenaiChat.py @@ -6,6 +6,7 @@ import json import base64 import time +import requests from aiohttp import ClientWebSocketResponse from copy import copy @@ -62,13 +63,26 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): supports_system_message = True default_model = "auto" default_vision_model = "gpt-4o" - models = ["auto", "gpt-4o-mini", "gpt-4o", "gpt-4", "gpt-4-gizmo"] + fallback_models = ["auto", "gpt-4", "gpt-4o", "gpt-4o-mini", "gpt-4o-canmore", "o1-preview", "o1-mini"] + vision_models = fallback_models _api_key: str = None _headers: dict = None _cookies: Cookies = None _expires: int = None + @classmethod + def get_models(cls): + if not cls.models: + try: + response = requests.get(f"{cls.url}/backend-anon/models") + response.raise_for_status() + data = response.json() + cls.models = [model.get("slug") for model in data.get("models")] + except Exception: + cls.models = cls.fallback_models + return cls.models + @classmethod async def create( cls, diff --git a/g4f/gui/client/index.html b/g4f/gui/client/index.html index e650d7e0950..63e47b3ff36 100644 --- a/g4f/gui/client/index.html +++ b/g4f/gui/client/index.html @@ -245,6 +245,7 @@

Settings