From 89e096334dfb1049e8cb427a866739d934cd7ad8 Mon Sep 17 00:00:00 2001 From: hlohaus <983577+hlohaus@users.noreply.github.com> Date: Fri, 31 Jan 2025 17:36:48 +0100 Subject: Support reasoning tokens by default Add new default HuggingFace provider Add format_image_prompt and get_last_user_message helper Add stop_browser callable to get_nodriver function Fix content type response in images route --- g4f/Provider/Blackbox.py | 8 +- g4f/Provider/Copilot.py | 7 +- g4f/Provider/OIVSCode.py | 5 +- g4f/Provider/PerplexityLabs.py | 19 +- g4f/Provider/PollinationsAI.py | 4 +- g4f/Provider/You.py | 4 +- g4f/Provider/__init__.py | 1 + g4f/Provider/hf/HuggingChat.py | 226 ++++++++++++++++++ g4f/Provider/hf/HuggingFaceAPI.py | 61 +++++ g4f/Provider/hf/HuggingFaceInference.py | 215 +++++++++++++++++ g4f/Provider/hf/__init__.py | 62 +++++ g4f/Provider/hf/models.py | 46 ++++ g4f/Provider/hf_space/BlackForestLabsFlux1Dev.py | 3 +- .../hf_space/BlackForestLabsFlux1Schnell.py | 3 +- g4f/Provider/hf_space/CohereForAI.py | 5 +- g4f/Provider/hf_space/Janus_Pro_7B.py | 60 ++--- g4f/Provider/hf_space/Qwen_Qwen_2_5M_Demo.py | 3 +- g4f/Provider/hf_space/StableDiffusion35Large.py | 3 +- g4f/Provider/hf_space/VoodoohopFlux1Schnell.py | 6 +- g4f/Provider/mini_max/HailuoAI.py | 3 +- g4f/Provider/needs_auth/Anthropic.py | 2 +- g4f/Provider/needs_auth/BingCreateImages.py | 3 +- g4f/Provider/needs_auth/DeepInfra.py | 3 +- g4f/Provider/needs_auth/DeepSeekAPI.py | 2 +- g4f/Provider/needs_auth/Gemini.py | 7 +- g4f/Provider/needs_auth/GithubCopilot.py | 4 +- g4f/Provider/needs_auth/HuggingChat.py | 265 --------------------- g4f/Provider/needs_auth/HuggingFace.py | 220 ----------------- g4f/Provider/needs_auth/HuggingFaceAPI.py | 57 ----- g4f/Provider/needs_auth/MicrosoftDesigner.py | 8 +- g4f/Provider/needs_auth/OpenaiChat.py | 6 +- g4f/Provider/needs_auth/__init__.py | 3 - g4f/Provider/template/BackendApi.py | 13 +- g4f/Provider/template/OpenaiTemplate.py | 18 +- 34 files changed, 701 insertions(+), 654 deletions(-) create mode 100644 g4f/Provider/hf/HuggingChat.py create mode 100644 g4f/Provider/hf/HuggingFaceAPI.py create mode 100644 g4f/Provider/hf/HuggingFaceInference.py create mode 100644 g4f/Provider/hf/__init__.py create mode 100644 g4f/Provider/hf/models.py delete mode 100644 g4f/Provider/needs_auth/HuggingChat.py delete mode 100644 g4f/Provider/needs_auth/HuggingFace.py delete mode 100644 g4f/Provider/needs_auth/HuggingFaceAPI.py (limited to 'g4f/Provider') diff --git a/g4f/Provider/Blackbox.py b/g4f/Provider/Blackbox.py index 10abded5..d57d27c9 100644 --- a/g4f/Provider/Blackbox.py +++ b/g4f/Provider/Blackbox.py @@ -13,7 +13,7 @@ from ..requests.raise_for_status import raise_for_status from .base_provider import AsyncGeneratorProvider, ProviderModelMixin from ..image import ImageResponse, to_data_uri from ..cookies import get_cookies_dir -from .helper import format_prompt +from .helper import format_prompt, format_image_prompt from ..providers.response import FinishReason, JsonConversation, Reasoning class Conversation(JsonConversation): @@ -216,9 +216,8 @@ class Blackbox(AsyncGeneratorProvider, ProviderModelMixin): async with ClientSession(headers=headers) as session: if model == "ImageGeneration2": - prompt = messages[-1]["content"] data = { - "query": prompt, + "query": format_image_prompt(messages, prompt), "agentMode": True } headers['content-type'] = 'text/plain;charset=UTF-8' @@ -307,8 +306,7 @@ class Blackbox(AsyncGeneratorProvider, ProviderModelMixin): image_url_match = re.search(r'!\[.*?\]\((.*?)\)', text_to_yield) if image_url_match: image_url = image_url_match.group(1) - prompt = messages[-1]["content"] - yield ImageResponse(images=[image_url], alt=prompt) + yield ImageResponse(image_url, format_image_prompt(messages, prompt)) else: if "" in text_to_yield and "" in chunk_text : chunk_text = text_to_yield.split('', 1) diff --git a/g4f/Provider/Copilot.py b/g4f/Provider/Copilot.py index 8e961fb5..f63b0563 100644 --- a/g4f/Provider/Copilot.py +++ b/g4f/Provider/Copilot.py @@ -28,6 +28,7 @@ from ..providers.response import BaseConversation, JsonConversation, RequestLogi from ..providers.asyncio import get_running_loop from ..requests import get_nodriver from ..image import ImageResponse, to_bytes, is_accepted_format +from .helper import get_last_user_message from .. import debug class Conversation(JsonConversation): @@ -139,7 +140,7 @@ class Copilot(AbstractProvider, ProviderModelMixin): else: conversation_id = conversation.conversation_id if prompt is None: - prompt = messages[-1]["content"] + prompt = get_last_user_message(messages) debug.log(f"Copilot: Use conversation: {conversation_id}") uploaded_images = [] @@ -206,7 +207,7 @@ class Copilot(AbstractProvider, ProviderModelMixin): yield Parameters(**{"cookies": {c.name: c.value for c in session.cookies.jar}}) async def get_access_token_and_cookies(url: str, proxy: str = None, target: str = "ChatAI",): - browser = await get_nodriver(proxy=proxy, user_data_dir="copilot") + browser, stop_browser = await get_nodriver(proxy=proxy, user_data_dir="copilot") try: page = await browser.get(url) access_token = None @@ -233,7 +234,7 @@ async def get_access_token_and_cookies(url: str, proxy: str = None, target: str await page.close() return access_token, cookies finally: - browser.stop() + stop_browser() def readHAR(url: str): api_key = None diff --git a/g4f/Provider/OIVSCode.py b/g4f/Provider/OIVSCode.py index ef88c5ed..fae8c179 100644 --- a/g4f/Provider/OIVSCode.py +++ b/g4f/Provider/OIVSCode.py @@ -9,7 +9,6 @@ class OIVSCode(OpenaiTemplate): working = True needs_auth = False - default_model = "gpt-4o-mini-2024-07-18" + default_model = "gpt-4o-mini" default_vision_model = default_model - vision_models = [default_model, "gpt-4o-mini"] - model_aliases = {"gpt-4o-mini": "gpt-4o-mini-2024-07-18"} \ No newline at end of file + vision_models = [default_model, "gpt-4o-mini"] \ No newline at end of file diff --git a/g4f/Provider/PerplexityLabs.py b/g4f/Provider/PerplexityLabs.py index a1050575..d8477064 100644 --- a/g4f/Provider/PerplexityLabs.py +++ b/g4f/Provider/PerplexityLabs.py @@ -5,7 +5,7 @@ import json from ..typing import AsyncResult, Messages from ..requests import StreamSession, raise_for_status -from ..providers.response import Reasoning, FinishReason +from ..providers.response import FinishReason from .base_provider import AsyncGeneratorProvider, ProviderModelMixin API_URL = "https://www.perplexity.ai/socket.io/" @@ -87,22 +87,7 @@ class PerplexityLabs(AsyncGeneratorProvider, ProviderModelMixin): continue try: data = json.loads(message[2:])[1] - new_content = data["output"][last_message:] - - if "" in new_content: - yield Reasoning(None, "thinking") - is_thinking = True - if "" in new_content: - new_content = new_content.split("", 1) - yield Reasoning(f"{new_content[0]}") - yield Reasoning(None, "finished") - yield new_content[1] - is_thinking = False - elif is_thinking: - yield Reasoning(new_content) - else: - yield new_content - + yield data["output"][last_message:] last_message = len(data["output"]) if data["final"]: yield FinishReason("stop") diff --git a/g4f/Provider/PollinationsAI.py b/g4f/Provider/PollinationsAI.py index f8664801..12268288 100644 --- a/g4f/Provider/PollinationsAI.py +++ b/g4f/Provider/PollinationsAI.py @@ -7,7 +7,7 @@ from urllib.parse import quote_plus from typing import Optional from aiohttp import ClientSession -from .helper import filter_none +from .helper import filter_none, format_image_prompt from .base_provider import AsyncGeneratorProvider, ProviderModelMixin from ..typing import AsyncResult, Messages, ImagesType from ..image import to_data_uri @@ -127,7 +127,7 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin): if model in cls.image_models: yield await cls._generate_image( model=model, - prompt=messages[-1]["content"] if prompt is None else prompt, + prompt=format_image_prompt(messages, prompt), proxy=proxy, width=width, height=height, diff --git a/g4f/Provider/You.py b/g4f/Provider/You.py index 7e5df0fd..52546280 100644 --- a/g4f/Provider/You.py +++ b/g4f/Provider/You.py @@ -77,7 +77,7 @@ class You(AsyncGeneratorProvider, ProviderModelMixin): except MissingRequirementsError: pass if not cookies or "afUserId" not in cookies: - browser = await get_nodriver(proxy=proxy) + browser, stop_browser = await get_nodriver(proxy=proxy) try: page = await browser.get(cls.url) await page.wait_for('[data-testid="user-profile-button"]', timeout=900) @@ -86,7 +86,7 @@ class You(AsyncGeneratorProvider, ProviderModelMixin): cookies[c.name] = c.value await page.close() finally: - browser.stop() + stop_browser() async with StreamSession( proxy=proxy, impersonate="chrome", diff --git a/g4f/Provider/__init__.py b/g4f/Provider/__init__.py index 0e0b2f4d..55ee07e0 100644 --- a/g4f/Provider/__init__.py +++ b/g4f/Provider/__init__.py @@ -9,6 +9,7 @@ from .deprecated import * from .needs_auth import * from .not_working import * from .local import * +from .hf import HuggingFace, HuggingChat, HuggingFaceAPI, HuggingFaceInference from .hf_space import HuggingSpace from .mini_max import HailuoAI, MiniMax from .template import OpenaiTemplate, BackendApi diff --git a/g4f/Provider/hf/HuggingChat.py b/g4f/Provider/hf/HuggingChat.py new file mode 100644 index 00000000..29f5b9c6 --- /dev/null +++ b/g4f/Provider/hf/HuggingChat.py @@ -0,0 +1,226 @@ +from __future__ import annotations + +import json +import re +import os +import requests +import base64 +from typing import AsyncIterator + +try: + from curl_cffi.requests import Session, CurlMime + has_curl_cffi = True +except ImportError: + has_curl_cffi = False + +from ..base_provider import ProviderModelMixin, AsyncAuthedProvider, AuthResult +from ..helper import format_prompt, format_image_prompt, get_last_user_message +from ...typing import AsyncResult, Messages, Cookies, ImagesType +from ...errors import MissingRequirementsError, MissingAuthError, ResponseError +from ...image import to_bytes +from ...requests import get_args_from_nodriver, DEFAULT_HEADERS +from ...requests.raise_for_status import raise_for_status +from ...providers.response import JsonConversation, ImageResponse, Sources, TitleGeneration, Reasoning, RequestLogin +from ...cookies import get_cookies +from .models import default_model, fallback_models, image_models, model_aliases +from ... import debug + +class Conversation(JsonConversation): + def __init__(self, models: dict): + self.models: dict = models + +class HuggingChat(AsyncAuthedProvider, ProviderModelMixin): + url = "https://huggingface.co/chat" + + working = True + use_nodriver = True + supports_stream = True + needs_auth = True + default_model = default_model + model_aliases = model_aliases + image_models = image_models + + @classmethod + def get_models(cls): + if not cls.models: + try: + text = requests.get(cls.url).text + text = re.sub(r',parameters:{[^}]+?}', '', text) + text = re.search(r'models:(\[.+?\]),oldModels:', text).group(1) + text = text.replace('void 0', 'null') + def add_quotation_mark(match): + return f'{match.group(1)}"{match.group(2)}":' + text = re.sub(r'([{,])([A-Za-z0-9_]+?):', add_quotation_mark, text) + models = json.loads(text) + cls.text_models = [model["id"] for model in models] + cls.models = cls.text_models + cls.image_models + cls.vision_models = [model["id"] for model in models if model["multimodal"]] + except Exception as e: + debug.log(f"HuggingChat: Error reading models: {type(e).__name__}: {e}") + cls.models = [*fallback_models] + return cls.models + + @classmethod + async def on_auth_async(cls, cookies: Cookies = None, proxy: str = None, **kwargs) -> AsyncIterator: + if cookies is None: + cookies = get_cookies("huggingface.co", single_browser=True) + if "hf-chat" in cookies: + yield AuthResult( + cookies=cookies, + impersonate="chrome", + headers=DEFAULT_HEADERS + ) + return + yield RequestLogin(cls.__name__, os.environ.get("G4F_LOGIN_URL") or "") + yield AuthResult( + **await get_args_from_nodriver( + cls.url, + proxy=proxy, + wait_for='form[action="/chat/logout"]' + ) + ) + + @classmethod + async def create_authed( + cls, + model: str, + messages: Messages, + auth_result: AuthResult, + prompt: str = None, + images: ImagesType = None, + return_conversation: bool = False, + conversation: Conversation = None, + web_search: bool = False, + **kwargs + ) -> AsyncResult: + if not has_curl_cffi: + raise MissingRequirementsError('Install "curl_cffi" package | pip install -U curl_cffi') + model = cls.get_model(model) + + session = Session(**auth_result.get_dict()) + + if conversation is None or not hasattr(conversation, "models"): + conversation = Conversation({}) + + if model not in conversation.models: + conversationId = cls.create_conversation(session, model) + debug.log(f"Conversation created: {json.dumps(conversationId[8:] + '...')}") + messageId = cls.fetch_message_id(session, conversationId) + conversation.models[model] = {"conversationId": conversationId, "messageId": messageId} + if return_conversation: + yield conversation + inputs = format_prompt(messages) + else: + conversationId = conversation.models[model]["conversationId"] + conversation.models[model]["messageId"] = cls.fetch_message_id(session, conversationId) + inputs = get_last_user_message(messages) + + settings = { + "inputs": inputs, + "id": conversation.models[model]["messageId"], + "is_retry": False, + "is_continue": False, + "web_search": web_search, + "tools": ["000000000000000000000001"] if model in cls.image_models else [], + } + + headers = { + 'accept': '*/*', + 'origin': 'https://huggingface.co', + 'referer': f'https://huggingface.co/chat/conversation/{conversationId}', + } + data = CurlMime() + data.addpart('data', data=json.dumps(settings, separators=(',', ':'))) + if images is not None: + for image, filename in images: + data.addpart( + "files", + filename=f"base64;{filename}", + data=base64.b64encode(to_bytes(image)) + ) + + response = session.post( + f'https://huggingface.co/chat/conversation/{conversationId}', + headers=headers, + multipart=data, + stream=True + ) + raise_for_status(response) + + sources = None + for line in response.iter_lines(): + if not line: + continue + try: + line = json.loads(line) + except json.JSONDecodeError as e: + debug.log(f"Failed to decode JSON: {line}, error: {e}") + continue + if "type" not in line: + raise RuntimeError(f"Response: {line}") + elif line["type"] == "stream": + yield line["token"].replace('\u0000', '') + elif line["type"] == "finalAnswer": + break + elif line["type"] == "file": + url = f"https://huggingface.co/chat/conversation/{conversationId}/output/{line['sha']}" + yield ImageResponse(url, format_image_prompt(messages, prompt), options={"cookies": auth_result.cookies}) + elif line["type"] == "webSearch" and "sources" in line: + sources = Sources(line["sources"]) + elif line["type"] == "title": + yield TitleGeneration(line["title"]) + elif line["type"] == "reasoning": + yield Reasoning(line.get("token"), line.get("status")) + + if sources is not None: + yield sources + + @classmethod + def create_conversation(cls, session: Session, model: str): + if model in cls.image_models: + model = cls.default_model + json_data = { + 'model': model, + } + response = session.post('https://huggingface.co/chat/conversation', json=json_data) + if response.status_code == 401: + raise MissingAuthError(response.text) + if response.status_code == 400: + raise ResponseError(f"{response.text}: Model: {model}") + raise_for_status(response) + return response.json().get('conversationId') + + @classmethod + def fetch_message_id(cls, session: Session, conversation_id: str): + # Get the data response and parse it properly + response = session.get(f'https://huggingface.co/chat/conversation/{conversation_id}/__data.json?x-sveltekit-invalidated=11') + raise_for_status(response) + + # Split the response content by newlines and parse each line as JSON + try: + json_data = None + for line in response.text.split('\n'): + if line.strip(): + try: + parsed = json.loads(line) + if isinstance(parsed, dict) and "nodes" in parsed: + json_data = parsed + break + except json.JSONDecodeError: + continue + + if not json_data: + raise RuntimeError("Failed to parse response data") + + if json_data["nodes"][-1]["type"] == "error": + if json_data["nodes"][-1]["status"] == 403: + raise MissingAuthError(json_data["nodes"][-1]["error"]["message"]) + raise ResponseError(json.dumps(json_data["nodes"][-1])) + + data = json_data["nodes"][1]["data"] + keys = data[data[0]["messages"]] + message_keys = data[keys[-1]] + return data[message_keys["id"]] + + except (KeyError, IndexError, TypeError) as e: + raise RuntimeError(f"Failed to extract message ID: {str(e)}") diff --git a/g4f/Provider/hf/HuggingFaceAPI.py b/g4f/Provider/hf/HuggingFaceAPI.py new file mode 100644 index 00000000..fdbb1f7c --- /dev/null +++ b/g4f/Provider/hf/HuggingFaceAPI.py @@ -0,0 +1,61 @@ +from __future__ import annotations + +from ..template.OpenaiTemplate import OpenaiTemplate +from .models import model_aliases +from ...providers.types import Messages +from .HuggingChat import HuggingChat +from ... import debug + +class HuggingFaceAPI(OpenaiTemplate): + label = "HuggingFace (Inference API)" + parent = "HuggingFace" + url = "https://api-inference.huggingface.com" + api_base = "https://api-inference.huggingface.co/v1" + working = True + needs_auth = True + + default_model = "meta-llama/Llama-3.2-11B-Vision-Instruct" + default_vision_model = default_model + vision_models = [default_vision_model, "Qwen/Qwen2-VL-7B-Instruct"] + model_aliases = model_aliases + + @classmethod + def get_models(cls, **kwargs): + if not cls.models: + HuggingChat.get_models() + cls.models = HuggingChat.text_models.copy() + for model in cls.vision_models: + if model not in cls.models: + cls.models.append(model) + return cls.models + + @classmethod + async def create_async_generator( + cls, + model: str, + messages: Messages, + api_base: str = None, + max_tokens: int = 2048, + max_inputs_lenght: int = 10000, + **kwargs + ): + if api_base is None: + model_name = model + if model in cls.model_aliases: + model_name = cls.model_aliases[model] + api_base = f"https://api-inference.huggingface.co/models/{model_name}/v1" + start = calculate_lenght(messages) + if start > max_inputs_lenght: + if len(messages) > 6: + messages = messages[:3] + messages[-3:] + if calculate_lenght(messages) > max_inputs_lenght: + if len(messages) > 2: + messages = [m for m in messages if m["role"] == "system"] + messages[-1:] + if len(messages) > 1 and calculate_lenght(messages) > max_inputs_lenght: + messages = [messages[-1]] + debug.log(f"Messages trimmed from: {start} to: {calculate_lenght(messages)}") + async for chunk in super().create_async_generator(model, messages, api_base=api_base, max_tokens=max_tokens, **kwargs): + yield chunk + +def calculate_lenght(messages: Messages) -> int: + return sum([len(message["content"]) + 16 for message in messages]) \ No newline at end of file diff --git a/g4f/Provider/hf/HuggingFaceInference.py b/g4f/Provider/hf/HuggingFaceInference.py new file mode 100644 index 00000000..df866ba3 --- /dev/null +++ b/g4f/Provider/hf/HuggingFaceInference.py @@ -0,0 +1,215 @@ +from __future__ import annotations + +import json +import base64 +import random +import requests + +from ...typing import AsyncResult, Messages +from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin, format_prompt +from ...errors import ModelNotFoundError, ModelNotSupportedError, ResponseError +from ...requests import StreamSession, raise_for_status +from ...providers.response import FinishReason +from ...image import ImageResponse +from ..helper import format_image_prompt +from .models import default_model, default_image_model, model_aliases, fallback_models +from ... import debug + +class HuggingFaceInference(AsyncGeneratorProvider, ProviderModelMixin): + url = "https://huggingface.co" + working = True + + default_model = default_model + default_image_model = default_image_model + model_aliases = model_aliases + + @classmethod + def get_models(cls) -> list[str]: + if not cls.models: + models = fallback_models.copy() + url = "https://huggingface.co/api/models?inference=warm&pipeline_tag=text-generation" + extra_models = [model["id"] for model in requests.get(url).json()] + extra_models.sort() + models.extend([model for model in extra_models if model not in models]) + if not cls.image_models: + url = "https://huggingface.co/api/models?pipeline_tag=text-to-image" + cls.image_models = [model["id"] for model in requests.get(url).json() if model["trendingScore"] >= 20] + cls.image_models.sort() + models.extend([model for model in cls.image_models if model not in models]) + cls.models = models + return cls.models + + @classmethod + async def create_async_generator( + cls, + model: str, + messages: Messages, + stream: bool = True, + proxy: str = None, + api_base: str = "https://api-inference.huggingface.co", + api_key: str = None, + max_tokens: int = 1024, + temperature: float = None, + prompt: str = None, + action: str = None, + extra_data: dict = {}, + **kwargs + ) -> AsyncResult: + try: + model = cls.get_model(model) + except ModelNotSupportedError: + pass + headers = { + 'accept': '*/*', + 'accept-language': 'en', + 'cache-control': 'no-cache', + 'origin': 'https://huggingface.co', + 'pragma': 'no-cache', + 'priority': 'u=1, i', + 'referer': 'https://huggingface.co/chat/', + 'sec-ch-ua': '"Not)A;Brand";v="99", "Google Chrome";v="127", "Chromium";v="127"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"macOS"', + 'sec-fetch-dest': 'empty', + 'sec-fetch-mode': 'cors', + 'sec-fetch-site': 'same-origin', + 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36', + } + if api_key is not None: + headers["Authorization"] = f"Bearer {api_key}" + payload = None + if cls.get_models() and model in cls.image_models: + stream = False + prompt = format_image_prompt(messages, prompt) + payload = {"inputs": prompt, "parameters": {"seed": random.randint(0, 2**32), **extra_data}} + else: + params = { + "return_full_text": False, + "max_new_tokens": max_tokens, + "temperature": temperature, + **extra_data + } + do_continue = action == "continue" + async with StreamSession( + headers=headers, + proxy=proxy, + timeout=600 + ) as session: + if payload is None: + async with session.get(f"https://huggingface.co/api/models/{model}") as response: + if response.status == 404: + raise ModelNotSupportedError(f"Model is not supported: {model} in: {cls.__name__}") + await raise_for_status(response) + model_data = await response.json() + model_type = None + if "config" in model_data and "model_type" in model_data["config"]: + model_type = model_data["config"]["model_type"] + debug.log(f"Model type: {model_type}") + inputs = get_inputs(messages, model_data, model_type, do_continue) + debug.log(f"Inputs len: {len(inputs)}") + if len(inputs) > 4096: + if len(messages) > 6: + messages = messages[:3] + messages[-3:] + else: + messages = [m for m in messages if m["role"] == "system"] + [messages[-1]] + inputs = get_inputs(messages, model_data, model_type, do_continue) + debug.log(f"New len: {len(inputs)}") + if model_type == "gpt2" and max_tokens >= 1024: + params["max_new_tokens"] = 512 + payload = {"inputs": inputs, "parameters": params, "stream": stream} + + async with session.post(f"{api_base.rstrip('/')}/models/{model}", json=payload) as response: + if response.status == 404: + raise ModelNotFoundError(f"Model is not supported: {model}") + await raise_for_status(response) + if stream: + first = True + is_special = False + async for line in response.iter_lines(): + if line.startswith(b"data:"): + data = json.loads(line[5:]) + if "error" in data: + raise ResponseError(data["error"]) + if not data["token"]["special"]: + chunk = data["token"]["text"] + if first and not do_continue: + first = False + chunk = chunk.lstrip() + if chunk: + yield chunk + else: + is_special = True + debug.log(f"Special token: {is_special}") + yield FinishReason("stop" if is_special else "length") + else: + if response.headers["content-type"].startswith("image/"): + base64_data = base64.b64encode(b"".join([chunk async for chunk in response.iter_content()])) + url = f"data:{response.headers['content-type']};base64,{base64_data.decode()}" + yield ImageResponse(url, prompt) + else: + yield (await response.json())[0]["generated_text"].strip() + +def format_prompt_mistral(messages: Messages, do_continue: bool = False) -> str: + system_messages = [message["content"] for message in messages if message["role"] == "system"] + question = " ".join([messages[-1]["content"], *system_messages]) + history = "\n".join([ + f"[INST]{messages[idx-1]['content']} [/INST] {message['content']}" + for idx, message in enumerate(messages) + if message["role"] == "assistant" + ]) + if do_continue: + return history[:-len('')] + return f"{history}\n[INST] {question} [/INST]" + +def format_prompt_qwen(messages: Messages, do_continue: bool = False) -> str: + prompt = "".join([ + f"<|im_start|>{message['role']}\n{message['content']}\n<|im_end|>\n" for message in messages + ]) + ("" if do_continue else "<|im_start|>assistant\n") + if do_continue: + return prompt[:-len("\n<|im_end|>\n")] + return prompt + +def format_prompt_qwen2(messages: Messages, do_continue: bool = False) -> str: + prompt = "".join([ + f"\u003C|{message['role'].capitalize()}|\u003E{message['content']}\u003C|end▁of▁sentence|\u003E" for message in messages + ]) + ("" if do_continue else "\u003C|Assistant|\u003E") + if do_continue: + return prompt[:-len("\u003C|Assistant|\u003E")] + return prompt + +def format_prompt_llama(messages: Messages, do_continue: bool = False) -> str: + prompt = "<|begin_of_text|>" + "".join([ + f"<|start_header_id|>{message['role']}<|end_header_id|>\n\n{message['content']}\n<|eot_id|>\n" for message in messages + ]) + ("" if do_continue else "<|start_header_id|>assistant<|end_header_id|>\n\n") + if do_continue: + return prompt[:-len("\n<|eot_id|>\n")] + return prompt + +def format_prompt_custom(messages: Messages, end_token: str = "", do_continue: bool = False) -> str: + prompt = "".join([ + f"<|{message['role']}|>\n{message['content']}{end_token}\n" for message in messages + ]) + ("" if do_continue else "<|assistant|>\n") + if do_continue: + return prompt[:-len(end_token + "\n")] + return prompt + +def get_inputs(messages: Messages, model_data: dict, model_type: str, do_continue: bool = False) -> str: + if model_type in ("gpt2", "gpt_neo", "gemma", "gemma2"): + inputs = format_prompt(messages, do_continue=do_continue) + elif model_type == "mistral" and model_data.get("author") == "mistralai": + inputs = format_prompt_mistral(messages, do_continue) + elif "config" in model_data and "tokenizer_config" in model_data["config"] and "eos_token" in model_data["config"]["tokenizer_config"]: + eos_token = model_data["config"]["tokenizer_config"]["eos_token"] + if eos_token in ("<|endoftext|>", "", ""): + inputs = format_prompt_custom(messages, eos_token, do_continue) + elif eos_token == "<|im_end|>": + inputs = format_prompt_qwen(messages, do_continue) + elif "content" in eos_token and eos_token["content"] == "\u003C|end▁of▁sentence|\u003E": + inputs = format_prompt_qwen2(messages, do_continue) + elif eos_token == "<|eot_id|>": + inputs = format_prompt_llama(messages, do_continue) + else: + inputs = format_prompt(messages, do_continue=do_continue) + else: + inputs = format_prompt(messages, do_continue=do_continue) + return inputs \ No newline at end of file diff --git a/g4f/Provider/hf/__init__.py b/g4f/Provider/hf/__init__.py new file mode 100644 index 00000000..dbc11892 --- /dev/null +++ b/g4f/Provider/hf/__init__.py @@ -0,0 +1,62 @@ +from __future__ import annotations + +import random + +from ...typing import AsyncResult, Messages +from ...providers.response import ImageResponse +from ...errors import ModelNotSupportedError +from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin +from .HuggingChat import HuggingChat +from .HuggingFaceAPI import HuggingFaceAPI +from .HuggingFaceInference import HuggingFaceInference +from ... import debug + +class HuggingFace(AsyncGeneratorProvider, ProviderModelMixin): + url = "https://huggingface.co" + login_url = "https://huggingface.co/settings/tokens" + working = True + supports_message_history = True + + @classmethod + def get_models(cls) -> list[str]: + if not cls.models: + cls.models = HuggingFaceInference.get_models() + cls.image_models = HuggingFaceInference.image_models + return cls.models + + @classmethod + async def create_async_generator( + cls, + model: str, + messages: Messages, + **kwargs + ) -> AsyncResult: + if "api_key" not in kwargs and "images" not in kwargs and random.random() >= 0.5: + try: + is_started = False + async for chunk in HuggingFaceInference.create_async_generator(model, messages, **kwargs): + if isinstance(chunk, (str, ImageResponse)): + is_started = True + yield chunk + if is_started: + return + except Exception as e: + if is_started: + raise e + debug.log(f"Inference failed: {e.__class__.__name__}: {e}") + if not cls.image_models: + cls.get_models() + if model in cls.image_models: + if "api_key" not in kwargs: + async for chunk in HuggingChat.create_async_generator(model, messages, **kwargs): + yield chunk + else: + async for chunk in HuggingFaceInference.create_async_generator(model, messages, **kwargs): + yield chunk + return + try: + async for chunk in HuggingFaceAPI.create_async_generator(model, messages, **kwargs): + yield chunk + except ModelNotSupportedError: + async for chunk in HuggingFaceInference.create_async_generator(model, messages, **kwargs): + yield chunk \ No newline at end of file diff --git a/g4f/Provider/hf/models.py b/g4f/Provider/hf/models.py new file mode 100644 index 00000000..00c652f7 --- /dev/null +++ b/g4f/Provider/hf/models.py @@ -0,0 +1,46 @@ +default_model = "Qwen/Qwen2.5-72B-Instruct" +default_image_model = "black-forest-labs/FLUX.1-dev" +image_models = [ + default_image_model, + "black-forest-labs/FLUX.1-schnell", +] +fallback_models = [ + default_model, + 'meta-llama/Llama-3.3-70B-Instruct', + 'CohereForAI/c4ai-command-r-plus-08-2024', + 'deepseek-ai/DeepSeek-R1-Distill-Qwen-32B', + 'Qwen/QwQ-32B-Preview', + 'nvidia/Llama-3.1-Nemotron-70B-Instruct-HF', + 'Qwen/Qwen2.5-Coder-32B-Instruct', + 'meta-llama/Llama-3.2-11B-Vision-Instruct', + 'mistralai/Mistral-Nemo-Instruct-2407', + 'microsoft/Phi-3.5-mini-instruct', +] + image_models +model_aliases = { + ### Chat ### + "qwen-2.5-72b": "Qwen/Qwen2.5-Coder-32B-Instruct", + "llama-3.3-70b": "meta-llama/Llama-3.3-70B-Instruct", + "command-r-plus": "CohereForAI/c4ai-command-r-plus-08-2024", + "deepseek-r1": "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B", + "qwq-32b": "Qwen/QwQ-32B-Preview", + "nemotron-70b": "nvidia/Llama-3.1-Nemotron-70B-Instruct-HF", + "qwen-2.5-coder-32b": "Qwen/Qwen2.5-Coder-32B-Instruct", + "llama-3.2-11b": "meta-llama/Llama-3.2-11B-Vision-Instruct", + "mistral-nemo": "mistralai/Mistral-Nemo-Instruct-2407", + "phi-3.5-mini": "microsoft/Phi-3.5-mini-instruct", + ### Image ### + "flux": "black-forest-labs/FLUX.1-dev", + "flux-dev": "black-forest-labs/FLUX.1-dev", + "flux-schnell": "black-forest-labs/FLUX.1-schnell", + ### Used in other providers ### + "qwen-2-vl-7b": "Qwen/Qwen2-VL-7B-Instruct", + "gemma-2-27b": "google/gemma-2-27b-it", + "qwen-2-72b": "Qwen/Qwen2-72B-Instruct", + "qvq-72b": "Qwen/QVQ-72B-Preview", + "sd-3.5": "stabilityai/stable-diffusion-3.5-large", +} +extra_models = [ + "meta-llama/Llama-3.2-11B-Vision-Instruct", + "nvidia/Llama-3.1-Nemotron-70B-Instruct-HF", + "NousResearch/Hermes-3-Llama-3.1-8B", +] \ No newline at end of file diff --git a/g4f/Provider/hf_space/BlackForestLabsFlux1Dev.py b/g4f/Provider/hf_space/BlackForestLabsFlux1Dev.py index b4357bff..48add133 100644 --- a/g4f/Provider/hf_space/BlackForestLabsFlux1Dev.py +++ b/g4f/Provider/hf_space/BlackForestLabsFlux1Dev.py @@ -7,6 +7,7 @@ from ...typing import AsyncResult, Messages from ...image import ImageResponse, ImagePreview from ...errors import ResponseError from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin +from ..helper import format_image_prompt class BlackForestLabsFlux1Dev(AsyncGeneratorProvider, ProviderModelMixin): url = "https://black-forest-labs-flux-1-dev.hf.space" @@ -44,7 +45,7 @@ class BlackForestLabsFlux1Dev(AsyncGeneratorProvider, ProviderModelMixin): if api_key is not None: headers["Authorization"] = f"Bearer {api_key}" async with ClientSession(headers=headers) as session: - prompt = messages[-1]["content"] if prompt is None else prompt + prompt = format_image_prompt(messages, prompt) data = { "data": [prompt, seed, randomize_seed, width, height, guidance_scale, num_inference_steps] } diff --git a/g4f/Provider/hf_space/BlackForestLabsFlux1Schnell.py b/g4f/Provider/hf_space/BlackForestLabsFlux1Schnell.py index 11d2ee8e..2a1a2583 100644 --- a/g4f/Provider/hf_space/BlackForestLabsFlux1Schnell.py +++ b/g4f/Provider/hf_space/BlackForestLabsFlux1Schnell.py @@ -8,6 +8,7 @@ from ...image import ImageResponse from ...errors import ResponseError from ...requests.raise_for_status import raise_for_status from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin +from ..helper import format_image_prompt class BlackForestLabsFlux1Schnell(AsyncGeneratorProvider, ProviderModelMixin): url = "https://black-forest-labs-flux-1-schnell.hf.space" @@ -42,7 +43,7 @@ class BlackForestLabsFlux1Schnell(AsyncGeneratorProvider, ProviderModelMixin): height = max(32, height - (height % 8)) if prompt is None: - prompt = messages[-1]["content"] + prompt = format_image_prompt(messages) payload = { "data": [ diff --git a/g4f/Provider/hf_space/CohereForAI.py b/g4f/Provider/hf_space/CohereForAI.py index 8fda88a5..1ecdd8f1 100644 --- a/g4f/Provider/hf_space/CohereForAI.py +++ b/g4f/Provider/hf_space/CohereForAI.py @@ -6,7 +6,7 @@ from aiohttp import ClientSession, FormData from ...typing import AsyncResult, Messages from ...requests import raise_for_status from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin -from ..helper import format_prompt +from ..helper import format_prompt, get_last_user_message from ...providers.response import JsonConversation, TitleGeneration class CohereForAI(AsyncGeneratorProvider, ProviderModelMixin): @@ -58,7 +58,7 @@ class CohereForAI(AsyncGeneratorProvider, ProviderModelMixin): ) as session: system_prompt = "\n".join([message["content"] for message in messages if message["role"] == "system"]) messages = [message for message in messages if message["role"] != "system"] - inputs = format_prompt(messages) if conversation is None else messages[-1]["content"] + inputs = format_prompt(messages) if conversation is None else get_last_user_message(messages) if conversation is None or conversation.model != model or conversation.preprompt != system_prompt: data = {"model": model, "preprompt": system_prompt} async with session.post(cls.conversation_url, json=data, proxy=proxy) as response: @@ -78,7 +78,6 @@ class CohereForAI(AsyncGeneratorProvider, ProviderModelMixin): data = node["data"] message_id = data[data[data[data[0]["messages"]][-1]]["id"]] data = FormData() - inputs = messages[-1]["content"] data.add_field( "data", json.dumps({"inputs": inputs, "id": message_id, "is_retry": False, "is_continue": False, "web_search": False, "tools": []}), diff --git a/g4f/Provider/hf_space/Janus_Pro_7B.py b/g4f/Provider/hf_space/Janus_Pro_7B.py index 43e5f518..92be38a0 100644 --- a/g4f/Provider/hf_space/Janus_Pro_7B.py +++ b/g4f/Provider/hf_space/Janus_Pro_7B.py @@ -8,8 +8,8 @@ import urllib.parse from ...typing import AsyncResult, Messages, Cookies from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin -from ..helper import format_prompt -from ...providers.response import JsonConversation, ImageResponse +from ..helper import format_prompt, format_image_prompt +from ...providers.response import JsonConversation, ImageResponse, Notification from ...requests.aiohttp import StreamSession, StreamResponse from ...requests.raise_for_status import raise_for_status from ...cookies import get_cookies @@ -38,7 +38,7 @@ class Janus_Pro_7B(AsyncGeneratorProvider, ProviderModelMixin): "headers": { "content-type": "application/json", "x-zerogpu-token": conversation.zerogpu_token, - "x-zerogpu-uuid": conversation.uuid, + "x-zerogpu-uuid": conversation.zerogpu_uuid, "referer": cls.referer, }, "json": {"data":[None,prompt,42,0.95,0.1],"event_data":None,"fn_index":2,"trigger_id":10,"session_hash":conversation.session_hash}, @@ -48,7 +48,7 @@ class Janus_Pro_7B(AsyncGeneratorProvider, ProviderModelMixin): "headers": { "content-type": "application/json", "x-zerogpu-token": conversation.zerogpu_token, - "x-zerogpu-uuid": conversation.uuid, + "x-zerogpu-uuid": conversation.zerogpu_uuid, "referer": cls.referer, }, "json": {"data":[prompt,1234,5,1],"event_data":None,"fn_index":3,"trigger_id":20,"session_hash":conversation.session_hash}, @@ -82,33 +82,14 @@ class Janus_Pro_7B(AsyncGeneratorProvider, ProviderModelMixin): method = "image" prompt = format_prompt(messages) if prompt is None and conversation is None else prompt - prompt = messages[-1]["content"] if prompt is None else prompt + prompt = format_image_prompt(messages, prompt) session_hash = generate_session_hash() if conversation is None else getattr(conversation, "session_hash") async with StreamSession(proxy=proxy, impersonate="chrome") as session: session_hash = generate_session_hash() if conversation is None else getattr(conversation, "session_hash") - user_uuid = None if conversation is None else getattr(conversation, "user_uuid", None) - zerogpu_token = "[object Object]" - - cookies = get_cookies("huggingface.co", raise_requirements_error=False) if cookies is None else cookies - if cookies: - # Get current UTC time + 10 minutes - dt = (datetime.now(timezone.utc) + timedelta(minutes=10)).isoformat(timespec='milliseconds') - encoded_dt = urllib.parse.quote(dt) - async with session.get(f"https://huggingface.co/api/spaces/deepseek-ai/Janus-Pro-7B/jwt?expiration={encoded_dt}&include_pro_status=true", cookies=cookies) as response: - zerogpu_token = (await response.json()) - zerogpu_token = zerogpu_token["token"] - if user_uuid is None: - async with session.get(cls.url, cookies=cookies) as response: - match = re.search(r""token":"([^&]+?)"", await response.text()) - if match: - zerogpu_token = match.group(1) - match = re.search(r""sessionUuid":"([^&]+?)"", await response.text()) - if match: - user_uuid = match.group(1) - + zerogpu_uuid, zerogpu_token = await get_zerogpu_token(session, conversation, cookies) if conversation is None or not hasattr(conversation, "session_hash"): - conversation = JsonConversation(session_hash=session_hash, zerogpu_token=zerogpu_token, uuid=user_uuid) + conversation = JsonConversation(session_hash=session_hash, zerogpu_token=zerogpu_token, zerogpu_uuid=zerogpu_uuid) conversation.zerogpu_token = zerogpu_token if return_conversation: yield conversation @@ -124,7 +105,7 @@ class Janus_Pro_7B(AsyncGeneratorProvider, ProviderModelMixin): try: json_data = json.loads(decoded_line[6:]) if json_data.get('msg') == 'log': - debug.log(json_data["log"]) + yield Notification(json_data["log"]) if json_data.get('msg') == 'process_generating': if 'output' in json_data and 'data' in json_data['output']: @@ -141,4 +122,27 @@ class Janus_Pro_7B(AsyncGeneratorProvider, ProviderModelMixin): break except json.JSONDecodeError: - debug.log("Could not parse JSON:", decoded_line) \ No newline at end of file + debug.log("Could not parse JSON:", decoded_line) + +async def get_zerogpu_token(session: StreamSession, conversation: JsonConversation, cookies: Cookies = None): + zerogpu_uuid = None if conversation is None else getattr(conversation, "zerogpu_uuid", None) + zerogpu_token = "[object Object]" + + cookies = get_cookies("huggingface.co", raise_requirements_error=False) if cookies is None else cookies + if zerogpu_uuid is None: + async with session.get(Janus_Pro_7B.url, cookies=cookies) as response: + match = re.search(r""token":"([^&]+?)"", await response.text()) + if match: + zerogpu_token = match.group(1) + match = re.search(r""sessionUuid":"([^&]+?)"", await response.text()) + if match: + zerogpu_uuid = match.group(1) + if cookies: + # Get current UTC time + 10 minutes + dt = (datetime.now(timezone.utc) + timedelta(minutes=10)).isoformat(timespec='milliseconds') + encoded_dt = urllib.parse.quote(dt) + async with session.get(f"https://huggingface.co/api/spaces/deepseek-ai/Janus-Pro-7B/jwt?expiration={encoded_dt}&include_pro_status=true", cookies=cookies) as response: + zerogpu_token = (await response.json()) + zerogpu_token = zerogpu_token["token"] + + return zerogpu_uuid, zerogpu_token \ No newline at end of file diff --git a/g4f/Provider/hf_space/Qwen_Qwen_2_5M_Demo.py b/g4f/Provider/hf_space/Qwen_Qwen_2_5M_Demo.py index 66a46f7a..f1a5091e 100644 --- a/g4f/Provider/hf_space/Qwen_Qwen_2_5M_Demo.py +++ b/g4f/Provider/hf_space/Qwen_Qwen_2_5M_Demo.py @@ -8,6 +8,7 @@ from ...typing import AsyncResult, Messages from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin from ..helper import format_prompt from ...providers.response import JsonConversation, Reasoning +from ..helper import get_last_user_message from ... import debug class Qwen_Qwen_2_5M_Demo(AsyncGeneratorProvider, ProviderModelMixin): @@ -41,7 +42,7 @@ class Qwen_Qwen_2_5M_Demo(AsyncGeneratorProvider, ProviderModelMixin): if return_conversation: yield JsonConversation(session_hash=session_hash) - prompt = format_prompt(messages) if conversation is None else messages[-1]["content"] + prompt = format_prompt(messages) if conversation is None else get_last_user_message(messages) headers = { 'accept': '*/*', diff --git a/g4f/Provider/hf_space/StableDiffusion35Large.py b/g4f/Provider/hf_space/StableDiffusion35Large.py index e047181d..82d349d8 100644 --- a/g4f/Provider/hf_space/StableDiffusion35Large.py +++ b/g4f/Provider/hf_space/StableDiffusion35Large.py @@ -7,6 +7,7 @@ from ...typing import AsyncResult, Messages from ...image import ImageResponse, ImagePreview from ...errors import ResponseError from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin +from ..helper import format_image_prompt class StableDiffusion35Large(AsyncGeneratorProvider, ProviderModelMixin): url = "https://stabilityai-stable-diffusion-3-5-large.hf.space" @@ -42,7 +43,7 @@ class StableDiffusion35Large(AsyncGeneratorProvider, ProviderModelMixin): if api_key is not None: headers["Authorization"] = f"Bearer {api_key}" async with ClientSession(headers=headers) as session: - prompt = messages[-1]["content"] if prompt is None else prompt + prompt = format_image_prompt(messages, prompt) data = { "data": [prompt, negative_prompt, seed, randomize_seed, width, height, guidance_scale, num_inference_steps] } diff --git a/g4f/Provider/hf_space/VoodoohopFlux1Schnell.py b/g4f/Provider/hf_space/VoodoohopFlux1Schnell.py index d4a052f5..3496f718 100644 --- a/g4f/Provider/hf_space/VoodoohopFlux1Schnell.py +++ b/g4f/Provider/hf_space/VoodoohopFlux1Schnell.py @@ -7,6 +7,7 @@ from ...typing import AsyncResult, Messages from ...image import ImageResponse from ...errors import ResponseError from ...requests.raise_for_status import raise_for_status +from ..helper import format_image_prompt from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin class VoodoohopFlux1Schnell(AsyncGeneratorProvider, ProviderModelMixin): @@ -37,10 +38,7 @@ class VoodoohopFlux1Schnell(AsyncGeneratorProvider, ProviderModelMixin): ) -> AsyncResult: width = max(32, width - (width % 8)) height = max(32, height - (height % 8)) - - if prompt is None: - prompt = messages[-1]["content"] - + prompt = format_image_prompt(messages, prompt) payload = { "data": [ prompt, diff --git a/g4f/Provider/mini_max/HailuoAI.py b/g4f/Provider/mini_max/HailuoAI.py index d8c145a1..9b5a8929 100644 --- a/g4f/Provider/mini_max/HailuoAI.py +++ b/g4f/Provider/mini_max/HailuoAI.py @@ -10,6 +10,7 @@ from ..base_provider import AsyncAuthedProvider, ProviderModelMixin, format_prom from ..mini_max.crypt import CallbackResults, get_browser_callback, generate_yy_header, get_body_to_yy from ...requests import get_args_from_nodriver, raise_for_status from ...providers.response import AuthResult, JsonConversation, RequestLogin, TitleGeneration +from ..helper import get_last_user_message from ... import debug class Conversation(JsonConversation): @@ -62,7 +63,7 @@ class HailuoAI(AsyncAuthedProvider, ProviderModelMixin): conversation = None form_data = { "characterID": 1 if conversation is None else getattr(conversation, "characterID", 1), - "msgContent": format_prompt(messages) if conversation is None else messages[-1]["content"], + "msgContent": format_prompt(messages) if conversation is None else get_last_user_message(messages), "chatID": 0 if conversation is None else getattr(conversation, "chatID", 0), "searchMode": 0 } diff --git a/g4f/Provider/needs_auth/Anthropic.py b/g4f/Provider/needs_auth/Anthropic.py index cdc8f680..6e356c9e 100644 --- a/g4f/Provider/needs_auth/Anthropic.py +++ b/g4f/Provider/needs_auth/Anthropic.py @@ -98,7 +98,7 @@ class Anthropic(OpenaiAPI): "text": messages[-1]["content"] } ] - system = "\n".join([message for message in messages if message.get("role") == "system"]) + system = "\n".join([message["content"] for message in messages if message.get("role") == "system"]) if system: messages = [message for message in messages if message.get("role") != "system"] else: diff --git a/g4f/Provider/needs_auth/BingCreateImages.py b/g4f/Provider/needs_auth/BingCreateImages.py index c2d403d7..b198d201 100644 --- a/g4f/Provider/needs_auth/BingCreateImages.py +++ b/g4f/Provider/needs_auth/BingCreateImages.py @@ -6,6 +6,7 @@ from ...errors import MissingAuthError from ...typing import AsyncResult, Messages, Cookies from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin from ..bing.create_images import create_images, create_session +from ..helper import format_image_prompt class BingCreateImages(AsyncGeneratorProvider, ProviderModelMixin): label = "Microsoft Designer in Bing" @@ -35,7 +36,7 @@ class BingCreateImages(AsyncGeneratorProvider, ProviderModelMixin): **kwargs ) -> AsyncResult: session = BingCreateImages(cookies, proxy, api_key) - yield await session.generate(messages[-1]["content"] if prompt is None else prompt) + yield await session.generate(format_image_prompt(messages, prompt)) async def generate(self, prompt: str) -> ImageResponse: """ diff --git a/g4f/Provider/needs_auth/DeepInfra.py b/g4f/Provider/needs_auth/DeepInfra.py index a5f6350a..b1d7c1d2 100644 --- a/g4f/Provider/needs_auth/DeepInfra.py +++ b/g4f/Provider/needs_auth/DeepInfra.py @@ -5,6 +5,7 @@ from ...typing import AsyncResult, Messages from ...requests import StreamSession, raise_for_status from ...image import ImageResponse from ..template import OpenaiTemplate +from ..helper import format_image_prompt class DeepInfra(OpenaiTemplate): url = "https://deepinfra.com" @@ -55,7 +56,7 @@ class DeepInfra(OpenaiTemplate): ) -> AsyncResult: if model in cls.get_image_models(): yield cls.create_async_image( - messages[-1]["content"] if prompt is None else prompt, + format_image_prompt(messages, prompt), model, **kwargs ) diff --git a/g4f/Provider/needs_auth/DeepSeekAPI.py b/g4f/Provider/needs_auth/DeepSeekAPI.py index ad7f32fe..365ac92e 100644 --- a/g4f/Provider/needs_auth/DeepSeekAPI.py +++ b/g4f/Provider/needs_auth/DeepSeekAPI.py @@ -31,7 +31,7 @@ try: challenge = self._get_pow_challenge() pow_response = self.pow_solver.solve_challenge(challenge) headers = self._get_headers(pow_response) - + response = requests.request( method=method, url=url, diff --git a/g4f/Provider/needs_auth/Gemini.py b/g4f/Provider/needs_auth/Gemini.py index e6d71255..8c555e85 100644 --- a/g4f/Provider/needs_auth/Gemini.py +++ b/g4f/Provider/needs_auth/Gemini.py @@ -25,6 +25,7 @@ from ...requests.aiohttp import get_connector from ...requests import get_nodriver from ...errors import MissingAuthError from ...image import ImageResponse, to_bytes +from ..helper import get_last_user_message from ... import debug REQUEST_HEADERS = { @@ -78,7 +79,7 @@ class Gemini(AsyncGeneratorProvider, ProviderModelMixin): if debug.logging: print("Skip nodriver login in Gemini provider") return - browser = await get_nodriver(proxy=proxy, user_data_dir="gemini") + browser, stop_browser = await get_nodriver(proxy=proxy, user_data_dir="gemini") try: login_url = os.environ.get("G4F_LOGIN_URL") if login_url: @@ -91,7 +92,7 @@ class Gemini(AsyncGeneratorProvider, ProviderModelMixin): await page.close() cls._cookies = cookies finally: - browser.stop() + stop_browser() @classmethod async def create_async_generator( @@ -107,7 +108,7 @@ class Gemini(AsyncGeneratorProvider, ProviderModelMixin): language: str = "en", **kwargs ) -> AsyncResult: - prompt = format_prompt(messages) if conversation is None else messages[-1]["content"] + prompt = format_prompt(messages) if conversation is None else get_last_user_message(messages) cls._cookies = cookies or cls._cookies or get_cookies(".google.com", False, True) base_connector = get_connector(connector, proxy) diff --git a/g4f/Provider/needs_auth/GithubCopilot.py b/g4f/Provider/needs_auth/GithubCopilot.py index 1deca50c..4b9db8e9 100644 --- a/g4f/Provider/needs_auth/GithubCopilot.py +++ b/g4f/Provider/needs_auth/GithubCopilot.py @@ -7,7 +7,7 @@ from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin, BaseConv from ...typing import AsyncResult, Messages, Cookies from ...requests.raise_for_status import raise_for_status from ...requests.aiohttp import get_connector -from ...providers.helper import format_prompt +from ...providers.helper import format_prompt, get_last_user_message from ...cookies import get_cookies class Conversation(BaseConversation): @@ -78,7 +78,7 @@ class GithubCopilot(AsyncGeneratorProvider, ProviderModelMixin): conversation_id = (await response.json()).get("thread_id") if return_conversation: yield Conversation(conversation_id) - content = messages[-1]["content"] + content = get_last_user_message(messages) else: content = format_prompt(messages) json_data = { diff --git a/g4f/Provider/needs_auth/HuggingChat.py b/g4f/Provider/needs_auth/HuggingChat.py deleted file mode 100644 index 316a8317..00000000 --- a/g4f/Provider/needs_auth/HuggingChat.py +++ /dev/null @@ -1,265 +0,0 @@ -from __future__ import annotations - -import json -import re -import os -import requests -import base64 -from typing import AsyncIterator - -try: - from curl_cffi.requests import Session, CurlMime - has_curl_cffi = True -except ImportError: - has_curl_cffi = False - -from ..base_provider import ProviderModelMixin, AsyncAuthedProvider, AuthResult -from ..helper import format_prompt -from ...typing import AsyncResult, Messages, Cookies, ImagesType -from ...errors import MissingRequirementsError, MissingAuthError, ResponseError -from ...image import to_bytes -from ...requests import get_args_from_nodriver, DEFAULT_HEADERS -from ...requests.raise_for_status import raise_for_status -from ...providers.response import JsonConversation, ImageResponse, Sources, TitleGeneration, Reasoning, RequestLogin -from ...cookies import get_cookies -from ... import debug - -class Conversation(JsonConversation): - def __init__(self, models: dict): - self.models: dict = models - -class HuggingChat(AsyncAuthedProvider, ProviderModelMixin): - url = "https://huggingface.co/chat" - - working = True - use_nodriver = True - supports_stream = True - needs_auth = True - - default_model = "Qwen/Qwen2.5-72B-Instruct" - default_image_model = "black-forest-labs/FLUX.1-dev" - image_models = [ - default_image_model, - "black-forest-labs/FLUX.1-schnell", - ] - fallback_models = [ - default_model, - 'meta-llama/Llama-3.3-70B-Instruct', - 'CohereForAI/c4ai-command-r-plus-08-2024', - 'deepseek-ai/DeepSeek-R1-Distill-Qwen-32B', - 'Qwen/QwQ-32B-Preview', - 'nvidia/Llama-3.1-Nemotron-70B-Instruct-HF', - 'Qwen/Qwen2.5-Coder-32B-Instruct', - 'meta-llama/Llama-3.2-11B-Vision-Instruct', - 'mistralai/Mistral-Nemo-Instruct-2407', - 'microsoft/Phi-3.5-mini-instruct', - ] + image_models - model_aliases = { - ### Chat ### - "qwen-2.5-72b": "Qwen/Qwen2.5-Coder-32B-Instruct", - "llama-3.3-70b": "meta-llama/Llama-3.3-70B-Instruct", - "command-r-plus": "CohereForAI/c4ai-command-r-plus-08-2024", - "deepseek-r1": "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B", - "qwq-32b": "Qwen/QwQ-32B-Preview", - "nemotron-70b": "nvidia/Llama-3.1-Nemotron-70B-Instruct-HF", - "qwen-2.5-coder-32b": "Qwen/Qwen2.5-Coder-32B-Instruct", - "llama-3.2-11b": "meta-llama/Llama-3.2-11B-Vision-Instruct", - "mistral-nemo": "mistralai/Mistral-Nemo-Instruct-2407", - "phi-3.5-mini": "microsoft/Phi-3.5-mini-instruct", - ### Image ### - "flux-dev": "black-forest-labs/FLUX.1-dev", - "flux-schnell": "black-forest-labs/FLUX.1-schnell", - ### Used in other providers ### - "qwen-2-vl-7b": "Qwen/Qwen2-VL-7B-Instruct", - "gemma-2-27b": "google/gemma-2-27b-it", - "qwen-2-72b": "Qwen/Qwen2-72B-Instruct", - "qvq-72b": "Qwen/QVQ-72B-Preview", - "sd-3.5": "stabilityai/stable-diffusion-3.5-large", - } - - @classmethod - def get_models(cls): - if not cls.models: - try: - text = requests.get(cls.url).text - text = re.sub(r',parameters:{[^}]+?}', '', text) - text = re.search(r'models:(\[.+?\]),oldModels:', text).group(1) - text = text.replace('void 0', 'null') - def add_quotation_mark(match): - return f'{match.group(1)}"{match.group(2)}":' - text = re.sub(r'([{,])([A-Za-z0-9_]+?):', add_quotation_mark, text) - models = json.loads(text) - cls.text_models = [model["id"] for model in models] - cls.models = cls.text_models + cls.image_models - cls.vision_models = [model["id"] for model in models if model["multimodal"]] - except Exception as e: - debug.log(f"HuggingChat: Error reading models: {type(e).__name__}: {e}") - cls.models = [*cls.fallback_models] - return cls.models - - @classmethod - async def on_auth_async(cls, cookies: Cookies = None, proxy: str = None, **kwargs) -> AsyncIterator: - if cookies is None: - cookies = get_cookies("huggingface.co", single_browser=True) - if "hf-chat" in cookies: - yield AuthResult( - cookies=cookies, - impersonate="chrome", - headers=DEFAULT_HEADERS - ) - return - login_url = os.environ.get("G4F_LOGIN_URL") - if login_url: - yield RequestLogin(cls.__name__, login_url) - yield AuthResult( - **await get_args_from_nodriver( - cls.url, - proxy=proxy, - wait_for='form[action="/chat/logout"]' - ) - ) - - @classmethod - async def create_authed( - cls, - model: str, - messages: Messages, - auth_result: AuthResult, - prompt: str = None, - images: ImagesType = None, - return_conversation: bool = False, - conversation: Conversation = None, - web_search: bool = False, - **kwargs - ) -> AsyncResult: - if not has_curl_cffi: - raise MissingRequirementsError('Install "curl_cffi" package | pip install -U curl_cffi') - model = cls.get_model(model) - - session = Session(**auth_result.get_dict()) - - if conversation is None or not hasattr(conversation, "models"): - conversation = Conversation({}) - - if model not in conversation.models: - conversationId = cls.create_conversation(session, model) - messageId = cls.fetch_message_id(session, conversationId) - conversation.models[model] = {"conversationId": conversationId, "messageId": messageId} - if return_conversation: - yield conversation - inputs = format_prompt(messages) - else: - conversationId = conversation.models[model]["conversationId"] - conversation.models[model]["messageId"] = cls.fetch_message_id(session, conversationId) - inputs = messages[-1]["content"] - - debug.log(f"Use: {json.dumps(conversation.models[model])}") - - settings = { - "inputs": inputs, - "id": conversation.models[model]["messageId"], - "is_retry": False, - "is_continue": False, - "web_search": web_search, - "tools": ["000000000000000000000001"] if model in cls.image_models else [], - } - - headers = { - 'accept': '*/*', - 'origin': 'https://huggingface.co', - 'referer': f'https://huggingface.co/chat/conversation/{conversationId}', - } - data = CurlMime() - data.addpart('data', data=json.dumps(settings, separators=(',', ':'))) - if images is not None: - for image, filename in images: - data.addpart( - "files", - filename=f"base64;{filename}", - data=base64.b64encode(to_bytes(image)) - ) - - response = session.post( - f'https://huggingface.co/chat/conversation/{conversationId}', - headers=headers, - multipart=data, - stream=True - ) - raise_for_status(response) - - sources = None - for line in response.iter_lines(): - if not line: - continue - try: - line = json.loads(line) - except json.JSONDecodeError as e: - debug.log(f"Failed to decode JSON: {line}, error: {e}") - continue - if "type" not in line: - raise RuntimeError(f"Response: {line}") - elif line["type"] == "stream": - yield line["token"].replace('\u0000', '') - elif line["type"] == "finalAnswer": - break - elif line["type"] == "file": - url = f"https://huggingface.co/chat/conversation/{conversationId}/output/{line['sha']}" - prompt = messages[-1]["content"] if prompt is None else prompt - yield ImageResponse(url, alt=prompt, options={"cookies": auth_result.cookies}) - elif line["type"] == "webSearch" and "sources" in line: - sources = Sources(line["sources"]) - elif line["type"] == "title": - yield TitleGeneration(line["title"]) - elif line["type"] == "reasoning": - yield Reasoning(line.get("token"), line.get("status")) - - if sources is not None: - yield sources - - @classmethod - def create_conversation(cls, session: Session, model: str): - if model in cls.image_models: - model = cls.default_model - json_data = { - 'model': model, - } - response = session.post('https://huggingface.co/chat/conversation', json=json_data) - if response.status_code == 401: - raise MissingAuthError(response.text) - raise_for_status(response) - return response.json().get('conversationId') - - @classmethod - def fetch_message_id(cls, session: Session, conversation_id: str): - # Get the data response and parse it properly - response = session.get(f'https://huggingface.co/chat/conversation/{conversation_id}/__data.json?x-sveltekit-invalidated=11') - raise_for_status(response) - - # Split the response content by newlines and parse each line as JSON - try: - json_data = None - for line in response.text.split('\n'): - if line.strip(): - try: - parsed = json.loads(line) - if isinstance(parsed, dict) and "nodes" in parsed: - json_data = parsed - break - except json.JSONDecodeError: - continue - - if not json_data: - raise RuntimeError("Failed to parse response data") - - if json_data["nodes"][-1]["type"] == "error": - if json_data["nodes"][-1]["status"] == 403: - raise MissingAuthError(json_data["nodes"][-1]["error"]["message"]) - raise ResponseError(json.dumps(json_data["nodes"][-1])) - - data = json_data["nodes"][1]["data"] - keys = data[data[0]["messages"]] - message_keys = data[keys[-1]] - return data[message_keys["id"]] - - except (KeyError, IndexError, TypeError) as e: - raise RuntimeError(f"Failed to extract message ID: {str(e)}") diff --git a/g4f/Provider/needs_auth/HuggingFace.py b/g4f/Provider/needs_auth/HuggingFace.py deleted file mode 100644 index 07e110b3..00000000 --- a/g4f/Provider/needs_auth/HuggingFace.py +++ /dev/null @@ -1,220 +0,0 @@ -from __future__ import annotations - -import json -import base64 -import random -import requests - -from ...typing import AsyncResult, Messages -from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin, format_prompt -from ...errors import ModelNotFoundError, ModelNotSupportedError, ResponseError -from ...requests import StreamSession, raise_for_status -from ...providers.response import FinishReason -from ...image import ImageResponse -from ... import debug - -from .HuggingChat import HuggingChat - -class HuggingFace(AsyncGeneratorProvider, ProviderModelMixin): - url = "https://huggingface.co" - login_url = "https://huggingface.co/settings/tokens" - working = True - supports_message_history = True - default_model = HuggingChat.default_model - default_image_model = HuggingChat.default_image_model - model_aliases = HuggingChat.model_aliases - extra_models = [ - "meta-llama/Llama-3.2-11B-Vision-Instruct", - "nvidia/Llama-3.1-Nemotron-70B-Instruct-HF", - "NousResearch/Hermes-3-Llama-3.1-8B", - ] - - @classmethod - def get_models(cls) -> list[str]: - if not cls.models: - url = "https://huggingface.co/api/models?inference=warm&pipeline_tag=text-generation" - models = [model["id"] for model in requests.get(url).json()] - models.extend(cls.extra_models) - models.sort() - if not cls.image_models: - url = "https://huggingface.co/api/models?pipeline_tag=text-to-image" - cls.image_models = [model["id"] for model in requests.get(url).json() if model["trendingScore"] >= 20] - cls.image_models.sort() - models.extend(cls.image_models) - cls.models = list(set(models)) - return cls.models - - @classmethod - async def create_async_generator( - cls, - model: str, - messages: Messages, - stream: bool = True, - proxy: str = None, - api_base: str = "https://api-inference.huggingface.co", - api_key: str = None, - max_tokens: int = 1024, - temperature: float = None, - prompt: str = None, - action: str = None, - extra_data: dict = {}, - **kwargs - ) -> AsyncResult: - try: - model = cls.get_model(model) - except ModelNotSupportedError: - pass - headers = { - 'accept': '*/*', - 'accept-language': 'en', - 'cache-control': 'no-cache', - 'origin': 'https://huggingface.co', - 'pragma': 'no-cache', - 'priority': 'u=1, i', - 'referer': 'https://huggingface.co/chat/', - 'sec-ch-ua': '"Not)A;Brand";v="99", "Google Chrome";v="127", "Chromium";v="127"', - 'sec-ch-ua-mobile': '?0', - 'sec-ch-ua-platform': '"macOS"', - 'sec-fetch-dest': 'empty', - 'sec-fetch-mode': 'cors', - 'sec-fetch-site': 'same-origin', - 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36', - } - if api_key is not None: - headers["Authorization"] = f"Bearer {api_key}" - payload = None - if cls.get_models() and model in cls.image_models: - stream = False - prompt = messages[-1]["content"] if prompt is None else prompt - payload = {"inputs": prompt, "parameters": {"seed": random.randint(0, 2**32), **extra_data}} - else: - params = { - "return_full_text": False, - "max_new_tokens": max_tokens, - "temperature": temperature, - **extra_data - } - do_continue = action == "continue" - async with StreamSession( - headers=headers, - proxy=proxy, - timeout=600 - ) as session: - if payload is None: - async with session.get(f"https://huggingface.co/api/models/{model}") as response: - if response.status == 404: - raise ModelNotSupportedError(f"Model is not supported: {model} in: {cls.__name__}") - await raise_for_status(response) - model_data = await response.json() - model_type = None - if "config" in model_data and "model_type" in model_data["config"]: - model_type = model_data["config"]["model_type"] - debug.log(f"Model type: {model_type}") - inputs = get_inputs(messages, model_data, model_type, do_continue) - debug.log(f"Inputs len: {len(inputs)}") - if len(inputs) > 4096: - if len(messages) > 6: - messages = messages[:3] + messages[-3:] - else: - messages = [m for m in messages if m["role"] == "system"] + [messages[-1]] - inputs = get_inputs(messages, model_data, model_type, do_continue) - debug.log(f"New len: {len(inputs)}") - if model_type == "gpt2" and max_tokens >= 1024: - params["max_new_tokens"] = 512 - payload = {"inputs": inputs, "parameters": params, "stream": stream} - - async with session.post(f"{api_base.rstrip('/')}/models/{model}", json=payload) as response: - if response.status == 404: - raise ModelNotFoundError(f"Model is not supported: {model}") - await raise_for_status(response) - if stream: - first = True - is_special = False - async for line in response.iter_lines(): - if line.startswith(b"data:"): - data = json.loads(line[5:]) - if "error" in data: - raise ResponseError(data["error"]) - if not data["token"]["special"]: - chunk = data["token"]["text"] - if first and not do_continue: - first = False - chunk = chunk.lstrip() - if chunk: - yield chunk - else: - is_special = True - debug.log(f"Special token: {is_special}") - yield FinishReason("stop" if is_special else "length") - else: - if response.headers["content-type"].startswith("image/"): - base64_data = base64.b64encode(b"".join([chunk async for chunk in response.iter_content()])) - url = f"data:{response.headers['content-type']};base64,{base64_data.decode()}" - yield ImageResponse(url, prompt) - else: - yield (await response.json())[0]["generated_text"].strip() - -def format_prompt_mistral(messages: Messages, do_continue: bool = False) -> str: - system_messages = [message["content"] for message in messages if message["role"] == "system"] - question = " ".join([messages[-1]["content"], *system_messages]) - history = "\n".join([ - f"[INST]{messages[idx-1]['content']} [/INST] {message['content']}" - for idx, message in enumerate(messages) - if message["role"] == "assistant" - ]) - if do_continue: - return history[:-len('')] - return f"{history}\n[INST] {question} [/INST]" - -def format_prompt_qwen(messages: Messages, do_continue: bool = False) -> str: - prompt = "".join([ - f"<|im_start|>{message['role']}\n{message['content']}\n<|im_end|>\n" for message in messages - ]) + ("" if do_continue else "<|im_start|>assistant\n") - if do_continue: - return prompt[:-len("\n<|im_end|>\n")] - return prompt - -def format_prompt_qwen2(messages: Messages, do_continue: bool = False) -> str: - prompt = "".join([ - f"\u003C|{message['role'].capitalize()}|\u003E{message['content']}\u003C|end▁of▁sentence|\u003E" for message in messages - ]) + ("" if do_continue else "\u003C|Assistant|\u003E") - if do_continue: - return prompt[:-len("\u003C|Assistant|\u003E")] - return prompt - -def format_prompt_llama(messages: Messages, do_continue: bool = False) -> str: - prompt = "<|begin_of_text|>" + "".join([ - f"<|start_header_id|>{message['role']}<|end_header_id|>\n\n{message['content']}\n<|eot_id|>\n" for message in messages - ]) + ("" if do_continue else "<|start_header_id|>assistant<|end_header_id|>\n\n") - if do_continue: - return prompt[:-len("\n<|eot_id|>\n")] - return prompt - -def format_prompt_custom(messages: Messages, end_token: str = "", do_continue: bool = False) -> str: - prompt = "".join([ - f"<|{message['role']}|>\n{message['content']}{end_token}\n" for message in messages - ]) + ("" if do_continue else "<|assistant|>\n") - if do_continue: - return prompt[:-len(end_token + "\n")] - return prompt - -def get_inputs(messages: Messages, model_data: dict, model_type: str, do_continue: bool = False) -> str: - if model_type in ("gpt2", "gpt_neo", "gemma", "gemma2"): - inputs = format_prompt(messages, do_continue=do_continue) - elif model_type == "mistral" and model_data.get("author") == "mistralai": - inputs = format_prompt_mistral(messages, do_continue) - elif "config" in model_data and "tokenizer_config" in model_data["config"] and "eos_token" in model_data["config"]["tokenizer_config"]: - eos_token = model_data["config"]["tokenizer_config"]["eos_token"] - if eos_token in ("<|endoftext|>", "", ""): - inputs = format_prompt_custom(messages, eos_token, do_continue) - elif eos_token == "<|im_end|>": - inputs = format_prompt_qwen(messages, do_continue) - elif "content" in eos_token and eos_token["content"] == "\u003C|end▁of▁sentence|\u003E": - inputs = format_prompt_qwen2(messages, do_continue) - elif eos_token == "<|eot_id|>": - inputs = format_prompt_llama(messages, do_continue) - else: - inputs = format_prompt(messages, do_continue=do_continue) - else: - inputs = format_prompt(messages, do_continue=do_continue) - return inputs \ No newline at end of file diff --git a/g4f/Provider/needs_auth/HuggingFaceAPI.py b/g4f/Provider/needs_auth/HuggingFaceAPI.py deleted file mode 100644 index c9a15260..00000000 --- a/g4f/Provider/needs_auth/HuggingFaceAPI.py +++ /dev/null @@ -1,57 +0,0 @@ -from __future__ import annotations - -from ..template import OpenaiTemplate -from .HuggingChat import HuggingChat -from ...providers.types import Messages -from ... import debug - -class HuggingFaceAPI(OpenaiTemplate): - label = "HuggingFace (Inference API)" - parent = "HuggingFace" - url = "https://api-inference.huggingface.com" - api_base = "https://api-inference.huggingface.co/v1" - working = True - needs_auth = True - - default_model = "meta-llama/Llama-3.2-11B-Vision-Instruct" - default_vision_model = default_model - vision_models = [default_vision_model, "Qwen/Qwen2-VL-7B-Instruct"] - model_aliases = HuggingChat.model_aliases - - @classmethod - def get_models(cls, **kwargs): - if not cls.models: - HuggingChat.get_models() - cls.models = list(set(HuggingChat.text_models + cls.vision_models)) - return cls.models - - @classmethod - async def create_async_generator( - cls, - model: str, - messages: Messages, - api_base: str = None, - max_tokens: int = 2048, - max_inputs_lenght: int = 10000, - **kwargs - ): - if api_base is None: - model_name = model - if model in cls.model_aliases: - model_name = cls.model_aliases[model] - api_base = f"https://api-inference.huggingface.co/models/{model_name}/v1" - start = calculate_lenght(messages) - if start > max_inputs_lenght: - if len(messages) > 6: - messages = messages[:3] + messages[-3:] - if calculate_lenght(messages) > max_inputs_lenght: - if len(messages) > 2: - messages = [m for m in messages if m["role"] == "system"] + messages[-1:] - if len(messages) > 1 and calculate_lenght(messages) > max_inputs_lenght: - messages = [messages[-1]] - debug.log(f"Messages trimmed from: {start} to: {calculate_lenght(messages)}") - async for chunk in super().create_async_generator(model, messages, api_base=api_base, max_tokens=max_tokens, **kwargs): - yield chunk - -def calculate_lenght(messages: Messages) -> int: - return sum([len(message["content"]) + 16 for message in messages]) \ No newline at end of file diff --git a/g4f/Provider/needs_auth/MicrosoftDesigner.py b/g4f/Provider/needs_auth/MicrosoftDesigner.py index c413c19b..3ce8e618 100644 --- a/g4f/Provider/needs_auth/MicrosoftDesigner.py +++ b/g4f/Provider/needs_auth/MicrosoftDesigner.py @@ -14,7 +14,7 @@ from ...requests.aiohttp import get_connector from ...requests import get_nodriver from ..Copilot import get_headers, get_har_files from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin -from ..helper import get_random_hex +from ..helper import get_random_hex, format_image_prompt from ... import debug class MicrosoftDesigner(AsyncGeneratorProvider, ProviderModelMixin): @@ -39,7 +39,7 @@ class MicrosoftDesigner(AsyncGeneratorProvider, ProviderModelMixin): image_size = "1024x1024" if model != cls.default_image_model and model in cls.image_models: image_size = model - yield await cls.generate(messages[-1]["content"] if prompt is None else prompt, image_size, proxy) + yield await cls.generate(format_image_prompt(messages, prompt), image_size, proxy) @classmethod async def generate(cls, prompt: str, image_size: str, proxy: str = None) -> ImageResponse: @@ -143,7 +143,7 @@ def readHAR(url: str) -> tuple[str, str]: return api_key, user_agent async def get_access_token_and_user_agent(url: str, proxy: str = None): - browser = await get_nodriver(proxy=proxy, user_data_dir="designer") + browser, stop_browser = await get_nodriver(proxy=proxy, user_data_dir="designer") try: page = await browser.get(url) user_agent = await page.evaluate("navigator.userAgent") @@ -168,4 +168,4 @@ async def get_access_token_and_user_agent(url: str, proxy: str = None): await page.close() return access_token, user_agent finally: - browser.stop() \ No newline at end of file + stop_browser() \ No newline at end of file diff --git a/g4f/Provider/needs_auth/OpenaiChat.py b/g4f/Provider/needs_auth/OpenaiChat.py index adcecb3f..8fde1090 100644 --- a/g4f/Provider/needs_auth/OpenaiChat.py +++ b/g4f/Provider/needs_auth/OpenaiChat.py @@ -98,7 +98,7 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin): default_model = "auto" default_image_model = "dall-e-3" image_models = [default_image_model] - text_models = [default_model, "gpt-4", "gpt-4o", "gpt-4o-mini", "gpt-4o-canmore", "o1", "o1-preview", "o1-mini"] + text_models = [default_model, "gpt-4", "gpt-4o", "gpt-4o-mini", "o1", "o1-preview", "o1-mini"] vision_models = text_models models = text_models + image_models synthesize_content_type = "audio/mpeg" @@ -598,7 +598,7 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin): @classmethod async def nodriver_auth(cls, proxy: str = None): - browser = await get_nodriver(proxy=proxy) + browser, stop_browser = await get_nodriver(proxy=proxy) try: page = browser.main_tab def on_request(event: nodriver.cdp.network.RequestWillBeSent): @@ -648,7 +648,7 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin): cls._create_request_args(RequestConfig.cookies, RequestConfig.headers, user_agent=user_agent) cls._set_api_key(cls._api_key) finally: - browser.stop() + stop_browser() @staticmethod def get_default_headers() -> Dict[str, str]: diff --git a/g4f/Provider/needs_auth/__init__.py b/g4f/Provider/needs_auth/__init__.py index 691bc5be..e00e3b1f 100644 --- a/g4f/Provider/needs_auth/__init__.py +++ b/g4f/Provider/needs_auth/__init__.py @@ -13,9 +13,6 @@ from .GigaChat import GigaChat from .GithubCopilot import GithubCopilot from .GlhfChat import GlhfChat from .Groq import Groq -from .HuggingChat import HuggingChat -from .HuggingFace import HuggingFace -from .HuggingFaceAPI import HuggingFaceAPI from .MetaAI import MetaAI from .MetaAIAccount import MetaAIAccount from .MicrosoftDesigner import MicrosoftDesigner diff --git a/g4f/Provider/template/BackendApi.py b/g4f/Provider/template/BackendApi.py index caf28219..8ef2ee4b 100644 --- a/g4f/Provider/template/BackendApi.py +++ b/g4f/Provider/template/BackendApi.py @@ -8,11 +8,11 @@ from urllib.parse import quote_plus from ...typing import Messages, AsyncResult from ...requests import StreamSession from ...providers.base_provider import AsyncGeneratorProvider, ProviderModelMixin -from ...providers.response import ProviderInfo, JsonConversation, PreviewResponse, SynthesizeData, TitleGeneration, RequestLogin -from ...providers.response import Parameters, FinishReason, Usage, Reasoning +from ...providers.response import * +from ...image import get_image_extension from ...errors import ModelNotSupportedError from ..needs_auth.OpenaiAccount import OpenaiAccount -from ..needs_auth.HuggingChat import HuggingChat +from ..hf.HuggingChat import HuggingChat from ... import debug class BackendApi(AsyncGeneratorProvider, ProviderModelMixin): @@ -98,8 +98,7 @@ class BackendApi(AsyncGeneratorProvider, ProviderModelMixin): yield PreviewResponse(data[data_type]) elif data_type == "content": def on_image(match): - extension = match.group(3).split(".")[-1].split("?")[0] - extension = "" if not extension or len(extension) > 4 else f".{extension}" + extension = get_image_extension(match.group(3)) filename = f"{int(time.time())}_{quote_plus(match.group(1)[:100], '')}{extension}" download_url = f"/download/{filename}?url={cls.url}{match.group(3)}" return f"[![{match.group(1)}]({download_url})](/images/{filename})" @@ -119,6 +118,6 @@ class BackendApi(AsyncGeneratorProvider, ProviderModelMixin): elif data_type == "finish": yield FinishReason(data[data_type]["reason"]) elif data_type == "log": - debug.log(data[data_type]) + yield DebugResponse.from_dict(data[data_type]) else: - debug.log(f"Unknown data: ({data_type}) {data}") \ No newline at end of file + yield DebugResponse.from_dict(data) diff --git a/g4f/Provider/template/OpenaiTemplate.py b/g4f/Provider/template/OpenaiTemplate.py index 62a44c55..3bf8c104 100644 --- a/g4f/Provider/template/OpenaiTemplate.py +++ b/g4f/Provider/template/OpenaiTemplate.py @@ -4,11 +4,11 @@ import json import time import requests -from ..helper import filter_none +from ..helper import filter_none, format_image_prompt from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin, RaiseErrorMixin from ...typing import Union, Optional, AsyncResult, Messages, ImagesType from ...requests import StreamSession, raise_for_status -from ...providers.response import FinishReason, ToolCalls, Usage, Reasoning, ImageResponse +from ...providers.response import FinishReason, ToolCalls, Usage, ImageResponse from ...errors import MissingAuthError, ResponseError from ...image import to_data_uri from ... import debug @@ -82,7 +82,7 @@ class OpenaiTemplate(AsyncGeneratorProvider, ProviderModelMixin, RaiseErrorMixin # Proxy for image generation feature if model and model in cls.image_models: data = { - "prompt": messages[-1]["content"] if prompt is None else prompt, + "prompt": format_image_prompt(messages, prompt), "model": model, } async with session.post(f"{api_base.rstrip('/')}/images/generations", json=data, ssl=cls.ssl) as response: @@ -154,17 +154,7 @@ class OpenaiTemplate(AsyncGeneratorProvider, ProviderModelMixin, RaiseErrorMixin delta = delta.lstrip() if delta: first = False - if is_thinking: - if "" in delta: - yield Reasoning(None, f"Finished in {round(time.time()-is_thinking, 2)} seconds") - is_thinking = 0 - else: - yield Reasoning(delta) - elif "" in delta: - is_thinking = time.time() - yield Reasoning(None, "Is thinking...") - else: - yield delta + yield delta if "usage" in data and data["usage"]: yield Usage(**data["usage"]) if "finish_reason" in choice and choice["finish_reason"] is not None: -- cgit v1.2.3