├── .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 | ---
--------------------------------------------------------------------------------