返回提交历史
Added
g4f/Provider/hf_space/Phi_4.py
+160
-0
Modified
g4f/Provider/hf_space/__init__.py
+13
-1
Modified
g4f/api/__init__.py
+3
-3
Modified
g4f/client/__init__.py
+4
-0
Modified
g4f/gui/client/demo.html
+6
-6
Modified
g4f/image/__init__.py
+20
-1
XFEstudio/gpt4free
Add Phi_4 provider, Update demo template
7d0102d1
代码差异
6 个文件
+206
-11
@@ -0,0 +1,160 @@
1
from __future__ import annotations
2
3
import json
4
import uuid
5
6
from ...typing import AsyncResult, Messages, Cookies, ImagesType
7
from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin
8
from ..helper import format_prompt, format_image_prompt
9
from ...providers.response import JsonConversation
10
from ...requests.aiohttp import StreamSession, StreamResponse, FormData
11
from ...requests.raise_for_status import raise_for_status
12
from ...image import to_bytes, is_accepted_format, is_data_an_wav
13
from ...errors import ResponseError
14
from ... import debug
15
from .Janus_Pro_7B import get_zerogpu_token
16
from .raise_for_status import raise_for_status
17
18
class Phi_4(AsyncGeneratorProvider, ProviderModelMixin):
19
space = "microsoft/phi-4-multimodal"
20
url = f"https://huggingface.co/spaces/{space}"
21
api_url = "https://microsoft-phi-4-multimodal.hf.space"
22
referer = f"{api_url}?__theme=light"
23
24
working = True
25
supports_stream = True
26
supports_system_message = True
27
supports_message_history = True
28
29
default_model = "phi-4-multimodal"
30
default_vision_model = default_model
31
models = [default_model]
32
33
@classmethod
34
def run(cls, method: str, session: StreamSession, prompt: str, conversation: JsonConversation, images: list = None):
35
headers = {
36
"content-type": "application/json",
37
"x-zerogpu-token": conversation.zerogpu_token,
38
"x-zerogpu-uuid": conversation.zerogpu_uuid,
39
"referer": cls.referer,
40
}
41
if method == "predict":
42
return session.post(f"{cls.api_url}/gradio_api/run/predict", **{
43
"headers": {k: v for k, v in headers.items() if v is not None},
44
"json": {
45
"data":[
46
[],
47
{
48
"text": prompt,
49
"files": images,
50
},
51
None
52
],
53
"event_data": None,
54
"fn_index": 10,
55
"trigger_id": 8,
56
"session_hash": conversation.session_hash
57
},
58
})
59
if method == "post":
60
return session.post(f"{cls.api_url}/gradio_api/queue/join?__theme=light", **{
61
"headers": {k: v for k, v in headers.items() if v is not None},
62
"json": {
63
"data": [[
64
{
65
"role": "user",
66
"content": prompt,
67
}
68
]] + [[
69
{
70
"role": "user",
71
"content": {"file": image}
72
} for image in images
73
]],
74
"event_data": None,
75
"fn_index": 11,
76
"trigger_id": 8,
77
"session_hash": conversation.session_hash
78
},
79
})
80
return session.get(f"{cls.api_url}/gradio_api/queue/data?session_hash={conversation.session_hash}", **{
81
"headers": {
82
"accept": "text/event-stream",
83
"content-type": "application/json",
84
"referer": cls.referer,
85
}
86
})
87
88
@classmethod
89
async def create_async_generator(
90
cls,
91
model: str,
92
messages: Messages,
93
images: ImagesType = None,
94
prompt: str = None,
95
proxy: str = None,
96
cookies: Cookies = None,
97
api_key: str = None,
98
zerogpu_uuid: str = "[object Object]",
99
return_conversation: bool = False,
100
conversation: JsonConversation = None,
101
**kwargs
102
) -> AsyncResult:
103
prompt = format_prompt(messages) if prompt is None and conversation is None else prompt
104
prompt = format_image_prompt(messages, prompt)
105
106
session_hash = uuid.uuid4().hex if conversation is None else getattr(conversation, "session_hash", uuid.uuid4().hex)
107
async with StreamSession(proxy=proxy, impersonate="chrome") as session:
108
if api_key is None:
109
zerogpu_uuid, api_key = await get_zerogpu_token(cls.space, session, conversation, cookies)
110
if conversation is None or not hasattr(conversation, "session_hash"):
111
conversation = JsonConversation(session_hash=session_hash, zerogpu_token=api_key, zerogpu_uuid=zerogpu_uuid)
112
else:
113
conversation.zerogpu_token = api_key
114
if return_conversation:
115
yield conversation
116
117
if images is not None:
118
data = FormData()
119
mimi_types = [None for i in range(len(images))]
120
for i in range(len(images)):
121
mimi_types[i] = is_data_an_wav(images[i][0], images[i][1])
122
images[i] = (to_bytes(images[i][0]), images[i][1])
123
for image, image_name in images:
124
data.add_field(f"files", to_bytes(image), filename=image_name)
125
async with session.post(f"{cls.api_url}/gradio_api/upload", params={"upload_id": session_hash}, data=data) as response:
126
await raise_for_status(response)
127
image_files = await response.json()
128
images = [{
129
"path": image_file,
130
"url": f"{cls.api_url}/gradio_api/file={image_file}",
131
"orig_name": images[i][1],
132
"size": len(images[i][0]),
133
"mime_type": mimi_types[i] or is_accepted_format(images[i][0]),
134
"meta": {
135
"_type": "gradio.FileData"
136
}
137
} for i, image_file in enumerate(image_files)]
138
139
140
async with cls.run("predict", session, prompt, conversation, images) as response:
141
await raise_for_status(response)
142
143
async with cls.run("post", session, prompt, conversation, images) as response:
144
await raise_for_status(response)
145
146
async with cls.run("get", session, prompt, conversation) as response:
147
response: StreamResponse = response
148
async for line in response.iter_lines():
149
if line.startswith(b'data: '):
150
try:
151
json_data = json.loads(line[6:])
152
if json_data.get('msg') == 'process_completed':
153
if 'output' in json_data and 'error' in json_data['output']:
154
raise ResponseError("Missing images input" if json_data['output']['error'] and "AttributeError" in json_data['output']['error'] else json_data['output']['error'])
155
if 'output' in json_data and 'data' in json_data['output']:
156
yield json_data['output']['data'][0][-1]["content"]
157
break
158
159
except json.JSONDecodeError:
160
debug.log("Could not parse JSON:", line.decode(errors="replace"))
@@ -11,6 +11,7 @@ from .BlackForestLabsFlux1Schnell import BlackForestLabsFlux1Schnell
11
11
from .VoodoohopFlux1Schnell import VoodoohopFlux1Schnell
12
12
from .CohereForAI import CohereForAI
13
13
from .Janus_Pro_7B import Janus_Pro_7B
14
from .Phi_4 import Phi_4
14
15
from .Qwen_QVQ_72B import Qwen_QVQ_72B
15
16
from .Qwen_Qwen_2_5M_Demo import Qwen_Qwen_2_5M_Demo
16
17
from .Qwen_Qwen_2_72B_Instruct import Qwen_Qwen_2_72B_Instruct
@@ -19,7 +20,6 @@ from .G4F import G4F
19
20
20
21
class HuggingSpace(AsyncGeneratorProvider, ProviderModelMixin):
21
22
url = "https://huggingface.co/spaces"
22
parent = "HuggingFace"
23
23
24
24
working = True
25
25
@@ -32,6 +32,7 @@ class HuggingSpace(AsyncGeneratorProvider, ProviderModelMixin):
32
32
VoodoohopFlux1Schnell,
33
33
CohereForAI,
34
34
Janus_Pro_7B,
35
Phi_4,
35
36
Qwen_QVQ_72B,
36
37
Qwen_Qwen_2_5M_Demo,
37
38
Qwen_Qwen_2_72B_Instruct,
@@ -94,3 +95,14 @@ class HuggingSpace(AsyncGeneratorProvider, ProviderModelMixin):
94
95
error = e
95
96
if not is_started and error is not None:
96
97
raise error
98
99
BlackForestLabsFlux1Dev.parent = HuggingSpace.__name__,
100
BlackForestLabsFlux1Schnell.parent = HuggingSpace.__name__,
101
VoodoohopFlux1Schnell.parent = HuggingSpace.__name__,
102
CohereForAI.parent = HuggingSpace.__name__,
103
Janus_Pro_7B.parent = HuggingSpace.__name__,
104
Phi_4.parent = HuggingSpace.__name__,
105
Qwen_QVQ_72B.parent = HuggingSpace.__name__,
106
Qwen_Qwen_2_5M_Demo.parent = HuggingSpace.__name__,
107
Qwen_Qwen_2_72B_Instruct.parent = HuggingSpace.__name__,
108
StableDiffusion35Large.parent = HuggingSpace.__name__,
@@ -38,7 +38,7 @@ import g4f.debug
38
38
from g4f.client import AsyncClient, ChatCompletion, ImagesResponse, convert_to_provider
39
39
from g4f.providers.response import BaseConversation, JsonConversation
40
40
from g4f.client.helper import filter_none
41
from g4f.image import is_data_uri_an_image
41
from g4f.image import is_data_uri_an_media
42
42
from g4f.image.copy_images import images_dir, copy_images, get_source_url
43
43
from g4f.errors import ProviderNotFoundError, ModelNotFoundError, MissingAuthError, NoValidHarFileError
44
44
from g4f.cookies import read_cookie_files, get_cookies_dir
@@ -320,13 +320,13 @@ class Api:
320
320
321
321
if config.image is not None:
322
322
try:
323
is_data_uri_an_image(config.image)
323
is_data_uri_an_media(config.image)
324
324
except ValueError as e:
325
325
return ErrorResponse.from_message(f"The image you send must be a data URI. Example: data:image/jpeg;base64,...", status_code=HTTP_422_UNPROCESSABLE_ENTITY)
326
326
if config.images is not None:
327
327
for image in config.images:
328
328
try:
329
is_data_uri_an_image(image[0])
329
is_data_uri_an_media(image[0])
330
330
except ValueError as e:
331
331
example = json.dumps({"images": [["data:image/jpeg;base64,...", "filename"]]})
332
332
return ErrorResponse.from_message(f'The image you send must be a data URI. Example: {example}', status_code=HTTP_422_UNPROCESSABLE_ENTITY)
@@ -290,6 +290,8 @@ class Completions:
290
290
ignore_stream: Optional[bool] = False,
291
291
**kwargs
292
292
) -> ChatCompletion:
293
if isinstance(messages, str):
294
messages = [{"role": "user", "content": messages}]
293
295
if image is not None:
294
296
kwargs["images"] = [(image, image_name)]
295
297
model, provider = get_model_and_provider(
@@ -576,6 +578,8 @@ class AsyncCompletions:
576
578
ignore_stream: Optional[bool] = False,
577
579
**kwargs
578
580
) -> Awaitable[ChatCompletion]:
581
if isinstance(messages, str):
582
messages = [{"role": "user", "content": messages}]
579
583
if image is not None:
580
584
kwargs["images"] = [(image, image_name)]
581
585
model, provider = get_model_and_provider(
@@ -107,6 +107,7 @@
107
107
z-index: -1;
108
108
object-fit: contain;
109
109
width: 100%;
110
background: black;
110
111
}
111
112
112
113
.description, form p a {
@@ -195,8 +196,7 @@
195
196
<script type="importmap">
196
197
{
197
198
"imports": {
198
"@huggingface/hub": "https://cdn.jsdelivr.net/npm/@huggingface/hub@0.21.0/+esm",
199
"@huggingface/space-header": "https://cdn.jsdelivr.net/npm/@huggingface/space-header/+esm"
199
"@huggingface/hub": "https://cdn.jsdelivr.net/npm/@huggingface/hub@0.21.0/+esm"
200
200
}
201
201
}
202
202
</script>
@@ -233,15 +233,12 @@
233
233
</form>
234
234
<script type="module">
235
235
import * as hub from "@huggingface/hub";
236
import { init } from "@huggingface/space-header";
237
236
import { oauthLoginUrl, oauthHandleRedirectIfPresent } from "@huggingface/hub";
238
237
239
238
const isIframe = window.self !== window.top;
240
239
const button = document.querySelector('form a.button');
241
240
if (isIframe) {
242
241
button.classList.remove('hidden');
243
} else {
244
init("roxky/g4f-space-new");
245
242
}
246
243
247
244
const form = document.querySelector("form");
@@ -342,7 +339,7 @@
342
339
return;
343
340
}
344
341
const lower = data.prompt.toLowerCase();
345
const tags = ["nsfw", "timeline", "soap", "orally", "heel", "latex", "bathroom", "boobs", "charts", " text ", "gel", "logo", "infographic", "warts", " bra ", "prostitute", "curvy", "breasts", "written", "bodies", "naked", "classroom", "malone", "dirty", "shoes", "shower", "banner", "fat", "nipples", "couple", "sexual", "sandal", "supplier", "overlord", "succubus", "platinum", "cracy", "crazy", "lamic", "ropes", "cables", "wires", "dirty", "messy", "cluttered", "chaotic", "disorganized", "disorderly", "untidy", "unorganized", "unorderly", "unsystematic", "disarranged", "disarrayed", "disheveled", "disordered", "jumbled", "muddled", "scattered", "shambolic", "sloppy", "unkept", "unruly"];
342
const tags = ["nsfw", "timeline", "blood", "soap", "orally", "heel", "latex", "bathroom", "boobs", "charts", " text ", "gel", "logo", "infographic", "warts", " bra ", "prostitute", "curvy", "breasts", "written", "bodies", "naked", "classroom", "malone", "dirty", "shoes", "shower", "banner", "fat", "nipples", "couple", "sexual", "sandal", "supplier", "overlord", "succubus", "platinum", "cracy", "crazy", "lamic", "ropes", "cables", "wires", "dirty", "messy", "cluttered", "chaotic", "disorganized", "disorderly", "untidy", "unorganized", "unorderly", "unsystematic", "disarranged", "disarrayed", "disheveled", "disordered", "jumbled", "muddled", "scattered", "shambolic", "sloppy", "unkept", "unruly"];
346
343
for (i in tags) {
347
344
if (lower.indexOf(tags[i]) != -1) {
348
345
console.log("Skipping image with tag: " + tags[i]);
@@ -355,6 +352,9 @@
355
352
eventSource.onerror = (event) => {
356
353
eventSource.close();
357
354
}
355
imageFeed.onerror = () => {
356
imageFeed.classList.add("hidden");
357
}
358
358
setInterval(() => {
359
359
if (images.length > 0) {
360
360
imageFeed.classList.remove("hidden");
@@ -76,6 +76,25 @@ def is_allowed_extension(filename: str) -> bool:
76
76
return '.' in filename and \
77
77
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
78
78
79
def is_data_uri_an_media(data_uri: str) -> str:
80
return is_data_an_wav(data_uri) or is_data_uri_an_image(data_uri)
81
82
def is_data_an_wav(data_uri: str, filename: str = None) -> str:
83
"""
84
Checks if the given data URI represents an image.
85
86
Args:
87
data_uri (str): The data URI to check.
88
89
Raises:
90
ValueError: If the data URI is invalid or the image format is not allowed.
91
"""
92
if filename and filename.endswith(".wav"):
93
return "audio/wav"
94
# Check if the data URI starts with 'data:image' and contains an image format (e.g., jpeg, png, gif)
95
if isinstance(data_uri, str) and re.match(r'data:audio/wav;base64,', data_uri):
96
return "audio/wav"
97
79
98
def is_data_uri_an_image(data_uri: str) -> bool:
80
99
"""
81
100
Checks if the given data URI represents an image.
@@ -199,7 +218,7 @@ def to_bytes(image: ImageType) -> bytes:
199
218
if isinstance(image, bytes):
200
219
return image
201
220
elif isinstance(image, str) and image.startswith("data:"):
202
is_data_uri_an_image(image)
221
is_data_uri_an_media(image)
203
222
return extract_data_uri(image)
204
223
elif isinstance(image, Image):
205
224
bytes_io = BytesIO()