返回提交历史
Modified
docs/async_client.md
+4
-5
Modified
docs/client.md
+1
-1
Modified
etc/unittest/client.py
+2
-2
Modified
g4f/Provider/DDG.py
+60
-84
Modified
g4f/Provider/DeepInfraChat.py
+9
-40
Modified
g4f/Provider/PollinationsAI.py
+1
-1
Modified
g4f/Provider/needs_auth/OpenaiAPI.py
+2
-0
Modified
g4f/api/stubs.py
+3
-1
Modified
g4f/client/__init__.py
+6
-10
Modified
g4f/errors.py
+7
-1
XFEstudio/gpt4free
Restore AsyncClient streaming return type Improve error handling in DDG use OpenaiAPI provider in DeepInfraChat Support api_base and response_type parameters in API
fd97ccea
代码差异
10 个文件
+95
-145
@@ -149,8 +149,8 @@ from g4f.client import AsyncClient
149
149
150
150
async def main():
151
151
client = AsyncClient()
152
153
stream = await client.chat.completions.create(
152
153
stream = client.chat.completions.stream(
154
154
model="gpt-4",
155
155
messages=[
156
156
{
@@ -158,10 +158,9 @@ async def main():
158
158
"content": "Say this is a test"
159
159
}
160
160
],
161
stream=True,
162
161
web_search = False
163
162
)
164
163
165
164
async for chunk in stream:
166
165
if chunk.choices and chunk.choices[0].delta.content:
167
166
print(chunk.choices[0].delta.content, end="")
@@ -366,7 +365,7 @@ async def main():
366
365
"query": "Latest advancements in AI",
367
366
"max_results": 5,
368
367
"max_words": 2500,
369
"backend": "api",
368
"backend": "auto",
370
369
"add_text": True,
371
370
"timeout": 5
372
371
},
@@ -386,7 +386,7 @@ tool_calls = [
386
386
"query": "Latest advancements in AI",
387
387
"max_results": 5,
388
388
"max_words": 2500,
389
"backend": "api",
389
"backend": "auto",
390
390
"add_text": True,
391
391
"timeout": 5
392
392
},
@@ -37,14 +37,14 @@ class AsyncTestPassModel(unittest.IsolatedAsyncioTestCase):
37
37
async def test_max_stream(self):
38
38
client = AsyncClient(provider=YieldProviderMock)
39
39
messages = [{'role': 'user', 'content': chunk} for chunk in ["How ", "are ", "you", "?"]]
40
response = await client.chat.completions.create(messages, "Hello", stream=True)
40
response = client.chat.completions.create(messages, "Hello", stream=True)
41
41
async for chunk in response:
42
42
chunk: ChatCompletionChunk = chunk
43
43
self.assertIsInstance(chunk, ChatCompletionChunk)
44
44
if chunk.choices[0].delta.content is not None:
45
45
self.assertIsInstance(chunk.choices[0].delta.content, str)
46
46
messages = [{'role': 'user', 'content': chunk} for chunk in ["You ", "You ", "Other", "?"]]
47
response = await client.chat.completions.create(messages, "Hello", stream=True, max_tokens=2)
47
response = client.chat.completions.create(messages, "Hello", stream=True, max_tokens=2)
48
48
response_list = []
49
49
async for chunk in response:
50
50
response_list.append(chunk)
@@ -11,19 +11,11 @@ from ..requests.raise_for_status import raise_for_status
11
11
from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
12
12
from .helper import format_prompt
13
13
from ..providers.response import FinishReason, JsonConversation
14
from ..errors import ModelNotSupportedError, ResponseStatusError, RateLimitError, TimeoutError, ConversationLimitError
14
15
15
16
class DuckDuckGoSearchException(Exception):
16
17
"""Base exception class for duckduckgo_search."""
17
18
18
class RatelimitException(DuckDuckGoSearchException):
19
"""Raised for rate limit exceeded errors during API requests."""
20
21
class TimeoutException(DuckDuckGoSearchException):
22
"""Raised for timeout errors during API requests."""
23
24
class ConversationLimitException(DuckDuckGoSearchException):
25
"""Raised for conversation limit during API requests to AI endpoint."""
26
27
19
class Conversation(JsonConversation):
28
20
vqd: str = None
29
21
message_history: Messages = []
@@ -61,7 +53,7 @@ class DDG(AsyncGeneratorProvider, ProviderModelMixin):
61
53
if model in cls.model_aliases:
62
54
model = cls.model_aliases[model]
63
55
if model not in cls.models:
64
raise ValueError(f"Model {model} not supported. Available models: {cls.models}")
56
raise ModelNotSupportedError(f"Model {model} not supported. Available models: {cls.models}")
65
57
return model
66
58
67
59
@classmethod
@@ -88,25 +80,20 @@ class DDG(AsyncGeneratorProvider, ProviderModelMixin):
88
80
try:
89
81
await cls.sleep()
90
82
async with session.get(cls.status_url, headers=headers) as response:
91
if response.status == 200:
92
vqd = response.headers.get("x-vqd-4", "")
93
if vqd:
94
return vqd
95
elif response.status == 429:
96
if attempt < max_retries - 1:
97
wait_time = random.uniform(1, 3) * (attempt + 1)
98
await asyncio.sleep(wait_time)
99
continue
83
await raise_for_status(response)
84
vqd = response.headers.get("x-vqd-4", "")
85
if vqd:
86
return vqd
100
87
response_text = await response.text()
101
raise Exception(f"Failed to fetch VQD token: {response.status} {response_text}")
102
except Exception as e:
88
raise RuntimeError(f"Failed to fetch VQD token: {response.status} {response_text}")
89
except ResponseStatusError as e:
103
90
if attempt < max_retries - 1:
104
91
wait_time = random.uniform(1, 3) * (attempt + 1)
105
92
await asyncio.sleep(wait_time)
106
93
else:
107
raise Exception(f"Failed to fetch VQD token after {max_retries} attempts: {str(e)}")
108
109
raise Exception("Failed to fetch VQD token: Maximum retries exceeded")
94
raise RuntimeError(f"Failed to fetch VQD token after {max_retries} attempts: {str(e)}")
95
96
raise RuntimeError("Failed to fetch VQD token: Maximum retries exceeded")
110
97
111
98
@classmethod
112
99
async def create_async_generator(
@@ -121,10 +108,10 @@ class DDG(AsyncGeneratorProvider, ProviderModelMixin):
121
108
**kwargs
122
109
) -> AsyncResult:
123
110
model = cls.validate_model(model)
124
111
125
112
if cookies is None and conversation is not None:
126
113
cookies = conversation.cookies
127
114
128
115
try:
129
116
async with ClientSession(timeout=ClientTimeout(total=timeout), cookies=cookies) as session:
130
117
if conversation is None:
@@ -140,67 +127,56 @@ class DDG(AsyncGeneratorProvider, ProviderModelMixin):
140
127
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
141
128
"x-vqd-4": conversation.vqd,
142
129
}
143
130
144
131
data = {
145
132
"model": model,
146
133
"messages": conversation.message_history,
147
134
}
148
135
149
136
await cls.sleep()
150
try:
151
async with session.post(cls.api_endpoint, json=data, headers=headers, proxy=proxy) as response:
152
await raise_for_status(response)
153
reason = None
154
full_message = ""
155
156
async for line in response.content:
157
line = line.decode("utf-8").strip()
158
if line.startswith("data:"):
159
try:
160
message = json.loads(line[5:].strip())
161
162
if "action" in message and message["action"] == "error":
163
error_type = message.get("type", "")
164
if message.get("status") == 429:
165
if error_type == "ERR_CONVERSATION_LIMIT":
166
raise ConversationLimitException(error_type)
167
raise RatelimitException(error_type)
168
raise DuckDuckGoSearchException(error_type)
169
170
if "message" in message:
171
if message["message"]:
172
yield message["message"]
173
full_message += message["message"]
174
reason = "length"
175
else:
176
reason = "stop"
177
except json.JSONDecodeError:
178
continue
179
180
if return_conversation:
181
conversation.message_history.append({"role": "assistant", "content": full_message})
182
conversation.vqd = response.headers.get("x-vqd-4", conversation.vqd)
183
conversation.cookies = {
184
n: c.value
185
for n, c in session.cookie_jar.filter_cookies(cls.url).items()
186
}
187
188
if reason is not None:
189
yield FinishReason(reason)
190
191
if return_conversation:
192
yield conversation
193
194
except asyncio.TimeoutError as e:
195
raise TimeoutException(f"Request timed out: {str(e)}")
196
except Exception as e:
197
if "time" in str(e).lower():
198
raise TimeoutException(f"Request timed out: {str(e)}")
199
raise DuckDuckGoSearchException(f"Request failed: {str(e)}")
200
201
except Exception as e:
202
if isinstance(e, (RatelimitException, TimeoutException, ConversationLimitException)):
203
raise
204
if "time" in str(e).lower():
205
raise TimeoutException(f"Request timed out: {str(e)}")
206
raise DuckDuckGoSearchException(f"Request failed: {str(e)}")
137
async with session.post(cls.api_endpoint, json=data, headers=headers, proxy=proxy) as response:
138
await raise_for_status(response)
139
reason = None
140
full_message = ""
141
142
async for line in response.content:
143
line = line.decode("utf-8").strip()
144
145
if line.startswith("data:"):
146
try:
147
message = json.loads(line[5:].strip())
148
except json.JSONDecodeError:
149
continue
150
151
if "action" in message and message["action"] == "error":
152
error_type = message.get("type", "")
153
if message.get("status") == 429:
154
if error_type == "ERR_CONVERSATION_LIMIT":
155
raise ConversationLimitError(error_type)
156
raise RateLimitError(error_type)
157
raise DuckDuckGoSearchException(error_type)
158
159
if "message" in message:
160
if message["message"]:
161
yield message["message"]
162
full_message += message["message"]
163
reason = "length"
164
else:
165
reason = "stop"
166
167
if return_conversation:
168
conversation.message_history.append({"role": "assistant", "content": full_message})
169
conversation.vqd = response.headers.get("x-vqd-4", conversation.vqd)
170
conversation.cookies = {
171
n: c.value
172
for n, c in session.cookie_jar.filter_cookies(cls.url).items()
173
}
174
175
if reason is not None:
176
yield FinishReason(reason)
177
178
if return_conversation:
179
yield conversation
180
181
except asyncio.TimeoutError as e:
182
raise TimeoutError(f"Request timed out: {str(e)}")
@@ -1,15 +1,13 @@
1
1
from __future__ import annotations
2
2
3
import json
4
from aiohttp import ClientSession
5
6
3
from ..typing import AsyncResult, Messages
7
from ..requests.raise_for_status import raise_for_status
8
from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
4
from .needs_auth import OpenaiAPI
9
5
10
class DeepInfraChat(AsyncGeneratorProvider, ProviderModelMixin):
6
class DeepInfraChat(OpenaiAPI):
11
7
url = "https://deepinfra.com/chat"
12
api_endpoint = "https://api.deepinfra.com/v1/openai/chat/completions"
8
login_url = None
9
needs_auth = False
10
api_base = "https://api.deepinfra.com/v1/openai"
13
11
14
12
working = True
15
13
supports_stream = True
@@ -47,44 +45,15 @@ class DeepInfraChat(AsyncGeneratorProvider, ProviderModelMixin):
47
45
cls,
48
46
model: str,
49
47
messages: Messages,
50
proxy: str = None,
48
headers: dict = {},
51
49
**kwargs
52
50
) -> AsyncResult:
53
model = cls.get_model(model)
54
55
51
headers = {
56
52
'Accept-Language': 'en-US,en;q=0.9',
57
'Content-Type': 'application/json',
58
53
'Origin': 'https://deepinfra.com',
59
54
'Referer': 'https://deepinfra.com/',
60
55
'X-Deepinfra-Source': 'web-page',
61
'accept': 'text/event-stream',
56
**headers
62
57
}
63
async with ClientSession(headers=headers) as session:
64
data = {
65
"model": model,
66
"messages": messages,
67
"stream": True
68
}
69
async with session.post(cls.api_endpoint, json=data, proxy=proxy) as response:
70
await raise_for_status(response)
71
async for chunk in response.content:
72
if chunk:
73
chunk_text = chunk.decode(errors="ignore")
74
try:
75
# Handle streaming response
76
if chunk_text.startswith("data: "):
77
if chunk_text.strip() == "data: [DONE]":
78
continue
79
chunk_data = json.loads(chunk_text[6:])
80
content = chunk_data["choices"][0]["delta"].get("content")
81
if content:
82
yield content
83
# Handle non-streaming response
84
else:
85
chunk_data = json.loads(chunk_text)
86
content = chunk_data["choices"][0]["message"].get("content")
87
if content:
88
yield content
89
except (json.JSONDecodeError, KeyError):
90
continue
58
async for chunk in super().create_async_generator(model, messages, headers=headers, **kwargs):
59
yield chunk
@@ -40,7 +40,7 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
40
40
default_vision_model = "gpt-4o"
41
41
extra_image_models = ["midjourney", "dall-e-3", "flux-pro", "flux-realism", "flux-cablyai", "flux-anime", "flux-3d"]
42
42
vision_models = [default_vision_model, "gpt-4o-mini"]
43
extra_text_models = [*vision_models, "claude", "karma", "command-r", "llamalight", "mistral-large", "sur", "sur-mistral", "any-dark"]
43
extra_text_models = [*vision_models, "claude", "claude-email", "karma", "command-r", "llamalight", "mistral-large", "sur", "sur-mistral", "any-dark"]
44
44
model_aliases = {
45
45
"qwen-2-72b": "qwen",
46
46
"qwen-2.5-coder-32b": "qwen-coder",
@@ -140,6 +140,8 @@ class OpenaiAPI(AsyncGeneratorProvider, ProviderModelMixin, RaiseErrorMixin):
140
140
if delta:
141
141
first = False
142
142
yield delta
143
if "usage" in data and data["usage"]:
144
yield Usage(**data["usage"])
143
145
if "finish_reason" in choice and choice["finish_reason"] is not None:
144
146
yield FinishReason(choice["finish_reason"])
145
147
break
@@ -21,6 +21,7 @@ class ChatCompletionsConfig(BaseModel):
21
21
max_tokens: Optional[int] = None
22
22
stop: Union[list[str], str, None] = None
23
23
api_key: Optional[str] = None
24
api_base: str = None
24
25
web_search: Optional[bool] = None
25
26
proxy: Optional[str] = None
26
27
conversation_id: Optional[str] = None
@@ -31,13 +32,14 @@ class ChatCompletionsConfig(BaseModel):
31
32
tool_calls: list = Field(default=[], examples=[[
32
33
{
33
34
"function": {
34
"arguments": {"query":"search query", "max_results":5, "max_words": 2500, "backend": "api", "add_text": True, "timeout": 5},
35
"arguments": {"query":"search query", "max_results":5, "max_words": 2500, "backend": "auto", "add_text": True, "timeout": 5},
35
36
"name": "search_tool"
36
37
},
37
38
"type": "function"
38
39
}
39
40
]])
40
41
tools: list = None
42
response_format: Optional[dict] = None
41
43
42
44
class ImageGenerationConfig(BaseModel):
43
45
prompt: str
@@ -237,7 +237,6 @@ class Completions:
237
237
max_tokens: Optional[int] = None,
238
238
stop: Optional[Union[list[str], str]] = None,
239
239
api_key: Optional[str] = None,
240
ignored: Optional[list[str]] = None,
241
240
ignore_working: Optional[bool] = False,
242
241
ignore_stream: Optional[bool] = False,
243
242
**kwargs
@@ -268,8 +267,6 @@ class Completions:
268
267
),
269
268
**kwargs
270
269
)
271
if not hasattr(response, '__iter__'):
272
response = [response]
273
270
274
271
response = iter_response(response, stream, response_format, max_tokens, stop)
275
272
response = iter_append_model_and_provider(response, model, provider)
@@ -471,7 +468,7 @@ class Images:
471
468
elif response_format == "b64_json":
472
469
# Convert URLs directly to base64 without saving
473
470
async def get_b64_from_url(url: str) -> Image:
474
async with aiohttp.ClientSession() as session:
471
async with aiohttp.ClientSession(cookies=response.get("cookies")) as session:
475
472
async with session.get(url, proxy=proxy) as resp:
476
473
if resp.status == 200:
477
474
image_data = await resp.read()
@@ -512,7 +509,7 @@ class AsyncCompletions:
512
509
self.client: AsyncClient = client
513
510
self.provider: ProviderType = provider
514
511
515
async def create(
512
def create(
516
513
self,
517
514
messages: Messages,
518
515
model: str,
@@ -525,11 +522,10 @@ class AsyncCompletions:
525
522
max_tokens: Optional[int] = None,
526
523
stop: Optional[Union[list[str], str]] = None,
527
524
api_key: Optional[str] = None,
528
ignored: Optional[list[str]] = None,
529
525
ignore_working: Optional[bool] = False,
530
526
ignore_stream: Optional[bool] = False,
531
527
**kwargs
532
) -> Awaitable[ChatCompletion, AsyncIterator[ChatCompletionChunk]]:
528
) -> Awaitable[ChatCompletion]:
533
529
model, provider = get_model_and_provider(
534
530
model,
535
531
self.provider if provider is None else provider,
@@ -556,14 +552,14 @@ class AsyncCompletions:
556
552
),
557
553
**kwargs
558
554
)
559
555
560
556
response = async_iter_response(response, stream, response_format, max_tokens, stop)
561
557
response = async_iter_append_model_and_provider(response, model, provider)
562
558
563
559
if stream:
564
560
return response
565
561
else:
566
return await anext(response)
562
return anext(response)
567
563
568
564
def stream(
569
565
self,
@@ -47,4 +47,10 @@ class RateLimitError(ResponseStatusError):
47
47
...
48
48
49
49
class NoValidHarFileError(Exception):
50
...
50
...
51
52
class TimeoutError(Exception):
53
"""Raised for timeout errors during API requests."""
54
55
class ConversationLimitError(Exception):
56
"""Raised for conversation limit during API requests to AI endpoint."""