From 5b4e2a31f33b734987b078cd3635a470bdbfe70e Mon Sep 17 00:00:00 2001 From: Wizerd Date: Fri, 15 Dec 2023 22:40:16 +0800 Subject: [PATCH] =?UTF-8?q?[feat]=20=E9=87=8D=E7=A3=85=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=EF=BC=9A=E6=94=AF=E6=8C=81=E5=A4=A7=E9=83=A8=E5=88=86GPTS?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Readme.md | 43 ++++++++-- docker-compose.yml | 3 +- gpts.json | 5 ++ main.py | 210 +++++++++++++++++++++++---------------------- 4 files changed, 153 insertions(+), 108 deletions(-) create mode 100644 gpts.json diff --git a/Readme.md b/Readme.md index 916dcc9..fc13c0f 100644 --- a/Readme.md +++ b/Readme.md @@ -8,6 +8,12 @@ # 更新日志 +### 0.1.0 + +- 重磅更新 + +- 已支持访问大部分的GPTS + ### 0.0.11 - 修复一些偶现的bug @@ -43,11 +49,11 @@ 目前支持的模型包括: -1. gpt-4-classic:纯文字生成的 GPT-4,未加入任何插件,对应的是官方的 GPT-4-Classic +1. gpt-4-s:支持代码解释器、bing联网、dalle绘图的 GPT-4,对应的是官方的默认 GPT-4(绘图的响应有时候有些不稳定) -2. gpt-4-s:支持代码解释器、bing联网、dalle绘图的 GPT-4,对应的是官方的默认 GPT-4(绘图的响应有时候有些不稳定) +2. gpt-4-mobile:支持代码解释器、bing联网、dalle绘图的 GPT-4,对应的是官方的手机版 GPT-4,截止至2023年12月15日,本模型使用量不计入 GPT-4 用量(即不受每 3 小时 40 次的限制) -3. gpt-4-mobile:支持代码解释器、bing联网、dalle绘图的 GPT-4,对应的是官方的手机版 GPT-4,截止至2023年12月15日,本模型使用量不计入 GPT-4 用量(即不受每 3 小时 40 次的限制) +3. 几乎所有的 GPTS(配置方式见下文) # Docker-Compose 部署 @@ -55,7 +61,30 @@ # 环境变量说明: -- UPLOAD_BASE_URL 用于dalle模型生成图片的时候展示所用,需要设置为使用如 [ChatGPT-Next-Web](https://github.com/ChatGPTNextWebTeam/ChatGPT-Next-Web) 的用户可以访问到的 Uploader 容器地址,如:http://127.0.0.1:50012 +- UPLOAD_BASE_URL:用于dalle模型生成图片的时候展示所用,需要设置为使用如 [ChatGPT-Next-Web](https://github.com/ChatGPTNextWebTeam/ChatGPT-Next-Web) 的用户可以访问到的 Uploader 容器地址,如:http://127.0.0.1:50012 + +- KEY_FOR_GPTS_INFO:仅获取 GPTS 信息的 key,需要该 key 能够访问所有配置的 GPTS。后续发送消息仍需要在请求头携带请求所用的 key。 + +# GPTS配置说明 + +如果需要使用 GPTS,需要修改 `gpts.json` 文件,其中每个对象的key即为调用对应 GPTS 的时候使用的模型名称,而 `id` 则为对应的模型id,该 `id` 对应每个 GPTS 的链接的后缀。配置多个GPTS的时候用逗号隔开。 + +例如:PandoraNext的官方 GPTS 的链接为:`https://chat.oaifree.com/g/g-CFsXuTRfy-pandoranextzhu-shou`,则该模型的 `id` 的值应为 `g-CFsXuTRfy-pandoranextzhu-shou`,而模型名可以自定义。 + +示例: + +```json +{ + "gpt-4-classic": { + "id":"g-YyyyMT9XH-chatgpt-classic" + }, + "pandoraNext":{ + "id":"g-CFsXuTRfy-pandoranextzhu-shou" + } +} +``` + +注意:使用该配置的时候需要保证正确填写 `docker-compose.yml` 的环境变量 `KEY_FOR_GPTS_INFO`,同时该变量设置的 `key` 允许访问所有配置的 GPTS。 # 示例 @@ -71,7 +100,7 @@ services: environment: - OPENAI_API_KEY= - BASE_URL= - - CUSTOM_MODELS=+gpt-4-s,+gpt-4-classic,+gpt-4-mobile + - CUSTOM_MODELS=+gpt-4-s,+gpt-4-mobile,+ ``` @@ -92,3 +121,7 @@ services: ### GPT-4-Mobile ![api-4](https://github.com/Ink-Osier/PandoraToV1Api/assets/133617214/2eb4fd4f-7c66-4a1f-a54a-3c280a36e509) + +### GPTS + +![api-5](https://github.com/Ink-Osier/PandoraToV1Api/assets/133617214/299df56a-d245-4920-8892-94e1a9cc644a) diff --git a/docker-compose.yml b/docker-compose.yml index 94ecae0..95fbd5a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,10 +10,11 @@ services: - BASE_URL= - PROXY_API_PREFIX= - UPLOAD_BASE_URL= + - KEY_FOR_GPTS_INFO=<一个仅用于获取GPTs信息的fk> # 如果不需要额外使用gpts,可以不填 volumes: - ./log:/app/log - ./images:/app/images - + - ./gpts.json:/app/gpts.json uploader: image: wizerd/pandora-to-api:latest diff --git a/gpts.json b/gpts.json new file mode 100644 index 0000000..7df6e78 --- /dev/null +++ b/gpts.json @@ -0,0 +1,5 @@ +{ + "gpt-4-classic": { + "id":"g-YyyyMT9XH-chatgpt-classic" + } +} \ No newline at end of file diff --git a/main.py b/main.py index 87a82cd..752803d 100644 --- a/main.py +++ b/main.py @@ -17,6 +17,85 @@ def generate_unique_id(prefix): return unique_id +def get_accessible_model_list(): + return [config['name'] for config in gpts_configurations] + + +def find_model_config(model_name): + for config in gpts_configurations: + if config['name'] == model_name: + return config + return None + +# 从 gpts.json 读取配置 +def load_gpts_config(file_path): + with open(file_path, 'r', encoding='utf-8') as file: + return json.load(file) + +# 根据 ID 发送请求并获取配置信息 +def fetch_gizmo_info(base_url, proxy_api_prefix, model_id): + url = f"{base_url}/{proxy_api_prefix}/backend-api/gizmos/{model_id}" + headers = { + "Authorization": f"Bearer {KEY_FOR_GPTS_INFO}" + } + + response = requests.get(url, headers=headers) + # print(f"fetch_gizmo_info_response: {response.text}") + if response.status_code == 200: + return response.json() + else: + return None + +gpts_configurations = [ + { + "name":"gpt-4-s" + }, + { + "name":"gpt-4-mobile" + } +] + +# 将配置添加到全局列表 +def add_config_to_global_list(base_url, proxy_api_prefix, gpts_data): + global gpts_configurations + # print(f"gpts_data: {gpts_data}") + for model_name, model_info in gpts_data.items(): + # print(f"model_name: {model_name}") + # print(f"model_info: {model_info}") + model_id = model_info['id'] + gizmo_info = fetch_gizmo_info(base_url, proxy_api_prefix, model_id) + if gizmo_info: + gpts_configurations.append({ + 'name': model_name, + 'id': model_id, + 'config': gizmo_info + }) + +def generate_gpts_payload(model): + model_config = find_model_config(model) + if model_config: + gizmo_info = model_config['config'] + gizmo_id = gizmo_info['gizmo']['id'] + + payload = { + "action": "next", + "messages": [], + "parent_message_id": str(uuid.uuid4()), + "model": "gpt-4-gizmo", + "timezone_offset_min": -480, + "history_and_training_disabled": False, + "conversation_mode": { + "gizmo": gizmo_info, + "kind": "gizmo_interaction", + "gizmo_id": gizmo_id + }, + "force_paragen": False, + "force_rate_limit": False + } + return payload + else: + return None + # 创建 Flask 应用 app = Flask(__name__) @@ -25,9 +104,10 @@ app = Flask(__name__) BASE_URL = os.getenv('BASE_URL', '') PROXY_API_PREFIX = os.getenv('PROXY_API_PREFIX', '') UPLOAD_BASE_URL = os.getenv('UPLOAD_BASE_URL', '') +KEY_FOR_GPTS_INFO = os.getenv('KEY_FOR_GPTS_INFO', '') -VERSION = '0.0.11' -UPDATE_INFO = '修复一些偶现的bug' +VERSION = '0.1.0' +UPDATE_INFO = '适配大部分GPTS模型' with app.app_context(): # 输出版本信息 @@ -47,6 +127,21 @@ with app.app_context(): print(f"==========================================") + print(f"GPTS 配置信息") + + # 加载配置并添加到全局列表 + gpts_data = load_gpts_config("./gpts.json") + add_config_to_global_list(BASE_URL, PROXY_API_PREFIX, gpts_data) + # print("当前可用GPTS:" + get_accessible_model_list()) + # 输出当前可用 GPTS name + print(f"当前可用 GPTS 列表: {get_accessible_model_list()}") + + print(f"==========================================") + + # print(f"GPTs Payload 生成测试") + + # print(f"gpt-4-classic: {generate_gpts_payload('gpt-4-classic')}") + # 定义获取 token 的函数 def get_token(): @@ -59,7 +154,8 @@ def get_token(): return None import os -accessable_model_list = ['gpt-4-classic', 'gpt-4-s', 'gpt-4-mobile'] + + # 定义发送请求的函数 def send_text_prompt_and_get_response(messages, api_key, stream, model): @@ -82,103 +178,7 @@ def send_text_prompt_and_get_response(messages, api_key, stream, model): payload = {} print(f"model: {model}") - - if model == 'gpt-4-classic': - payload = { - # 构建 payload - "action": "next", - "messages": formatted_messages, - "parent_message_id": str(uuid.uuid4()), - "model": "gpt-4-gizmo", - "timezone_offset_min": -480, - "history_and_training_disabled": False, - "conversation_mode": { - "gizmo": { - "gizmo": { - "id": "g-YyyyMT9XH", - "organization_id": "org-OROoM5KiDq6bcfid37dQx4z4", - "short_url": "g-YyyyMT9XH-chatgpt-classic", - "author": { - "user_id": "user-u7SVk5APwT622QC7DPe41GHJ", - "display_name": "ChatGPT", - "link_to":None, - "selected_display": "name", - "is_verified":True - }, - "voice": { - "id": "ember" - }, - "workspace_id":None, - "model":None, - "instructions":None, - "settings":None, - "display": { - "name": "ChatGPT Classic", - "description": "The latest version of GPT-4 with no additional capabilities", - "welcome_message": "Hello", - "prompt_starters":None, - "profile_picture_url": "", - "categories": [] - }, - "share_recipient": "marketplace", - "updated_at": "2023-11-26T17:46:07.341305+00:00", - "last_interacted_at": "2023-12-11T09:49:34.943245+00:00", - "tags": [ - "public", - "first_party" - ], - "version":None, - "live_version":None, - "training_disabled":None, - "allowed_sharing_recipients":None, - "review_info":None, - "appeal_info":None, - "vanity_metrics":None - }, - "tools": [], - "files": [], - "product_features": { - "attachments": { - "type": "retrieval", - "accepted_mime_types": [ - "text/x-script.python", - "application/x-latext", - "text/x-c++", - "text/javascript", - "text/x-java", - "text/x-typescript", - "application/vnd.openxmlformats-officedocument.presentationml.presentation", - "text/x-csharp", - "text/plain", - "application/pdf", - "text/x-sh", - "text/markdown", - "text/x-c", - "text/x-ruby", - "text/x-tex", - "text/x-php", - "application/vnd.openxmlformats-officedocument.wordprocessingml.document", - "application/json", - "text/html", - "application/msword" - ], - "image_mime_types": [ - "image/webp", - "image/jpeg", - "image/png", - "image/gif" - ], - "can_accept_all_mime_types":True - } - } - }, - "kind": "gizmo_interaction", - "gizmo_id": "g-YyyyMT9XH" - }, - "force_paragen":False, - "force_rate_limit":False - } - elif model == 'gpt-4-s': + if model == 'gpt-4-s': payload = { # 构建 payload "action": "next", @@ -202,6 +202,10 @@ def send_text_prompt_and_get_response(messages, api_key, stream, model): "history_and_training_disabled": False, "conversation_mode":{"kind":"primary_assistant"},"force_paragen":False,"force_rate_limit":False } + else: + payload = generate_gpts_payload(model) + if not payload: + raise Exception('model is not accessible') response = requests.post(url, headers=headers, json=payload, stream=True) # print(response) return response @@ -325,8 +329,10 @@ def chat_completions(): data = request.json messages = data.get('messages') model = data.get('model') - if model not in accessable_model_list: - return jsonify({"error": "model is not accessable"}), 401 + accessible_model_list = get_accessible_model_list() + if model not in accessible_model_list: + return jsonify({"error": "model is not accessible"}), 401 + stream = data.get('stream', False) auth_header = request.headers.get('Authorization')