XFEstudio/gpt4free
feat: add audio transcription endpoint and MarkItDown audio provider integration
- Added new MarkItDown audio provider in g4f/Provider/audio/MarkItDown.py for handling audio transcription using markitdown external module - Included MarkItDown provider import in g4f/Provider/audio/__init__.py - Implemented /v1/audio/transcriptions POST API endpoint with support for file upload, model selection, provider choice, and prompt in g4f/api/__init__.py - Added TranscriptionResponseModel Pydantic schema to g4f/api/stubs.py for transcription responses - Fixed media tuple handling in g4f/client/__init__.py to correctly unpack and assign file/name pairs in Completions, Images, and AsyncCompletions classes - Updated g4f/Provider/LambdaChat.py to remove redundant origin attribute and simplify URL assignment - Added handling in AnyProvider to append provider if model is matched in provider map and provider is working (g4f/providers/any_provider.py) - Modified backend_api.py to fix web_search logic and default filter_markdown parameter extraction from query parameters
b15a83ae
代码差异
@@ -21,7 +21,6 @@ accounts.txt
**/__pycache__/
__pycache__/
dist/
*.log
*.pyc
*.egg-info/
@@ -66,6 +65,7 @@ bench.py
to-reverse.txt
g4f/Provider/OpenaiChat2.py
generated_images/
generated_media/
projects/windows/
*.bak
@@ -5,8 +5,7 @@ from .hf.HuggingChat import HuggingChat
class LambdaChat(HuggingChat):
label = "Lambda Chat"
domain = "lambda.chat"
origin = f"https://{domain}"
url = origin
url = f"https://{domain}"
working = True
use_nodriver = False
needs_auth = False
@@ -22,10 +21,7 @@ class LambdaChat(HuggingChat):
"lfm-40b",
"llama3.3-70b-instruct-fp8"
]
models = fallback_models.copy()
model_aliases = {
"deepseek-v3": default_model,
"hermes-3": "hermes-3-llama-3.1-405b-fp8",
"nemotron-70b": "llama3.1-nemotron-70b-instruct",
"llama-3.3-70b": "llama3.3-70b-instruct-fp8"
@@ -0,0 +1,42 @@
from __future__ import annotations
import tempfile
import shutil
import os
try:
from markitdown import MarkItDown as MaItDo, StreamInfo
has_markitdown = True
except ImportError:
has_markitdown = False
from ...typing import AsyncResult, Messages, MediaListType
from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin
class MarkItDown(AsyncGeneratorProvider, ProviderModelMixin):
working = has_markitdown
@classmethod
async def create_async_generator(
cls,
model: str,
messages: Messages,
media: MediaListType = None,
**kwargs
) -> AsyncResult:
md = MaItDo()
for file, filename in media:
try:
text = md.convert(file, stream_info=StreamInfo(filename=filename)).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)
text = text.split("### Audio Transcript:\n")[-1]
if text:
yield text
@@ -1,2 +1,3 @@
from .EdgeTTS import EdgeTTS
from .gTTS import gTTS
from .gTTS import gTTS
from .MarkItDown import MarkItDown
@@ -11,7 +11,7 @@ import os.path
import hashlib
import asyncio
from urllib.parse import quote_plus
from fastapi import FastAPI, Response, Request, UploadFile, Depends
from fastapi import FastAPI, Response, Request, UploadFile, Form, Depends
from fastapi.middleware.wsgi import WSGIMiddleware
from fastapi.responses import StreamingResponse, RedirectResponse, HTMLResponse, JSONResponse
from fastapi.exceptions import RequestValidationError
@@ -57,7 +57,8 @@ from .stubs import (
ChatCompletionsConfig, ImageGenerationConfig,
ProviderResponseModel, ModelResponseModel,
ErrorResponseModel, ProviderResponseDetailModel,
FileResponseModel, UploadResponseModel
FileResponseModel, UploadResponseModel,
TranscriptionResponseModel
)
from g4f import debug
@@ -483,6 +484,47 @@ class Api:
'params': [*provider.get_parameters()] if hasattr(provider, "get_parameters") else []
}
responses = {
HTTP_200_OK: {"model": TranscriptionResponseModel},
HTTP_401_UNAUTHORIZED: {"model": ErrorResponseModel},
HTTP_404_NOT_FOUND: {"model": ErrorResponseModel},
HTTP_500_INTERNAL_SERVER_ERROR: {"model": ErrorResponseModel},
}
@self.app.post("/v1/audio/transcriptions", responses=responses)
@self.app.post("/api/{path_provider}/audio/transcriptions", responses=responses)
async def generate_image(
file: UploadFile,
model: Annotated[Optional[str], Form()] = None,
provider: Annotated[Optional[str], Form()] = AppConfig.media_provider,
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
):
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,
media=[[file.file, file.filename]],
modalities=["text"],
**filter_none(
provider=provider if path_provider is None else path_provider,
api_key=api_key
)
)
return {"text": response.choices[0].message.content, "model": response.model, "provider": response.provider}
except (ModelNotFoundError, ProviderNotFoundError) as e:
logger.exception(e)
return ErrorResponse.from_exception(e, None, HTTP_404_NOT_FOUND)
except MissingAuthError as e:
logger.exception(e)
return ErrorResponse.from_exception(e, None, HTTP_401_UNAUTHORIZED)
except Exception as e:
logger.exception(e)
return ErrorResponse.from_exception(e, None, HTTP_500_INTERNAL_SERVER_ERROR)
@self.app.post("/v1/upload_cookies", responses={
HTTP_200_OK: {"model": List[FileResponseModel]},
})
@@ -113,4 +113,9 @@ class ErrorResponseMessageModel(BaseModel):
message: str
class FileResponseModel(BaseModel):
filename: str
filename: str
class TranscriptionResponseModel(BaseModel):
text: str
model: str
provider: str
@@ -300,9 +300,9 @@ class Completions:
kwargs["media"] = [(image, image_name)]
elif "images" in kwargs:
kwargs["media"] = kwargs.pop("images")
for idx, media in kwargs.get("media", []):
for idx, media in enumerate(kwargs.get("media", [])):
if not isinstance(media, (list, tuple)):
kwargs["media"][idx] = (media[0], media[1] if media[1] is not None else getattr(image, "name", None))
kwargs["media"][idx] = (media, getattr(media, "name", None))
if provider is None:
provider = self.provider
if provider is None:
@@ -496,10 +496,9 @@ class Images:
prompt = "create a variation of this image"
if image is not None:
kwargs["media"] = image
for idx, media in kwargs.get("media", []):
for idx, media in enumerate(kwargs.get("media", [])):
if not isinstance(media, (list, tuple)):
kwargs["media"][idx] = (media[0], media[1] if media[1] is not None else getattr(image, "name", None))
kwargs["media"][idx] = (media, getattr(media, "name", None))
error = None
response = None
if isinstance(provider_handler, IterListProvider):
@@ -605,9 +604,9 @@ class AsyncCompletions:
kwargs["media"] = [(image, image_name)]
elif "images" in kwargs:
kwargs["media"] = kwargs.pop("images")
for idx, media in kwargs.get("media", []):
for idx, media in enumerate(kwargs.get("media", [])):
if not isinstance(media, (list, tuple)):
kwargs["media"][idx] = (media[0], media[1] if media[1] is not None else getattr(image, "name", None))
kwargs["media"][idx] = (media, getattr(media, "name", None))
if provider is None:
provider = self.provider
if provider is None:
@@ -220,6 +220,8 @@ class Backend_Api(Api):
}]
web_search = request.args.get("web_search")
if web_search:
is_true_web_search = web_search.lower() in ["true", "1"]
web_search = None if is_true_web_search else web_search
tool_calls.append({
"function": {
"name": "search_tool",
@@ -227,7 +229,7 @@ class Backend_Api(Api):
},
"type": "function"
})
do_filter = request.args.get("filter_markdown")
do_filter = request.args.get("filter_markdown", request.args.get("json"))
cache_id = request.args.get('cache')
parameters = {
"model": request.args.get("model"),
@@ -137,6 +137,11 @@ class AnyProvider(AsyncGeneratorProvider, ProviderModelMixin):
providers = models.default_vision.best_provider.providers
else:
providers = models.default.best_provider.providers
elif model in Provider.__map__:
provider = Provider.__map__[model]
if provider.working and getattr(provider, "parent", provider.__name__) not in ignored:
model = None
providers.append(provider)
else:
for provider in [
OpenaiChat, Cloudflare, LMArenaProvider, PerplexityLabs, Gemini, Grok, DeepSeekAPI, FreeRouter, Blackbox,