From 77697be33381a01350d0818ff069469faea2f4ac Mon Sep 17 00:00:00 2001 From: Arran Hobson Sayers Date: Thu, 12 Oct 2023 02:35:11 +0100 Subject: Unify g4f tools into one CLI --- README.md | 43 +++++++--- etc/interference/app.py | 163 -------------------------------------- etc/interference/requirements.txt | 5 -- g4f/api/__init__.py | 162 +++++++++++++++++++++++++++++++++++++ g4f/api/run.py | 4 + g4f/cli.py | 28 +++++++ g4f/gui/__init__.py | 2 +- g4f/gui/run.py | 30 ++++--- g4f/interference/__init__.py | 94 ---------------------- g4f/interference/run.py | 4 - requirements.txt | 4 +- setup.py | 15 ++-- 12 files changed, 254 insertions(+), 300 deletions(-) delete mode 100644 etc/interference/app.py delete mode 100644 etc/interference/requirements.txt create mode 100644 g4f/api/__init__.py create mode 100644 g4f/api/run.py create mode 100644 g4f/cli.py delete mode 100644 g4f/interference/__init__.py delete mode 100644 g4f/interference/run.py diff --git a/README.md b/README.md index 973804d9..2a79c686 100644 --- a/README.md +++ b/README.md @@ -7,18 +7,39 @@ By using this repository or any code related to it, you agree to the [legal noti pip install -U g4f ``` +or if you just want to use the gui or interference api, install with [pipx](https://pypa.github.io/pipx/) + +```sh +pipx install g4f +``` + ## New features - Telegram Channel: https://t.me/g4f_channel - g4f GUI is back !!: Install g4f with pip and then run: -```py + +```sh +g4f gui +``` + +or + +```sh python -m g4f.gui.run ``` + preview: image -- run interference from pypi package: +- run interference api from pypi package: + +```sh +g4f api +``` + +or + ```py python -m g4f.interference.run ``` @@ -33,7 +54,7 @@ python -m g4f.interference.run - [Usage](#usage) - [The `g4f` Package](#the-g4f-package) - [interference openai-proxy api (use with openai python package)](#interference-openai-proxy-api-use-with-openai-python-package) -- [Providers](#models) +- [Models](#models) - [gpt-3.5 / gpt-4](#gpt-35--gpt-4) - [Other Models](#other-models) - [Related gpt4free projects](#related-gpt4free-projects) @@ -319,26 +340,26 @@ print(f"Result:", response) ### interference openai-proxy api (use with openai python package) -#### run interference from pypi package: +#### run interference api from pypi package: ```py -from g4f.interference import run_interference +from g4f.api import run_api -run_interference() +run_api() ``` -#### run interference from repo: +#### run interference api from repo: If you want to use the embedding function, you need to get a huggingface token. You can get one at https://huggingface.co/settings/tokens make sure your role is set to write. If you have your token, just use it instead of the OpenAI api-key. -get requirements: +run server: ```sh -pip install -r etc/interference/requirements.txt +g4f api ``` -run server: +or ```sh -python3 -m etc/interference.app +python -m g4f.api ``` ```py diff --git a/etc/interference/app.py b/etc/interference/app.py deleted file mode 100644 index 5abbcff2..00000000 --- a/etc/interference/app.py +++ /dev/null @@ -1,163 +0,0 @@ -import json -import time -import random -import string -import requests - -from typing import Any -from flask import Flask, request -from flask_cors import CORS -from transformers import AutoTokenizer -from g4f import ChatCompletion - -app = Flask(__name__) -CORS(app) - -@app.route('/chat/completions', methods=['POST']) -def chat_completions(): - model = request.get_json().get('model', 'gpt-3.5-turbo') - stream = request.get_json().get('stream', False) - messages = request.get_json().get('messages') - - response = ChatCompletion.create(model = model, - stream = stream, messages = messages) - - completion_id = ''.join(random.choices(string.ascii_letters + string.digits, k=28)) - completion_timestamp = int(time.time()) - - if not stream: - return { - 'id': f'chatcmpl-{completion_id}', - 'object': 'chat.completion', - 'created': completion_timestamp, - 'model': model, - 'choices': [ - { - 'index': 0, - 'message': { - 'role': 'assistant', - 'content': response, - }, - 'finish_reason': 'stop', - } - ], - 'usage': { - 'prompt_tokens': None, - 'completion_tokens': None, - 'total_tokens': None, - }, - } - - def streaming(): - for chunk in response: - completion_data = { - 'id': f'chatcmpl-{completion_id}', - 'object': 'chat.completion.chunk', - 'created': completion_timestamp, - 'model': model, - 'choices': [ - { - 'index': 0, - 'delta': { - 'content': chunk, - }, - 'finish_reason': None, - } - ], - } - - content = json.dumps(completion_data, separators=(',', ':')) - yield f'data: {content}\n\n' - time.sleep(0.1) - - end_completion_data: dict[str, Any] = { - 'id': f'chatcmpl-{completion_id}', - 'object': 'chat.completion.chunk', - 'created': completion_timestamp, - 'model': model, - 'choices': [ - { - 'index': 0, - 'delta': {}, - 'finish_reason': 'stop', - } - ], - } - content = json.dumps(end_completion_data, separators=(',', ':')) - yield f'data: {content}\n\n' - - return app.response_class(streaming(), mimetype='text/event-stream') - - -# Get the embedding from huggingface -def get_embedding(input_text, token): - huggingface_token = token - embedding_model = 'sentence-transformers/all-mpnet-base-v2' - max_token_length = 500 - - # Load the tokenizer for the 'all-mpnet-base-v2' model - tokenizer = AutoTokenizer.from_pretrained(embedding_model) - # Tokenize the text and split the tokens into chunks of 500 tokens each - tokens = tokenizer.tokenize(input_text) - token_chunks = [tokens[i:i + max_token_length] - for i in range(0, len(tokens), max_token_length)] - - # Initialize an empty list - embeddings = [] - - # Create embeddings for each chunk - for chunk in token_chunks: - # Convert the chunk tokens back to text - chunk_text = tokenizer.convert_tokens_to_string(chunk) - - # Use the Hugging Face API to get embeddings for the chunk - api_url = f'https://api-inference.huggingface.co/pipeline/feature-extraction/{embedding_model}' - headers = {'Authorization': f'Bearer {huggingface_token}'} - chunk_text = chunk_text.replace('\n', ' ') - - # Make a POST request to get the chunk's embedding - response = requests.post(api_url, headers=headers, json={ - 'inputs': chunk_text, 'options': {'wait_for_model': True}}) - - # Parse the response and extract the embedding - chunk_embedding = response.json() - # Append the embedding to the list - embeddings.append(chunk_embedding) - - # averaging all the embeddings - # this isn't very effective - # someone a better idea? - num_embeddings = len(embeddings) - average_embedding = [sum(x) / num_embeddings for x in zip(*embeddings)] - embedding = average_embedding - return embedding - - -@app.route('/embeddings', methods=['POST']) -def embeddings(): - input_text_list = request.get_json().get('input') - input_text = ' '.join(map(str, input_text_list)) - token = request.headers.get('Authorization').replace('Bearer ', '') - embedding = get_embedding(input_text, token) - - return { - 'data': [ - { - 'embedding': embedding, - 'index': 0, - 'object': 'embedding' - } - ], - 'model': 'text-embedding-ada-002', - 'object': 'list', - 'usage': { - 'prompt_tokens': None, - 'total_tokens': None - } - } - -def main(): - app.run(host='0.0.0.0', port=1337, debug=True) - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/etc/interference/requirements.txt b/etc/interference/requirements.txt deleted file mode 100644 index eaa3265b..00000000 --- a/etc/interference/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -flask_cors -watchdog~=3.0.0 -transformers -tensorflow -torch \ No newline at end of file diff --git a/g4f/api/__init__.py b/g4f/api/__init__.py new file mode 100644 index 00000000..c52085dc --- /dev/null +++ b/g4f/api/__init__.py @@ -0,0 +1,162 @@ +import json +import random +import string +import time + +import requests +from flask import Flask, request +from flask_cors import CORS +from transformers import AutoTokenizer + +from g4f import ChatCompletion + +app = Flask(__name__) +CORS(app) + + +@app.route("/") +def index(): + return "interference api, url: http://127.0.0.1:1337" + + +@app.route("/chat/completions", methods=["POST"]) +def chat_completions(): + model = request.get_json().get("model", "gpt-3.5-turbo") + stream = request.get_json().get("stream", False) + messages = request.get_json().get("messages") + + response = ChatCompletion.create(model=model, stream=stream, messages=messages) + + completion_id = "".join(random.choices(string.ascii_letters + string.digits, k=28)) + completion_timestamp = int(time.time()) + + if not stream: + return { + "id": f"chatcmpl-{completion_id}", + "object": "chat.completion", + "created": completion_timestamp, + "model": model, + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": response, + }, + "finish_reason": "stop", + } + ], + "usage": { + "prompt_tokens": None, + "completion_tokens": None, + "total_tokens": None, + }, + } + + def streaming(): + for chunk in response: + completion_data = { + "id": f"chatcmpl-{completion_id}", + "object": "chat.completion.chunk", + "created": completion_timestamp, + "model": model, + "choices": [ + { + "index": 0, + "delta": { + "content": chunk, + }, + "finish_reason": None, + } + ], + } + + content = json.dumps(completion_data, separators=(",", ":")) + yield f"data: {content}\n\n" + time.sleep(0.1) + + end_completion_data = { + "id": f"chatcmpl-{completion_id}", + "object": "chat.completion.chunk", + "created": completion_timestamp, + "model": model, + "choices": [ + { + "index": 0, + "delta": {}, + "finish_reason": "stop", + } + ], + } + content = json.dumps(end_completion_data, separators=(",", ":")) + yield f"data: {content}\n\n" + + return app.response_class(streaming(), mimetype="text/event-stream") + + +# Get the embedding from huggingface +def get_embedding(input_text, token): + huggingface_token = token + embedding_model = "sentence-transformers/all-mpnet-base-v2" + max_token_length = 500 + + # Load the tokenizer for the 'all-mpnet-base-v2' model + tokenizer = AutoTokenizer.from_pretrained(embedding_model) + # Tokenize the text and split the tokens into chunks of 500 tokens each + tokens = tokenizer.tokenize(input_text) + token_chunks = [ + tokens[i : i + max_token_length] + for i in range(0, len(tokens), max_token_length) + ] + + # Initialize an empty list + embeddings = [] + + # Create embeddings for each chunk + for chunk in token_chunks: + # Convert the chunk tokens back to text + chunk_text = tokenizer.convert_tokens_to_string(chunk) + + # Use the Hugging Face API to get embeddings for the chunk + api_url = f"https://api-inference.huggingface.co/pipeline/feature-extraction/{embedding_model}" + headers = {"Authorization": f"Bearer {huggingface_token}"} + chunk_text = chunk_text.replace("\n", " ") + + # Make a POST request to get the chunk's embedding + response = requests.post( + api_url, + headers=headers, + json={"inputs": chunk_text, "options": {"wait_for_model": True}}, + ) + + # Parse the response and extract the embedding + chunk_embedding = response.json() + # Append the embedding to the list + embeddings.append(chunk_embedding) + + # averaging all the embeddings + # this isn't very effective + # someone a better idea? + num_embeddings = len(embeddings) + average_embedding = [sum(x) / num_embeddings for x in zip(*embeddings)] + embedding = average_embedding + return embedding + + +@app.route("/embeddings", methods=["POST"]) +def embeddings(): + input_text_list = request.get_json().get("input") + input_text = " ".join(map(str, input_text_list)) + token = request.headers.get("Authorization").replace("Bearer ", "") + embedding = get_embedding(input_text, token) + + return { + "data": [{"embedding": embedding, "index": 0, "object": "embedding"}], + "model": "text-embedding-ada-002", + "object": "list", + "usage": {"prompt_tokens": None, "total_tokens": None}, + } + + +def run_api(): + app.run(host="0.0.0.0", port=1337) diff --git a/g4f/api/run.py b/g4f/api/run.py new file mode 100644 index 00000000..6e9b63f3 --- /dev/null +++ b/g4f/api/run.py @@ -0,0 +1,4 @@ +from g4f.api import run_api + +if __name__ == "__main__": + run_api() diff --git a/g4f/cli.py b/g4f/cli.py new file mode 100644 index 00000000..42401cc8 --- /dev/null +++ b/g4f/cli.py @@ -0,0 +1,28 @@ +import argparse + +from g4f.api import run_api +from g4f.gui.run import gui_parser, run_gui_args + + +def run_gui(args): + print("Running GUI...") + + +def main(): + parser = argparse.ArgumentParser(description="Run gpt4free") + subparsers = parser.add_subparsers(dest="mode", help="Mode to run the g4f in.") + subparsers.add_parser("api") + subparsers.add_parser("gui", parents=[gui_parser()], add_help=False) + + args = parser.parse_args() + if args.mode == "api": + run_api() + elif args.mode == "gui": + run_gui_args(args) + else: + parser.print_help() + exit(1) + + +if __name__ == "__main__": + main() diff --git a/g4f/gui/__init__.py b/g4f/gui/__init__.py index 48b78881..a8000e71 100644 --- a/g4f/gui/__init__.py +++ b/g4f/gui/__init__.py @@ -27,4 +27,4 @@ def run_gui(host: str = '0.0.0.0', port: int = 80, debug: bool = False) -> None: print(f"Running on port {config['port']}") app.run(**config) - print(f"Closing port {config['port']}") \ No newline at end of file + print(f"Closing port {config['port']}") diff --git a/g4f/gui/run.py b/g4f/gui/run.py index 731c7cbf..0f94814c 100644 --- a/g4f/gui/run.py +++ b/g4f/gui/run.py @@ -1,18 +1,24 @@ -from g4f.gui import run_gui from argparse import ArgumentParser +from g4f.gui import run_gui -if __name__ == '__main__': - - parser = ArgumentParser(description='Run the GUI') - - parser.add_argument('-host', type=str, default='0.0.0.0', help='hostname') - parser.add_argument('-port', type=int, default=80, help='port') - parser.add_argument('-debug', action='store_true', help='debug mode') - args = parser.parse_args() - port = args.port +def gui_parser(): + parser = ArgumentParser(description="Run the GUI") + parser.add_argument("-host", type=str, default="0.0.0.0", help="hostname") + parser.add_argument("-port", type=int, default=80, help="port") + parser.add_argument("-debug", action="store_true", help="debug mode") + return parser + + +def run_gui_args(args): host = args.host + port = args.port debug = args.debug - - run_gui(host, port, debug) \ No newline at end of file + run_gui(host, port, debug) + + +if __name__ == "__main__": + parser = gui_parser() + args = parser.parse_args() + run_gui_args(args) diff --git a/g4f/interference/__init__.py b/g4f/interference/__init__.py deleted file mode 100644 index d756faa7..00000000 --- a/g4f/interference/__init__.py +++ /dev/null @@ -1,94 +0,0 @@ -import json -import time -import random -import string - -from typing import Any -from flask import Flask, request -from flask_cors import CORS -from g4f import ChatCompletion - -app = Flask(__name__) -CORS(app) - -@app.route('/') -def index(): - return 'interference api, url: http://127.0.0.1:1337' - -@app.route('/chat/completions', methods=['POST']) -def chat_completions(): - model = request.get_json().get('model', 'gpt-3.5-turbo') - stream = request.get_json().get('stream', False) - messages = request.get_json().get('messages') - - response = ChatCompletion.create(model = model, - stream = stream, messages = messages) - - completion_id = ''.join(random.choices(string.ascii_letters + string.digits, k=28)) - completion_timestamp = int(time.time()) - - if not stream: - return { - 'id': f'chatcmpl-{completion_id}', - 'object': 'chat.completion', - 'created': completion_timestamp, - 'model': model, - 'choices': [ - { - 'index': 0, - 'message': { - 'role': 'assistant', - 'content': response, - }, - 'finish_reason': 'stop', - } - ], - 'usage': { - 'prompt_tokens': None, - 'completion_tokens': None, - 'total_tokens': None, - }, - } - - def streaming(): - for chunk in response: - completion_data = { - 'id': f'chatcmpl-{completion_id}', - 'object': 'chat.completion.chunk', - 'created': completion_timestamp, - 'model': model, - 'choices': [ - { - 'index': 0, - 'delta': { - 'content': chunk, - }, - 'finish_reason': None, - } - ], - } - - content = json.dumps(completion_data, separators=(',', ':')) - yield f'data: {content}\n\n' - time.sleep(0.1) - - end_completion_data: dict[str, Any] = { - 'id': f'chatcmpl-{completion_id}', - 'object': 'chat.completion.chunk', - 'created': completion_timestamp, - 'model': model, - 'choices': [ - { - 'index': 0, - 'delta': {}, - 'finish_reason': 'stop', - } - ], - } - content = json.dumps(end_completion_data, separators=(',', ':')) - yield f'data: {content}\n\n' - - return app.response_class(streaming(), mimetype='text/event-stream') - -def run_interference(): - app.run(host='0.0.0.0', port=1337, debug=True) \ No newline at end of file diff --git a/g4f/interference/run.py b/g4f/interference/run.py deleted file mode 100644 index e527ce11..00000000 --- a/g4f/interference/run.py +++ /dev/null @@ -1,4 +0,0 @@ -from g4f.interference import run_interference - -if __name__ == '__main__': - run_interference() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 75549d68..4b0a46fd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,6 @@ flask flask-cors typing-extensions PyExecJS -duckduckgo-search \ No newline at end of file +duckduckgo-search +transformers +tensorflow diff --git a/setup.py b/setup.py index 2e83cc82..5fb56896 100644 --- a/setup.py +++ b/setup.py @@ -11,10 +11,7 @@ with codecs.open(os.path.join(here, "README.md"), encoding="utf-8") as fh: with open("requirements.txt") as f: required = f.read().splitlines() -with open("etc/interference/requirements.txt") as f: - api_required = f.read().splitlines() - -VERSION = '0.1.6.1' +VERSION = "0.1.6.1" DESCRIPTION = ( "The official gpt4free repository | various collection of powerful language models" ) @@ -29,13 +26,13 @@ setup( long_description_content_type="text/markdown", long_description=long_description, packages=find_packages(), - package_data={"g4f": ["g4f/gui/client/*", "g4f/gui/server/*"]}, + package_data={ + "g4f": ["g4f/interference/*", "g4f/gui/client/*", "g4f/gui/server/*"] + }, include_package_data=True, - data_files=["etc/interference/app.py"], install_requires=required, - extras_require={"api": api_required}, entry_points={ - "console_scripts": ["g4f=interference.app:main"], + "console_scripts": ["g4f=g4f.cli:main"], }, url="https://github.com/xtekky/gpt4free", # Link to your GitHub repository project_urls={ @@ -75,4 +72,4 @@ setup( "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", ], -) \ No newline at end of file +) -- cgit v1.2.3