diff --git a/etc/tool/commit.py b/etc/tool/commit.py index 74241fb2..0107886e 100755 --- a/etc/tool/commit.py +++ b/etc/tool/commit.py @@ -201,7 +201,7 @@ def generate_commit_message(diff_text: str, model: str = DEFAULT_MODEL) -> Optio spinner = None content.append(chunk.choices[0].delta.content) print(chunk.choices[0].delta.content, end="", flush=True) - return "".join(content).strip().strip("`") + return "".join(content).strip("`").strip() except Exception as e: # Stop spinner if it's running if 'spinner' in locals() and spinner: diff --git a/g4f/Provider/PollinationsAI.py b/g4f/Provider/PollinationsAI.py index 8441a12a..b9c9b697 100644 --- a/g4f/Provider/PollinationsAI.py +++ b/g4f/Provider/PollinationsAI.py @@ -59,6 +59,11 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin): "gpt-4o-mini": "openai", "gpt-4": "openai-large", "gpt-4o": "openai-large", + "gpt-4.1": "openai", + "gpt-4.1-nano": "openai", + "gpt-4.1-mini": "openai-large", + "gpt-4.1-xlarge": "openai-xlarge", + "o4-mini": "openai-reasoning", "qwen-2.5-coder-32b": "qwen-coder", "llama-3.3-70b": "llama", "llama-4-scout": "llamascout", @@ -67,10 +72,12 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin): "llama-3.3-70b": "llama-scaleway", "phi-4": "phi", "deepseek-r1": "deepseek-reasoning-large", - "deepseek-r1": "deepseek-reasoning", + "deepseek-r1-distill-llama-70b": "deepseek-reasoning-large", + "deepseek-r1-distill-qwen-32b": "deepseek-reasoning", "deepseek-v3": "deepseek", "llama-3.2-11b": "llama-vision", "gpt-4o-audio": "openai-audio", + "gpt-4o-audio-preview": "openai-audio", ### Image Models ### "sdxl-turbo": "turbo", @@ -331,7 +338,6 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin): "cache": cache, **extra_parameters }) - print(f"Requesting {url} with data: {data}") async with session.post(url, json=data) as response: await raise_for_status(response) if response.headers["content-type"].startswith("text/plain"): diff --git a/g4f/Provider/audio/MarkItDown.py b/g4f/Provider/audio/MarkItDown.py index cb028b4e..b05707d7 100644 --- a/g4f/Provider/audio/MarkItDown.py +++ b/g4f/Provider/audio/MarkItDown.py @@ -1,7 +1,5 @@ from __future__ import annotations -import tempfile -import shutil import os try: @@ -11,6 +9,7 @@ except ImportError: has_markitdown = False from ...typing import AsyncResult, Messages, MediaListType +from ...tools.files import get_tempfile from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin class MarkItDown(AsyncGeneratorProvider, ProviderModelMixin): @@ -26,17 +25,15 @@ class MarkItDown(AsyncGeneratorProvider, ProviderModelMixin): ) -> AsyncResult: md = MaItDo() for file, filename in media: + text = None try: - text = md.convert(file, stream_info=StreamInfo(filename=filename)).text_content + text = md.convert(file, stream_info=StreamInfo(filename=filename) if filename else None).text_content except TypeError: - # Copy SpooledTemporaryFile to a NamedTemporaryFile - copyfile = tempfile.NamedTemporaryFile(suffix=filename, delete=False) - shutil.copyfileobj(file, copyfile) - copyfile.close() - file.close() - # Use the NamedTemporaryFile for conversion - text = md.convert(copyfile.name, stream_info=StreamInfo(filename=filename)).text_content - os.remove(copyfile.name) + copyfile = get_tempfile(file, filename) + try: + text = md.convert(copyfile).text_content + finally: + os.remove(copyfile) text = text.split("### Audio Transcript:\n")[-1] if text: yield text \ No newline at end of file diff --git a/g4f/Provider/needs_auth/OpenaiChat.py b/g4f/Provider/needs_auth/OpenaiChat.py index 2022a5e0..47ba2d45 100644 --- a/g4f/Provider/needs_auth/OpenaiChat.py +++ b/g4f/Provider/needs_auth/OpenaiChat.py @@ -23,11 +23,11 @@ from ...requests.raise_for_status import raise_for_status from ...requests import StreamSession from ...requests import get_nodriver from ...image import ImageRequest, to_image, to_bytes, is_accepted_format -from ...errors import MissingAuthError, NoValidHarFileError +from ...errors import MissingAuthError, NoValidHarFileError, ModelNotSupportedError from ...providers.response import JsonConversation, FinishReason, SynthesizeData, AuthResult, ImageResponse, ImagePreview from ...providers.response import Sources, TitleGeneration, RequestLogin, Reasoning from ...tools.media import merge_media -from ..helper import format_cookies, format_image_prompt +from ..helper import format_cookies, format_image_prompt, to_string from ..openai.models import default_model, default_image_model, models, image_models, text_models from ..openai.har_file import get_request_config from ..openai.har_file import RequestConfig, arkReq, arkose_url, start_url, conversation_url, backend_url, backend_anon_url @@ -221,7 +221,7 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin): messages = [{ "id": str(uuid.uuid4()), "author": {"role": message["role"]}, - "content": {"content_type": "text", "parts": [message["content"]]}, + "content": {"content_type": "text", "parts": [to_string(message["content"])]}, "metadata": {"serialization_metadata": {"custom_symbol_offsets": []}, **({"system_hints": system_hints} if system_hints else {})}, "create_time": time.time(), } for message in messages] @@ -356,7 +356,10 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin): except Exception as e: debug.error("OpenaiChat: Upload image failed") debug.error(e) - model = cls.get_model(model) + try: + model = cls.get_model(model) + except ModelNotSupportedError: + pass if conversation is None: conversation = Conversation(None, str(uuid.uuid4()), getattr(auth_result, "cookies", {}).get("oai-did")) else: diff --git a/g4f/Provider/openai/models.py b/g4f/Provider/openai/models.py index 2548205a..28ff42bf 100644 --- a/g4f/Provider/openai/models.py +++ b/g4f/Provider/openai/models.py @@ -1,6 +1,6 @@ default_model = "auto" default_image_model = "dall-e-3" image_models = [default_image_model] -text_models = [default_model, "gpt-4", "gpt-4.5", "gpt-4o", "gpt-4o-mini", "o1", "o1-preview", "o1-mini", "o3-mini", "o3-mini-high"] +text_models = [default_model, "gpt-4", "gpt-4.1", "gpt-4.5", "gpt-4o", "gpt-4o-mini", "o1", "o1-preview", "o1-mini", "o3-mini", "o3-mini-high"] vision_models = text_models models = text_models + image_models \ No newline at end of file diff --git a/g4f/api/__init__.py b/g4f/api/__init__.py index 0f9de28d..48d5aa83 100644 --- a/g4f/api/__init__.py +++ b/g4f/api/__init__.py @@ -494,28 +494,20 @@ class Api: } @self.app.post("/v1/audio/transcriptions", responses=responses) @self.app.post("/api/{path_provider}/audio/transcriptions", responses=responses) - @self.app.post("/api/markitdown", responses=responses) async def convert( file: UploadFile, - model: Annotated[Optional[str], Form()] = None, - provider: Annotated[Optional[str], Form()] = "MarkItDown", path_provider: str = None, - prompt: Annotated[Optional[str], Form()] = "Transcribe this audio", - api_key: Annotated[Optional[str], Form()] = None, - credentials: Annotated[HTTPAuthorizationCredentials, Depends(Api.security)] = None + model: Annotated[Optional[str], Form()] = None, + provider: Annotated[Optional[str], Form()] = None, + prompt: Annotated[Optional[str], Form()] = "Transcribe this audio" ): - if credentials is not None and credentials.credentials != "secret": - api_key = credentials.credentials try: response = await self.client.chat.completions.create( messages=prompt, model=model, + provider=provider if path_provider is None else path_provider, media=[[file.file, file.filename]], - modalities=["text"], - **filter_none( - provider=provider if path_provider is None else path_provider, - api_key=api_key - ) + modalities=["text"] ) return {"text": response.choices[0].message.content, "model": response.model, "provider": response.provider} except (ModelNotFoundError, ProviderNotFoundError) as e: @@ -528,6 +520,12 @@ class Api: logger.exception(e) return ErrorResponse.from_exception(e, None, HTTP_500_INTERNAL_SERVER_ERROR) + @self.app.post("/api/markitdown", responses=responses) + async def markitdown( + file: UploadFile + ): + return await convert(file, "MarkItDown") + responses = { HTTP_200_OK: {"class": FileResponse}, HTTP_401_UNAUTHORIZED: {"model": ErrorResponseModel}, diff --git a/g4f/tools/files.py b/g4f/tools/files.py index 118a0cdf..c2bd5bdc 100644 --- a/g4f/tools/files.py +++ b/g4f/tools/files.py @@ -13,6 +13,8 @@ import zipfile import asyncio import hashlib import base64 +import tempfile +import shutil try: import PyPDF2 @@ -578,4 +580,11 @@ async def get_async_streaming(bucket_dir: str, delete_files = False, refine_chun except Exception as e: if event_stream: yield f'data: {json.dumps({"error": {"message": str(e)}})}\n\n' - raise e \ No newline at end of file + raise e + +def get_tempfile(file, suffix): + copyfile = tempfile.NamedTemporaryFile(suffix=suffix, delete=False) + shutil.copyfileobj(file, copyfile) + copyfile.close() + file.close() + return copyfile.name \ No newline at end of file