返回提交历史
Modified
g4f/Provider/PollinationsAI.py
+0
-2
Modified
g4f/Provider/needs_auth/GeminiPro.py
+19
-9
Modified
g4f/image/__init__.py
+1
-1
Modified
g4f/image/copy_images.py
+9
-2
Modified
g4f/requests/__init__.py
+26
-1
XFEstudio/gpt4free
Support inlineData in GeminiPro
315495e1
代码差异
5 个文件
+55
-15
@@ -339,7 +339,6 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
339
339
extra_parameters=extra_parameters,
340
340
referrer=referrer,
341
341
api_key=api_key,
342
download_media=download_media,
343
342
extra_body=extra_body,
344
343
**kwargs
345
344
):
@@ -465,7 +464,6 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
465
464
extra_parameters: list[str],
466
465
referrer: str,
467
466
api_key: str,
468
download_media: bool,
469
467
extra_body: dict,
470
468
**kwargs
471
469
) -> AsyncResult:
@@ -9,10 +9,11 @@ from aiohttp import ClientSession, BaseConnector
9
9
from ...typing import AsyncResult, Messages, MediaListType
10
10
from ...image import to_bytes, is_data_an_media
11
11
from ...errors import MissingAuthError, ModelNotFoundError
12
from ...requests.raise_for_status import raise_for_status
12
from ...requests import raise_for_status, iter_lines
13
13
from ...providers.response import Usage, FinishReason
14
from ...image.copy_images import save_response_media
14
15
from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin
15
from ..helper import get_connector, to_string
16
from ..helper import get_connector, to_string, format_media_prompt
16
17
from ... import debug
17
18
18
19
class GeminiPro(AsyncGeneratorProvider, ProviderModelMixin):
@@ -123,6 +124,7 @@ class GeminiPro(AsyncGeneratorProvider, ProviderModelMixin):
123
124
"maxOutputTokens": kwargs.get("max_tokens"),
124
125
"topP": kwargs.get("top_p"),
125
126
"topK": kwargs.get("top_k"),
127
**{"responseModalities": ["AUDIO"]} if "tts" in model else {},
126
128
},
127
129
"tools": [{
128
130
"function_declarations": [{
@@ -152,16 +154,24 @@ class GeminiPro(AsyncGeneratorProvider, ProviderModelMixin):
152
154
raise RuntimeError(f"Response {response.status}: {data['error']['message']}")
153
155
if stream:
154
156
lines = []
155
async for chunk in response.content:
156
if chunk == b"[{\n":
157
lines = [b"{\n"]
158
elif chunk == b",\r\n" or chunk == b"]":
157
buffer = b""
158
async for chunk in iter_lines(response.content.iter_any()):
159
buffer += chunk
160
if chunk == b"[{":
161
lines = [b"{"]
162
elif chunk == b"," or chunk == b"]":
159
163
try:
160
164
data = b"".join(lines)
161
165
data = json.loads(data)
162
166
content = data["candidates"][0]["content"]
163
if "parts" in content:
164
yield content["parts"][0]["text"]
167
if "parts" in content and content["parts"]:
168
if "text" in content["parts"][0]:
169
yield content["parts"][0]["text"]
170
elif "inlineData" in content["parts"][0]:
171
async for media in save_response_media(
172
content["parts"][0]["inlineData"], format_media_prompt(messages)
173
):
174
yield media
165
175
if "finishReason" in data["candidates"][0]:
166
176
yield FinishReason(data["candidates"][0]["finishReason"].lower())
167
177
usage = data.get("usageMetadata")
@@ -173,7 +183,7 @@ class GeminiPro(AsyncGeneratorProvider, ProviderModelMixin):
173
183
)
174
184
except Exception as e:
175
185
data = data.decode(errors="ignore") if isinstance(data, bytes) else data
176
raise RuntimeError(f"Read chunk failed: {data}") from e
186
raise RuntimeError(f"Read chunk failed") from e
177
187
lines = []
178
188
else:
179
189
lines.append(chunk)
@@ -257,7 +257,7 @@ def to_bytes(image: ImageType) -> bytes:
257
257
raise FileNotFoundError(f"File not found: {path}")
258
258
else:
259
259
raise ValueError("Invalid image format. Expected bytes, str, or PIL Image.")
260
elif isinstance(image, Image):
260
elif isinstance(image, Image.Image):
261
261
bytes_io = BytesIO()
262
262
image.save(bytes_io, image.format)
263
263
image.seek(0)
@@ -60,11 +60,18 @@ def update_filename(response, filename: str) -> str:
60
60
timestamp = datetime.strptime(date, '%a, %d %b %Y %H:%M:%S %Z').timestamp()
61
61
return str(int(timestamp)) + "_" + filename.split("_", maxsplit=1)[-1]
62
62
63
async def save_response_media(response, prompt: str, tags: list[str]) -> AsyncIterator:
63
async def save_response_media(response, prompt: str, tags: list[str] = []) -> AsyncIterator:
64
64
"""Save media from response to local file and return URL"""
65
if isinstance(response, dict):
66
content_type = response.get("mimeType")
67
response = response.get("data")
68
elif hasattr(response, "headers"):
69
content_type = response.headers["content-type"]
70
else:
71
content_type = "audio/mpeg"
72
65
73
if isinstance(response, str):
66
74
response = base64.b64decode(response)
67
content_type = response.headers["content-type"] if hasattr(response, "headers") else "audio/mpeg"
68
75
extension = MEDIA_TYPE_MAP.get(content_type)
69
76
if extension is None:
70
77
raise ValueError(f"Unsupported media type: {content_type}")
@@ -207,4 +207,29 @@ async def see_stream(iter_lines: Iterator[bytes]) -> AsyncIterator[dict]:
207
207
if line.startswith(b"data: "):
208
208
if line[6:].startswith(b"[DONE]"):
209
209
break
210
yield json.loads(line[6:])
210
yield json.loads(line[6:])
211
212
async def iter_lines(iter_response: AsyncIterator[bytes], delimiter=None):
213
"""
214
iterate streaming content line by line, separated by ``\\n``.
215
216
Copied from: https://requests.readthedocs.io/en/latest/_modules/requests/models/
217
which is under the License: Apache 2.0
218
"""
219
pending = None
220
221
async for chunk in iter_response:
222
if pending is not None:
223
chunk = pending + chunk
224
lines = chunk.split(delimiter) if delimiter else chunk.splitlines()
225
pending = (
226
lines.pop()
227
if lines and lines[-1] and chunk and lines[-1][-1] == chunk[-1]
228
else None
229
)
230
231
for line in lines:
232
yield line
233
234
if pending is not None:
235
yield pending