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

XFEstudio/gpt4free

Add HuggingFaceMedia provider with Video Generation Add Support for Video Response in UI Improve Support for Audio Response in UI Fix ModelNotSupported errors in HuggingSpace providers

8eaaf5db
hlohaus <983577+hlohaus@users.noreply.github.com>
提交于

代码差异

21 个文件 +356 -128
Modified g4f/Provider/PollinationsAI.py +8 -6
@@ -14,7 +14,8 @@ from ..image import to_data_uri, is_data_an_audio, to_input_audio
14 14 from ..errors import ModelNotFoundError
15 15 from ..requests.raise_for_status import raise_for_status
16 16 from ..requests.aiohttp import get_connector
17 from ..providers.response import ImageResponse, ImagePreview, FinishReason, Usage, Audio, ToolCalls
17 from ..image.copy_images import save_response_media
18 from ..providers.response import FinishReason, Usage, ToolCalls
18 19 from .. import debug
19 20
20 21 DEFAULT_HEADERS = {
@@ -239,8 +240,9 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
239 240 async with ClientSession(headers=DEFAULT_HEADERS, connector=get_connector(proxy=proxy)) as session:
240 241 async with session.get(url, allow_redirects=True) as response:
241 242 await raise_for_status(response)
242 image_url = str(response.url)
243 yield ImageResponse(image_url, prompt)
243 async for chunk in save_response_media(response, prompt):
244 yield chunk
245 return
244 246
245 247 @classmethod
246 248 async def _generate_text(
@@ -305,10 +307,10 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
305 307 })
306 308 async with session.post(url, json=data) as response:
307 309 await raise_for_status(response)
308 if response.headers["content-type"] == "audio/mpeg":
309 yield Audio(await response.read())
310 async for chunk in save_response_media(response, messages[-1]["content"]):
311 yield chunk
310 312 return
311 elif response.headers["content-type"].startswith("text/plain"):
313 if response.headers["content-type"].startswith("text/plain"):
312 314 yield await response.text()
313 315 return
314 316 elif response.headers["content-type"].startswith("text/event-stream"):
Modified g4f/Provider/__init__.py +1 -1
@@ -9,7 +9,7 @@ from .deprecated import *
9 9 from .needs_auth import *
10 10 from .not_working import *
11 11 from .local import *
12 from .hf import HuggingFace, HuggingChat, HuggingFaceAPI, HuggingFaceInference
12 from .hf import HuggingFace, HuggingChat, HuggingFaceAPI, HuggingFaceInference, HuggingFaceMedia
13 13 from .hf_space import *
14 14 from .mini_max import HailuoAI, MiniMax
15 15 from .template import OpenaiTemplate, BackendApi
Modified g4f/Provider/hf/HuggingChat.py +4 -3
@@ -24,7 +24,7 @@ from ...requests import get_args_from_nodriver, DEFAULT_HEADERS
24 24 from ...requests.raise_for_status import raise_for_status
25 25 from ...providers.response import JsonConversation, ImageResponse, Sources, TitleGeneration, Reasoning, RequestLogin
26 26 from ...cookies import get_cookies
27 from .models import default_model, fallback_models, image_models, model_aliases, llama_models
27 from .models import default_model, default_vision_model, fallback_models, image_models, model_aliases
28 28 from ... import debug
29 29
30 30 class Conversation(JsonConversation):
@@ -41,6 +41,7 @@ class HuggingChat(AsyncAuthedProvider, ProviderModelMixin):
41 41 supports_stream = True
42 42 needs_auth = True
43 43 default_model = default_model
44 default_vision_model = default_vision_model
44 45 model_aliases = model_aliases
45 46 image_models = image_models
46 47 text_models = fallback_models
@@ -107,8 +108,8 @@ class HuggingChat(AsyncAuthedProvider, ProviderModelMixin):
107 108 ) -> AsyncResult:
108 109 if not has_curl_cffi:
109 110 raise MissingRequirementsError('Install "curl_cffi" package | pip install -U curl_cffi')
110 if model == llama_models["name"]:
111 model = llama_models["text"] if media is None else llama_models["vision"]
111 if not model and media is not None:
112 model = cls.default_vision_model
112 113 model = cls.get_model(model)
113 114
114 115 session = Session(**auth_result.get_dict())
Modified g4f/Provider/hf/HuggingFaceAPI.py +16 -10
@@ -6,27 +6,30 @@ from ...providers.types import Messages
6 6 from ...typing import MediaListType
7 7 from ...requests import StreamSession, raise_for_status
8 8 from ...errors import ModelNotSupportedError
9 from ...providers.helper import get_last_user_message
10 9 from ...providers.response import ProviderInfo
11 10 from ..template.OpenaiTemplate import OpenaiTemplate
12 from .models import model_aliases, vision_models, default_vision_model, llama_models, text_models
11 from .models import model_aliases, vision_models, default_llama_model, default_vision_model, text_models
13 12 from ... import debug
14 13
15 14 class HuggingFaceAPI(OpenaiTemplate):
16 label = "HuggingFace (Inference API)"
15 label = "HuggingFace (Text Generation)"
17 16 parent = "HuggingFace"
18 17 url = "https://api-inference.huggingface.com"
19 18 api_base = "https://api-inference.huggingface.co/v1"
20 19 working = True
21 20 needs_auth = True
22 21
23 default_model = default_vision_model
22 default_model = default_llama_model
24 23 default_vision_model = default_vision_model
25 24 vision_models = vision_models
26 25 model_aliases = model_aliases
27 26 fallback_models = text_models + vision_models
28 27
29 provider_mapping: dict[str, dict] = {}
28 provider_mapping: dict[str, dict] = {
29 "google/gemma-3-27b-it": {
30 "hf-inference/models/google/gemma-3-27b-it": {
31 "task": "conversational",
32 "providerId": "google/gemma-3-27b-it"}}}
30 33
31 34 @classmethod
32 35 def get_model(cls, model: str, **kwargs) -> str:
@@ -47,7 +50,9 @@ class HuggingFaceAPI(OpenaiTemplate):
47 50 if [
48 51 provider
49 52 for provider in model.get("inferenceProviderMapping")
50 if provider.get("task") == "conversational"]]
53 if provider.get("status") == "live" and provider.get("task") == "conversational"
54 ]
55 ] + list(cls.provider_mapping.keys())
51 56 else:
52 57 cls.models = cls.fallback_models
53 58 return cls.models
@@ -78,11 +83,12 @@ class HuggingFaceAPI(OpenaiTemplate):
78 83 media: MediaListType = None,
79 84 **kwargs
80 85 ):
81 if model == llama_models["name"]:
82 model = llama_models["text"] if media is None else llama_models["vision"]
83 if model in cls.model_aliases:
84 model = cls.model_aliases[model]
86 if not model and media is not None:
87 model = cls.default_vision_model
88 model = cls.get_model(model)
85 89 provider_mapping = await cls.get_mapping(model, api_key)
90 if not provider_mapping:
91 raise ModelNotSupportedError(f"Model is not supported: {model} in: {cls.__name__}")
86 92 for provider_key in provider_mapping:
87 93 api_path = provider_key if provider_key == "novita" else f"{provider_key}/v1"
88 94 api_base = f"https://router.huggingface.co/{api_path}"
Modified g4f/Provider/hf/HuggingFaceInference.py +5 -6
@@ -10,6 +10,7 @@ from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin, format_p
10 10 from ...errors import ModelNotSupportedError, ResponseError
11 11 from ...requests import StreamSession, raise_for_status
12 12 from ...providers.response import FinishReason, ImageResponse
13 from ...image.copy_images import save_response_media
13 14 from ..helper import format_image_prompt, get_last_user_message
14 15 from .models import default_model, default_image_model, model_aliases, text_models, image_models, vision_models
15 16 from ... import debug
@@ -176,12 +177,10 @@ class HuggingFaceInference(AsyncGeneratorProvider, ProviderModelMixin):
176 177 debug.log(f"Special token: {is_special}")
177 178 yield FinishReason("stop" if is_special else "length")
178 179 else:
179 if response.headers["content-type"].startswith("image/"):
180 base64_data = base64.b64encode(b"".join([chunk async for chunk in response.iter_content()]))
181 url = f"data:{response.headers['content-type']};base64,{base64_data.decode()}"
182 yield ImageResponse(url, inputs)
183 else:
184 yield (await response.json())[0]["generated_text"].strip()
180 async for chunk in save_response_media(response, prompt):
181 yield chunk
182 return
183 yield (await response.json())[0]["generated_text"].strip()
185 184
186 185 def format_prompt_mistral(messages: Messages, do_continue: bool = False) -> str:
187 186 system_messages = [message["content"] for message in messages if message["role"] == "system"]
Added g4f/Provider/hf/HuggingFaceMedia.py +175 -0
@@ -0,0 +1,175 @@
1 from __future__ import annotations
2
3 import random
4 import requests
5
6 from ...providers.types import Messages
7 from ...requests import StreamSession, raise_for_status
8 from ...errors import ModelNotSupportedError
9 from ...providers.helper import format_image_prompt
10 from ...providers.base_provider import AsyncGeneratorProvider, ProviderModelMixin
11 from ...providers.response import ProviderInfo, ImageResponse, VideoResponse
12 from ...image.copy_images import save_response_media
13 from ... import debug
14
15 class HuggingFaceMedia(AsyncGeneratorProvider, ProviderModelMixin):
16 label = "HuggingFace (Image / Video Generation)"
17 parent = "HuggingFace"
18 url = "https://huggingface.co"
19 working = True
20 needs_auth = True
21
22 tasks = ["text-to-image", "text-to-video"]
23 provider_mapping: dict[str, dict] = {}
24 task_mapping: dict[str, str] = {}
25
26 @classmethod
27 def get_models(cls, **kwargs) -> list[str]:
28 if not cls.models:
29 url = "https://huggingface.co/api/models?inference=warm&expand[]=inferenceProviderMapping"
30 response = requests.get(url)
31 if response.ok:
32 models = response.json()
33 cls.models = [
34 model["id"]
35 for model in models
36 if [
37 provider
38 for provider in model.get("inferenceProviderMapping")
39 if provider.get("status") == "live" and provider.get("task") in cls.tasks
40 ]
41 ]
42 cls.task_mapping = {
43 model["id"]: [
44 provider.get("task")
45 for provider in model.get("inferenceProviderMapping")
46 ].pop()
47 for model in models
48 }
49 else:
50 cls.models = []
51 return cls.models
52
53 @classmethod
54 async def get_mapping(cls, model: str, api_key: str = None):
55 if model in cls.provider_mapping:
56 return cls.provider_mapping[model]
57 headers = {
58 'Content-Type': 'application/json',
59 }
60 if api_key is not None:
61 headers["Authorization"] = f"Bearer {api_key}"
62 async with StreamSession(
63 timeout=30,
64 headers=headers,
65 ) as session:
66 async with session.get(f"https://huggingface.co/api/models/{model}?expand[]=inferenceProviderMapping") as response:
67 await raise_for_status(response)
68 model_data = await response.json()
69 cls.provider_mapping[model] = {key: value for key, value in model_data.get("inferenceProviderMapping").items() if value["status"] == "live"}
70 return cls.provider_mapping[model]
71
72 @classmethod
73 async def create_async_generator(
74 cls,
75 model: str,
76 messages: Messages,
77 api_key: str = None,
78 extra_data: dict = {},
79 prompt: str = None,
80 proxy: str = None,
81 timeout: int = 0,
82 **kwargs
83 ):
84 provider_mapping = await cls.get_mapping(model, api_key)
85 headers = {
86 'Accept-Encoding': 'gzip, deflate',
87 'Content-Type': 'application/json',
88 }
89 new_mapping = {
90 "hf-free" if key == "hf-inference" else key: value for key, value in provider_mapping.items()
91 if key in ["replicate", "together", "hf-inference"]
92 }
93 provider_mapping = {**new_mapping, **provider_mapping}
94 last_response = None
95 for provider_key, provider in provider_mapping.items():
96 yield ProviderInfo(**{**cls.get_dict(), "label": f"HuggingFace ({provider_key})", "url": f"{cls.url}/{model}"})
97
98 api_base = f"https://router.huggingface.co/{provider_key}"
99 task = provider["task"]
100 provider_id = provider["providerId"]
101 if task not in cls.tasks:
102 raise ModelNotSupportedError(f"Model is not supported: {model} in: {cls.__name__} task: {task}")
103
104 prompt = format_image_prompt(messages, prompt)
105 if task == "text-to-video":
106 extra_data = {
107 "num_inference_steps": 20,
108 "video_size": "landscape_16_9",
109 **extra_data
110 }
111 else:
112 extra_data = {
113 "width": 1024,
114 "height": 1024,
115 **extra_data
116 }
117 if provider_key == "fal-ai":
118 url = f"{api_base}/{provider_id}"
119 data = {
120 "prompt": prompt,
121 "image_size": "square_hd",
122 **extra_data
123 }
124 elif provider_key == "replicate":
125 url = f"{api_base}/v1/models/{provider_id}/prediction"
126 data = {
127 "input": {
128 "prompt": prompt,
129 **extra_data
130 }
131 }
132 elif provider_key in ("hf-inference", "hf-free"):
133 api_base = "https://api-inference.huggingface.co"
134 url = f"{api_base}/models/{provider_id}"
135 data = {
136 "inputs": prompt,
137 "parameters": {
138 "seed": random.randint(0, 2**32),
139 **extra_data
140 }
141 }
142 elif task == "text-to-image":
143 url = f"{api_base}/v1/images/generations"
144 data = {
145 "response_format": "url",
146 "prompt": prompt,
147 "model": provider_id,
148 **extra_data
149 }
150
151 async with StreamSession(
152 headers=headers if provider_key == "free" or api_key is None else {**headers, "Authorization": f"Bearer {api_key}"},
153 proxy=proxy,
154 timeout=timeout
155 ) as session:
156 async with session.post(url, json=data) as response:
157 if response.status in (400, 401, 402):
158 last_response = response
159 debug.error(f"{cls.__name__}: Error {response.status} with {provider_key} and {provider_id}")
160 continue
161 if response.status == 404:
162 raise ModelNotSupportedError(f"Model is not supported: {model}")
163 await raise_for_status(response)
164 async for chunk in save_response_media(response, prompt):
165 yield chunk
166 return
167 result = await response.json()
168 if "video" in result:
169 yield VideoResponse(result["video"]["url"], prompt)
170 elif task == "text-to-image":
171 yield ImageResponse([item["url"] for item in result.get("images", result.get("data"))], prompt)
172 elif task == "text-to-video":
173 yield VideoResponse(result["output"], prompt)
174 return
175 await raise_for_status(last_response)
Modified g4f/Provider/hf/__init__.py +7 -0
@@ -9,6 +9,7 @@ from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin
9 9 from .HuggingChat import HuggingChat
10 10 from .HuggingFaceAPI import HuggingFaceAPI
11 11 from .HuggingFaceInference import HuggingFaceInference
12 from .HuggingFaceMedia import HuggingFaceMedia
12 13 from .models import model_aliases, vision_models, default_vision_model
13 14 from ... import debug
14 15
@@ -51,6 +52,12 @@ class HuggingFace(AsyncGeneratorProvider, ProviderModelMixin):
51 52 debug.error(f"{cls.__name__} {type(e).__name__}; {e}")
52 53 if not cls.image_models:
53 54 cls.get_models()
55 try:
56 async for chunk in HuggingFaceMedia.create_async_generator(model, messages, **kwargs):
57 yield chunk
58 return
59 except ModelNotSupportedError:
60 pass
54 61 if model in cls.image_models:
55 62 if "api_key" not in kwargs:
56 63 async for chunk in HuggingChat.create_async_generator(model, messages, **kwargs):
Modified g4f/Provider/hf/models.py +2 -6
@@ -47,9 +47,5 @@ extra_models = [
47 47 "NousResearch/Hermes-3-Llama-3.1-8B",
48 48 ]
49 49 default_vision_model = "meta-llama/Llama-3.2-11B-Vision-Instruct"
50 vision_models = [default_vision_model, "Qwen/Qwen2-VL-7B-Instruct"]
51 llama_models = {
52 "name": "llama-3",
53 "text": "meta-llama/Llama-3.3-70B-Instruct",
54 "vision": "meta-llama/Llama-3.2-11B-Vision-Instruct",
55 }
50 default_llama_model = "meta-llama/Llama-3.3-70B-Instruct"
51 vision_models = [default_vision_model, "Qwen/Qwen2-VL-7B-Instruct"]
Modified g4f/Provider/hf_space/BlackForestLabs_Flux1Dev.py +0 -1
@@ -67,7 +67,6 @@ class BlackForestLabs_Flux1Dev(AsyncGeneratorProvider, ProviderModelMixin):
67 67 zerogpu_uuid: str = "[object Object]",
68 68 **kwargs
69 69 ) -> AsyncResult:
70 model = cls.get_model(model)
71 70 async with StreamSession(impersonate="chrome", proxy=proxy) as session:
72 71 prompt = format_image_prompt(messages, prompt)
73 72 data = [prompt, seed, randomize_seed, width, height, guidance_scale, num_inference_steps]
Modified g4f/Provider/hf_space/BlackForestLabs_Flux1Schnell.py +0 -2
@@ -37,8 +37,6 @@ class BlackForestLabs_Flux1Schnell(AsyncGeneratorProvider, ProviderModelMixin):
37 37 randomize_seed: bool = True,
38 38 **kwargs
39 39 ) -> AsyncResult:
40
41 model = cls.get_model(model)
42 40 width = max(32, width - (width % 8))
43 41 height = max(32, height - (height % 8))
44 42 prompt = format_image_prompt(messages, prompt)
Modified g4f/Provider/hf_space/CohereForAI_C4AI_Command.py +6 -1
@@ -24,9 +24,14 @@ class CohereForAI_C4AI_Command(AsyncGeneratorProvider, ProviderModelMixin):
24 24 "command-r": "command-r",
25 25 "command-r7b": "command-r7b-12-2024",
26 26 }
27
28 27 models = list(model_aliases.keys())
29 28
29 @classmethod
30 def get_model(cls, model: str, **kwargs) -> str:
31 if model in cls.model_aliases.values():
32 return model
33 return super().get_model(model, **kwargs)
34
30 35 @classmethod
31 36 async def create_async_generator(
32 37 cls, model: str, messages: Messages,
Modified g4f/Provider/needs_auth/OpenaiChat.py +15 -1
@@ -203,7 +203,21 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
203 203 Returns:
204 204 A list of messages with the user input and the image, if any
205 205 """
206 # Create a message object with the user role and the content
206 # merged_messages = []
207 # last_message = None
208 # for message in messages:
209 # current_message = last_message
210 # if current_message is not None:
211 # if current_message["role"] == message["role"]:
212 # current_message["content"] += "\n" + message["content"]
213 # else:
214 # merged_messages.append(current_message)
215 # last_message = message.copy()
216 # else:
217 # last_message = message.copy()
218 # if last_message is not None:
219 # merged_messages.append(last_message)
220
207 221 messages = [{
208 222 "id": str(uuid.uuid4()),
209 223 "author": {"role": message["role"]},
Modified g4f/api/__init__.py +3 -3
Modified g4f/client/__init__.py +2 -2
Modified g4f/gui/client/qrcode.html +2 -3
Modified g4f/gui/client/static/js/chat.v1.js +11 -10
Modified g4f/gui/server/api.py +12 -11
Modified g4f/gui/server/backend_api.py +21 -14
Modified g4f/image/copy_images.py +53 -30
Modified g4f/models.py +0 -6
Modified g4f/requests/raise_for_status.py +13 -12