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

XFEstudio/gpt4free

Refactor provider methods to unify async and sync handling, enhance clarity, and improve error management

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

代码差异

10 个文件 +139 -474
Modified g4f/Provider/github/GithubCopilot.py +6 -1
@@ -174,7 +174,12 @@ class GithubCopilot(OpenaiTemplate):
174 174 "Please run 'g4f auth github-copilot' to authenticate."
175 175 ) from e
176 176 raise
177 return super().get_models(api_key, base_url, timeout)
177 response = super().get_models(api_key, base_url, timeout)
178 if isinstance(response, dict):
179 for key in list(response.keys()):
180 if key.startswith("accounts/") or key.startswith("text-embedding-") or key in ("minimax-m2.5", "goldeneye-free-auto"):
181 del response[key]
182 return response
178 183
179 184 @classmethod
180 185 def get_headers(cls, stream: bool, api_key: str | None = None, headers: dict[str, str] | None = None) -> dict[str, str]:
Modified g4f/__init__.py +7 -2
@@ -11,7 +11,9 @@ from .client import Client, AsyncClient, ClientFactory, create_custom_provider
11 11 from .typing import Messages, CreateResult, AsyncResult, ImageType
12 12 from .cookies import get_cookies, set_cookies
13 13 from .providers.types import ProviderType
14 from .providers.base_provider import get_async_provider_method, get_provider_method
14 15 from .providers.helper import concat_chunks, async_concat_chunks
16 from .providers.asyncio import to_sync_generator
15 17 from .client.service import get_model_and_provider
16 18
17 19 # Configure logger
@@ -69,7 +71,9 @@ class ChatCompletion:
69 71 model, messages, provider, stream, image, image_name,
70 72 ignore_working, ignore_stream, **kwargs
71 73 )
72 result = provider.create_function(model, messages, stream=stream, **kwargs)
74 method = get_provider_method(provider)
75 result = method(model, messages, stream=stream, **kwargs)
76 result = to_sync_generator(result)
73 77 return result if stream or ignore_stream else concat_chunks(result)
74 78
75 79 @staticmethod
@@ -86,7 +90,8 @@ class ChatCompletion:
86 90 model, messages, provider, stream, image, image_name,
87 91 ignore_working, ignore_stream, **kwargs
88 92 )
89 result = provider.async_create_function(model, messages, stream=stream, **kwargs)
93 method = get_async_provider_method(provider)
94 result = method(model, messages, stream=stream, **kwargs)
90 95 if not stream and not ignore_stream and hasattr(result, "__aiter__"):
91 96 result = async_concat_chunks(result)
92 97 return result
Modified g4f/providers/any_provider.py +0 -2
@@ -539,8 +539,6 @@ class AnyProvider(AsyncGeneratorProvider, AnyModelProviderMixin):
539 539 ):
540 540 yield chunk
541 541
542 async_create_function = create_async_generator
543
544 542
545 543 # Clean model names function
546 544 def clean_name(name: str) -> str:
Modified g4f/providers/asyncio.py +20 -0
@@ -45,6 +45,23 @@ async def async_generator_to_list(generator: AsyncIterator) -> list:
45 45
46 46 def to_sync_generator(generator: AsyncIterator, stream: bool = True, timeout: int = None) -> Iterator:
47 47 loop = get_running_loop(check_nested=False)
48 if asyncio.iscoroutine(generator):
49 if loop is not None:
50 try:
51 result = loop.run_until_complete(generator)
52 except RuntimeError as e:
53 if asyncio.iscoroutine(generator):
54 try:
55 generator.close()
56 except Exception:
57 pass
58 raise NestAsyncioError(
59 'Install "nest-asyncio2" package | pip install -U nest-asyncio2'
60 ) from e
61 else:
62 result = asyncio.run(generator)
63 yield result
64 return
48 65 if not stream:
49 66 yield from asyncio.run(async_generator_to_list(generator))
50 67 return
@@ -72,6 +89,9 @@ def to_sync_generator(generator: AsyncIterator, stream: bool = True, timeout: in
72 89
73 90 # Helper function to convert a synchronous iterator to an async iterator
74 91 async def to_async_iterator(iterator) -> AsyncIterator:
92 if isinstance(iterator, (str, bytes)):
93 yield iterator
94 return
75 95 if hasattr(iterator, '__aiter__'):
76 96 async for item in iterator:
77 97 yield item
Modified g4f/providers/base_provider.py +38 -197
@@ -2,12 +2,10 @@ from __future__ import annotations
2 2
3 3 import asyncio
4 4 import random
5 from asyncio import AbstractEventLoop
6 from concurrent.futures import ThreadPoolExecutor
7 5 from abc import abstractmethod
8 6 import json
9 7 from inspect import signature, Parameter
10 from typing import Optional, _GenericAlias
8 from typing import Optional, _GenericAlias, AsyncIterator
11 9 from pathlib import Path
12 10 from aiohttp import ClientSession
13 11 try:
@@ -22,7 +20,7 @@ from .response import BaseConversation, AuthResult
22 20 from .helper import concat_chunks
23 21 from ..cookies import get_cookies_dir
24 22 from ..requests import raise_for_status
25 from ..errors import ModelNotFoundError, ResponseError, MissingAuthError, NoValidHarFileError, PaymentRequiredError, CloudflareError
23 from ..errors import ResponseError, MissingAuthError, NoValidHarFileError, PaymentRequiredError, CloudflareError
26 24 from .. import debug
27 25
28 26 SAFE_PARAMETERS = [
@@ -71,92 +69,45 @@ PARAMETER_EXAMPLES = {
71 69 "aspect_ratio": "1:1",
72 70 }
73 71
74 class AbstractProvider(BaseProvider):
75
76 @classmethod
77 @abstractmethod
78 def create_completion(
79 cls,
80 model: str,
81 messages: Messages,
82 **kwargs
83 ) -> CreateResult:
84 """
85 Create a completion with the given parameters.
86
87 Args:
88 model (str): The model to use.
89 messages (Messages): The messages to process.
90 stream (bool): Whether to use streaming.
91 **kwargs: Additional keyword arguments.
92
93 Returns:
94 CreateResult: The result of the creation process.
95 """
96 raise NotImplementedError()
97
98 @classmethod
99 async def create_async(
100 cls,
101 model: str,
102 messages: Messages,
103 *,
104 timeout: int = None,
105 loop: AbstractEventLoop = None,
106 executor: ThreadPoolExecutor = None,
107 **kwargs
108 ) -> str:
109 """
110 Asynchronously creates a result based on the given model and messages.
111
112 Args:
113 cls (type): The class on which this method is called.
114 model (str): The model to use for creation.
115 messages (Messages): The messages to process.
116 loop (AbstractEventLoop, optional): The event loop to use. Defaults to None.
117 executor (ThreadPoolExecutor, optional): The executor for running async tasks. Defaults to None.
118 **kwargs: Additional keyword arguments.
119
120 Returns:
121 str: The created result as a string.
122 """
123 loop = asyncio.get_running_loop() if loop is None else loop
124
125 def create_func() -> str:
126 return concat_chunks(cls.create_completion(model=model, messages=messages, **kwargs))
127 try:
128 return await asyncio.wait_for(
129 loop.run_in_executor(executor, create_func), timeout=timeout
130 )
131 except TimeoutError as e:
132 raise TimeoutError("The operation timed out after {} seconds in {}".format(timeout, cls.__name__)) from e
133
134 @classmethod
135 def create_function(cls, *args, **kwargs) -> CreateResult:
136 """
137 Creates a completion using the synchronous method.
138
139 Args:
140 **kwargs: Additional keyword arguments.
141
142 Returns:
143 CreateResult: The result of the completion creation.
144 """
145 return cls.create_completion(*args, **kwargs)
72 async def wait_for(response: AsyncIterator, timeout: int = None) -> AsyncIterator:
73 if timeout is not None:
74 while True:
75 try:
76 yield await asyncio.wait_for(
77 response.__anext__(),
78 timeout=timeout
79 )
80 except TimeoutError as e:
81 raise TimeoutError("The operation timed out after {} seconds".format(timeout)) from e
82 except StopAsyncIteration:
83 break
84 else:
85 async for chunk in response:
86 yield chunk
87
88 def get_async_provider_method(provider: type) -> Optional[callable]:
89 if hasattr(provider, "create_async_generator"):
90 return provider.create_async_generator
91 if hasattr(provider, "create_async"):
92 return provider.create_async
93 if hasattr(provider, "create_completion"):
94 async def wrapper(*args, **kwargs):
95 for chunk in provider.create_completion(*args, **kwargs):
96 yield chunk
97 return wrapper
98 raise NotImplementedError(f"{provider.__name__} does not implement an async method")
146 99
147 @classmethod
148 def async_create_function(cls, *args, **kwargs) -> AsyncResult:
149 """
150 Creates a completion using the synchronous method.
151 100
152 Args:
153 **kwargs: Additional keyword arguments.
154
155 Returns:
156 CreateResult: The result of the completion creation.
157 """
158 return cls.create_async(*args, **kwargs)
101 def get_provider_method(provider: type) -> Optional[callable]:
102 if hasattr(provider, "create_completion"):
103 return provider.create_completion
104 if hasattr(provider, "create_async_generator"):
105 return provider.create_async_generator
106 if hasattr(provider, "create_async"):
107 return provider.create_async
108 raise NotImplementedError(f"{provider.__name__} does not implement a create method")
159 109
110 class AbstractProvider(BaseProvider):
160 111 @classmethod
161 112 def get_parameters(cls, as_json: bool = False) -> dict[str, Parameter]:
162 113 params = {name: parameter for name, parameter in signature(
@@ -240,29 +191,6 @@ class AsyncProvider(AbstractProvider):
240 191 Provides asynchronous functionality for creating completions.
241 192 """
242 193
243 @classmethod
244 def create_completion(
245 cls,
246 model: str,
247 messages: Messages,
248 **kwargs
249 ) -> CreateResult:
250 """
251 Creates a completion result synchronously.
252
253 Args:
254 cls (type): The class on which this method is called.
255 model (str): The model to use for creation.
256 messages (Messages): The messages to process.
257 loop (AbstractEventLoop, optional): The event loop to use. Defaults to None.
258 **kwargs: Additional keyword arguments.
259
260 Returns:
261 CreateResult: The result of the completion creation.
262 """
263 get_running_loop(check_nested=False)
264 yield asyncio.run(cls.create_async(model, messages, **kwargs))
265
266 194 @staticmethod
267 195 @abstractmethod
268 196 async def create_async(
@@ -309,33 +237,6 @@ class AsyncGeneratorProvider(AbstractProvider):
309 237 await raise_for_status(response)
310 238 return await response.json()
311 239
312 @classmethod
313 def create_completion(
314 cls,
315 model: str,
316 messages: Messages,
317 timeout: int = None,
318 stream_timeout: int = None,
319 **kwargs
320 ) -> CreateResult:
321 """
322 Creates a streaming completion result synchronously.
323
324 Args:
325 cls (type): The class on which this method is called.
326 model (str): The model to use for creation.
327 messages (Messages): The messages to process.
328 loop (AbstractEventLoop, optional): The event loop to use. Defaults to None.
329 **kwargs: Additional keyword arguments.
330
331 Returns:
332 CreateResult: The result of the streaming completion creation.
333 """
334 return to_sync_generator(
335 cls.create_async_generator(model, messages, **kwargs),
336 timeout=stream_timeout if cls.use_stream_timeout is None else timeout,
337 )
338
339 240 @staticmethod
340 241 @abstractmethod
341 242 async def create_async_generator(
@@ -359,34 +260,6 @@ class AsyncGeneratorProvider(AbstractProvider):
359 260 """
360 261 raise NotImplementedError()
361 262
362 @classmethod
363 async def async_create_function(cls, *args, **kwargs) -> AsyncResult:
364 """
365 Creates a completion using the synchronous method.
366
367 Args:
368 **kwargs: Additional keyword arguments.
369
370 Returns:
371 CreateResult: The result of the completion creation.
372 """
373 response = cls.create_async_generator(*args, **kwargs)
374 if "stream_timeout" in kwargs or "timeout" in kwargs:
375 timeout = kwargs.get("stream_timeout") if cls.use_stream_timeout else kwargs.get("timeout")
376 while True:
377 try:
378 yield await asyncio.wait_for(
379 response.__anext__(),
380 timeout=timeout
381 )
382 except TimeoutError as e:
383 raise TimeoutError("The operation timed out after {} seconds in {}".format(timeout, cls.__name__)) from e
384 except StopAsyncIteration:
385 break
386 else:
387 async for chunk in response:
388 yield chunk
389
390 263 class ProviderModelMixin:
391 264 default_model: str = None
392 265 models: list[str] = []
@@ -466,13 +339,6 @@ class AsyncAuthedProvider(AsyncGeneratorProvider, AuthFileMixin):
466 339 raise MissingAuthError(f"API key is required for {cls.__name__}")
467 340 return AuthResult()
468 341
469 @classmethod
470 def on_auth(cls, **kwargs) -> AuthResult:
471 auth_result = cls.on_auth_async(**kwargs)
472 if hasattr(auth_result, "__aiter__"):
473 return to_sync_generator(auth_result)
474 return asyncio.run(auth_result)
475
476 342 @classmethod
477 343 def write_cache_file(cls, cache_file: Path, auth_result: AuthResult = None):
478 344 if auth_result is not None:
@@ -505,31 +371,6 @@ class AsyncAuthedProvider(AsyncGeneratorProvider, AuthFileMixin):
505 371 else:
506 372 raise MissingAuthError
507 373
508 @classmethod
509 def create_completion(
510 cls,
511 model: str,
512 messages: Messages,
513 **kwargs
514 ) -> CreateResult:
515 auth_result: AuthResult = None
516 cache_file = cls.get_cache_file()
517 try:
518 auth_result = cls.get_auth_result()
519 yield from to_sync_generator(cls.create_authed(model, messages, auth_result, **kwargs))
520 except (MissingAuthError, NoValidHarFileError, CloudflareError):
521 response = cls.on_auth(**kwargs)
522 for chunk in response:
523 if isinstance(chunk, AuthResult):
524 auth_result = chunk
525 else:
526 yield chunk
527 for chunk in to_sync_generator(cls.create_authed(model, messages, auth_result, **kwargs), kwargs.get("stream_timeout", kwargs.get("timeout"))):
528 if cache_file is not None:
529 cls.write_cache_file(cache_file, auth_result)
530 cache_file = None
531 yield chunk
532
533 374 @classmethod
534 375 async def create_async_generator(
535 376 cls,
Modified g4f/providers/retry_provider.py +53 -226
@@ -2,14 +2,42 @@ from __future__ import annotations
2 2
3 3 import random
4 4
5 from ..typing import Dict, Type, List, CreateResult, Messages, AsyncResult
5 from ..typing import Dict, Type, List, Messages, AsyncResult
6 6 from .types import BaseProvider, BaseRetryProvider, ProviderType
7 7 from .response import ProviderInfo, JsonConversation, is_content
8 from .base_provider import get_async_provider_method, to_async_iterator
8 9 from .. import debug
9 10 from ..tools.run_tools import AuthManager
10 11 from ..config import AppConfig
11 12 from ..errors import RetryProviderError, RetryNoProviderError, MissingAuthError, NoValidHarFileError
12 13
14
15 def _resolve_model(provider: Type[BaseProvider], model: str) -> str:
16 alias = model or getattr(provider, "default_model", None)
17 if hasattr(provider, "model_aliases"):
18 alias = provider.model_aliases.get(model, model)
19 if isinstance(alias, list):
20 alias = random.choice(alias)
21 return alias
22
23
24 def _prepare_provider_kwargs(
25 provider: Type[BaseProvider],
26 api_key,
27 conversation: JsonConversation,
28 kwargs: dict,
29 ) -> dict:
30 extra_body = kwargs.copy()
31 current_api_key = api_key.get(provider.get_parent()) if isinstance(api_key, dict) else api_key
32 if not current_api_key or AppConfig.disable_custom_api_key:
33 current_api_key = AuthManager.load_api_key(provider)
34 if current_api_key:
35 extra_body["api_key"] = current_api_key
36 if conversation is not None and hasattr(conversation, provider.__name__):
37 extra_body["conversation"] = JsonConversation(**getattr(conversation, provider.__name__))
38 return extra_body
39
40
13 41 class RotatedProvider(BaseRetryProvider):
14 42 """
15 43 A provider that rotates through a list of providers, attempting one provider per
@@ -48,69 +76,6 @@ class RotatedProvider(BaseRetryProvider):
48 76 #new_provider_name = self.providers[self.current_index].__name__
49 77 #debug.log(f"Rotated to next provider: {new_provider_name}")
50 78
51 def create_completion(
52 self,
53 model: str,
54 messages: Messages,
55 ignored: list[str] = [], # 'ignored' is less relevant now but kept for compatibility
56 api_key: str = None,
57 **kwargs,
58 ) -> CreateResult:
59 """
60 Create a completion using the current provider and rotating on failure.
61
62 It will try each provider in the list once per call, rotating after each
63 failed attempt, until one succeeds or all have failed.
64 """
65 exceptions: Dict[str, Exception] = {}
66
67 # Loop over the number of providers, giving each one a chance
68 for _ in range(len(self.providers)):
69 provider = self._get_current_provider()
70 self.last_provider = provider
71 self._rotate_provider()
72
73 # Skip if provider is in the ignored list
74 if provider.get_parent() in ignored:
75 continue
76
77 alias = model or getattr(provider, "default_model", None)
78 if hasattr(provider, "model_aliases"):
79 alias = provider.model_aliases.get(model, model)
80 if isinstance(alias, list):
81 alias = random.choice(alias)
82
83 debug.log(f"Attempting provider: {provider.__name__} with model: {alias}")
84 yield ProviderInfo(**provider.get_dict(), model=alias, alias=model)
85
86 extra_body = kwargs.copy()
87 current_api_key = api_key.get(provider.get_parent()) if isinstance(api_key, dict) else api_key
88 if not current_api_key or AppConfig.disable_custom_api_key:
89 current_api_key = AuthManager.load_api_key(provider)
90 if current_api_key:
91 extra_body["api_key"] = current_api_key
92
93 try:
94 # Attempt to get a response from the current provider
95 response = provider.create_function(alias, messages, **extra_body)
96 started = False
97 for chunk in response:
98 if chunk:
99 yield chunk
100 if is_content(chunk):
101 started = True
102 if started:
103 provider.live += 1
104 # Success, so we return and do not rotate
105 return
106 except Exception as e:
107 provider.live -= 1
108 exceptions[provider.__name__] = e
109 debug.error(f"{provider.__name__} failed: {e}")
110
111 # If the loop completes, all providers have failed
112 raise_exceptions(exceptions)
113
114 79 async def create_async_generator(
115 80 self,
116 81 model: str,
@@ -133,28 +98,18 @@ class RotatedProvider(BaseRetryProvider):
133 98 if provider.get_parent() in ignored:
134 99 continue
135 100
136 alias = model or getattr(provider, "default_model", None)
137 if hasattr(provider, "model_aliases"):
138 alias = provider.model_aliases.get(model, model)
139 if isinstance(alias, list):
140 alias = random.choice(alias)
101 alias = _resolve_model(provider, model)
141 102
142 103 debug.log(f"Attempting provider: {provider.__name__} with model: {alias}")
143 104 yield ProviderInfo(**provider.get_dict(), model=alias)
144 105
145 extra_body = kwargs.copy()
146 current_api_key = api_key.get(provider.get_parent()) if isinstance(api_key, dict) else api_key
147 if not current_api_key or AppConfig.disable_custom_api_key:
148 current_api_key = AuthManager.load_api_key(provider)
149 if current_api_key:
150 extra_body["api_key"] = current_api_key
151 if conversation and hasattr(conversation, provider.__name__):
152 extra_body["conversation"] = JsonConversation(**getattr(conversation, provider.__name__))
106 extra_body = _prepare_provider_kwargs(provider, api_key, conversation, kwargs)
153 107
154 108 try:
155 response = provider.async_create_function(alias, messages, **extra_body)
109 method = get_async_provider_method(provider)
110 response = method(model=alias, messages=messages, **extra_body)
156 111 started = False
157 async for chunk in response:
112 async for chunk in to_async_iterator(response):
158 113 if isinstance(chunk, JsonConversation):
159 114 if conversation is None: conversation = JsonConversation()
160 115 setattr(conversation, provider.__name__, chunk.get_dict())
@@ -173,10 +128,6 @@ class RotatedProvider(BaseRetryProvider):
173 128
174 129 raise_exceptions(exceptions)
175 130
176 # Maintain API compatibility
177 create_function = create_completion
178 async_create_function = create_async_generator
179
180 131 class IterListProvider(BaseRetryProvider):
181 132 def __init__(
182 133 self,
@@ -196,61 +147,6 @@ class IterListProvider(BaseRetryProvider):
196 147 self.working = True
197 148 self.last_provider: Type[BaseProvider] = None
198 149
199 def create_completion(
200 self,
201 model: str,
202 messages: Messages,
203 ignored: list[str] = [],
204 api_key: str = None,
205 **kwargs,
206 ) -> CreateResult:
207 """
208 Create a completion using available providers.
209 Args:
210 model (str): The model to be used for completion.
211 messages (Messages): The messages to be used for generating completion.
212 Yields:
213 CreateResult: Tokens or results from the completion.
214 Raises:
215 Exception: Any exception encountered during the completion process.
216 """
217 exceptions = {}
218 started: bool = False
219 for provider in self.get_providers(ignored):
220 self.last_provider = provider
221 alias = model
222 if not model:
223 alias = getattr(provider, "default_model", None)
224 if hasattr(provider, "model_aliases"):
225 alias = provider.model_aliases.get(model, model)
226 if isinstance(alias, list):
227 alias = random.choice(alias)
228 debug.log(f"Using provider: {provider.__name__} with model: {alias}")
229 yield ProviderInfo(**provider.get_dict(), model=alias)
230 extra_body = kwargs.copy()
231 current_api_key = api_key.get(provider.get_parent()) if isinstance(api_key, dict) else api_key
232 if not current_api_key or AppConfig.disable_custom_api_key:
233 current_api_key = AuthManager.load_api_key(provider)
234 if current_api_key:
235 extra_body["api_key"] = current_api_key
236 try:
237 response = provider.create_function(alias, messages, **extra_body)
238 for chunk in response:
239 if chunk:
240 yield chunk
241 if is_content(chunk):
242 started = True
243 if started:
244 return
245 except Exception as e:
246 exceptions[provider.__name__] = e
247 debug.error(f"{provider.__name__}:", e)
248 if started:
249 raise e
250 yield e
251
252 raise_exceptions(exceptions)
253
254 150 async def create_async_generator(
255 151 self,
256 152 model: str,
@@ -265,41 +161,23 @@ class IterListProvider(BaseRetryProvider):
265 161
266 162 for provider in self.get_providers(ignored):
267 163 self.last_provider = provider
268 alias = model
269 if not model:
270 alias = getattr(provider, "default_model", None)
271 if hasattr(provider, "model_aliases"):
272 alias = provider.model_aliases.get(model, model)
273 if isinstance(alias, list):
274 alias = random.choice(alias)
164 alias = _resolve_model(provider, model)
275 165 debug.log(f"Using {provider.__name__} provider with model {alias}")
276 166 yield ProviderInfo(**provider.get_dict(), model=alias)
277 extra_body = kwargs.copy()
278 current_api_key = api_key.get(provider.get_parent()) if isinstance(api_key, dict) else api_key
279 if not current_api_key or AppConfig.disable_custom_api_key:
280 current_api_key = AuthManager.load_api_key(provider)
281 if current_api_key:
282 extra_body["api_key"] = current_api_key
283 if conversation is not None and hasattr(conversation, provider.__name__):
284 extra_body["conversation"] = JsonConversation(**getattr(conversation, provider.__name__))
167 extra_body = _prepare_provider_kwargs(provider, api_key, conversation, kwargs)
285 168 try:
286 response = provider.async_create_function(model, messages, **extra_body)
287 if hasattr(response, "__aiter__"):
288 async for chunk in response:
289 if isinstance(chunk, JsonConversation):
290 if conversation is None:
291 conversation = JsonConversation()
292 setattr(conversation, provider.__name__, chunk.get_dict())
293 yield conversation
294 elif chunk:
295 yield chunk
296 if is_content(chunk):
297 started = True
298 elif response:
299 response = await response
300 if response:
301 yield response
302 started = True
169 method = get_async_provider_method(provider)
170 response = method(model=alias, messages=messages, **extra_body)
171 async for chunk in to_async_iterator(response):
172 if isinstance(chunk, JsonConversation):
173 if conversation is None:
174 conversation = JsonConversation()
175 setattr(conversation, provider.__name__, chunk.get_dict())
176 yield conversation
177 elif chunk:
178 yield chunk
179 if is_content(chunk):
180 started = True
303 181 if started:
304 182 return
305 183 except Exception as e:
@@ -307,13 +185,9 @@ class IterListProvider(BaseRetryProvider):
307 185 debug.error(f"{provider.__name__}:", e)
308 186 if started:
309 187 raise e
310 yield e
311 188
312 189 raise_exceptions(exceptions)
313 190
314 create_function = create_completion
315 async_create_function = create_async_generator
316
317 191 def get_providers(self, ignored: list[str]) -> list[ProviderType]:
318 192 providers = [p for p in self.providers if p.__name__ not in ignored]
319 193 if self.shuffle:
@@ -340,48 +214,6 @@ class RetryProvider(IterListProvider):
340 214 self.single_provider_retry = single_provider_retry
341 215 self.max_retries = max_retries
342 216
343 def create_completion(
344 self,
345 model: str,
346 messages: Messages,
347 **kwargs,
348 ) -> CreateResult:
349 """
350 Create a completion using available providers.
351 Args:
352 model (str): The model to be used for completion.
353 messages (Messages): The messages to be used for generating completion.
354 Yields:
355 CreateResult: Tokens or results from the completion.
356 Raises:
357 Exception: Any exception encountered during the completion process.
358 """
359 if self.single_provider_retry:
360 exceptions = {}
361 started: bool = False
362 provider = self.providers[0]
363 self.last_provider = provider
364 for attempt in range(self.max_retries):
365 try:
366 if debug.logging:
367 print(f"Using {provider.__name__} provider (attempt {attempt + 1})")
368 response = provider.create_function(model, messages, **kwargs)
369 for chunk in response:
370 yield chunk
371 if is_content(chunk):
372 started = True
373 if started:
374 return
375 except Exception as e:
376 exceptions[provider.__name__] = e
377 if debug.logging:
378 print(f"{provider.__name__}: {e.__class__.__name__}: {e}")
379 if started:
380 raise e
381 raise_exceptions(exceptions)
382 else:
383 yield from super().create_completion(model, messages, **kwargs)
384
385 217 async def create_async_generator(
386 218 self,
387 219 model: str,
@@ -397,16 +229,11 @@ class RetryProvider(IterListProvider):
397 229 for attempt in range(self.max_retries):
398 230 try:
399 231 debug.log(f"Using {provider.__name__} provider (attempt {attempt + 1})")
400 response = provider.async_create_function(model, messages, **kwargs)
401 if hasattr(response, "__aiter__"):
402 async for chunk in response:
403 yield chunk
404 if is_content(chunk):
405 started = True
406 else:
407 response = await response
408 if response:
409 yield response
232 method = get_async_provider_method(provider)
233 response = method(model=model, messages=messages, **kwargs)
234 async for chunk in to_async_iterator(response):
235 yield chunk
236 if is_content(chunk):
410 237 started = True
411 238 if started:
412 239 return
Modified g4f/providers/tool_support.py +5 -4
@@ -7,8 +7,8 @@ from typing import Optional, Union
7 7 from ..typing import AsyncResult, Messages, MediaListType
8 8 from ..client.service import get_model_and_provider
9 9 from ..client.helper import filter_json
10 from ..providers.types import ProviderType
11 from .base_provider import AsyncGeneratorProvider
10 from .types import ProviderType
11 from .base_provider import AsyncGeneratorProvider, get_async_provider_method, to_async_iterator
12 12 from .response import ToolCalls, FinishReason, Usage
13 13
14 14
@@ -74,14 +74,15 @@ class ToolSupportProvider(AsyncGeneratorProvider):
74 74 finish = None
75 75 chunks = []
76 76 has_usage = False
77 async for chunk in provider.async_create_function(
77 method = get_async_provider_method(provider)
78 async for chunk in to_async_iterator(method(
78 79 model,
79 80 messages,
80 81 stream=stream,
81 82 media=media,
82 83 response_format=response_format,
83 84 **kwargs,
84 ):
85 )):
85 86 if isinstance(chunk, str):
86 87 chunks.append(chunk)
87 88 elif isinstance(chunk, Usage):
Modified g4f/providers/types.py +1 -38
@@ -26,8 +26,6 @@ class BaseProvider(ABC):
26 26 supports_message_history: bool = False
27 27 supports_system_message: bool = False
28 28 params: str
29 create_function: callable
30 async_create_function: callable
31 29 live: int = 0
32 30
33 31 @classmethod
@@ -44,42 +42,6 @@ class BaseProvider(ABC):
44 42 def get_parent(cls) -> str:
45 43 return getattr(cls, "parent", cls.__name__)
46 44
47 @abstractmethod
48 def create_function(
49 *args,
50 **kwargs
51 ) -> CreateResult:
52 """
53 Create a function to generate a response based on the model and messages.
54
55 Args:
56 model (str): The model to use.
57 messages (Messages): The messages to process.
58 stream (bool): Whether to stream the response.
59
60 Returns:
61 CreateResult: The result of the creation.
62 """
63 raise NotImplementedError()
64
65 @staticmethod
66 def async_create_function(
67 *args,
68 **kwargs
69 ) -> CreateResult:
70 """
71 Asynchronously create a function to generate a response based on the model and messages.
72
73 Args:
74 model (str): The model to use.
75 messages (Messages): The messages to process.
76 stream (bool): Whether to stream the response.
77
78 Returns:
79 CreateResult: The result of the creation.
80 """
81 raise NotImplementedError()
82
83 45 class BaseRetryProvider(BaseProvider):
84 46 """
85 47 Base class for a provider that implements retry logic.
@@ -93,6 +55,7 @@ class BaseRetryProvider(BaseProvider):
93 55
94 56 __name__: str = "RetryProvider"
95 57 supports_stream: bool = True
58 use_stream_timeout: bool = True
96 59 last_provider: Type[BaseProvider] = None
97 60
98 61 ProviderType = Union[Type[BaseProvider], BaseRetryProvider]
Modified g4f/requests/__init__.py +1 -1
@@ -152,7 +152,7 @@ async def get_args_from_nodriver(
152 152 await page.wait_for(wait_for, timeout=timeout)
153 153 if callback is not None:
154 154 await callback(page)
155 for c in await page.send(nodriver.cdp.network.get_cookies([url])):
155 for c in await asyncio.wait_for(page.send(nodriver.cdp.network.get_cookies([url])), timeout=timeout):
156 156 cookies[c.name] = c.value
157 157 await stop_browser()
158 158 return {
Modified g4f/tools/run_tools.py +8 -3
@@ -22,6 +22,7 @@ from ..providers.helper import filter_none
22 22 from ..providers.asyncio import to_async_iterator, to_sync_generator
23 23 from ..providers.response import Reasoning, FinishReason, Sources, Usage, ProviderInfo
24 24 from ..providers.types import ProviderType
25 from ..providers.base_provider import get_async_provider_method, get_provider_method, wait_for
25 26 from ..cookies import get_cookies_dir
26 27 from ..config import AppConfig
27 28 from .web_search import do_search, get_search_message
@@ -296,9 +297,12 @@ async def async_iter_run_tools(
296 297 kwargs.update(extra_kwargs)
297 298
298 299 # Generate response
300 method = get_async_provider_method(provider)
299 301 response = to_async_iterator(
300 provider.async_create_function(model=model, messages=messages, **kwargs)
302 method(model=model, messages=messages, **kwargs)
301 303 )
304 timeout = kwargs.get("stream_timeout") if provider.use_stream_timeout else kwargs.get("timeout")
305 response = wait_for(response, timeout=timeout) if stream else response
302 306
303 307 try:
304 308 usage_model = model
@@ -471,9 +475,10 @@ def iter_run_tools(
471 475 usage_provider = provider.__name__
472 476 completion_tokens = 0
473 477 usage = None
474 for chunk in provider.create_function(
478 method = get_provider_method(provider)
479 for chunk in to_sync_generator(method(
475 480 model=model, messages=messages, provider=provider, **kwargs
476 ):
481 )):
477 482 if isinstance(chunk, FinishReason):
478 483 if sources is not None:
479 484 yield sources