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

XFEstudio/gpt4free

Set default model in HuggingFaceMedia Improve handling of shared chats Show api_key input if required

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

代码差异

18 个文件 +206 -111
Added etc/examples/video.py +19 -0
@@ -0,0 +1,19 @@
1 import g4f.Provider
2 from g4f.client import Client
3
4 client = Client(
5 provider=g4f.Provider.HuggingFaceMedia,
6 api_key="hf_***" # Your API key here
7 )
8
9 video_models = client.models.get_video()
10
11 print(video_models)
12
13 result = client.media.generate(
14 model=video_models[0],
15 prompt="G4F AI technology is the best in the world.",
16 response_format="url"
17 )
18
19 print(result.data[0].url)
Modified g4f/Provider/hf/HuggingFaceMedia.py +8 -4
@@ -66,6 +66,8 @@ class HuggingFaceMedia(AsyncGeneratorProvider, ProviderModelMixin):
66 66 for provider_data in provider_keys:
67 67 prepend_models.append(f"{model}:{provider_data.get('provider')}")
68 68 cls.models = prepend_models + [model for model in new_models if model not in prepend_models]
69 cls.image_models = [model for model, task in cls.task_mapping.items() if task == "text-to-image"]
70 cls.video_models = [model for model, task in cls.task_mapping.items() if task == "text-to-video"]
69 71 else:
70 72 cls.models = []
71 73 return cls.models
@@ -99,12 +101,14 @@ class HuggingFaceMedia(AsyncGeneratorProvider, ProviderModelMixin):
99 101 prompt: str = None,
100 102 proxy: str = None,
101 103 timeout: int = 0,
102 aspect_ratio: str = "1:1",
104 aspect_ratio: str = None,
103 105 **kwargs
104 106 ):
105 107 selected_provider = None
106 if ":" in model:
108 if model and ":" in model:
107 109 model, selected_provider = model.split(":", 1)
110 elif not model:
111 model = cls.get_models()[0]
108 112 provider_mapping = await cls.get_mapping(model, api_key)
109 113 headers = {
110 114 'Accept-Encoding': 'gzip, deflate',
@@ -133,11 +137,11 @@ class HuggingFaceMedia(AsyncGeneratorProvider, ProviderModelMixin):
133 137 extra_data = {
134 138 "num_inference_steps": 20,
135 139 "resolution": "480p",
136 "aspect_ratio": aspect_ratio,
140 "aspect_ratio": "16:9" if aspect_ratio is None else aspect_ratio,
137 141 **extra_data
138 142 }
139 143 else:
140 extra_data = use_aspect_ratio(extra_data, aspect_ratio)
144 extra_data = use_aspect_ratio(extra_data, "1:1" if aspect_ratio is None else aspect_ratio)
141 145 if provider_key == "fal-ai":
142 146 url = f"{api_base}/{provider_id}"
143 147 data = {
Modified g4f/Provider/needs_auth/Grok.py +2 -1
@@ -30,6 +30,7 @@ class Grok(AsyncAuthedProvider, ProviderModelMixin):
30 30
31 31 default_model = "grok-3"
32 32 models = [default_model, "grok-3-thinking", "grok-2"]
33 model_aliases = {"grok-3-r1": "grok-3-thinking"}
33 34
34 35 @classmethod
35 36 async def on_auth_async(cls, cookies: Cookies = None, proxy: str = None, **kwargs) -> AsyncIterator:
@@ -73,7 +74,7 @@ class Grok(AsyncAuthedProvider, ProviderModelMixin):
73 74 "sendFinalMetadata": True,
74 75 "customInstructions": "",
75 76 "deepsearchPreset": "",
76 "isReasoning": model.endswith("-thinking"),
77 "isReasoning": model.endswith("-thinking") or model.endswith("-r1"),
77 78 }
78 79
79 80 @classmethod
Modified g4f/Provider/template/OpenaiTemplate.py +2 -2
@@ -92,7 +92,7 @@ class OpenaiTemplate(AsyncGeneratorProvider, ProviderModelMixin, RaiseErrorMixin
92 92 }
93 93 async with session.post(f"{api_base.rstrip('/')}/images/generations", json=data, ssl=cls.ssl) as response:
94 94 data = await response.json()
95 cls.raise_error(data)
95 cls.raise_error(data, response.status)
96 96 await raise_for_status(response)
97 97 yield ImageResponse([image["url"] for image in data["data"]], prompt)
98 98 return
@@ -135,7 +135,7 @@ class OpenaiTemplate(AsyncGeneratorProvider, ProviderModelMixin, RaiseErrorMixin
135 135 content_type = response.headers.get("content-type", "text/event-stream" if stream else "application/json")
136 136 if content_type.startswith("application/json"):
137 137 data = await response.json()
138 cls.raise_error(data)
138 cls.raise_error(data, response.status)
139 139 await raise_for_status(response)
140 140 choice = data["choices"][0]
141 141 if "content" in choice["message"] and choice["message"]["content"]:
Modified g4f/api/__init__.py +5 -0
@@ -10,6 +10,7 @@ from email.utils import formatdate
10 10 import os.path
11 11 import hashlib
12 12 import asyncio
13 from urllib.parse import quote_plus
13 14 from fastapi import FastAPI, Response, Request, UploadFile, Depends
14 15 from fastapi.middleware.wsgi import WSGIMiddleware
15 16 from fastapi.responses import StreamingResponse, RedirectResponse, HTMLResponse, JSONResponse
@@ -562,6 +563,10 @@ class Api:
562 563 })
563 564 async def get_media(filename, request: Request):
564 565 target = os.path.join(images_dir, os.path.basename(filename))
566 if not os.path.isfile(target):
567 other_name = os.path.join(images_dir, os.path.basename(quote_plus(filename)))
568 if os.path.isfile(other_name):
569 target = other_name
565 570 ext = os.path.splitext(filename)[1][1:]
566 571 mime_type = EXTENSIONS_MAP.get(ext)
567 572 stat_result = SimpleNamespace()
Modified g4f/client/__init__.py +15 -9
@@ -19,7 +19,7 @@ from ..providers.asyncio import to_sync_generator
19 19 from ..Provider.needs_auth import BingCreateImages, OpenaiAccount
20 20 from ..tools.run_tools import async_iter_run_tools, iter_run_tools
21 21 from .stubs import ChatCompletion, ChatCompletionChunk, Image, ImagesResponse, UsageModel, ToolCallModel
22 from .image_models import ImageModels
22 from .image_models import MediaModels
23 23 from .types import IterResponse, ImageProvider, Client as BaseClient
24 24 from .service import get_model_and_provider, convert_to_provider
25 25 from .helper import find_stop, filter_json, filter_none, safe_aclose
@@ -267,8 +267,11 @@ class Client(BaseClient):
267 267 ) -> None:
268 268 super().__init__(**kwargs)
269 269 self.chat: Chat = Chat(self, provider)
270 if image_provider is None:
271 image_provider = provider
272 self.models: MediaModels = MediaModels(self, image_provider)
270 273 self.images: Images = Images(self, image_provider)
271 self.media: Images = Images(self, image_provider)
274 self.media: Images = self.images
272 275
273 276 class Completions:
274 277 def __init__(self, client: Client, provider: Optional[ProviderType] = None):
@@ -349,7 +352,6 @@ class Images:
349 352 def __init__(self, client: Client, provider: Optional[ProviderType] = None):
350 353 self.client: Client = client
351 354 self.provider: Optional[ProviderType] = provider
352 self.models: ImageModels = ImageModels(client)
353 355
354 356 def generate(
355 357 self,
@@ -369,7 +371,7 @@ class Images:
369 371 if provider is None:
370 372 provider_handler = self.provider
371 373 if provider_handler is None:
372 provider_handler = self.models.get(model, default)
374 provider_handler = self.client.models.get(model, default)
373 375 elif isinstance(provider, str):
374 376 provider_handler = convert_to_provider(provider)
375 377 else:
@@ -385,19 +387,21 @@ class Images:
385 387 provider: Optional[ProviderType] = None,
386 388 response_format: Optional[str] = None,
387 389 proxy: Optional[str] = None,
390 api_key: Optional[str] = None,
388 391 **kwargs
389 392 ) -> ImagesResponse:
390 393 provider_handler = await self.get_provider_handler(model, provider, BingCreateImages)
391 394 provider_name = provider_handler.__name__ if hasattr(provider_handler, "__name__") else type(provider_handler).__name__
392 395 if proxy is None:
393 396 proxy = self.client.proxy
394
397 if api_key is None:
398 api_key = self.client.api_key
395 399 error = None
396 400 response = None
397 401 if isinstance(provider_handler, IterListProvider):
398 402 for provider in provider_handler.providers:
399 403 try:
400 response = await self._generate_image_response(provider, provider.__name__, model, prompt, **kwargs)
404 response = await self._generate_image_response(provider, provider.__name__, model, prompt, proxy=proxy, **kwargs)
401 405 if response is not None:
402 406 provider_name = provider.__name__
403 407 break
@@ -405,7 +409,7 @@ class Images:
405 409 error = e
406 410 debug.error(f"{provider.__name__} {type(e).__name__}: {e}")
407 411 else:
408 response = await self._generate_image_response(provider_handler, provider_name, model, prompt, **kwargs)
412 response = await self._generate_image_response(provider_handler, provider_name, model, prompt, proxy=proxy, api_key=api_key, **kwargs)
409 413
410 414 if isinstance(response, MediaResponse):
411 415 return await self._process_image_response(
@@ -534,7 +538,7 @@ class Images:
534 538 else:
535 539 # Save locally for None (default) case
536 540 images = await copy_media(response.get_list(), response.get("cookies"), proxy)
537 images = [Image.model_construct(url=f"/media/{os.path.basename(image)}", revised_prompt=response.alt) for image in images]
541 images = [Image.model_construct(url=image, revised_prompt=response.alt) for image in images]
538 542
539 543 return ImagesResponse.model_construct(
540 544 created=int(time.time()),
@@ -552,6 +556,9 @@ class AsyncClient(BaseClient):
552 556 ) -> None:
553 557 super().__init__(**kwargs)
554 558 self.chat: AsyncChat = AsyncChat(self, provider)
559 if image_provider is None:
560 image_provider = provider
561 self.models: MediaModels = MediaModels(self, image_provider)
555 562 self.images: AsyncImages = AsyncImages(self, image_provider)
556 563 self.media: AsyncImages = self.images
557 564
@@ -635,7 +642,6 @@ class AsyncImages(Images):
635 642 def __init__(self, client: AsyncClient, provider: Optional[ProviderType] = None):
636 643 self.client: AsyncClient = client
637 644 self.provider: Optional[ProviderType] = provider
638 self.models: ImageModels = ImageModels(client)
639 645
640 646 async def generate(
641 647 self,
Modified g4f/client/image_models.py +33 -5
@@ -1,15 +1,43 @@
1 1 from __future__ import annotations
2 2
3 from ..models import ModelUtils
3 from ..models import ModelUtils, ImageModel
4 4 from ..Provider import ProviderUtils
5 from ..providers.types import ProviderType
5 6
6 class ImageModels():
7 def __init__(self, client):
7 class MediaModels():
8 def __init__(self, client, provider: ProviderType = None):
8 9 self.client = client
10 self.provider = provider
9 11
10 def get(self, name, default=None):
12 def get(self, name, default=None) -> ProviderType:
11 13 if name in ModelUtils.convert:
12 14 return ModelUtils.convert[name].best_provider
13 15 if name in ProviderUtils.convert:
14 16 return ProviderUtils.convert[name]
15 return default
17 return default
18
19 def get_all(self, api_key: str = None, **kwargs) -> list[str]:
20 if self.provider is None:
21 return []
22 if api_key is None:
23 api_key = self.client.api_key
24 return self.provider.get_models(
25 **kwargs,
26 **{} if api_key is None else {"api_key": api_key}
27 )
28
29 def get_image(self, **kwargs) -> list[str]:
30 if self.provider is None:
31 return [model_id for model_id, model in ModelUtils.convert.items() if isinstance(model, ImageModel)]
32 self.get_all(**kwargs)
33 if hasattr(self.provider, "image_models"):
34 return self.provider.image_models
35 return []
36
37 def get_video(self, **kwargs) -> list[str]:
38 if self.provider is None:
39 return []
40 self.get_all(**kwargs)
41 if hasattr(self.provider, "video_models"):
42 return self.provider.video_models
43 return []
Modified g4f/gui/client/index.html +6 -3
@@ -61,9 +61,12 @@
61 61 const gpt_image = '<img src="/static/img/gpt.png" alt="your avatar">';
62 62 </script>
63 63 <script src="/static/js/highlight.min.js" async></script>
64
65 <script>window.conversation_id = "{{conversation_id}}"</script>
66 <script>window.chat_id = "{{chat_id}}"; window.share_url = "{{share_url}}";</script>
64 <script>
65 window.conversation_id = "{{conversation_id}}";
66 window.chat_id = "{{chat_id}}";
67 window.share_url = "{{share_url}}";
68 window.start_id = "{{conversation_id}}";
69 </script>
67 70 <title>G4F Chat</title>
68 71 </head>
69 72 <body>
Modified g4f/gui/client/qrcode.html +2 -2
@@ -7,9 +7,9 @@
7 7 <script src="https://cdn.jsdelivr.net/npm/qrcodejs/qrcode.min.js"></script>
8 8 <style>
9 9 body { font-family: Arial, sans-serif; text-align: center; margin: 20px; }
10 video { width: 400px; height: 400px; border: 1px solid black; display: block; margin: auto; object-fit: cover;}
10 video { width: 400px; height: 400px; border: 1px solid black; display: block; margin: auto; object-fit: cover; max-width: 100%;}
11 11 #qrcode { margin-top: 20px; }
12 #qrcode img, #qrcode canvas { margin: 0 auto; width: 400px; height: 400px; }
12 #qrcode img, #qrcode canvas { margin: 0 auto; width: 400px; height: 400px; max-width: 100%;}
13 13 button { margin: 5px; padding: 10px; }
14 14 </style>
15 15 </head>
Modified g4f/gui/client/static/css/style.css +8 -8
@@ -881,7 +881,7 @@ input.model:hover
881 881 padding: var(--inner-gap) 28px;
882 882 }
883 883
884 #systemPrompt, #chatPrompt, .settings textarea, form textarea {
884 #systemPrompt, #chatPrompt, .settings textarea, form textarea, .chat-body textarea {
885 885 font-size: 15px;
886 886 color: var(--colour-3);
887 887 outline: none;
@@ -1305,7 +1305,7 @@ form textarea {
1305 1305 padding: 0;
1306 1306 }
1307 1307
1308 .settings textarea {
1308 .settings textarea, .chat-body textarea {
1309 1309 height: 30px;
1310 1310 min-height: 30px;
1311 1311 padding: 6px;
@@ -1315,7 +1315,7 @@ form textarea {
1315 1315 text-wrap: nowrap;
1316 1316 }
1317 1317
1318 form .field .fa-xmark {
1318 .field .fa-xmark {
1319 1319 line-height: 20px;
1320 1320 cursor: pointer;
1321 1321 margin-left: auto;
@@ -1323,11 +1323,11 @@ form .field .fa-xmark {
1323 1323 margin-top: 0;
1324 1324 }
1325 1325
1326 form .field.saved .fa-xmark {
1326 .field.saved .fa-xmark {
1327 1327 color: var(--accent)
1328 1328 }
1329 1329
1330 .settings .field, form .field {
1330 .settings .field, form .field, .chat-body .field {
1331 1331 padding: var(--inner-gap) var(--inner-gap) var(--inner-gap) 0;
1332 1332 }
1333 1333
@@ -1359,7 +1359,7 @@ form .field.saved .fa-xmark {
1359 1359 border: none;
1360 1360 }
1361 1361
1362 .settings input, form input {
1362 .settings input, form input, .chat-body input {
1363 1363 background-color: transparent;
1364 1364 padding: 2px;
1365 1365 border: none;
@@ -1368,11 +1368,11 @@ form .field.saved .fa-xmark {
1368 1368 color: var(--colour-3);
1369 1369 }
1370 1370
1371 .settings input:focus, form input:focus {
1371 .settings input:focus, form input:focus, .chat-body input:focus {
1372 1372 outline: none;
1373 1373 }
1374 1374
1375 .settings .label, form .label, .settings label, form label {
1375 .settings .label, form .label, .settings label, form label, .chat-body label {
1376 1376 font-size: 15px;
1377 1377 margin-left: var(--inner-gap);
1378 1378 }
Modified g4f/gui/client/static/js/chat.v1.js +30 -9
@@ -28,7 +28,7 @@ const switchInput = document.getElementById("switch");
28 28 const searchButton = document.getElementById("search");
29 29 const paperclip = document.querySelector(".user-input .fa-paperclip");
30 30
31 const optionElementsSelector = ".settings input, .settings textarea, #model, #model2, #provider";
31 const optionElementsSelector = ".settings input, .settings textarea, .chat-body input, #model, #model2, #provider";
32 32
33 33 let provider_storage = {};
34 34 let message_storage = {};
@@ -153,7 +153,7 @@ const iframe_close = Object.assign(document.createElement("button"), {
153 153 });
154 154 iframe_close.onclick = () => iframe_container.classList.add("hidden");
155 155 iframe_container.appendChild(iframe_close);
156 chat.appendChild(iframe_container);
156 document.body.appendChild(iframe_container);
157 157
158 158 class HtmlRenderPlugin {
159 159 constructor(options = {}) {
@@ -843,6 +843,16 @@ async function add_message_chunk(message, message_id, provider, scroll, finish_m
843 843 conversation.data[key] = value;
844 844 }
845 845 await save_conversation(conversation_id, conversation);
846 } else if (message.type == "auth") {
847 error_storage[message_id] = message.message
848 content_map.inner.innerHTML += markdown_render(`**An error occured:** ${message.message}`);
849 let provider = provider_storage[message_id]?.name;
850 let configEl = document.querySelector(`.settings .${provider}-api_key`);
851 if (configEl) {
852 configEl = configEl.parentElement.cloneNode(true);
853 content_map.content.appendChild(configEl);
854 await register_settings_storage();
855 }
846 856 } else if (message.type == "provider") {
847 857 provider_storage[message_id] = message.provider;
848 858 let provider_el = content_map.content.querySelector('.provider');
@@ -1122,10 +1132,6 @@ const ask_gpt = async (message_id, message_index = -1, regenerate = false, provi
1122 1132 let api_key;
1123 1133 if (is_demo && !provider) {
1124 1134 api_key = localStorage.getItem("HuggingFace-api_key");
1125 if (!api_key) {
1126 location.href = "/";
1127 return;
1128 }
1129 1135 } else {
1130 1136 api_key = get_api_key_by_provider(provider);
1131 1137 }
@@ -1221,6 +1227,7 @@ function sanitize(input, replacement) {
1221 1227 }
1222 1228
1223 1229 async function set_conversation_title(conversation_id, title) {
1230 window.chat_id = null;
1224 1231 conversation = await get_conversation(conversation_id)
1225 1232 conversation.new_title = title;
1226 1233 const new_id = sanitize(title, " ");
@@ -1742,12 +1749,22 @@ const load_conversations = async () => {
1742 1749
1743 1750 let html = [];
1744 1751 conversations.forEach((conversation) => {
1752 // const length = conversation.items.map((item) => (
1753 // !item.content.toLowerCase().includes("hello") &&
1754 // !item.content.toLowerCase().includes("hi") &&
1755 // item.content
1756 // ) ? 1 : 0).reduce((a,b)=>a+b, 0);
1757 // if (!length) {
1758 // appStorage.removeItem(`conversation:${conversation.id}`);
1759 // return;
1760 // }
1761 const shareIcon = (conversation.id == window.start_id && window.chat_id) ? '<i class="fa-solid fa-qrcode"></i>': '';
1745 1762 html.push(`
1746 1763 <div class="convo" id="convo-${conversation.id}">
1747 1764 <div class="left" onclick="set_conversation('${conversation.id}')">
1748 1765 <i class="fa-regular fa-comments"></i>
1749 1766 <span class="datetime">${conversation.updated ? toLocaleDateString(conversation.updated) : ""}</span>
1750 <span class="convo-title">${escapeHtml(conversation.new_title ? conversation.new_title : conversation.title)}</span>
1767 <span class="convo-title">${shareIcon} ${escapeHtml(conversation.new_title ? conversation.new_title : conversation.title)}</span>
1751 1768 </div>
1752 1769 <i onclick="show_option('${conversation.id}')" class="fa-solid fa-ellipsis-vertical" id="conv-${conversation.id}"></i>
1753 1770 <div id="cho-${conversation.id}" class="choise" style="display:none;">
@@ -2060,7 +2077,6 @@ window.addEventListener('load', async function() {
2060 2077 if (!window.conversation_id) {
2061 2078 window.conversation_id = window.chat_id;
2062 2079 }
2063 window.start_id = window.conversation_id
2064 2080 const response = await fetch(`${window.share_url}/backend-api/v2/chat/${window.chat_id ? window.chat_id : window.conversation_id}`, {
2065 2081 headers: {'accept': 'application/json'},
2066 2082 });
@@ -2075,6 +2091,7 @@ window.addEventListener('load', async function() {
2075 2091 `conversation:${conversation.id}`,
2076 2092 JSON.stringify(conversation)
2077 2093 );
2094 await load_conversations();
2078 2095 let refreshOnHide = true;
2079 2096 document.addEventListener("visibilitychange", () => {
2080 2097 if (document.hidden) {
@@ -2091,6 +2108,9 @@ window.addEventListener('load', async function() {
2091 2108 if (!refreshOnHide) {
2092 2109 return;
2093 2110 }
2111 if (window.conversation_id != window.start_id) {
2112 return;
2113 }
2094 2114 const response = await fetch(`${window.share_url}/backend-api/v2/chat/${window.chat_id}`, {
2095 2115 headers: {'accept': 'application/json', 'if-none-match': conversation.updated},
2096 2116 });
@@ -2102,6 +2122,7 @@ window.addEventListener('load', async function() {
2102 2122 `conversation:${conversation.id}`,
2103 2123 JSON.stringify(conversation)
2104 2124 );
2125 await load_conversations();
2105 2126 await load_conversation(conversation);
2106 2127 }
2107 2128 }
@@ -2284,7 +2305,7 @@ async function on_api() {
2284 2305 }
2285 2306 } else if (provider.login_url) {
2286 2307 if (!login_urls[provider.name]) {
2287 login_urls[provider.name] = [provider.label, provider.login_url, [], provider.auth];
2308 login_urls[provider.name] = [provider.label, provider.login_url, [provider.name], provider.auth];
2288 2309 } else {
2289 2310 login_urls[provider.name][0] = provider.label;
2290 2311 login_urls[provider.name][1] = provider.login_url;
Modified g4f/gui/server/api.py +5 -2
@@ -7,7 +7,7 @@ from typing import Iterator
7 7 from flask import send_from_directory
8 8 from inspect import signature
9 9
10 from ...errors import VersionNotFoundError
10 from ...errors import VersionNotFoundError, MissingAuthError
11 11 from ...image.copy_images import copy_media, ensure_images_dir, images_dir
12 12 from ...tools.run_tools import iter_run_tools
13 13 from ...Provider import ProviderUtils, __providers__
@@ -187,7 +187,8 @@ class Api:
187 187 media = chunk
188 188 if download_media or chunk.get("cookies"):
189 189 chunk.alt = format_image_prompt(kwargs.get("messages"), chunk.alt)
190 media = asyncio.run(copy_media(chunk.get_list(), chunk.get("cookies"), chunk.get("headers"), proxy=proxy, alt=chunk.alt))
190 tags = [tag for tag in [model, kwargs.get("aspect_ratio")] if tag]
191 media = asyncio.run(copy_media(chunk.get_list(), chunk.get("cookies"), chunk.get("headers"), proxy=proxy, alt=chunk.alt, tags=tags))
191 192 media = ImageResponse(media, chunk.alt) if isinstance(chunk, ImageResponse) else VideoResponse(media, chunk.alt)
192 193 yield self._format_json("content", str(media), images=chunk.get_list(), alt=chunk.alt)
193 194 elif isinstance(chunk, SynthesizeData):
@@ -214,6 +215,8 @@ class Api:
214 215 yield self._format_json(chunk.type, **chunk.get_dict())
215 216 else:
216 217 yield self._format_json("content", str(chunk))
218 except MissingAuthError as e:
219 yield self._format_json('auth', type(e).__name__, message=get_error_message(e))
217 220 except Exception as e:
218 221 logger.exception(e)
219 222 debug.error(e)
Modified g4f/gui/server/backend_api.py +32 -8
Modified g4f/image/__init__.py +5 -13
Modified g4f/image/copy_images.py +4 -2
Modified g4f/models.py +9 -30
Modified g4f/providers/base_provider.py +5 -1
Modified g4f/requests/raise_for_status.py +16 -12