From 5ca47b44b2b42abb4f48163c17500b5ee67ab28f Mon Sep 17 00:00:00 2001 From: Heiner Lohaus Date: Tue, 5 Sep 2023 17:27:24 +0200 Subject: Add to many provider async and stream support, Fix Ails, AItianhu, ChatgptAi, ChatgptLogin Provider, Add fallback cookies to Bing, Improve OpenaiChat Provider --- g4f/Provider/AItianhu.py | 76 +++++++++++++-------- g4f/Provider/Acytoo.py | 47 ++++++------- g4f/Provider/Aichat.py | 55 ++++++++-------- g4f/Provider/Ails.py | 89 +++++++++++-------------- g4f/Provider/Bing.py | 23 ++----- g4f/Provider/ChatgptAi.py | 84 ++++++++++++++---------- g4f/Provider/ChatgptLogin.py | 149 ++++++++++++------------------------------ g4f/Provider/DeepAi.py | 31 ++++----- g4f/Provider/H2o.py | 3 +- g4f/Provider/HuggingChat.py | 52 +++++++-------- g4f/Provider/Liaobots.py | 18 ++--- g4f/Provider/Opchatgpts.py | 60 ++--------------- g4f/Provider/OpenaiChat.py | 104 +++++++++++++++-------------- g4f/Provider/Vercel.py | 99 ++++++++++++++++------------ g4f/Provider/Wewordle.py | 55 +++++++--------- g4f/Provider/Yqcloud.py | 15 +++-- g4f/Provider/base_provider.py | 11 ++-- requirements.txt | 1 - testing/test_async.py | 37 +++++++++++ testing/test_providers.py | 23 ++++--- tool/readme_table.py | 27 +------- 21 files changed, 497 insertions(+), 562 deletions(-) create mode 100644 testing/test_async.py diff --git a/g4f/Provider/AItianhu.py b/g4f/Provider/AItianhu.py index 0982d3c6..2e129896 100644 --- a/g4f/Provider/AItianhu.py +++ b/g4f/Provider/AItianhu.py @@ -1,43 +1,62 @@ from __future__ import annotations import json +from aiohttp import ClientSession, http -import requests +from ..typing import AsyncGenerator +from .base_provider import AsyncGeneratorProvider, format_prompt -from ..typing import Any, CreateResult -from .base_provider import BaseProvider - -class AItianhu(BaseProvider): - url = "https://www.aitianhu.com/" - working = False +class AItianhu(AsyncGeneratorProvider): + url = "https://www.aitianhu.com" + working = True supports_gpt_35_turbo = True - @staticmethod - def create_completion( + @classmethod + async def create_async_generator( + cls, model: str, messages: list[dict[str, str]], - stream: bool, **kwargs: Any) -> CreateResult: - - base = "\n".join(f"{message['role']}: {message['content']}" for message in messages) - base += "\nassistant: " - + proxy: str = None, + **kwargs + ) -> AsyncGenerator: headers = { - "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36" + "User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/116.0", + "Accept": "application/json, text/plain, */*", + "Accept-Language": "de,en-US;q=0.7,en;q=0.3", + "Content-Type": "application/json", + "Origin": cls.url, + "Connection": "keep-alive", + "Referer": cls.url + "/", + "Sec-Fetch-Dest": "empty", + "Sec-Fetch-Mode": "cors", + "Sec-Fetch-Site": "same-origin", } - data: dict[str, Any] = { - "prompt": base, - "options": {}, - "systemMessage": "You are ChatGPT, a large language model trained by OpenAI. Follow the user's instructions carefully. Respond using markdown.", - "temperature": kwargs.get("temperature", 0.8), - "top_p": kwargs.get("top_p", 1), - } - url = "https://www.aitianhu.com/api/chat-process" - response = requests.post(url, headers=headers, json=data) - response.raise_for_status() - lines = response.text.strip().split("\n") - res = json.loads(lines[-1]) - yield res["text"] + async with ClientSession( + headers=headers, + version=http.HttpVersion10 + ) as session: + data = { + "prompt": format_prompt(messages), + "options": {}, + "systemMessage": "You are ChatGPT, a large language model trained by OpenAI. Follow the user's instructions carefully.", + "temperature": 0.8, + "top_p": 1, + **kwargs + } + async with session.post( + cls.url + "/api/chat-process", + proxy=proxy, + json=data, + ssl=False, + ) as response: + response.raise_for_status() + async for line in response.content: + line = json.loads(line.decode('utf-8')) + token = line["detail"]["choices"][0]["delta"].get("content") + if token: + yield token + @classmethod @property @@ -46,6 +65,7 @@ class AItianhu(BaseProvider): ("model", "str"), ("messages", "list[dict[str, str]]"), ("stream", "bool"), + ("proxy", "str"), ("temperature", "float"), ("top_p", "int"), ] diff --git a/g4f/Provider/Acytoo.py b/g4f/Provider/Acytoo.py index 48a3a344..d36ca6da 100644 --- a/g4f/Provider/Acytoo.py +++ b/g4f/Provider/Acytoo.py @@ -1,32 +1,37 @@ from __future__ import annotations -import time +from aiohttp import ClientSession -import requests +from ..typing import AsyncGenerator +from .base_provider import AsyncGeneratorProvider -from ..typing import Any, CreateResult -from .base_provider import BaseProvider - -class Acytoo(BaseProvider): - url = 'https://chat.acytoo.com/' +class Acytoo(AsyncGeneratorProvider): + url = 'https://chat.acytoo.com' working = True supports_gpt_35_turbo = True @classmethod - def create_completion( + async def create_async_generator( cls, model: str, messages: list[dict[str, str]], - stream: bool, **kwargs: Any) -> CreateResult: - - response = requests.post(f'{cls.url}api/completions', - headers=_create_header(), json=_create_payload(messages, kwargs.get('temperature', 0.5))) - - response.raise_for_status() - response.encoding = 'utf-8' - - yield response.text + proxy: str = None, + **kwargs + ) -> AsyncGenerator: + + async with ClientSession( + headers=_create_header() + ) as session: + async with session.post( + cls.url + '/api/completions', + proxy=proxy, + json=_create_payload(messages, **kwargs) + ) as response: + response.raise_for_status() + async for stream in response.content.iter_any(): + if stream: + yield stream.decode() def _create_header(): @@ -36,15 +41,11 @@ def _create_header(): } -def _create_payload(messages: list[dict[str, str]], temperature): - payload_messages = [ - message | {'createdAt': int(time.time()) * 1000} for message in messages - ] - +def _create_payload(messages: list[dict[str, str]], temperature: float = 0.5, **kwargs): return { 'key' : '', 'model' : 'gpt-3.5-turbo', - 'messages' : payload_messages, + 'messages' : messages, 'temperature' : temperature, 'password' : '' } \ No newline at end of file diff --git a/g4f/Provider/Aichat.py b/g4f/Provider/Aichat.py index 59640533..8edd17e2 100644 --- a/g4f/Provider/Aichat.py +++ b/g4f/Provider/Aichat.py @@ -1,25 +1,22 @@ from __future__ import annotations -import requests +from aiohttp import ClientSession -from ..typing import Any, CreateResult -from .base_provider import BaseProvider +from .base_provider import AsyncProvider, format_prompt -class Aichat(BaseProvider): +class Aichat(AsyncProvider): url = "https://chat-gpt.org/chat" working = True supports_gpt_35_turbo = True @staticmethod - def create_completion( + async def create_async( model: str, messages: list[dict[str, str]], - stream: bool, **kwargs: Any) -> CreateResult: - - chat = "\n".join(f"{message['role']}: {message['content']}" for message in messages) - chat += "\nassistant: " - + proxy: str = None, + **kwargs + ) -> str: headers = { "authority": "chat-gpt.org", "accept": "*/*", @@ -35,21 +32,23 @@ class Aichat(BaseProvider): "sec-fetch-site": "same-origin", "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36", } - - json_data = { - "message": base, - "temperature": kwargs.get('temperature', 0.5), - "presence_penalty": 0, - "top_p": kwargs.get('top_p', 1), - "frequency_penalty": 0, - } - - response = requests.post( - "https://chat-gpt.org/api/text", - headers=headers, - json=json_data, - ) - response.raise_for_status() - if not response.json()['response']: - raise Exception("Error Response: " + response.json()) - yield response.json()["message"] + async with ClientSession( + headers=headers + ) as session: + json_data = { + "message": format_prompt(messages), + "temperature": kwargs.get('temperature', 0.5), + "presence_penalty": 0, + "top_p": kwargs.get('top_p', 1), + "frequency_penalty": 0, + } + async with session.post( + "https://chat-gpt.org/api/text", + proxy=proxy, + json=json_data + ) as response: + response.raise_for_status() + result = await response.json() + if not result['response']: + raise Exception(f"Error Response: {result}") + return result["message"] diff --git a/g4f/Provider/Ails.py b/g4f/Provider/Ails.py index 4eb21729..d533ae24 100644 --- a/g4f/Provider/Ails.py +++ b/g4f/Provider/Ails.py @@ -1,36 +1,36 @@ from __future__ import annotations import hashlib -import json import time import uuid +import json from datetime import datetime +from aiohttp import ClientSession -import requests - -from ..typing import SHA256, Any, CreateResult -from .base_provider import BaseProvider +from ..typing import SHA256, AsyncGenerator +from .base_provider import AsyncGeneratorProvider -class Ails(BaseProvider): +class Ails(AsyncGeneratorProvider): url: str = "https://ai.ls" working = True - supports_stream = True supports_gpt_35_turbo = True @staticmethod - def create_completion( + async def create_async_generator( model: str, messages: list[dict[str, str]], - stream: bool, **kwargs: Any) -> CreateResult: - + stream: bool, + proxy: str = None, + **kwargs + ) -> AsyncGenerator: headers = { "authority": "api.caipacity.com", "accept": "*/*", "accept-language": "en,fr-FR;q=0.9,fr;q=0.8,es-ES;q=0.7,es;q=0.6,en-US;q=0.5,am;q=0.4,de;q=0.3", "authorization": "Bearer free", "client-id": str(uuid.uuid4()), - "client-v": _get_client_v(), + "client-v": "0.1.278", "content-type": "application/json", "origin": "https://ai.ls", "referer": "https://ai.ls/", @@ -41,42 +41,39 @@ class Ails(BaseProvider): "sec-fetch-mode": "cors", "sec-fetch-site": "cross-site", "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36", + "from-url": "https://ai.ls/?chat=1" } - - timestamp = _format_timestamp(int(time.time() * 1000)) - sig = { - "d": datetime.now().strftime("%Y-%m-%d"), - "t": timestamp, - "s": _hash({"t": timestamp, "m": messages[-1]["content"]}), - } - - json_data = json.dumps( - separators=(",", ":"), - obj={ + async with ClientSession( + headers=headers + ) as session: + timestamp = _format_timestamp(int(time.time() * 1000)) + json_data = { "model": "gpt-3.5-turbo", "temperature": kwargs.get("temperature", 0.6), "stream": True, "messages": messages, + "d": datetime.now().strftime("%Y-%m-%d"), + "t": timestamp, + "s": _hash({"t": timestamp, "m": messages[-1]["content"]}), } - | sig, - ) - - response = requests.post( - "https://api.caipacity.com/v1/chat/completions", - headers=headers, - data=json_data, - stream=True, - ) - response.raise_for_status() + async with session.post( + "https://api.caipacity.com/v1/chat/completions", + proxy=proxy, + json=json_data + ) as response: + response.raise_for_status() + start = "data: " + async for line in response.content: + line = line.decode('utf-8') + if line.startswith(start) and line != "data: [DONE]": + line = line[len(start):-1] + line = json.loads(line) + token = line["choices"][0]["delta"].get("content") + if token: + if "ai.ls" in token or "ai.ci" in token: + raise Exception("Response Error: " + token) + yield token - for token in response.iter_lines(): - if b"content" in token: - completion_chunk = json.loads(token.decode().replace("data: ", "")) - token = completion_chunk["choices"][0]["delta"].get("content") - if "ai.ls" in token.lower() or "ai.ci" in token.lower(): - raise Exception("Response Error: " + token) - if token != None: - yield token @classmethod @property @@ -106,14 +103,4 @@ def _format_timestamp(timestamp: int) -> str: e = timestamp n = e % 10 r = n + 1 if n % 2 == 0 else n - return str(e - n + r) - - -def _get_client_v(): - response = requests.get("https://ai.ls/?chat=1") - response.raise_for_status() - js_path = response.text.split('crossorigin href="')[1].split('"')[0] - - response = requests.get("https://ai.ls" + js_path) - response.raise_for_status() - return response.text.split('G4="')[1].split('"')[0] + return str(e - n + r) \ No newline at end of file diff --git a/g4f/Provider/Bing.py b/g4f/Provider/Bing.py index cec82108..179ca29b 100644 --- a/g4f/Provider/Bing.py +++ b/g4f/Provider/Bing.py @@ -1,23 +1,14 @@ from __future__ import annotations -import asyncio -import json -import os -import random - -import aiohttp -from aiohttp import ClientSession - -from ..typing import Any, AsyncGenerator, CreateResult, Union +from aiohttp import ClientSession +from ..typing import Any, AsyncGenerator, Union from .base_provider import AsyncGeneratorProvider, get_cookies class Bing(AsyncGeneratorProvider): url = "https://bing.com/chat" - needs_auth = True working = True supports_gpt_4 = True - supports_stream = True @staticmethod def create_async_generator( @@ -34,18 +25,16 @@ class Bing(AsyncGeneratorProvider): prompt = messages[-1]["content"] context = create_context(messages[:-1]) - if cookies and "SRCHD" in cookies: - #TODO: Will implement proper cookie retrieval later and use a try-except mechanism in 'stream_generate' instead of defaulting the cookie value like this - cookies_dict = { - 'SRCHD' : cookies["SRCHD"], + if not cookies or "SRCHD" not in cookies: + cookies = { + 'SRCHD' : 'AF=NOFORM', 'PPLState' : '1', 'KievRPSSecAuth': '', 'SUID' : '', 'SRCHUSR' : '', 'SRCHHPGUSR' : '', } - - return stream_generate(prompt, context, cookies_dict) + return stream_generate(prompt, context, cookies) def create_context(messages: list[dict[str, str]]): context = "".join(f"[{message['role']}](#message)\n{message['content']}\n\n" for message in messages) diff --git a/g4f/Provider/ChatgptAi.py b/g4f/Provider/ChatgptAi.py index 7613ccf1..e6416cc3 100644 --- a/g4f/Provider/ChatgptAi.py +++ b/g4f/Provider/ChatgptAi.py @@ -1,32 +1,28 @@ from __future__ import annotations import re +import html +import json +from aiohttp import ClientSession -import requests +from ..typing import AsyncGenerator +from .base_provider import AsyncGeneratorProvider -from ..typing import Any, CreateResult -from .base_provider import BaseProvider +class ChatgptAi(AsyncGeneratorProvider): + url: str = "https://chatgpt.ai/" + working = True + supports_gpt_35_turbo = True + _system_data = None -class ChatgptAi(BaseProvider): - url: str = "https://chatgpt.ai/gpt-4/" - working = True - supports_gpt_4 = True - - @staticmethod - def create_completion( + @classmethod + async def create_async_generator( + cls, model: str, messages: list[dict[str, str]], - stream: bool, **kwargs: Any) -> CreateResult: - - chat = "\n".join(f"{message['role']}: {message['content']}" for message in messages) - chat += "\nassistant: " - - response = requests.get("https://chatgpt.ai/") - nonce, post_id, _, bot_id = re.findall( - r'data-nonce="(.*)"\n data-post-id="(.*)"\n data-url="(.*)"\n data-bot-id="(.*)"\n data-width', - response.text)[0] - + proxy: str = None, + **kwargs + ) -> AsyncGenerator: headers = { "authority" : "chatgpt.ai", "accept" : "*/*", @@ -34,7 +30,7 @@ class ChatgptAi(BaseProvider): "cache-control" : "no-cache", "origin" : "https://chatgpt.ai", "pragma" : "no-cache", - "referer" : "https://chatgpt.ai/gpt-4/", + "referer" : cls.url, "sec-ch-ua" : '"Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114"', "sec-ch-ua-mobile" : "?0", "sec-ch-ua-platform" : '"Windows"', @@ -43,17 +39,37 @@ class ChatgptAi(BaseProvider): "sec-fetch-site" : "same-origin", "user-agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36", } - data = { - "_wpnonce" : nonce, - "post_id" : post_id, - "url" : "https://chatgpt.ai/gpt-4", - "action" : "wpaicg_chat_shortcode_message", - "message" : chat, - "bot_id" : bot_id, - } + async with ClientSession( + headers=headers + ) as session: + if not cls._system_data: + async with session.get(cls.url, proxy=proxy) as response: + response.raise_for_status() + match = re.findall(r"data-system='([^']+)'", await response.text()) + if not match: + raise RuntimeError("No system data") + cls._system_data = json.loads(html.unescape(match[0])) - response = requests.post( - "https://chatgpt.ai/wp-admin/admin-ajax.php", headers=headers, data=data) - - response.raise_for_status() - yield response.json()["data"] \ No newline at end of file + data = { + "botId": cls._system_data["botId"], + "clientId": "", + "contextId": cls._system_data["contextId"], + "id": cls._system_data["id"], + "messages": messages[:-1], + "newMessage": messages[-1]["content"], + "session": cls._system_data["sessionId"], + "stream": True + } + async with session.post( + "https://chatgpt.ai/wp-json/mwai-ui/v1/chats/submit", + proxy=proxy, + json=data + ) as response: + response.raise_for_status() + start = "data: " + async for line in response.content: + line = line.decode('utf-8') + if line.startswith(start): + line = json.loads(line[len(start):-1]) + if line["type"] == "live": + yield line["data"] \ No newline at end of file diff --git a/g4f/Provider/ChatgptLogin.py b/g4f/Provider/ChatgptLogin.py index e4584d32..8b868f8e 100644 --- a/g4f/Provider/ChatgptLogin.py +++ b/g4f/Provider/ChatgptLogin.py @@ -1,70 +1,58 @@ from __future__ import annotations -import base64 -import os -import re +import os, re +from aiohttp import ClientSession -import requests +from .base_provider import AsyncProvider, format_prompt -from ..typing import Any, CreateResult -from .base_provider import BaseProvider - -class ChatgptLogin(BaseProvider): +class ChatgptLogin(AsyncProvider): url = "https://opchatgpts.net" supports_gpt_35_turbo = True working = True + _nonce = None - @staticmethod - def create_completion( + @classmethod + async def create_async( + cls, model: str, messages: list[dict[str, str]], - stream: bool, **kwargs: Any) -> CreateResult: - + **kwargs + ) -> str: headers = { - "authority" : "chatgptlogin.ac", - "accept" : "*/*", - "accept-language" : "en,fr-FR;q=0.9,fr;q=0.8,es-ES;q=0.7,es;q=0.6,en-US;q=0.5,am;q=0.4,de;q=0.3", - "content-type" : "application/json", - "origin" : "https://opchatgpts.net", - "referer" : "https://opchatgpts.net/chatgpt-free-use/", - "sec-ch-ua" : '"Chromium";v="116", "Not)A;Brand";v="24", "Google Chrome";v="116"', - "sec-ch-ua-mobile" : "?0", - "sec-ch-ua-platform" : '"Windows"', - "sec-fetch-dest" : "empty", - "sec-fetch-mode" : "cors", - "sec-fetch-site" : "same-origin", - "user-agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36", - "x-wp-nonce" : _get_nonce(), - } - - conversation = _transform(messages) - - json_data = { - "env" : "chatbot", - "session" : "N/A", - "prompt" : "Converse as if you were an AI assistant. Be friendly, creative.", - "context" : "Converse as if you were an AI assistant. Be friendly, creative.", - "messages" : conversation, - "newMessage" : messages[-1]["content"], - "userName" : '
User:
', - "aiName" : '
AI:
', - "model" : "gpt-3.5-turbo", - "temperature" : kwargs.get("temperature", 0.8), - "maxTokens" : 1024, - "maxResults" : 1, - "apiKey" : "", - "service" : "openai", - "embeddingsIndex": "", - "stop" : "", - "clientId" : os.urandom(6).hex() + "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36", + "Accept" : "*/*", + "Accept-language" : "en,fr-FR;q=0.9,fr;q=0.8,es-ES;q=0.7,es;q=0.6,en-US;q=0.5,am;q=0.4,de;q=0.3", + "Origin" : "https://opchatgpts.net", + "Alt-Used" : "opchatgpts.net", + "Referer" : "https://opchatgpts.net/chatgpt-free-use/", + "Sec-Fetch-Dest" : "empty", + "Sec-Fetch-Mode" : "cors", + "Sec-Fetch-Site" : "same-origin", } - - response = requests.post("https://opchatgpts.net/wp-json/ai-chatbot/v1/chat", - headers=headers, json=json_data) - - response.raise_for_status() - yield response.json()["reply"] + async with ClientSession( + headers=headers + ) as session: + if not cls._nonce: + async with session.get( + "https://opchatgpts.net/chatgpt-free-use/", + params={"id": os.urandom(6).hex()}, + ) as response: + result = re.search(r'data-nonce="(.*?)"', await response.text()) + if not result: + raise RuntimeError("No nonce value") + cls._nonce = result.group(1) + data = { + "_wpnonce": cls._nonce, + "post_id": 28, + "url": "https://opchatgpts.net/chatgpt-free-use", + "action": "wpaicg_chat_shortcode_message", + "message": format_prompt(messages), + "bot_id": 0 + } + async with session.post("https://opchatgpts.net/wp-admin/admin-ajax.php", data=data) as response: + response.raise_for_status() + return (await response.json())["data"] @classmethod @property @@ -76,55 +64,4 @@ class ChatgptLogin(BaseProvider): ("temperature", "float"), ] param = ", ".join([": ".join(p) for p in params]) - return f"g4f.provider.{cls.__name__} supports: ({param})" - - -def _get_nonce() -> str: - res = requests.get("https://opchatgpts.net/chatgpt-free-use/", - headers = { - "Referer" : "https://opchatgpts.net/chatgpt-free-use/", - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"}) - - result = re.search( - r'class="mwai-chat mwai-chatgpt">.*Send