From 3c1c42e1a072ea43658fa18a1de3ee38275bbd48 Mon Sep 17 00:00:00 2001 From: Junyan Qin <1010553892@qq.com> Date: Sat, 23 Sep 2023 08:42:37 +0000 Subject: [PATCH 01/15] feat(adapter): add supports for claude_api --- free_one_api/impls/adapter/claude.py | 90 ++++++++++++++++++++++++ free_one_api/impls/adapter/revChatGPT.py | 2 +- free_one_api/impls/app.py | 1 + free_one_api/models/adapter/llm.py | 2 +- 4 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 free_one_api/impls/adapter/claude.py diff --git a/free_one_api/impls/adapter/claude.py b/free_one_api/impls/adapter/claude.py new file mode 100644 index 0000000..6cf2c0e --- /dev/null +++ b/free_one_api/impls/adapter/claude.py @@ -0,0 +1,90 @@ +import typing +import traceback +import uuid +import random + +import claude_api as claude + +from free_one_api.entities import request, response + +from ...models import adapter +from ...models.adapter import llm +from ...entities import request, response, exceptions + + +@adapter.llm_adapter +class ClaudeAdapter(llm.LLMLibAdapter): + + @classmethod + def name(cls) -> str: + return "KoushikNavuluri/Claude-API" + + @classmethod + def description(self) -> str: + return "Use KoushikNavuluri/Claude-API to access Claude web edition." + + def supported_models(self) -> list[str]: + return [ + "gpt-3.5-turbo", + "gpt-4" + ] + + def function_call_supported(self) -> bool: + return False + + def stream_mode_supported(self) -> bool: + return False + + def multi_round_supported(self) -> bool: + return True + + @classmethod + def config_comment(cls) -> str: + return \ +"""Currently support single message only. +You should provide cookie string as `cookie` in config: +{ + "cookie": "your cookie string" +} + +Method of getting cookie string, please refer to https://github.com/KoushikNavuluri/Claude-API +""" + + @classmethod + def supported_path(cls) -> str: + return "/v1/chat/completions" + + chatbot: claude.Client + + def __init__(self, config: dict): + self.config = config + self.chatbot = claude.Client(self.config["cookie"]) + + async def test(self) -> (bool, str): + try: + conversation_id = self.chatbot.create_new_chat()['uuid'] + response = self.chatbot.send_message("Hello, Claude!", conversation_id) + return True, "" + except Exception as e: + traceback.print_exc() + return False, str(e) + + async def query(self, req: request.Request) -> typing.AsyncGenerator[response.Response, None]: + prompt = "" + + for msg in req.messages: + prompt += f"{msg['role']}: {msg['content']}\n" + + prompt += "assistant: " + + random_int = random.randint(0, 1000000000) + + conversation_id = self.chatbot.create_new_chat()['uuid'] + resp_text = self.chatbot.send_message(prompt, conversation_id) + + yield response.Response( + id=random_int, + finish_reason=response.FinishReason.STOP, + normal_message=resp_text, + function_call=None + ) diff --git a/free_one_api/impls/adapter/revChatGPT.py b/free_one_api/impls/adapter/revChatGPT.py index 3e1c367..83bb80f 100644 --- a/free_one_api/impls/adapter/revChatGPT.py +++ b/free_one_api/impls/adapter/revChatGPT.py @@ -90,7 +90,7 @@ async def test(self) -> (bool, str): traceback.print_exc() return False, str(e) - async def query(self, req: request.Request) -> typing.Generator[response.Response, None, None]: + async def query(self, req: request.Request) -> typing.AsyncGenerator[response.Response, None]: new_messages = [] for i in range(len(req.messages)): new_messages.append({ diff --git a/free_one_api/impls/app.py b/free_one_api/impls/app.py index d640e62..a9eca46 100644 --- a/free_one_api/impls/app.py +++ b/free_one_api/impls/app.py @@ -13,6 +13,7 @@ from ..models.router import group as routergroup from .adapter import revChatGPT +from .adapter import claude class Application: diff --git a/free_one_api/models/adapter/llm.py b/free_one_api/models/adapter/llm.py index 7eae01f..788995e 100644 --- a/free_one_api/models/adapter/llm.py +++ b/free_one_api/models/adapter/llm.py @@ -89,7 +89,7 @@ async def test(self) -> (bool, str): return False, "not implemented" @abc.abstractmethod - async def query(self, req: request.Request) -> typing.Generator[response.Response, None, None]: + async def query(self, req: request.Request) -> typing.AsyncGenerator[response.Response, None]: """Query reply from LLM lib. Always in streaming mode. If upstream lib doesn't support streaming, just yield one time. From e6d9f434270d0aaadb2d5ff5ae46ab13e2107bd1 Mon Sep 17 00:00:00 2001 From: Junyan Qin <1010553892@qq.com> Date: Sat, 23 Sep 2023 08:42:54 +0000 Subject: [PATCH 02/15] chore: add requirements --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7d4262f..92172ad 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,5 @@ quart aiosqlite PyYaml revChatGPT -tiktoken \ No newline at end of file +tiktoken +claude-api \ No newline at end of file From 8ea4671d7c9b84c90807bcdc9c49569b262b6341 Mon Sep 17 00:00:00 2001 From: Junyan Qin <1010553892@qq.com> Date: Sat, 23 Sep 2023 08:55:27 +0000 Subject: [PATCH 03/15] perf: color of claude adapter --- web/src/components/Channel.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/components/Channel.vue b/web/src/components/Channel.vue index 3277333..25f5d9b 100644 --- a/web/src/components/Channel.vue +++ b/web/src/components/Channel.vue @@ -66,7 +66,7 @@ const adapter_color = { "xtekky/gpt4free": "#CC33FF", "acheong08/EdgeGPT": "#0388FF", "Soulter/hugging-chat-api": "#FFBB03", - "KoushikNavuluri/Claude-API": "#AAAAAA", + "KoushikNavuluri/Claude-API": "#dfd6c8", } function deleteChannelConfirmed(channel_id) { From 5a245f6d5b3719ce8e00a7c5866bf8628a03531c Mon Sep 17 00:00:00 2001 From: Junyan Qin <1010553892@qq.com> Date: Sat, 23 Sep 2023 09:01:11 +0000 Subject: [PATCH 04/15] doc(README): add libs table --- README.md | 9 +++++---- README_en.md | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index a727648..26f5601 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,10 @@ ### 支持的 LLM 库 -- [acheong08/ChatGPT](https://github.com/acheong08/ChatGPT) - ChatGPT 网页版逆向工程 - - gpt-3.5-turbo - - gpt-4 +|Adapter|Multi Round|Stream|Function Call|Status| +|---|---|---|---|---| +|[acheong08/ChatGPT](https://github.com/acheong08/ChatGPT)|✅|✅|❌|✅| +|[KoushikNavuluri/Claude-API](https://github.com/KoushikNavuluri/Claude-API)|✅|❌|❌|✅| ### 支持的 API 路径 @@ -65,7 +66,7 @@ python main.py 1. 创建一个 channel,按照说明填写配置,然后创建一个新的 key。 -![add_channel](assets/add_channel.png) +image 2. 将 url (e.g. http://localhost:3000/v1 ) 设置为 OpenAI 的 api_base ,将生成的 key 设置为 OpenAI api key。 3. 现在你可以使用 OpenAI API 来访问逆向工程的 LLM 库了。 diff --git a/README_en.md b/README_en.md index 00492f4..3ae570e 100644 --- a/README_en.md +++ b/README_en.md @@ -27,9 +27,10 @@ So other application supports OpenAI GPT API can use reverse engineered LLM libs ### Supported LLM libs -- [acheong08/ChatGPT](https://github.com/acheong08/ChatGPT) - - gpt-3.5-turbo - - gpt-4 +|Adapter|Multi Round|Stream|Function Call|Status| +|---|---|---|---|---| +|[acheong08/ChatGPT](https://github.com/acheong08/ChatGPT)|✅|✅|❌|✅| +|[KoushikNavuluri/Claude-API](https://github.com/KoushikNavuluri/Claude-API)|✅|❌|❌|✅| ### Supported API paths @@ -65,7 +66,7 @@ then you can open the admin page at `http://localhost:3000/`. 1. Create channel on the admin page, create a new key. -![add_channel](assets/add_channel.png) +image 2. Set the url (e.g. http://localhost:3000/v1 ) as OpenAI endpoint, and set the generated key as OpenAI api key. 3. Then you can use the OpenAI API to access the reverse engineered LLM lib. From 15441f6c658e4cd0b34d7aa8103e3149adba2fbe Mon Sep 17 00:00:00 2001 From: Junyan Qin <1010553892@qq.com> Date: Sat, 23 Sep 2023 09:44:29 +0000 Subject: [PATCH 05/15] feat(claude): delete conversation after asking --- free_one_api/impls/adapter/claude.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/free_one_api/impls/adapter/claude.py b/free_one_api/impls/adapter/claude.py index 6cf2c0e..897c1db 100644 --- a/free_one_api/impls/adapter/claude.py +++ b/free_one_api/impls/adapter/claude.py @@ -82,6 +82,8 @@ async def query(self, req: request.Request) -> typing.AsyncGenerator[response.Re conversation_id = self.chatbot.create_new_chat()['uuid'] resp_text = self.chatbot.send_message(prompt, conversation_id) + self.chatbot.delete_conversation(conversation_id) + yield response.Response( id=random_int, finish_reason=response.FinishReason.STOP, From d86d8fb043cc9dd175ded20e3ad1d03f5e179a74 Mon Sep 17 00:00:00 2001 From: Junyan Qin <1010553892@qq.com> Date: Sun, 24 Sep 2023 15:44:44 +0000 Subject: [PATCH 06/15] feat: add supports for google bard revlib --- free_one_api/impls/adapter/bard.py | 89 +++++++++++++++++++++++++++ free_one_api/impls/adapter/claude.py | 2 +- free_one_api/impls/adapter/hugchat.py | 0 free_one_api/impls/app.py | 1 + requirements.txt | 3 +- web/src/components/Channel.vue | 26 +------- 6 files changed, 96 insertions(+), 25 deletions(-) create mode 100644 free_one_api/impls/adapter/bard.py create mode 100644 free_one_api/impls/adapter/hugchat.py diff --git a/free_one_api/impls/adapter/bard.py b/free_one_api/impls/adapter/bard.py new file mode 100644 index 0000000..6867f34 --- /dev/null +++ b/free_one_api/impls/adapter/bard.py @@ -0,0 +1,89 @@ +import typing +import traceback +import uuid +import random + +import bardapi as bard + +from free_one_api.entities import request, response + +from ...models import adapter +from ...models.adapter import llm +from ...entities import request, response, exceptions + + +@adapter.llm_adapter +class BardAdapter(llm.LLMLibAdapter): + + @classmethod + def name(cls) -> str: + return "dsdanielpark/Bard-API" + + @classmethod + def description(self) -> str: + return "Use dsdanielpark/Bard-API to access Claude web edition." + + def supported_models(self) -> list[str]: + return [ + "gpt-3.5-turbo", + "gpt-4" + ] + + def function_call_supported(self) -> bool: + return False + + def stream_mode_supported(self) -> bool: + return False + + def multi_round_supported(self) -> bool: + return True + + @classmethod + def config_comment(cls) -> str: + return \ +"""Currently supports non stream mode only. +You should provide __Secure-1PSID as token extracted from cookies of Bard site. + +{ + "token": "bQhxxxxxxxxxxx" +} + +Method of getting __Secure-1PSID string, please refer to https://github.com/dsdanielpark/Bard-API +""" + + @classmethod + def supported_path(cls) -> str: + return "/v1/chat/completions" + + chatbot: bard.Bard + + def __init__(self, config: dict): + self.config = config + self.chatbot = bard.Bard(token=config['token']) + + async def test(self) -> (bool, str): + try: + self.chatbot.get_answer("hello, please reply 'hi' only.") + return True, "" + except Exception as e: + traceback.print_exc() + return False, str(e) + + async def query(self, req: request.Request) -> typing.AsyncGenerator[response.Response, None]: + prompt = "" + + for msg in req.messages: + prompt += f"{msg['role']}: {msg['content']}\n" + + prompt += "assistant: " + + random_int = random.randint(0, 1000000000) + + resp_text = self.chatbot.get_answer(prompt)['content'] + + yield response.Response( + id=random_int, + finish_reason=response.FinishReason.STOP, + normal_message=resp_text, + function_call=None + ) diff --git a/free_one_api/impls/adapter/claude.py b/free_one_api/impls/adapter/claude.py index 897c1db..fa067af 100644 --- a/free_one_api/impls/adapter/claude.py +++ b/free_one_api/impls/adapter/claude.py @@ -41,7 +41,7 @@ def multi_round_supported(self) -> bool: @classmethod def config_comment(cls) -> str: return \ -"""Currently support single message only. +"""Currently supports non stream mode only. You should provide cookie string as `cookie` in config: { "cookie": "your cookie string" diff --git a/free_one_api/impls/adapter/hugchat.py b/free_one_api/impls/adapter/hugchat.py new file mode 100644 index 0000000..e69de29 diff --git a/free_one_api/impls/app.py b/free_one_api/impls/app.py index a9eca46..b766443 100644 --- a/free_one_api/impls/app.py +++ b/free_one_api/impls/app.py @@ -14,6 +14,7 @@ from .adapter import revChatGPT from .adapter import claude +from .adapter import bard class Application: diff --git a/requirements.txt b/requirements.txt index 92172ad..bba4bba 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,5 @@ aiosqlite PyYaml revChatGPT tiktoken -claude-api \ No newline at end of file +claude-api +bardapi \ No newline at end of file diff --git a/web/src/components/Channel.vue b/web/src/components/Channel.vue index 25f5d9b..b057f6c 100644 --- a/web/src/components/Channel.vue +++ b/web/src/components/Channel.vue @@ -63,10 +63,11 @@ onresize = () => { const adapter_color = { "acheong08/ChatGPT": "#00BB00", + "KoushikNavuluri/Claude-API": "#dfd6c8", + "dsdanielpark/Bard-API": "#AACAFF", // 168,199,250 "xtekky/gpt4free": "#CC33FF", "acheong08/EdgeGPT": "#0388FF", "Soulter/hugging-chat-api": "#FFBB03", - "KoushikNavuluri/Claude-API": "#dfd6c8", } function deleteChannelConfirmed(channel_id) { @@ -218,28 +219,7 @@ const showingChannelData = reactive({ "latency": 0.13 // no need for creation } }); -const usableAdapterList = ref([ - { - "name": "acheong08/ChatGPT", - "config_comment": "this is the comment" - }, - { - "name": "xtekky/gpt4free", - "config_comment": "this is the comment" - }, - { - "name": "KoushikNavuluri/Claude-API", - "config_comment": "this is the comment" - }, - { - "name": "Soulter/hugging-chat-api", - "config_comment": "this is the comment" - }, - { - "name": "acheong08/EdgeGPT", - "config_comment": "this is the comment" - } -]); +const usableAdapterList = ref([]); const usableAdapterMap = ref({ "acheong08/ChatGPT": { "name": "acheong08/ChatGPT", From 8c7a7091d7f8d604c75ba742b0e5cddc9f0cd8c8 Mon Sep 17 00:00:00 2001 From: Junyan Qin <1010553892@qq.com> Date: Mon, 25 Sep 2023 01:13:38 +0000 Subject: [PATCH 07/15] feat: lazy load for bard --- free_one_api/impls/adapter/bard.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/free_one_api/impls/adapter/bard.py b/free_one_api/impls/adapter/bard.py index 6867f34..056a806 100644 --- a/free_one_api/impls/adapter/bard.py +++ b/free_one_api/impls/adapter/bard.py @@ -55,11 +55,16 @@ def config_comment(cls) -> str: def supported_path(cls) -> str: return "/v1/chat/completions" - chatbot: bard.Bard + _chatbot: bard.Bard = None + + @property + def chatbot(self) -> bard.Bard: + if self._chatbot == None: + self._chatbot = bard.Bard(token=self.config['token']) + return self._chatbot def __init__(self, config: dict): self.config = config - self.chatbot = bard.Bard(token=config['token']) async def test(self) -> (bool, str): try: From 956f46d4be93cf18f385c18677058c0d01b4a9f6 Mon Sep 17 00:00:00 2001 From: Junyan Qin <1010553892@qq.com> Date: Mon, 25 Sep 2023 01:13:56 +0000 Subject: [PATCH 08/15] feat: update latency to -1 when test failed --- free_one_api/impls/channel/mgr.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/free_one_api/impls/channel/mgr.py b/free_one_api/impls/channel/mgr.py index 417b926..c98fb3c 100644 --- a/free_one_api/impls/channel/mgr.py +++ b/free_one_api/impls/channel/mgr.py @@ -101,12 +101,12 @@ async def test_channel(self, channel_id: int) -> int: res, error = await chan.adapter.test() if not res: raise ValueError(error) + latency = int((time.time() - now)*100)/100 except Exception as e: raise ValueError("Test failed.") from e - latency = int((time.time() - now)*100)/100 - - chan.latency = latency - await self.update_channel(chan) + finally: + chan.latency = latency + await self.update_channel(chan) return latency async def select_channel( From 36af1a9f0beea6bad41cdf2b9846925b821d85d1 Mon Sep 17 00:00:00 2001 From: Junyan Qin <1010553892@qq.com> Date: Mon, 25 Sep 2023 01:16:56 +0000 Subject: [PATCH 09/15] doc: add bard to table --- README.md | 1 + README_en.md | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index 26f5601..539e804 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ |---|---|---|---|---| |[acheong08/ChatGPT](https://github.com/acheong08/ChatGPT)|✅|✅|❌|✅| |[KoushikNavuluri/Claude-API](https://github.com/KoushikNavuluri/Claude-API)|✅|❌|❌|✅| +|[dsdanielpark/Bard-API](https://github.com/dsdanielpark/Bard-API)|✅|❌|❌|✅| ### 支持的 API 路径 diff --git a/README_en.md b/README_en.md index 3ae570e..a07f799 100644 --- a/README_en.md +++ b/README_en.md @@ -31,6 +31,7 @@ So other application supports OpenAI GPT API can use reverse engineered LLM libs |---|---|---|---|---| |[acheong08/ChatGPT](https://github.com/acheong08/ChatGPT)|✅|✅|❌|✅| |[KoushikNavuluri/Claude-API](https://github.com/KoushikNavuluri/Claude-API)|✅|❌|❌|✅| +|[dsdanielpark/Bard-API](https://github.com/dsdanielpark/Bard-API)|✅|❌|❌|✅| ### Supported API paths From 349bcbceb6599c86c736c8a8f3abee388099cea5 Mon Sep 17 00:00:00 2001 From: Junyan Qin <1010553892@qq.com> Date: Mon, 25 Sep 2023 10:07:02 +0000 Subject: [PATCH 10/15] feat: add supports for gpt4free --- free_one_api/impls/adapter/bard.py | 2 +- free_one_api/impls/adapter/gpt4free.py | 240 +++++++++++++++++++++++++ free_one_api/impls/app.py | 1 + 3 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 free_one_api/impls/adapter/gpt4free.py diff --git a/free_one_api/impls/adapter/bard.py b/free_one_api/impls/adapter/bard.py index 056a806..d2ba946 100644 --- a/free_one_api/impls/adapter/bard.py +++ b/free_one_api/impls/adapter/bard.py @@ -21,7 +21,7 @@ def name(cls) -> str: @classmethod def description(self) -> str: - return "Use dsdanielpark/Bard-API to access Claude web edition." + return "Use dsdanielpark/Bard-API to access Google Bard web edition." def supported_models(self) -> list[str]: return [ diff --git a/free_one_api/impls/adapter/gpt4free.py b/free_one_api/impls/adapter/gpt4free.py new file mode 100644 index 0000000..c0b016e --- /dev/null +++ b/free_one_api/impls/adapter/gpt4free.py @@ -0,0 +1,240 @@ +import typing +import traceback +import uuid +import logging +import random + +import g4f + +from free_one_api.entities import request, response + +from ...models import adapter +from ...models.adapter import llm +from ...entities import request, response, exceptions + + +@adapter.llm_adapter +class GPT4FreeAdapter(llm.LLMLibAdapter): + + @classmethod + def name(cls) -> str: + return "xtekky/gpt4free" + + @classmethod + def description(self) -> str: + return "Use xtekky/gpt4free to access lots of GPT providers." + + def supported_models(self) -> list[str]: + return [ + "gpt-3.5-turbo", + "gpt-4" + ] + + def function_call_supported(self) -> bool: + return False + + def stream_mode_supported(self) -> bool: + return True + + def multi_round_supported(self) -> bool: + return True + + @classmethod + def config_comment(cls) -> str: + return \ +"""GPT4Free is so unstable that it is not recommended to use. +You don't need to provide any authentification. + +Please refer to https://github.com/xtekky/gpt4free +""" + + @classmethod + def supported_path(cls) -> str: + return "/v1/chat/completions" + + def __init__(self, config: dict): + self.config = config + + _use_provider: g4f.Provider = None + _use_stream_provider: g4f.Provider = None + + async def use_provider(self, stream: bool) -> g4f.Provider.BaseProvider: + if self._use_provider is None: + await self._select_provider() + if stream and self._use_stream_provider is not None: + return self._use_stream_provider + return self._use_provider + + async def _select_provider(self): + non_stream_tested = False + if self._use_provider is not None: + try: + resp = await g4f.ChatCompletion.create_async( + model="gpt-3.5-turbo", + messages=[ + { + "role": "user", + "content": "Hi, My name is Rock." + } + ], + provider=self._use_provider + ) + non_stream_tested = True + except Exception as e: + self._use_provider = None + if non_stream_tested and self._use_stream_provider is not None: + try: + resp = self._use_stream_provider.create_async_generator( + model="gpt-3.5-turbo", + messages=[ + { + "role": "user", + "content": "Hi, My name is Rock." + } + ] + ) + async for _ in resp: + return + except Exception as e: + self._use_stream_provider = None + + self._use_provider = None + self._use_stream_provider = None + + from g4f.Provider import __all__ as providers + + exclude = [ + 'Acytoo', + 'BaseProvider' + ] + + for provider in providers: + + # print("Testing provider", provider) + logging.info("Testing provider %s", provider) + + if provider in exclude: + continue + + provider = getattr(g4f.Provider, provider) + + try: + assert hasattr(provider, 'supports_stream') + resp = await g4f.ChatCompletion.create_async( + model="gpt-3.5-turbo", + messages=[ + { + "role": "user", + "content": "Hi, My name is Rock." + } + ], + provider=provider + ) + + if 'Rock' in resp and '<' not in resp: + if self._use_provider is None: + self._use_provider = provider + + if provider.supports_stream: + try: + assert hasattr(provider, 'create_async_generator') + resp = provider.create_async_generator( + model="gpt-3.5-turbo", + messages=[ + { + "role": "user", + "content": "Hi, My name is Rock." + } + ] + ) + async for _ in resp: + pass + if self._use_stream_provider is None: + self._use_stream_provider = provider + except Exception as e: + traceback.print_exc() + print("provider", provider, "does not really support stream mode") + + + if self._use_provider is not None and self._use_stream_provider is not None: + print("selected provider", self._use_provider, self._use_stream_provider) + break + except Exception as e: + # traceback.print_exc() + continue + + if self._use_provider is None: + raise exceptions.QueryHandlingError(404, "no_provider_found", "No provider available.") + + async def test(self) -> (bool, str): + try: + await self._select_provider() + resp = await g4f.ChatCompletion.create_async( + model="gpt-3.5-turbo", + messages=[{ + "role": "user", + "content": "Hello, please reply 'hi' only." + }], + provider=await self.use_provider(stream=False) + ) + return True, "" + except Exception as e: + traceback.print_exc() + return False, str(e) + + async def query(self, req: request.Request) -> typing.AsyncGenerator[response.Response, None]: + provider = await self.use_provider(stream=True) + + if not req.stream: + resp = await g4f.ChatCompletion.create_async( + model=req.model, + messages=req.messages, + provider=provider + ) + else: + resp = provider.create_async_generator( + model=req.model, + messages=req.messages + ) + + if isinstance(resp, typing.Generator): + for resp_text in resp: + random_int = random.randint(0, 1000000000) + yield response.Response( + id=random_int, + finish_reason=response.FinishReason.NULL, + normal_message=resp_text, + function_call=None + ) + yield response.Response( + id=random_int, + finish_reason=response.FinishReason.STOP, + normal_message="", + function_call=None + ) + elif isinstance(resp, typing.AsyncGenerator): + async for resp_text in resp: + + random_int = random.randint(0, 1000000000) + + yield response.Response( + id=random_int, + finish_reason=response.FinishReason.NULL, + normal_message=resp_text, + function_call=None + ) + yield response.Response( + id=random_int, + finish_reason=response.FinishReason.STOP, + normal_message="", + function_call=None + ) + else: + random_int = random.randint(0, 1000000000) + + yield response.Response( + id=random_int, + finish_reason=response.FinishReason.STOP, + normal_message=resp, + function_call=None + ) diff --git a/free_one_api/impls/app.py b/free_one_api/impls/app.py index b766443..b5b5857 100644 --- a/free_one_api/impls/app.py +++ b/free_one_api/impls/app.py @@ -15,6 +15,7 @@ from .adapter import revChatGPT from .adapter import claude from .adapter import bard +from .adapter import gpt4free class Application: From 9040f7f240b11a963732ea795006c95a10a1b5df Mon Sep 17 00:00:00 2001 From: Junyan Qin <1010553892@qq.com> Date: Mon, 25 Sep 2023 10:07:14 +0000 Subject: [PATCH 11/15] chore: add gpt4free --- requirements.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index bba4bba..16baf3e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,6 @@ PyYaml revChatGPT tiktoken claude-api -bardapi \ No newline at end of file +bardapi +hugchat +g4f \ No newline at end of file From dc46b27c4f17f32cceec43801e8cbdbdee7d1cb0 Mon Sep 17 00:00:00 2001 From: Junyan Qin <1010553892@qq.com> Date: Mon, 25 Sep 2023 10:18:27 +0000 Subject: [PATCH 12/15] doc(README): add gpt4free --- README.md | 1 + README_en.md | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index 539e804..bdcbb9d 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ |[acheong08/ChatGPT](https://github.com/acheong08/ChatGPT)|✅|✅|❌|✅| |[KoushikNavuluri/Claude-API](https://github.com/KoushikNavuluri/Claude-API)|✅|❌|❌|✅| |[dsdanielpark/Bard-API](https://github.com/dsdanielpark/Bard-API)|✅|❌|❌|✅| +|[xtekky/gpt4free](https://github.com/xtekky/gpt4free)|✅|✅|❌|✅| ### 支持的 API 路径 diff --git a/README_en.md b/README_en.md index a07f799..d494b55 100644 --- a/README_en.md +++ b/README_en.md @@ -32,6 +32,7 @@ So other application supports OpenAI GPT API can use reverse engineered LLM libs |[acheong08/ChatGPT](https://github.com/acheong08/ChatGPT)|✅|✅|❌|✅| |[KoushikNavuluri/Claude-API](https://github.com/KoushikNavuluri/Claude-API)|✅|❌|❌|✅| |[dsdanielpark/Bard-API](https://github.com/dsdanielpark/Bard-API)|✅|❌|❌|✅| +|[xtekky/gpt4free](https://github.com/xtekky/gpt4free)|✅|✅|❌|✅| ### Supported API paths From 85f1625fc75c9f926153fe335b2a1ce905a31045 Mon Sep 17 00:00:00 2001 From: Junyan Qin <1010553892@qq.com> Date: Mon, 25 Sep 2023 15:24:08 +0000 Subject: [PATCH 13/15] feat: add supports for huggingchat --- free_one_api/impls/adapter/hugchat.py | 123 ++++++++++++++++++++++++++ free_one_api/impls/app.py | 1 + requirements.txt | 3 +- 3 files changed, 126 insertions(+), 1 deletion(-) diff --git a/free_one_api/impls/adapter/hugchat.py b/free_one_api/impls/adapter/hugchat.py index e69de29..27a8baa 100644 --- a/free_one_api/impls/adapter/hugchat.py +++ b/free_one_api/impls/adapter/hugchat.py @@ -0,0 +1,123 @@ +import typing +import traceback +import uuid +import random + +import requests + +import hugchat.hugchat as hugchat +import hugchat.login as login + +from ...models import adapter +from ...models.adapter import llm +from ...entities import request +from ...entities import response, exceptions + + +@adapter.llm_adapter +class HuggingChatAdapter(llm.LLMLibAdapter): + + @classmethod + def name(cls) -> str: + return "Soulter/hugging-chat-api" + + @classmethod + def description(self) -> str: + return "Use Soulter/hugging-chat-api to access reverse engineering huggingchat." + + def supported_models(self) -> list[str]: + return [ + "gpt-3.5-turbo", + "gpt-4" + ] + + def function_call_supported(self) -> bool: + return False + + def stream_mode_supported(self) -> bool: + return True + + def multi_round_supported(self) -> bool: + return True + + @classmethod + def config_comment(cls) -> str: + return \ +"""Please provide email and passwd to sign up for HuggingChat: + +{ + "email": "your email", + "passwd": "your password" +} + +Please refer to https://github.com/Soulter/hugging-chat-api +""" + + @classmethod + def supported_path(self) -> str: + return "/v1/chat/completions" + + _chatbot: hugchat.ChatBot = None + + @property + def chatbot(self) -> hugchat.ChatBot: + if self._chatbot is None: + sign = login.Login(self.config['email'], self.config['passwd']) + cookie: requests.sessions.RequestsCookieJar = None + try: + cookie = sign.loadCookiesFromDir("hugchatCookies") + except: + cookie = sign.login() + sign.saveCookiesToDir("hugchatCookies") + + self._chatbot = hugchat.ChatBot(cookies=cookie.get_dict()) + return self._chatbot + + def __init__(self, config: dict): + self.config = config + + async def test(self) -> (bool, str): + try: + self.chatbot.change_conversation(self.chatbot.new_conversation()) + for data in self.chatbot.query( + "Hi, respond 'Hello, world!' please.", + stream=True + ): + pass + + self.chatbot.delete_conversation(self.chatbot.current_conversation) + + return True, "" + except Exception as e: + traceback.print_exc() + return False, str(e) + + async def query(self, req: request.Request) -> typing.AsyncGenerator[response.Response, None]: + prompt = "" + + for msg in req.messages: + prompt += f"{msg['role']}: {msg['content']}\n" + + prompt += "assistant: " + + random_int = random.randint(0, 1000000000) + self.chatbot.change_conversation(self.chatbot.new_conversation()) + + for resp in self.chatbot.query( + text=prompt, + stream=True + ): + yield response.Response( + id=random_int, + finish_reason=response.FinishReason.NULL, + normal_message=resp['token'], + function_call=None + ) + self.chatbot.delete_conversation(self.chatbot.current_conversation) + + yield response.Response( + id=random_int, + finish_reason=response.FinishReason.STOP, + normal_message="", + function_call=None + ) diff --git a/free_one_api/impls/app.py b/free_one_api/impls/app.py index b5b5857..1683883 100644 --- a/free_one_api/impls/app.py +++ b/free_one_api/impls/app.py @@ -16,6 +16,7 @@ from .adapter import claude from .adapter import bard from .adapter import gpt4free +from .adapter import hugchat class Application: diff --git a/requirements.txt b/requirements.txt index 16baf3e..bf31d84 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,5 @@ tiktoken claude-api bardapi hugchat -g4f \ No newline at end of file +g4f +revTongYi \ No newline at end of file From c3acdb41dbbc2bac5c7c4a7708f335b17ff03763 Mon Sep 17 00:00:00 2001 From: Junyan Qin <1010553892@qq.com> Date: Mon, 25 Sep 2023 15:25:48 +0000 Subject: [PATCH 14/15] doc: add huggingchat --- README.md | 1 + README_en.md | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index bdcbb9d..12cd53a 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ |[KoushikNavuluri/Claude-API](https://github.com/KoushikNavuluri/Claude-API)|✅|❌|❌|✅| |[dsdanielpark/Bard-API](https://github.com/dsdanielpark/Bard-API)|✅|❌|❌|✅| |[xtekky/gpt4free](https://github.com/xtekky/gpt4free)|✅|✅|❌|✅| +|[Soulter/hugging-chat-api](https://github.com/Soulter/hugging-chat-api)|✅|✅|❌|✅| ### 支持的 API 路径 diff --git a/README_en.md b/README_en.md index d494b55..6e9b724 100644 --- a/README_en.md +++ b/README_en.md @@ -33,6 +33,7 @@ So other application supports OpenAI GPT API can use reverse engineered LLM libs |[KoushikNavuluri/Claude-API](https://github.com/KoushikNavuluri/Claude-API)|✅|❌|❌|✅| |[dsdanielpark/Bard-API](https://github.com/dsdanielpark/Bard-API)|✅|❌|❌|✅| |[xtekky/gpt4free](https://github.com/xtekky/gpt4free)|✅|✅|❌|✅| +|[Soulter/hugging-chat-api](https://github.com/Soulter/hugging-chat-api)|✅|✅|❌|✅| ### Supported API paths From c14e11b0936a48c2fe0da8b4097e4e9bf2dbdddb Mon Sep 17 00:00:00 2001 From: Junyan Qin <1010553892@qq.com> Date: Tue, 26 Sep 2023 03:05:20 +0000 Subject: [PATCH 15/15] feat: add supports for revTongYi --- README.md | 15 ++-- README_en.md | 11 +-- free_one_api/impls/adapter/qianwen.py | 112 ++++++++++++++++++++++++++ free_one_api/impls/app.py | 1 + 4 files changed, 127 insertions(+), 12 deletions(-) create mode 100644 free_one_api/impls/adapter/qianwen.py diff --git a/README.md b/README.md index 12cd53a..de8bcbb 100644 --- a/README.md +++ b/README.md @@ -27,13 +27,14 @@ ### 支持的 LLM 库 -|Adapter|Multi Round|Stream|Function Call|Status| -|---|---|---|---|---| -|[acheong08/ChatGPT](https://github.com/acheong08/ChatGPT)|✅|✅|❌|✅| -|[KoushikNavuluri/Claude-API](https://github.com/KoushikNavuluri/Claude-API)|✅|❌|❌|✅| -|[dsdanielpark/Bard-API](https://github.com/dsdanielpark/Bard-API)|✅|❌|❌|✅| -|[xtekky/gpt4free](https://github.com/xtekky/gpt4free)|✅|✅|❌|✅| -|[Soulter/hugging-chat-api](https://github.com/Soulter/hugging-chat-api)|✅|✅|❌|✅| +|Adapter|Multi Round|Stream|Function Call|Status|Comment| +|---|---|---|---|---|---| +|[acheong08/ChatGPT](https://github.com/acheong08/ChatGPT)|✅|✅|❌|✅|ChatGPT 网页版| +|[KoushikNavuluri/Claude-API](https://github.com/KoushikNavuluri/Claude-API)|✅|❌|❌|✅|Claude 网页版| +|[dsdanielpark/Bard-API](https://github.com/dsdanielpark/Bard-API)|✅|❌|❌|✅|Google Bard 网页版| +|[xtekky/gpt4free](https://github.com/xtekky/gpt4free)|✅|✅|❌|✅|gpt4free 接入多个平台的破解| +|[Soulter/hugging-chat-api](https://github.com/Soulter/hugging-chat-api)|✅|✅|❌|✅|huggingface的对话模型| +|[xw5xr6/revTongYi](https://github.com/xw5xr6/revTongYi)|✅|✅|❌|✅|阿里云通义千问网页版| ### 支持的 API 路径 diff --git a/README_en.md b/README_en.md index 6e9b724..5a091ea 100644 --- a/README_en.md +++ b/README_en.md @@ -29,11 +29,12 @@ So other application supports OpenAI GPT API can use reverse engineered LLM libs |Adapter|Multi Round|Stream|Function Call|Status| |---|---|---|---|---| -|[acheong08/ChatGPT](https://github.com/acheong08/ChatGPT)|✅|✅|❌|✅| -|[KoushikNavuluri/Claude-API](https://github.com/KoushikNavuluri/Claude-API)|✅|❌|❌|✅| -|[dsdanielpark/Bard-API](https://github.com/dsdanielpark/Bard-API)|✅|❌|❌|✅| -|[xtekky/gpt4free](https://github.com/xtekky/gpt4free)|✅|✅|❌|✅| -|[Soulter/hugging-chat-api](https://github.com/Soulter/hugging-chat-api)|✅|✅|❌|✅| +|[acheong08/ChatGPT](https://github.com/acheong08/ChatGPT)|✅|✅|❌|✅|ChatGPT Web Version| +|[KoushikNavuluri/Claude-API](https://github.com/KoushikNavuluri/Claude-API)|✅|❌|❌|✅|Claude Web Version| +|[dsdanielpark/Bard-API](https://github.com/dsdanielpark/Bard-API)|✅|❌|❌|✅|Google Bard Web Version| +|[xtekky/gpt4free](https://github.com/xtekky/gpt4free)|✅|✅|❌|✅|gpt4free cracked multiple platforms| +|[Soulter/hugging-chat-api](https://github.com/Soulter/hugging-chat-api)|✅|✅|❌|✅|hubbingface chat model| +|[xw5xr6/revTongYi](https://github.com/xw5xr6/revTongYi)|✅|✅|❌|✅|Aliyun TongYi QianWen Web Version| ### Supported API paths diff --git a/free_one_api/impls/adapter/qianwen.py b/free_one_api/impls/adapter/qianwen.py new file mode 100644 index 0000000..fcb33a5 --- /dev/null +++ b/free_one_api/impls/adapter/qianwen.py @@ -0,0 +1,112 @@ +import typing +import traceback +import uuid +import random + +import revTongYi.qianwen as qwen + +from free_one_api.entities import request, response + +from ...models import adapter +from ...models.adapter import llm +from ...entities import request, response, exceptions + + +@adapter.llm_adapter +class QianWenAdapter(llm.LLMLibAdapter): + + @classmethod + def name(cls) -> str: + return "xw5xr6/revTongYi" + + @classmethod + def description(self) -> str: + return "Use xw5xr6/revTongYi to access Aliyun TongYi QianWen." + + def supported_models(self) -> list[str]: + return [ + "gpt-3.5-turbo", + "gpt-4" + ] + + def function_call_supported(self) -> bool: + return False + + def stream_mode_supported(self) -> bool: + return True + + def multi_round_supported(self) -> bool: + return True + + @classmethod + def config_comment(cls) -> str: + return \ +"""RevTongYi use cookies that can be extracted from https://qianwen.aliyun.com/ +You should provide cookie string as `cookie` in config: +{ + "cookie": "your cookie string" +} + +Method of getting cookie string, please refer to https://github.com/xw5xr6/revTongYi +""" + + @classmethod + def supported_path(cls) -> str: + return "/v1/chat/completions" + + chatbot: qwen.Chatbot + + def __init__(self, config: dict): + self.config = config + self.chatbot = qwen.Chatbot( + cookies_str=config['cookie'] + ) + + async def test(self) -> (bool, str): + try: + self.chatbot.create_session("Hello, reply 'hi' only.") + resp = self.chatbot.ask( + "Hello, reply 'hi' only.", + ) + + self.chatbot.delete_session(self.chatbot.sessionId) + + return True, "" + except Exception as e: + traceback.print_exc() + return False, str(e) + + async def query(self, req: request.Request) -> typing.AsyncGenerator[response.Response, None]: + prompt = "" + + for msg in req.messages: + prompt += f"{msg['role']}: {msg['content']}\n" + + prompt += "assistant: " + + random_int = random.randint(0, 1000000000) + + prev_text = "" + self.chatbot.create_session(prompt) + + for resp in self.chatbot.ask( + prompt=prompt, + stream=True, + ): + + yield response.Response( + id=random_int, + finish_reason=response.FinishReason.NULL, + normal_message=resp['content'][0].replace(prev_text, ""), + function_call=None + ) + prev_text = resp['content'][0] + + self.chatbot.delete_session(self.chatbot.sessionId) + + yield response.Response( + id=random_int, + finish_reason=response.FinishReason.STOP, + normal_message="", + function_call=None + ) diff --git a/free_one_api/impls/app.py b/free_one_api/impls/app.py index 1683883..3e3230b 100644 --- a/free_one_api/impls/app.py +++ b/free_one_api/impls/app.py @@ -17,6 +17,7 @@ from .adapter import bard from .adapter import gpt4free from .adapter import hugchat +from .adapter import qianwen class Application: