返回提交历史
Modified
etc/unittest/__main__.py
+1
-0
Added
etc/unittest/integration.py
+25
-0
Modified
g4f/Provider/base_provider.py
+1
-0
Added
g4f/Provider/needs_auth/Groq.py
+23
-0
Added
g4f/Provider/needs_auth/Openai.py
+74
-0
Modified
g4f/Provider/needs_auth/__init__.py
+3
-1
Modified
g4f/client.py
+4
-1
Modified
g4f/providers/base_provider.py
+19
-18
Modified
g4f/providers/types.py
+5
-1
Modified
g4f/requests/aiohttp.py
+12
-4
XFEstudio/gpt4free
Add Groq and Openai interfaces, Add integration tests
d44b39b3
代码差异
10 个文件
+167
-25
@@ -5,5 +5,6 @@ from .main import *
5
5
from .model import *
6
6
from .client import *
7
7
from .include import *
8
from .integration import *
8
9
9
10
unittest.main()
@@ -0,0 +1,25 @@
1
import unittest
2
import json
3
4
from g4f.client import Client, ChatCompletion
5
from g4f.Provider import Bing, OpenaiChat
6
7
DEFAULT_MESSAGES = [{"role": "system", "content": 'Response in json, Example: {"success: True"}'},
8
{"role": "user", "content": "Say success true in json"}]
9
10
class TestProviderIntegration(unittest.TestCase):
11
12
def test_bing(self):
13
client = Client(provider=Bing)
14
response = client.chat.completions.create(DEFAULT_MESSAGES, "", response_format={"type": "json_object"})
15
self.assertIsInstance(response, ChatCompletion)
16
self.assertIn("success", json.loads(response.choices[0].message.content))
17
18
def test_openai(self):
19
client = Client(provider=OpenaiChat)
20
response = client.chat.completions.create(DEFAULT_MESSAGES, "", response_format={"type": "json_object"})
21
self.assertIsInstance(response, ChatCompletion)
22
self.assertIn("success", json.loads(response.choices[0].message.content))
23
24
if __name__ == '__main__':
25
unittest.main()
@@ -1,2 +1,3 @@
1
1
from ..providers.base_provider import *
2
from ..providers.types import FinishReason
2
3
from .helper import get_cookies, format_prompt
@@ -0,0 +1,23 @@
1
from __future__ import annotations
2
3
from .Openai import Openai
4
from ...typing import AsyncResult, Messages
5
6
class Groq(Openai):
7
url = "https://console.groq.com/playground"
8
working = True
9
default_model = "mixtral-8x7b-32768"
10
models = ["mixtral-8x7b-32768", "llama2-70b-4096", "gemma-7b-it"]
11
model_aliases = {"mixtral-8x7b": "mixtral-8x7b-32768", "llama2-70b": "llama2-70b-4096"}
12
13
@classmethod
14
def create_async_generator(
15
cls,
16
model: str,
17
messages: Messages,
18
api_base: str = "https://api.groq.com/openai/v1",
19
**kwargs
20
) -> AsyncResult:
21
return super().create_async_generator(
22
model, messages, api_base=api_base, **kwargs
23
)
@@ -0,0 +1,74 @@
1
from __future__ import annotations
2
3
import json
4
5
from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin, FinishReason
6
from ...typing import AsyncResult, Messages
7
from ...requests.raise_for_status import raise_for_status
8
from ...requests import StreamSession
9
from ...errors import MissingAuthError
10
11
class Openai(AsyncGeneratorProvider, ProviderModelMixin):
12
url = "https://openai.com"
13
working = True
14
needs_auth = True
15
supports_message_history = True
16
supports_system_message = True
17
18
@classmethod
19
async def create_async_generator(
20
cls,
21
model: str,
22
messages: Messages,
23
proxy: str = None,
24
timeout: int = 120,
25
api_key: str = None,
26
api_base: str = "https://api.openai.com/v1",
27
temperature: float = None,
28
max_tokens: int = None,
29
top_p: float = None,
30
stop: str = None,
31
stream: bool = False,
32
**kwargs
33
) -> AsyncResult:
34
if api_key is None:
35
raise MissingAuthError('Add a "api_key"')
36
async with StreamSession(
37
proxies={"all": proxy},
38
headers=cls.get_headers(api_key),
39
timeout=timeout
40
) as session:
41
data = {
42
"messages": messages,
43
"model": cls.get_model(model),
44
"temperature": temperature,
45
"max_tokens": max_tokens,
46
"top_p": top_p,
47
"stop": stop,
48
"stream": stream,
49
}
50
async with session.post(f"{api_base.rstrip('/')}/chat/completions", json=data) as response:
51
await raise_for_status(response)
52
async for line in response.iter_lines():
53
if line.startswith(b"data: ") or not stream:
54
async for chunk in cls.read_line(line[6:] if stream else line, stream):
55
yield chunk
56
57
@staticmethod
58
async def read_line(line: str, stream: bool):
59
if line == b"[DONE]":
60
return
61
choice = json.loads(line)["choices"][0]
62
if stream and "content" in choice["delta"] and choice["delta"]["content"]:
63
yield choice["delta"]["content"]
64
elif not stream and "content" in choice["message"]:
65
yield choice["message"]["content"]
66
if "finish_reason" in choice and choice["finish_reason"] is not None:
67
yield FinishReason(choice["finish_reason"])
68
69
@staticmethod
70
def get_headers(api_key: str) -> dict:
71
return {
72
"Authorization": f"Bearer {api_key}",
73
"Content-Type": "application/json",
74
}
@@ -4,4 +4,6 @@ from .Theb import Theb
4
4
from .ThebApi import ThebApi
5
5
from .OpenaiChat import OpenaiChat
6
6
from .OpenAssistant import OpenAssistant
7
from .Poe import Poe
7
from .Poe import Poe
8
from .Openai import Openai
9
from .Groq import Groq
@@ -8,7 +8,7 @@ import string
8
8
9
9
from .stubs import ChatCompletion, ChatCompletionChunk, Image, ImagesResponse
10
10
from .typing import Union, Iterator, Messages, ImageType
11
from .providers.types import BaseProvider, ProviderType
11
from .providers.types import BaseProvider, ProviderType, FinishReason
12
12
from .image import ImageResponse as ImageProviderResponse
13
13
from .errors import NoImageResponseError, RateLimitError, MissingAuthError
14
14
from . import get_model_and_provider, get_last_provider
@@ -47,6 +47,9 @@ def iter_response(
47
47
finish_reason = None
48
48
completion_id = ''.join(random.choices(string.ascii_letters + string.digits, k=28))
49
49
for idx, chunk in enumerate(response):
50
if isinstance(chunk, FinishReason):
51
finish_reason = chunk.reason
52
break
50
53
content += str(chunk)
51
54
if max_tokens is not None and idx + 1 >= max_tokens:
52
55
finish_reason = "length"
@@ -6,9 +6,10 @@ from asyncio import AbstractEventLoop
6
6
from concurrent.futures import ThreadPoolExecutor
7
7
from abc import abstractmethod
8
8
from inspect import signature, Parameter
9
from ..typing import CreateResult, AsyncResult, Messages, Union
10
from .types import BaseProvider
11
from ..errors import NestAsyncioError, ModelNotSupportedError
9
from typing import Callable, Union
10
from ..typing import CreateResult, AsyncResult, Messages
11
from .types import BaseProvider, FinishReason
12
from ..errors import NestAsyncioError, ModelNotSupportedError, MissingRequirementsError
12
13
from .. import debug
13
14
14
15
if sys.version_info < (3, 10):
@@ -21,17 +22,23 @@ if sys.platform == 'win32':
21
22
if isinstance(asyncio.get_event_loop_policy(), asyncio.WindowsProactorEventLoopPolicy):
22
23
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
23
24
24
def get_running_loop() -> Union[AbstractEventLoop, None]:
25
def get_running_loop(check_nested: bool) -> Union[AbstractEventLoop, None]:
25
26
try:
26
27
loop = asyncio.get_running_loop()
27
if not hasattr(loop.__class__, "_nest_patched"):
28
raise NestAsyncioError(
29
'Use "create_async" instead of "create" function in a running event loop. Or use "nest_asyncio" package.'
30
)
28
if check_nested and not hasattr(loop.__class__, "_nest_patched"):
29
try:
30
import nest_asyncio
31
nest_asyncio.apply(loop)
32
except ImportError:
33
raise MissingRequirementsError('Install "nest_asyncio" package')
31
34
return loop
32
35
except RuntimeError:
33
36
pass
34
37
38
# Fix for RuntimeError: async generator ignored GeneratorExit
39
async def await_callback(callback: Callable):
40
return await callback()
41
35
42
class AbstractProvider(BaseProvider):
36
43
"""
37
44
Abstract class for providing asynchronous functionality to derived classes.
@@ -132,7 +139,7 @@ class AsyncProvider(AbstractProvider):
132
139
Returns:
133
140
CreateResult: The result of the completion creation.
134
141
"""
135
get_running_loop()
142
get_running_loop(check_nested=True)
136
143
yield asyncio.run(cls.create_async(model, messages, **kwargs))
137
144
138
145
@staticmethod
@@ -158,7 +165,6 @@ class AsyncProvider(AbstractProvider):
158
165
"""
159
166
raise NotImplementedError()
160
167
161
162
168
class AsyncGeneratorProvider(AsyncProvider):
163
169
"""
164
170
Provides asynchronous generator functionality for streaming results.
@@ -187,9 +193,9 @@ class AsyncGeneratorProvider(AsyncProvider):
187
193
Returns:
188
194
CreateResult: The result of the streaming completion creation.
189
195
"""
190
loop = get_running_loop()
196
loop = get_running_loop(check_nested=True)
191
197
new_loop = False
192
if not loop:
198
if loop is None:
193
199
loop = asyncio.new_event_loop()
194
200
asyncio.set_event_loop(loop)
195
201
new_loop = True
@@ -197,16 +203,11 @@ class AsyncGeneratorProvider(AsyncProvider):
197
203
generator = cls.create_async_generator(model, messages, stream=stream, **kwargs)
198
204
gen = generator.__aiter__()
199
205
200
# Fix for RuntimeError: async generator ignored GeneratorExit
201
async def await_callback(callback):
202
return await callback()
203
204
206
try:
205
207
while True:
206
208
yield loop.run_until_complete(await_callback(gen.__anext__))
207
209
except StopAsyncIteration:
208
210
...
209
# Fix for: ResourceWarning: unclosed event loop
210
211
finally:
211
212
if new_loop:
212
213
loop.close()
@@ -233,7 +234,7 @@ class AsyncGeneratorProvider(AsyncProvider):
233
234
"""
234
235
return "".join([
235
236
chunk async for chunk in cls.create_async_generator(model, messages, stream=False, **kwargs)
236
if not isinstance(chunk, Exception)
237
if not isinstance(chunk, (Exception, FinishReason))
237
238
])
238
239
239
240
@staticmethod
@@ -97,4 +97,8 @@ class BaseRetryProvider(BaseProvider):
97
97
__name__: str = "RetryProvider"
98
98
supports_stream: bool = True
99
99
100
ProviderType = Union[Type[BaseProvider], BaseRetryProvider]
100
ProviderType = Union[Type[BaseProvider], BaseRetryProvider]
101
102
class FinishReason():
103
def __init__(self, reason: str):
104
self.reason = reason
@@ -15,11 +15,19 @@ class StreamResponse(ClientResponse):
15
15
async for chunk in self.content.iter_any():
16
16
yield chunk
17
17
18
async def json(self) -> Any:
19
return await super().json(content_type=None)
18
async def json(self, content_type: str = None) -> Any:
19
return await super().json(content_type=content_type)
20
20
21
21
class StreamSession(ClientSession):
22
def __init__(self, headers: dict = {}, timeout: int = None, proxies: dict = {}, impersonate = None, **kwargs):
22
def __init__(
23
self,
24
headers: dict = {},
25
timeout: int = None,
26
connector: BaseConnector = None,
27
proxies: dict = {},
28
impersonate = None,
29
**kwargs
30
):
23
31
if impersonate:
24
32
headers = {
25
33
**DEFAULT_HEADERS,
@@ -29,7 +37,7 @@ class StreamSession(ClientSession):
29
37
**kwargs,
30
38
timeout=ClientTimeout(timeout) if timeout else None,
31
39
response_class=StreamResponse,
32
connector=get_connector(kwargs.get("connector"), proxies.get("https")),
40
connector=get_connector(connector, proxies.get("all", proxies.get("https"))),
33
41
headers=headers
34
42
)
35
43