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

XFEstudio/gpt4free

Add Groq and Openai interfaces, Add integration tests

d44b39b3
Heiner Lohaus <hlohaus@users.noreply.github.com>
提交于

代码差异

10 个文件 +167 -25
Modified etc/unittest/__main__.py +1 -0
@@ -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()
Added etc/unittest/integration.py +25 -0
@@ -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()
Modified g4f/Provider/base_provider.py +1 -0
@@ -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
Added g4f/Provider/needs_auth/Groq.py +23 -0
@@ -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 )
Added g4f/Provider/needs_auth/Openai.py +74 -0
@@ -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 }
Modified g4f/Provider/needs_auth/__init__.py +3 -1
@@ -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
Modified g4f/client.py +4 -1
@@ -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"
Modified g4f/providers/base_provider.py +19 -18
@@ -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
Modified g4f/providers/types.py +5 -1
@@ -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
Modified g4f/requests/aiohttp.py +12 -4
@@ -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