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

XFEstudio/gpt4free

Add Phi_4 provider, Update demo template

7d0102d1
hlohaus <983577+hlohaus@users.noreply.github.com>
提交于

代码差异

6 个文件 +206 -11
Added g4f/Provider/hf_space/Phi_4.py +160 -0
@@ -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"))
Modified g4f/Provider/hf_space/__init__.py +13 -1
@@ -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__,
Modified g4f/api/__init__.py +3 -3
@@ -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)
Modified g4f/client/__init__.py +4 -0
@@ -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(
Modified g4f/gui/client/demo.html +6 -6
@@ -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");
Modified g4f/image/__init__.py +20 -1
@@ -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()