XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet
公开
关注 0 Fork 0 Star 1
返回提交历史

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
hlohaus <983577+hlohaus@users.noreply.github.com>
提交于

代码差异

9 个文件 +110 -18
Modified .gitignore +1 -1
@@ -21,7 +21,6 @@ accounts.txt
21 21 **/__pycache__/
22 22 __pycache__/
23 23
24 dist/
25 24 *.log
26 25 *.pyc
27 26 *.egg-info/
@@ -66,6 +65,7 @@ bench.py
66 65 to-reverse.txt
67 66 g4f/Provider/OpenaiChat2.py
68 67 generated_images/
68 generated_media/
69 69 projects/windows/
70 70
71 71 *.bak
Modified g4f/Provider/LambdaChat.py +1 -5
@@ -5,8 +5,7 @@ from .hf.HuggingChat import HuggingChat
5 5 class LambdaChat(HuggingChat):
6 6 label = "Lambda Chat"
7 7 domain = "lambda.chat"
8 origin = f"https://{domain}"
9 url = origin
8 url = f"https://{domain}"
10 9 working = True
11 10 use_nodriver = False
12 11 needs_auth = False
@@ -22,10 +21,7 @@ class LambdaChat(HuggingChat):
22 21 "lfm-40b",
23 22 "llama3.3-70b-instruct-fp8"
24 23 ]
25 models = fallback_models.copy()
26
27 24 model_aliases = {
28 "deepseek-v3": default_model,
29 25 "hermes-3": "hermes-3-llama-3.1-405b-fp8",
30 26 "nemotron-70b": "llama3.1-nemotron-70b-instruct",
31 27 "llama-3.3-70b": "llama3.3-70b-instruct-fp8"
Added g4f/Provider/audio/MarkItDown.py +42 -0
@@ -0,0 +1,42 @@
1 from __future__ import annotations
2
3 import tempfile
4 import shutil
5 import os
6
7 try:
8 from markitdown import MarkItDown as MaItDo, StreamInfo
9 has_markitdown = True
10 except ImportError:
11 has_markitdown = False
12
13 from ...typing import AsyncResult, Messages, MediaListType
14 from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin
15
16 class MarkItDown(AsyncGeneratorProvider, ProviderModelMixin):
17 working = has_markitdown
18
19 @classmethod
20 async def create_async_generator(
21 cls,
22 model: str,
23 messages: Messages,
24 media: MediaListType = None,
25 **kwargs
26 ) -> AsyncResult:
27 md = MaItDo()
28 for file, filename in media:
29 try:
30 text = md.convert(file, stream_info=StreamInfo(filename=filename)).text_content
31 except TypeError:
32 # Copy SpooledTemporaryFile to a NamedTemporaryFile
33 copyfile = tempfile.NamedTemporaryFile(suffix=filename, delete=False)
34 shutil.copyfileobj(file, copyfile)
35 copyfile.close()
36 file.close()
37 # Use the NamedTemporaryFile for conversion
38 text = md.convert(copyfile.name, stream_info=StreamInfo(filename=filename)).text_content
39 os.remove(copyfile.name)
40 text = text.split("### Audio Transcript:\n")[-1]
41 if text:
42 yield text
Modified g4f/Provider/audio/__init__.py +2 -1
@@ -1,2 +1,3 @@
1 1 from .EdgeTTS import EdgeTTS
2 from .gTTS import gTTS
2 from .gTTS import gTTS
3 from .MarkItDown import MarkItDown
Modified g4f/api/__init__.py +44 -2
@@ -11,7 +11,7 @@ import os.path
11 11 import hashlib
12 12 import asyncio
13 13 from urllib.parse import quote_plus
14 from fastapi import FastAPI, Response, Request, UploadFile, Depends
14 from fastapi import FastAPI, Response, Request, UploadFile, Form, Depends
15 15 from fastapi.middleware.wsgi import WSGIMiddleware
16 16 from fastapi.responses import StreamingResponse, RedirectResponse, HTMLResponse, JSONResponse
17 17 from fastapi.exceptions import RequestValidationError
@@ -57,7 +57,8 @@ from .stubs import (
57 57 ChatCompletionsConfig, ImageGenerationConfig,
58 58 ProviderResponseModel, ModelResponseModel,
59 59 ErrorResponseModel, ProviderResponseDetailModel,
60 FileResponseModel, UploadResponseModel
60 FileResponseModel, UploadResponseModel,
61 TranscriptionResponseModel
61 62 )
62 63 from g4f import debug
63 64
@@ -483,6 +484,47 @@ class Api:
483 484 'params': [*provider.get_parameters()] if hasattr(provider, "get_parameters") else []
484 485 }
485 486
487 responses = {
488 HTTP_200_OK: {"model": TranscriptionResponseModel},
489 HTTP_401_UNAUTHORIZED: {"model": ErrorResponseModel},
490 HTTP_404_NOT_FOUND: {"model": ErrorResponseModel},
491 HTTP_500_INTERNAL_SERVER_ERROR: {"model": ErrorResponseModel},
492 }
493 @self.app.post("/v1/audio/transcriptions", responses=responses)
494 @self.app.post("/api/{path_provider}/audio/transcriptions", responses=responses)
495 async def generate_image(
496 file: UploadFile,
497 model: Annotated[Optional[str], Form()] = None,
498 provider: Annotated[Optional[str], Form()] = AppConfig.media_provider,
499 path_provider: str = None,
500 prompt: Annotated[Optional[str], Form()] = "Transcribe this audio",
501 api_key: Annotated[Optional[str], Form()] = None,
502 credentials: Annotated[HTTPAuthorizationCredentials, Depends(Api.security)] = None
503 ):
504 if credentials is not None and credentials.credentials != "secret":
505 api_key = credentials.credentials
506 try:
507 response = await self.client.chat.completions.create(
508 messages=prompt,
509 model=model,
510 media=[[file.file, file.filename]],
511 modalities=["text"],
512 **filter_none(
513 provider=provider if path_provider is None else path_provider,
514 api_key=api_key
515 )
516 )
517 return {"text": response.choices[0].message.content, "model": response.model, "provider": response.provider}
518 except (ModelNotFoundError, ProviderNotFoundError) as e:
519 logger.exception(e)
520 return ErrorResponse.from_exception(e, None, HTTP_404_NOT_FOUND)
521 except MissingAuthError as e:
522 logger.exception(e)
523 return ErrorResponse.from_exception(e, None, HTTP_401_UNAUTHORIZED)
524 except Exception as e:
525 logger.exception(e)
526 return ErrorResponse.from_exception(e, None, HTTP_500_INTERNAL_SERVER_ERROR)
527
486 528 @self.app.post("/v1/upload_cookies", responses={
487 529 HTTP_200_OK: {"model": List[FileResponseModel]},
488 530 })
Modified g4f/api/stubs.py +6 -1
@@ -113,4 +113,9 @@ class ErrorResponseMessageModel(BaseModel):
113 113 message: str
114 114
115 115 class FileResponseModel(BaseModel):
116 filename: str
116 filename: str
117
118 class TranscriptionResponseModel(BaseModel):
119 text: str
120 model: str
121 provider: str
Modified g4f/client/__init__.py +6 -7
@@ -300,9 +300,9 @@ class Completions:
300 300 kwargs["media"] = [(image, image_name)]
301 301 elif "images" in kwargs:
302 302 kwargs["media"] = kwargs.pop("images")
303 for idx, media in kwargs.get("media", []):
303 for idx, media in enumerate(kwargs.get("media", [])):
304 304 if not isinstance(media, (list, tuple)):
305 kwargs["media"][idx] = (media[0], media[1] if media[1] is not None else getattr(image, "name", None))
305 kwargs["media"][idx] = (media, getattr(media, "name", None))
306 306 if provider is None:
307 307 provider = self.provider
308 308 if provider is None:
@@ -496,10 +496,9 @@ class Images:
496 496 prompt = "create a variation of this image"
497 497 if image is not None:
498 498 kwargs["media"] = image
499 for idx, media in kwargs.get("media", []):
499 for idx, media in enumerate(kwargs.get("media", [])):
500 500 if not isinstance(media, (list, tuple)):
501 kwargs["media"][idx] = (media[0], media[1] if media[1] is not None else getattr(image, "name", None))
502
501 kwargs["media"][idx] = (media, getattr(media, "name", None))
503 502 error = None
504 503 response = None
505 504 if isinstance(provider_handler, IterListProvider):
@@ -605,9 +604,9 @@ class AsyncCompletions:
605 604 kwargs["media"] = [(image, image_name)]
606 605 elif "images" in kwargs:
607 606 kwargs["media"] = kwargs.pop("images")
608 for idx, media in kwargs.get("media", []):
607 for idx, media in enumerate(kwargs.get("media", [])):
609 608 if not isinstance(media, (list, tuple)):
610 kwargs["media"][idx] = (media[0], media[1] if media[1] is not None else getattr(image, "name", None))
609 kwargs["media"][idx] = (media, getattr(media, "name", None))
611 610 if provider is None:
612 611 provider = self.provider
613 612 if provider is None:
Modified g4f/gui/server/backend_api.py +3 -1
@@ -220,6 +220,8 @@ class Backend_Api(Api):
220 220 }]
221 221 web_search = request.args.get("web_search")
222 222 if web_search:
223 is_true_web_search = web_search.lower() in ["true", "1"]
224 web_search = None if is_true_web_search else web_search
223 225 tool_calls.append({
224 226 "function": {
225 227 "name": "search_tool",
@@ -227,7 +229,7 @@ class Backend_Api(Api):
227 229 },
228 230 "type": "function"
229 231 })
230 do_filter = request.args.get("filter_markdown")
232 do_filter = request.args.get("filter_markdown", request.args.get("json"))
231 233 cache_id = request.args.get('cache')
232 234 parameters = {
233 235 "model": request.args.get("model"),
Modified g4f/providers/any_provider.py +5 -0
@@ -137,6 +137,11 @@ class AnyProvider(AsyncGeneratorProvider, ProviderModelMixin):
137 137 providers = models.default_vision.best_provider.providers
138 138 else:
139 139 providers = models.default.best_provider.providers
140 elif model in Provider.__map__:
141 provider = Provider.__map__[model]
142 if provider.working and getattr(provider, "parent", provider.__name__) not in ignored:
143 model = None
144 providers.append(provider)
140 145 else:
141 146 for provider in [
142 147 OpenaiChat, Cloudflare, LMArenaProvider, PerplexityLabs, Gemini, Grok, DeepSeekAPI, FreeRouter, Blackbox,