From 3c2755bc72efa0d8e5d8b2883443530ba67ecad4 Mon Sep 17 00:00:00 2001 From: Heiner Lohaus Date: Tue, 26 Sep 2023 10:03:37 +0200 Subject: Add ChatgptDuo and Aibn Provider Add support for "nest_asyncio", Reuse event_loops with event_loop_policy Support for "create_async" with synchron provider --- g4f/Provider/Aibn.py | 51 +++++++++++++++++++++ g4f/Provider/ChatgptDuo.py | 51 +++++++++++++++++++++ g4f/Provider/PerplexityAi.py | 2 +- g4f/Provider/Vercel.py | 2 +- g4f/Provider/__init__.py | 4 ++ g4f/Provider/base_provider.py | 102 ++++++++++++------------------------------ g4f/Provider/helper.py | 54 ++++++++++++++++++++++ 7 files changed, 191 insertions(+), 75 deletions(-) create mode 100644 g4f/Provider/Aibn.py create mode 100644 g4f/Provider/ChatgptDuo.py create mode 100644 g4f/Provider/helper.py (limited to 'g4f/Provider') diff --git a/g4f/Provider/Aibn.py b/g4f/Provider/Aibn.py new file mode 100644 index 00000000..1ef928be --- /dev/null +++ b/g4f/Provider/Aibn.py @@ -0,0 +1,51 @@ +from __future__ import annotations + +import time +import hashlib + +from ..typing import AsyncGenerator +from g4f.requests import AsyncSession +from .base_provider import AsyncGeneratorProvider + + +class Aibn(AsyncGeneratorProvider): + url = "https://aibn.cc" + supports_gpt_35_turbo = True + working = True + + @classmethod + async def create_async_generator( + cls, + model: str, + messages: list[dict[str, str]], + **kwargs + ) -> AsyncGenerator: + async with AsyncSession(impersonate="chrome107") as session: + timestamp = int(time.time()) + data = { + "messages": messages, + "pass": None, + "sign": generate_signature(timestamp, messages[-1]["content"]), + "time": timestamp + } + async with session.post(f"{cls.url}/api/generate", json=data) as response: + response.raise_for_status() + async for chunk in response.content.iter_any(): + yield chunk.decode() + + @classmethod + @property + def params(cls): + params = [ + ("model", "str"), + ("messages", "list[dict[str, str]]"), + ("stream", "bool"), + ("temperature", "float"), + ] + param = ", ".join([": ".join(p) for p in params]) + return f"g4f.provider.{cls.__name__} supports: ({param})" + + +def generate_signature(timestamp: int, message: str, secret: str = "undefined"): + data = f"{timestamp}:{message}:{secret}" + return hashlib.sha256(data.encode()).hexdigest() \ No newline at end of file diff --git a/g4f/Provider/ChatgptDuo.py b/g4f/Provider/ChatgptDuo.py new file mode 100644 index 00000000..07f4c16c --- /dev/null +++ b/g4f/Provider/ChatgptDuo.py @@ -0,0 +1,51 @@ +from __future__ import annotations + +from g4f.requests import AsyncSession +from .base_provider import AsyncProvider, format_prompt + + +class ChatgptDuo(AsyncProvider): + url = "https://chatgptduo.com" + supports_gpt_35_turbo = True + working = True + + @classmethod + async def create_async( + cls, + model: str, + messages: list[dict[str, str]], + **kwargs + ) -> str: + async with AsyncSession(impersonate="chrome107") as session: + prompt = format_prompt(messages), + data = { + "prompt": prompt, + "search": prompt, + "purpose": "ask", + } + async with session.post(f"{cls.url}/", data=data) as response: + response.raise_for_status() + data = await response.json() + + cls._sources = [{ + "title": source["title"], + "url": source["link"], + "snippet": source["snippet"] + } for source in data["results"]] + + return data["answer"] + + @classmethod + def get_sources(cls): + return cls._sources + + @classmethod + @property + def params(cls): + params = [ + ("model", "str"), + ("messages", "list[dict[str, str]]"), + ("stream", "bool"), + ] + param = ", ".join([": ".join(p) for p in params]) + return f"g4f.provider.{cls.__name__} supports: ({param})" \ No newline at end of file diff --git a/g4f/Provider/PerplexityAi.py b/g4f/Provider/PerplexityAi.py index 269cdafd..fc0fd48c 100644 --- a/g4f/Provider/PerplexityAi.py +++ b/g4f/Provider/PerplexityAi.py @@ -58,7 +58,7 @@ class PerplexityAi(AsyncProvider): result = json.loads(json.loads(line[3:])[0]["text"]) cls._sources = [{ - "name": source["name"], + "title": source["name"], "url": source["url"], "snippet": source["snippet"] } for source in result["web_results"]] diff --git a/g4f/Provider/Vercel.py b/g4f/Provider/Vercel.py index 4102c07b..2d20ca6a 100644 --- a/g4f/Provider/Vercel.py +++ b/g4f/Provider/Vercel.py @@ -62,7 +62,7 @@ class Vercel(BaseProvider): response.raise_for_status() except: continue - for token in response.iter_content(chunk_size=8): + for token in response.iter_content(chunk_size=None): yield token.decode() break diff --git a/g4f/Provider/__init__.py b/g4f/Provider/__init__.py index ebe01603..59c91dd5 100644 --- a/g4f/Provider/__init__.py +++ b/g4f/Provider/__init__.py @@ -1,5 +1,6 @@ from __future__ import annotations from .Acytoo import Acytoo +from .Aibn import Aibn from .Aichat import Aichat from .Ails import Ails from .AiService import AiService @@ -10,6 +11,7 @@ from .Bard import Bard from .Bing import Bing from .ChatBase import ChatBase from .ChatgptAi import ChatgptAi +from .ChatgptDuo import ChatgptDuo from .ChatgptLogin import ChatgptLogin from .CodeLinkAva import CodeLinkAva from .DeepAi import DeepAi @@ -49,6 +51,7 @@ __all__ = [ 'AsyncGeneratorProvider', 'RetryProvider', 'Acytoo', + 'Aibn', 'Aichat', 'Ails', 'AiService', @@ -59,6 +62,7 @@ __all__ = [ 'Bing', 'ChatBase', 'ChatgptAi', + 'ChatgptDuo', 'ChatgptLogin', 'CodeLinkAva', 'DeepAi', diff --git a/g4f/Provider/base_provider.py b/g4f/Provider/base_provider.py index e8a54f78..a21dc871 100644 --- a/g4f/Provider/base_provider.py +++ b/g4f/Provider/base_provider.py @@ -1,13 +1,10 @@ from __future__ import annotations -import asyncio -import functools -from asyncio import SelectorEventLoop, AbstractEventLoop +from asyncio import AbstractEventLoop from concurrent.futures import ThreadPoolExecutor from abc import ABC, abstractmethod -import browser_cookie3 - +from .helper import get_event_loop, get_cookies, format_prompt from ..typing import AsyncGenerator, CreateResult @@ -40,20 +37,18 @@ class BaseProvider(ABC): **kwargs ) -> str: if not loop: - loop = asyncio.get_event_loop() - - partial_func = functools.partial( - cls.create_completion, - model, - messages, - False, - **kwargs - ) - response = await loop.run_in_executor( + loop = get_event_loop() + def create_func(): + return "".join(cls.create_completion( + model, + messages, + False, + **kwargs + )) + return await loop.run_in_executor( executor, - partial_func + create_func ) - return "".join(response) @classmethod @property @@ -76,11 +71,9 @@ class AsyncProvider(BaseProvider): stream: bool = False, **kwargs ) -> CreateResult: - loop = create_event_loop() - try: - yield loop.run_until_complete(cls.create_async(model, messages, **kwargs)) - finally: - loop.close() + loop = get_event_loop() + coro = cls.create_async(model, messages, **kwargs) + yield loop.run_until_complete(coro) @staticmethod @abstractmethod @@ -103,22 +96,19 @@ class AsyncGeneratorProvider(AsyncProvider): stream: bool = True, **kwargs ) -> CreateResult: - loop = create_event_loop() - try: - generator = cls.create_async_generator( - model, - messages, - stream=stream, - **kwargs - ) - gen = generator.__aiter__() - while True: - try: - yield loop.run_until_complete(gen.__anext__()) - except StopAsyncIteration: - break - finally: - loop.close() + loop = get_event_loop() + generator = cls.create_async_generator( + model, + messages, + stream=stream, + **kwargs + ) + gen = generator.__aiter__() + while True: + try: + yield loop.run_until_complete(gen.__anext__()) + except StopAsyncIteration: + break @classmethod async def create_async( @@ -143,38 +133,4 @@ class AsyncGeneratorProvider(AsyncProvider): messages: list[dict[str, str]], **kwargs ) -> AsyncGenerator: - raise NotImplementedError() - - -# Don't create a new event loop in a running async loop. -# Force use selector event loop on windows and linux use it anyway. -def create_event_loop() -> SelectorEventLoop: - try: - asyncio.get_running_loop() - except RuntimeError: - return SelectorEventLoop() - raise RuntimeError( - 'Use "create_async" instead of "create" function in a running event loop.') - - -_cookies = {} - -def get_cookies(cookie_domain: str) -> dict: - if cookie_domain not in _cookies: - _cookies[cookie_domain] = {} - try: - for cookie in browser_cookie3.load(cookie_domain): - _cookies[cookie_domain][cookie.name] = cookie.value - except: - pass - return _cookies[cookie_domain] - - -def format_prompt(messages: list[dict[str, str]], add_special_tokens=False): - if add_special_tokens or len(messages) > 1: - formatted = "\n".join( - ["%s: %s" % ((message["role"]).capitalize(), message["content"]) for message in messages] - ) - return f"{formatted}\nAssistant:" - else: - return messages[0]["content"] \ No newline at end of file + raise NotImplementedError() \ No newline at end of file diff --git a/g4f/Provider/helper.py b/g4f/Provider/helper.py new file mode 100644 index 00000000..e14ae65e --- /dev/null +++ b/g4f/Provider/helper.py @@ -0,0 +1,54 @@ +from __future__ import annotations + +import asyncio +import sys +from asyncio import AbstractEventLoop + +import browser_cookie3 + +_cookies: dict[str, dict[str, str]] = {} + +# Use own event_loop_policy with a selector event loop on windows. +if sys.platform == 'win32': + _event_loop_policy = asyncio.WindowsSelectorEventLoopPolicy() +else: + _event_loop_policy = asyncio.get_event_loop_policy() + +# If event loop is already running, handle nested event loops +# If "nest_asyncio" is installed, patch the event loop. +def get_event_loop() -> AbstractEventLoop: + try: + asyncio.get_running_loop() + except RuntimeError: + return _event_loop_policy.get_event_loop() + try: + event_loop = _event_loop_policy.get_event_loop() + if not hasattr(event_loop.__class__, "_nest_patched"): + import nest_asyncio + nest_asyncio.apply(event_loop) + return event_loop + except ImportError: + raise RuntimeError( + 'Use "create_async" instead of "create" function in a running event loop. Or install the "nest_asyncio" package.') + +# Load cookies for a domain from all supported browser. +# Cache the results in the "_cookies" variable +def get_cookies(cookie_domain: str) -> dict: + if cookie_domain not in _cookies: + _cookies[cookie_domain] = {} + try: + for cookie in browser_cookie3.load(cookie_domain): + _cookies[cookie_domain][cookie.name] = cookie.value + except: + pass + return _cookies[cookie_domain] + + +def format_prompt(messages: list[dict[str, str]], add_special_tokens=False): + if add_special_tokens or len(messages) > 1: + formatted = "\n".join( + ["%s: %s" % ((message["role"]).capitalize(), message["content"]) for message in messages] + ) + return f"{formatted}\nAssistant:" + else: + return messages[0]["content"] \ No newline at end of file -- cgit v1.2.3