返回提交历史
Modified
docs/file.md
+30
-1
Modified
g4f/Provider/ARTA.py
+20
-50
Modified
g4f/Provider/AllenAI.py
+12
-28
Modified
g4f/Provider/Blackbox.py
+3
-2
Modified
g4f/tools/files.py
+6
-5
Modified
g4f/tools/run_tools.py
+10
-11
Modified
g4f/typing.py
+1
-7
XFEstudio/gpt4free
feat: improve file handling and streamline provider implementations
- Added file upload usage example with bucket_id in docs/file.md - Fixed ARTA provider by refactoring error handling with a new raise_error function - Simplified aspect_ratio handling in ARTA with proper default - Improved AllenAI provider by cleaning up image handling logic - Fixed Blackbox provider's media handling to properly process images - Updated file tools to handle URL downloads correctly - Fixed bucket_id pattern matching in ToolHandler.process_bucket_tool - Cleaned up imports in typing.py by removing unnecessary sys import - Fixed inconsistent function parameters in g4f/tools/files.py - Fixed return value of upload_and_process function to return bucket_id
c083f852
代码差异
7 个文件
+82
-104
@@ -75,16 +75,45 @@ def upload_and_process(files_or_urls, bucket_id=None):
75
75
else:
76
76
print(f"Unhandled SSE event: {line}")
77
77
response.close()
78
return bucket_id5
78
79
79
80
# Example with URLs
80
81
urls = [{"url": "https://github.com/xtekky/gpt4free/issues"}]
81
82
bucket_id = upload_and_process(urls)
82
83
83
84
#Example with files
84
files = {'files': open('document.pdf', 'rb'), 'files': open('data.json', 'rb')}
85
files = {'files': ('document.pdf', open('document.pdf', 'rb'))}
85
86
bucket_id = upload_and_process(files)
86
87
```
87
88
89
**Usage of Uploaded Files:**
90
```python
91
from g4f.client import Client
92
93
# Enable debug mode
94
import g4f.debug
95
g4f.debug.logging = True
96
97
client = Client()
98
99
# Upload example file
100
files = {'files': ('demo.docx', open('demo.docx', 'rb'))}
101
bucket_id = upload_and_process(files)
102
103
# Send request with file:
104
response = client.chat.completions.create(
105
[{"role": "user", "content": [
106
{"type": "text", "text": "Discribe this file."},
107
{"bucket_id": bucket_id}
108
]}],
109
)
110
print(response.choices[0].message.content)
111
```
112
113
**Example Output:**
114
```
115
This document is a demonstration of the DOCX Input plugin capabilities in the software ...
116
```
88
117
89
118
**Example Usage (JavaScript):**
90
119
@@ -5,7 +5,7 @@ import time
5
5
import json
6
6
import random
7
7
from pathlib import Path
8
from aiohttp import ClientSession
8
from aiohttp import ClientSession, ClientResponse
9
9
import asyncio
10
10
11
11
from ..typing import AsyncResult, Messages
@@ -92,17 +92,8 @@ class ARTA(AsyncGeneratorProvider, ProviderModelMixin):
92
92
# Step 1: Generate Authentication Token
93
93
auth_payload = {"clientType": "CLIENT_TYPE_ANDROID"}
94
94
async with session.post(cls.auth_url, json=auth_payload, proxy=proxy) as auth_response:
95
if auth_response.status >= 400:
96
error_text = await auth_response.text()
97
raise ResponseError(f"Failed to obtain authentication token. Status: {auth_response.status}, Response: {error_text}")
98
99
try:
100
auth_data = await auth_response.json()
101
except Exception as e:
102
error_text = await auth_response.text()
103
content_type = auth_response.headers.get('Content-Type', 'unknown')
104
raise ResponseError(f"Failed to parse auth response as JSON. Content-Type: {content_type}, Error: {str(e)}, Response: {error_text}")
105
95
await raise_error(f"Failed to obtain authentication token", auth_response)
96
auth_data = await auth_response.json()
106
97
auth_token = auth_data.get("idToken")
107
98
#refresh_token = auth_data.get("refreshToken")
108
99
if not auth_token:
@@ -118,17 +109,8 @@ class ARTA(AsyncGeneratorProvider, ProviderModelMixin):
118
109
"refresh_token": refresh_token,
119
110
}
120
111
async with session.post(cls.token_refresh_url, data=payload, proxy=proxy) as response:
121
if response.status >= 400:
122
error_text = await response.text()
123
raise ResponseError(f"Failed to refresh token. Status: {response.status}, Response: {error_text}")
124
125
try:
126
response_data = await response.json()
127
except Exception as e:
128
error_text = await response.text()
129
content_type = response.headers.get('Content-Type', 'unknown')
130
raise ResponseError(f"Failed to parse token refresh response as JSON. Content-Type: {content_type}, Error: {str(e)}, Response: {error_text}")
131
112
await raise_error(f"Failed to refresh token", response)
113
response_data = await response.json()
132
114
return response_data.get("id_token"), response_data.get("refresh_token")
133
115
134
116
@classmethod
@@ -156,7 +138,7 @@ class ARTA(AsyncGeneratorProvider, ProviderModelMixin):
156
138
n: int = 1,
157
139
guidance_scale: int = 7,
158
140
num_inference_steps: int = 30,
159
aspect_ratio: str = "1:1",
141
aspect_ratio: str = None,
160
142
seed: int = None,
161
143
**kwargs
162
144
) -> AsyncResult:
@@ -179,7 +161,7 @@ class ARTA(AsyncGeneratorProvider, ProviderModelMixin):
179
161
"images_num": str(n),
180
162
"cfg_scale": str(guidance_scale),
181
163
"steps": str(num_inference_steps),
182
"aspect_ratio": aspect_ratio,
164
"aspect_ratio": "1:1" if aspect_ratio is None else aspect_ratio,
183
165
"seed": str(seed),
184
166
}
185
167
@@ -188,45 +170,26 @@ class ARTA(AsyncGeneratorProvider, ProviderModelMixin):
188
170
}
189
171
190
172
async with session.post(cls.image_generation_url, data=image_payload, headers=headers, proxy=proxy) as image_response:
191
if image_response.status >= 400:
192
error_text = await image_response.text()
193
raise ResponseError(f"Failed to initiate image generation. Status: {image_response.status}, Response: {error_text}")
194
195
try:
196
image_data = await image_response.json()
197
except Exception as e:
198
error_text = await image_response.text()
199
content_type = image_response.headers.get('Content-Type', 'unknown')
200
raise ResponseError(f"Failed to parse response as JSON. Content-Type: {content_type}, Error: {str(e)}, Response: {error_text}")
201
173
await raise_error(f"Failed to initiate image generation", image_response)
174
image_data = await image_response.json()
202
175
record_id = image_data.get("record_id")
203
176
if not record_id:
204
177
raise ResponseError(f"Failed to initiate image generation: {image_data}")
205
178
206
179
# Step 3: Check Generation Status
207
180
status_url = cls.status_check_url.format(record_id=record_id)
208
counter = 4
209
181
start_time = time.time()
210
182
last_status = None
211
183
while True:
212
184
async with session.get(status_url, headers=headers, proxy=proxy) as status_response:
213
if status_response.status >= 400:
214
error_text = await status_response.text()
215
raise ResponseError(f"Failed to check image generation status. Status: {status_response.status}, Response: {error_text}")
216
217
try:
218
status_data = await status_response.json()
219
except Exception as e:
220
error_text = await status_response.text()
221
content_type = status_response.headers.get('Content-Type', 'unknown')
222
raise ResponseError(f"Failed to parse status response as JSON. Content-Type: {content_type}, Error: {str(e)}, Response: {error_text}")
223
185
await raise_error(f"Failed to check image generation status", status_response)
186
status_data = await status_response.json()
224
187
status = status_data.get("status")
225
188
226
189
if status == "DONE":
227
190
image_urls = [image["url"] for image in status_data.get("response", [])]
228
191
duration = time.time() - start_time
229
yield Reasoning(label="Generated", status=f"{n} image(s) in {duration:.2f}s")
192
yield Reasoning(label="Generated", status=f"{n} image in {duration:.2f}s" if n == 1 else f"{n} images in {duration:.2f}s")
230
193
yield ImageResponse(urls=image_urls, alt=prompt)
231
194
return
232
195
elif status in ("IN_QUEUE", "IN_PROGRESS"):
@@ -238,4 +201,11 @@ class ARTA(AsyncGeneratorProvider, ProviderModelMixin):
238
201
yield Reasoning(label="Generating")
239
202
await asyncio.sleep(2) # Poll every 2 seconds
240
203
else:
241
raise ResponseError(f"Image generation failed with status: {status}")
204
raise ResponseError(f"Image generation failed with status: {status}")
205
206
async def raise_error(response: ClientResponse, message: str):
207
if response.ok:
208
return
209
error_text = await response.text()
210
content_type = response.headers.get('Content-Type', 'unknown')
211
raise ResponseError(f"{message}. Content-Type: {content_type}, Response: {error_text}")
@@ -83,17 +83,7 @@ class AllenAI(AsyncGeneratorProvider, ProviderModelMixin):
83
83
) -> AsyncResult:
84
84
actual_model = cls.get_model(model)
85
85
86
# Use format_image_prompt for vision models when media is provided
87
if media is not None and len(media) > 0:
88
# For vision models, use format_image_prompt
89
if actual_model in cls.vision_models:
90
prompt = format_image_prompt(messages)
91
else:
92
# For non-vision models with images, still use the last user message
93
prompt = get_last_user_message(messages)
94
else:
95
# For text-only messages, use the standard format
96
prompt = format_prompt(messages) if conversation is None else get_last_user_message(messages)
86
prompt = format_prompt(messages) if conversation is None else get_last_user_message(messages)
97
87
98
88
# Determine the correct host for the model
99
89
if host is None:
@@ -157,18 +147,16 @@ class AllenAI(AsyncGeneratorProvider, ProviderModelMixin):
157
147
if media is not None and len(media) > 0:
158
148
conversation = Conversation(actual_model)
159
149
160
# Add image if provided
161
if media is not None and len(media) > 0:
162
# For each image in the media list (using merge_media to handle different formats)
163
for image, image_name in merge_media(media, messages):
164
image_bytes = to_bytes(image)
165
form_data.extend([
166
f'--{boundary}\r\n'
167
f'Content-Disposition: form-data; name="files"; filename="{image_name}"\r\n'
168
f'Content-Type: {is_accepted_format(image_bytes)}\r\n\r\n'
169
])
170
form_data.append(image_bytes.decode('latin1'))
171
form_data.append('\r\n')
150
# For each image in the media list (using merge_media to handle different formats)
151
for image, image_name in merge_media(media, messages):
152
image_bytes = to_bytes(image)
153
form_data.extend([
154
f'--{boundary}\r\n'
155
f'Content-Disposition: form-data; name="files"; filename="{image_name}"\r\n'
156
f'Content-Type: {is_accepted_format(image_bytes)}\r\n\r\n'
157
])
158
form_data.append(image_bytes.decode('latin1'))
159
form_data.append('\r\n')
172
160
173
161
form_data.append(f'--{boundary}--\r\n')
174
162
data = "".join(form_data).encode('latin1')
@@ -182,11 +170,7 @@ class AllenAI(AsyncGeneratorProvider, ProviderModelMixin):
182
170
await raise_for_status(response)
183
171
current_parent = None
184
172
185
async for chunk in response.content:
186
if not chunk:
187
continue
188
decoded = chunk.decode(errors="ignore")
189
for line in decoded.splitlines():
173
async for line in response.content:
190
174
line = line.strip()
191
175
if not line:
192
176
continue
@@ -581,14 +581,15 @@ class Blackbox(AsyncGeneratorProvider, ProviderModelMixin):
581
581
}
582
582
current_messages.append(current_msg)
583
583
584
if media is not None:
584
media = list(merge_media(media, messages))
585
if media:
585
586
current_messages[-1]['data'] = {
586
587
"imagesData": [
587
588
{
588
589
"filePath": f"/{image_name}",
589
590
"contents": to_data_uri(image)
590
591
}
591
for image, image_name in merge_media(media, messages)
592
for image, image_name in media
592
593
],
593
594
"fileText": "",
594
595
"title": ""
@@ -518,11 +518,12 @@ async def async_read_and_download_urls(bucket_dir: Path, delete_files: bool = Fa
518
518
if urls:
519
519
count = 0
520
520
with open(os.path.join(bucket_dir, FILE_LIST), 'a') as f:
521
async for filename in download_urls(bucket_dir, urls):
522
f.write(f"{filename}\n")
523
if event_stream:
524
count += 1
525
yield f'data: {json.dumps({"action": "download", "count": count})}\n\n'
521
for url in urls:
522
async for filename in download_urls(bucket_dir, **url):
523
f.write(f"{filename}\n")
524
if event_stream:
525
count += 1
526
yield f'data: {json.dumps({"action": "download", "count": count})}\n\n'
526
527
527
528
def stream_chunks(bucket_dir: Path, delete_files: bool = False, refine_chunks_with_spacy: bool = False, event_stream: bool = False) -> Iterator[str]:
528
529
size = 0
@@ -80,7 +80,7 @@ class ToolHandler:
80
80
has_bucket = False
81
81
for message in messages:
82
82
if "content" in message and isinstance(message["content"], str):
83
new_message_content = re.sub(r'{"bucket_id":"([^"]*)"}', on_bucket, message["content"])
83
new_message_content = re.sub(r'{"bucket_id":\s*"([^"]*)"}', on_bucket, message["content"])
84
84
if new_message_content != message["content"]:
85
85
has_bucket = True
86
86
message["content"] = new_message_content
@@ -97,29 +97,28 @@ class ToolHandler:
97
97
"""Process all tool calls and return updated messages and kwargs"""
98
98
if not tool_calls:
99
99
return messages, {}
100
100
101
101
extra_kwargs = {}
102
102
messages = messages.copy()
103
103
sources = None
104
104
105
105
for tool in tool_calls:
106
106
if tool.get("type") != "function":
107
107
continue
108
108
109
109
function_name = tool.get("function", {}).get("name")
110
110
111
111
if function_name == TOOL_NAMES["SEARCH"]:
112
112
messages, sources = await ToolHandler.process_search_tool(messages, tool)
113
113
114
114
elif function_name == TOOL_NAMES["CONTINUE"]:
115
115
messages, kwargs = ToolHandler.process_continue_tool(messages, tool, provider)
116
116
extra_kwargs.update(kwargs)
117
117
118
118
elif function_name == TOOL_NAMES["BUCKET"]:
119
119
messages = ToolHandler.process_bucket_tool(messages, tool)
120
121
return messages, sources, extra_kwargs
122
120
121
return messages, sources, extra_kwargs
123
122
124
123
class AuthManager:
125
124
"""Handles API key management"""
@@ -128,13 +127,13 @@ class AuthManager:
128
127
def get_api_key_file(cls) -> Path:
129
128
"""Get the path to the API key file for a provider"""
130
129
return Path(get_cookies_dir()) / f"api_key_{cls.parent if hasattr(cls, 'parent') else cls.__name__}.json"
131
130
132
131
@staticmethod
133
132
def load_api_key(provider: Any) -> Optional[str]:
134
133
"""Load API key from config file if needed"""
135
134
if not getattr(provider, "needs_auth", False):
136
135
return None
137
136
138
137
auth_file = AuthManager.get_api_key_file(provider)
139
138
try:
140
139
if auth_file.exists():
@@ -1,6 +1,5 @@
1
import sys
2
1
import os
3
from typing import Any, AsyncGenerator, Generator, AsyncIterator, Iterator, NewType, Tuple, Union, List, Dict, Type, IO, Optional
2
from typing import Any, AsyncGenerator, Generator, AsyncIterator, Iterator, NewType, Tuple, Union, List, Dict, Type, IO, Optional, TypedDict
4
3
5
4
try:
6
5
from PIL.Image import Image
@@ -8,11 +7,6 @@ except ImportError:
8
7
class Image:
9
8
pass
10
9
11
if sys.version_info >= (3, 8):
12
from typing import TypedDict
13
else:
14
from typing_extensions import TypedDict
15
16
10
from .providers.response import ResponseType
17
11
18
12
SHA256 = NewType('sha_256_hash', str)