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

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
hlohaus <983577+hlohaus@users.noreply.github.com>
提交于

代码差异

7 个文件 +82 -104
Modified docs/file.md +30 -1
@@ -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
Modified g4f/Provider/ARTA.py +20 -50
@@ -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}")
Modified g4f/Provider/AllenAI.py +12 -28
@@ -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
Modified g4f/Provider/Blackbox.py +3 -2
@@ -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": ""
Modified g4f/tools/files.py +6 -5
@@ -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
Modified g4f/tools/run_tools.py +10 -11
@@ -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():
Modified g4f/typing.py +1 -7
@@ -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)