返回提交历史
Modified
g4f/Provider/github/GithubCopilot.py
+6
-1
Modified
g4f/__init__.py
+7
-2
Modified
g4f/providers/any_provider.py
+0
-2
Modified
g4f/providers/asyncio.py
+20
-0
Modified
g4f/providers/base_provider.py
+38
-197
Modified
g4f/providers/retry_provider.py
+53
-226
Modified
g4f/providers/tool_support.py
+5
-4
Modified
g4f/providers/types.py
+1
-38
Modified
g4f/requests/__init__.py
+1
-1
Modified
g4f/tools/run_tools.py
+8
-3
XFEstudio/gpt4free
Refactor provider methods to unify async and sync handling, enhance clarity, and improve error management
06df47f2
代码差异
10 个文件
+139
-474
@@ -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]:
@@ -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
@@ -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:
@@ -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
@@ -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,
@@ -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
@@ -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):
@@ -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]
@@ -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 {
@@ -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