├── .DS_Store ├── requirements.txt ├── docker-compose.yml ├── gunicorn-config.py ├── run.py ├── Dockerfile ├── api ├── config.py ├── training_service.py ├── __init__.py └── model_service.py ├── LICENSE ├── .gitignore └── README.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/assafelovic/gpt3-api/HEAD/.DS_Store -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | openai~=0.27.0 2 | flask~=2.0.1 3 | flask_cors 4 | python-dotenv==0.19.2 5 | gunicorn==20.1.0 -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | services: 3 | gpt3-api: 4 | container_name: gpt3-api 5 | restart: always 6 | env_file: .env 7 | build: . 8 | ports: 9 | - '5005:5005' -------------------------------------------------------------------------------- /gunicorn-config.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | from api.config import PORT 3 | 4 | bind = '0.0.0.0:{0}'.format(PORT) 5 | workers = 2 6 | accesslog = '-' 7 | loglevel = 'debug' 8 | capture_output = True 9 | enable_stdio_inheritance = True -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | from api import app 3 | from dotenv import load_dotenv 4 | from api.config import PORT, HOST, DEBUG 5 | 6 | if __name__ == "__main__": 7 | load_dotenv() 8 | app.run(host=HOST, port=PORT, debug=DEBUG) 9 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9 2 | 3 | COPY . . 4 | 5 | # set environment variables 6 | ENV PYTHONDONTWRITEBYTECODE 1 7 | ENV PYTHONUNBUFFERED 1 8 | 9 | # install python dependencies 10 | RUN pip install --upgrade pip 11 | RUN pip install --no-cache-dir -r requirements.txt 12 | 13 | # gunicorn 14 | CMD ["gunicorn", "--config", "gunicorn-config.py", "run:app"] -------------------------------------------------------------------------------- /api/config.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | import os 3 | 4 | ''' 5 | Flask API server configuration 6 | ''' 7 | DEBUG = True 8 | PORT = 5005 9 | HOST = '127.0.0.1' 10 | 11 | ''' 12 | OpenAI GPT configuration 13 | For security purposes, set OpenAI API Key in root folder .env file 14 | ''' 15 | OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY") 16 | GPT_MODEL = 'text-davinci-003' 17 | TEMPERATURE = 0.7 18 | MAX_TOKENS = 80 19 | 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Assaf Elovic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /api/training_service.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | import openai 3 | from api.config import OPENAI_API_KEY, GPT_MODEL 4 | 5 | 6 | class TrainingService: 7 | def __init__(self): 8 | openai.api_key = OPENAI_API_KEY 9 | 10 | ''' 11 | Upload training file (JSONL) to openai cloud. 12 | Returns the id of the uploaded training file. 13 | ''' 14 | def upload_file(self, filePath): 15 | with open(filePath) as f: 16 | response = openai.File.create(file=f, purpose='fine-tune') 17 | return response.get('id') 18 | 19 | ''' 20 | Create a fine tuned model training job with uploaded training file 21 | Returns the job response 22 | ''' 23 | def create_fine_tuned_model(self, training_file_id, model=GPT_MODEL): 24 | create_args = { 25 | "training_file": training_file_id, 26 | "model": model, 27 | "compute_classification_metrics": True, 28 | "classification_n_classes": 2, 29 | "batch_size": 128 30 | } 31 | r = openai.FineTune.create(**create_args) 32 | return r 33 | 34 | ''' 35 | Get status of a training job 36 | ''' 37 | def get_training_status(self, training_job_id): 38 | r = openai.FineTune.retrieve(id=training_job_id) 39 | return r 40 | 41 | ''' 42 | Get a fined tuned model id 43 | ''' 44 | def get_find_tuned_model_id(self, training_job_id): 45 | r = openai.FineTune.retrieve(self, id=training_job_id) 46 | return r.get('fine_tuned_model') 47 | 48 | ''' 49 | List of all current fine tuned models 50 | ''' 51 | def list_jobs(self): 52 | r = openai.FineTune.list() 53 | for deployment in r.data: 54 | print('{0}: {1} '.format(deployment.get("id"), deployment.get("fine_tuned_model"))) 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | .idea/ 131 | -------------------------------------------------------------------------------- /api/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | from flask import Flask, request 4 | from flask_cors import CORS 5 | from api.model_service import ModelService 6 | from api.config import GPT_MODEL, TEMPERATURE, MAX_TOKENS 7 | 8 | model_service = ModelService() 9 | 10 | # Create App 11 | app = Flask(__name__) 12 | 13 | # Enable CORS 14 | CORS(app) 15 | 16 | 17 | @app.route('/', methods=['GET']) 18 | def hello(): 19 | return { 20 | 'success': True, 21 | 'params': { 22 | 'temperature': TEMPERATURE, 23 | 'max_tokens': MAX_TOKENS, 24 | 'model': GPT_MODEL 25 | } 26 | } 27 | 28 | 29 | @app.route('/api/v1/predict', methods=['POST']) 30 | def predict(): 31 | json = request.json 32 | prompt = json.get("prompt") 33 | args = {'temperature': json.setdefault("temperature", TEMPERATURE), 34 | 'moderation': json.setdefault("moderation", False), 35 | 'max_tokens': json.setdefault("max_tokens", MAX_TOKENS), 36 | "model": json.setdefault("model", GPT_MODEL)} 37 | 38 | if not prompt: 39 | return {"success": False, "result": "Error reading query"}, 400 40 | result = model_service.predict(prompt, args) 41 | return {"success": True, "result": result}, 200 42 | 43 | @app.route('/api/v1/chat', methods=['POST']) 44 | def chat(): 45 | json = request.json 46 | messages = json.get("messages") 47 | chat_behavior = json.get("chat_behavior") 48 | args = {'messages': messages, 'chat_behavior': chat_behavior} 49 | 50 | if not messages: 51 | return {"success": False, "result": "Error reading query"}, 400 52 | result = model_service.chat(args) 53 | return {"success": True, "result": result}, 200 54 | 55 | @app.route('/api/v1/transcribe', methods=['POST']) 56 | def transcribe(): 57 | json = request.json 58 | audio_path = json.get("audio_path") 59 | args = {'audio_path': audio_path} 60 | 61 | if not audio_path: 62 | return {"success": False, "result": "Error reading query"}, 400 63 | result = model_service.transcribe(args) 64 | return {"success": True, "result": result}, 200 65 | 66 | 67 | @app.route('/api/v1/image', methods=['POST']) 68 | def image(): 69 | json = request.json 70 | prompt = json.get("prompt") 71 | args = {'n': json.setdefault("n", 1), 72 | 'size': json.setdefault("size", '512x512')} 73 | 74 | if not prompt: 75 | return {"success": False, "result": "Error reading query"}, 400 76 | result = model_service.image(prompt, args) 77 | return {"success": True, "result": result}, 200 78 | -------------------------------------------------------------------------------- /api/model_service.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | import openai 3 | 4 | from api.config import OPENAI_API_KEY, TEMPERATURE, MAX_TOKENS, GPT_MODEL 5 | 6 | 7 | class ModelService: 8 | 9 | def __init__(self): 10 | openai.api_key = OPENAI_API_KEY 11 | 12 | ''' 13 | The moderation endpoint is a tool you can use to check whether content is harmful and complies with OpenAI's usage policies. 14 | ''' 15 | def moderation(self, input): 16 | r = openai.Moderation.create( 17 | input=input 18 | ) 19 | return r 20 | 21 | 22 | """ 23 | core openai wrapper for completion API 24 | """ 25 | def completion(self, prompt, kwargs={}): 26 | temp = kwargs.setdefault('temperature', TEMPERATURE) 27 | max_tokens = kwargs.setdefault('max_tokens', MAX_TOKENS) 28 | model = kwargs.setdefault('model', GPT_MODEL) 29 | 30 | r = openai.Completion.create( 31 | model=model, 32 | prompt=prompt, 33 | temperature=temp, 34 | max_tokens=max_tokens, 35 | top_p=1, 36 | frequency_penalty=0, 37 | presence_penalty=0, 38 | ) 39 | 40 | return { 41 | "result": r["choices"][0]["text"].strip(), 42 | "model": model, 43 | "max_tokens": max_tokens, 44 | "temperature": temp 45 | } 46 | 47 | """ 48 | API Wrapper for ChatGPT 49 | """ 50 | def chat(self, kwargs={}): 51 | # Assume the following format: [{'role':'assistant'/'user', 'content': '...'},...] 52 | messages = kwargs.get("messages") 53 | 54 | # (Optional) The chat_behavior is a system message that helps set the behavior of the assistant. 55 | chat_behavior = kwargs.get("chat_behavior") 56 | if chat_behavior: 57 | messages.insert(0, {"role": "system", "content": chat_behavior}) 58 | 59 | r = openai.ChatCompletion.create( 60 | model="gpt-3.5-turbo", 61 | messages=messages 62 | ) 63 | completion_response = r["choices"][0]["message"]["content"].rstrip() 64 | return completion_response 65 | 66 | """ 67 | API Wrapper for OpenAI Whisper 68 | """ 69 | def transcribe(self, kwargs={}): 70 | audio_path = kwargs.get("audio_path") 71 | file = open(audio_path, "rb") 72 | transcription = openai.Audio.transcribe("whisper-1", file) 73 | return transcription 74 | 75 | """ 76 | API Wrapper for Dalle 2 77 | Please note, images returned as uri will be removed within 1 hour. 78 | """ 79 | def image(self, prompt, kwargs={}): 80 | n = kwargs.setdefault('n', 1) 81 | size = kwargs.setdefault('size', '512x512') 82 | 83 | r = openai.Image.create( 84 | prompt=prompt, 85 | n=n, 86 | size=size 87 | ) 88 | 89 | return { 90 | "url": r["data"][0]["url"], 91 | "n": n, 92 | "size": size 93 | } 94 | 95 | """ 96 | API Wrapper for prompt completion 97 | """ 98 | def predict(self, prompt, kwargs={}): 99 | result = self.completion(prompt, kwargs) 100 | if kwargs.get("moderation"): 101 | moderation_results = self.moderation(result.get("result")) 102 | result["moderation"] = moderation_results 103 | return result 104 | 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenAI Flask API Wrapper 2 | 3 | A basic Flask API interface for OpenAI GPT3, ChatGPT, Whisper and Dalle2 including training and examples. 4 | 5 |
6 | 7 | > Features: 8 | 9 | - Simple, intuitive codebase - can be extended with ease. 10 | - `Gunicorn` support 11 | - `Docker` support 12 | - Includes both prediction and training GPT3 services 13 | - Includes support for OpenAI moderation for validating responses 14 | 15 | 16 | ## API 17 | 18 | | Route | Method | Info | 19 | | --- | --- | --- | 20 | | `/` | **GET** | Get current GPT3 model params 21 | | `/api/v1/predict` | **POST** | Predict GPT3 prompt completion 22 | | `/api/v1/image` | **POST** | Generate Dalle-2 prompt image 23 | | `/api/v1/chat` | **POST** | ChatGPT wrapper including context support 24 | | `/api/v1/transcribe` | **POST** | OpenAI Whisper Transcription 25 | 26 |
27 | 28 | > **Before you run** - Set your OpenAI API Key in .env file 29 | 30 | ```bash 31 | $ echo 'OPENAI_API_KEY={Your key here}' .env 32 | ``` 33 | 34 |
35 | 36 | ## Quick Start with Docker 37 | 38 | 39 | ```bash 40 | $ git clone https://github.com/assafelovic/gpt3-api.git 41 | $ cd gpt3-api 42 | ``` 43 | 44 | ```bash 45 | $ docker-compose up --build 46 | ``` 47 | 48 | The API server will start using the PORT `5005`. 49 | 50 |
51 | 52 | ## Usage example 53 | Client sends post request with the params `prompts` (String) and optional params to `http://127.0.0.1:5005/api/v1/predict` 54 | ``` 55 | POST /api/v1/predict 56 | { 57 | prompt: "What is the meaning of life?", 58 | moderation: true, 59 | max_tokens: 128, 60 | temperature: 0.4 61 | } 62 | ``` 63 | 64 | and the server responds with the predicted result: 65 | 66 | ``` 67 | { 68 | "result": { 69 | "result": "The meaning of life is to find fulfillment and purpose through living a life of value and making a positive contribution to the world.", 70 | "temperature": 0.4, 71 | "max_tokens": 128, 72 | "model": "text-davinci-003", 73 | "moderation": { 74 | "id": "modr-6paOy8KtNPYQdPzDamKYvhQ1GQUGD", 75 | "model": "text-moderation-004", 76 | "results": [ 77 | { 78 | "categories": { 79 | "hate": false, 80 | "hate/threatening": false, 81 | "self-harm": false, 82 | "sexual": false, 83 | "sexual/minors": false, 84 | "violence": false, 85 | "violence/graphic": false 86 | }, 87 | "category_scores": { 88 | "hate": 3.4930130254906544e-07, 89 | "hate/threatening": 8.722193106658338e-11, 90 | "self-harm": 2.9834368309167303e-09, 91 | "sexual": 4.3741639643712915e-08, 92 | "sexual/minors": 5.4334679117085116e-11, 93 | "violence": 5.354117149636295e-08, 94 | "violence/graphic": 1.3893468597814262e-09 95 | }, 96 | "flagged": false 97 | } 98 | ] 99 | }, 100 | }, 101 | "success": true 102 | } 103 | ``` 104 | 105 | ## Installation 106 | 107 | > **Step #1** - Clone the project 108 | 109 | ```bash 110 | $ git clone https://github.com/assafelovic/gpt3-api.git 111 | $ cd gpt3-api 112 | ``` 113 | 114 |
115 | 116 | > **Step #2** - Install dependencies 117 | ```bash 118 | $ pip install -r requirements.txt 119 | ``` 120 |
121 | 122 | > **Step #3** - setup `flask` command for our app 123 | 124 | ```bash 125 | $ export FLASK_APP=run.py 126 | $ export FLASK_ENV=development 127 | ``` 128 |
129 | 130 | > **Step #4** - start development server at `localhost:5005` 131 | 132 | ```bash 133 | $ flask run 134 | ``` 135 | 136 | 137 | --- --------------------------------------------------------------------------------