Skip to content

Commit

Permalink
fix: handle rate limit errors on backend and frontend (#22)
Browse files Browse the repository at this point in the history
* chore: check for rate limits

* fix: only show one error message at a time

* fix: show user error from server
  • Loading branch information
knjk04 authored Jul 25, 2023
1 parent 06f9b48 commit cbb0502
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 21 deletions.
25 changes: 19 additions & 6 deletions src/backend/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import openai
from flask import Flask, abort, request

from backend.error_messages import ErrorMessages
from backend.http_status_codes import StatusCodes

app = Flask(__name__)
Expand All @@ -23,7 +24,7 @@ def get_models() -> list[str]:
def get_gpt_3_5_response(dataset: str) -> str:
openai.api_key = request.headers.get("Authorization")
if not openai.api_key:
abort(StatusCodes.UNAUTHORISED.value, "API key not given")
abort(StatusCodes.UNAUTHORISED.value, ErrorMessages.NO_API_KEY.value)

try:
response = openai.ChatCompletion.create(
Expand All @@ -36,17 +37,24 @@ def get_gpt_3_5_response(dataset: str) -> str:
}
]
)
# TODO: handle rate limit error
return response.choices[0].message.content
except openai.error.RateLimitError:
abort(
StatusCodes.BAD_REQUEST.value,
ErrorMessages.RATE_LIMIT.value
)
except openai.error.AuthenticationError:
abort(StatusCodes.UNAUTHORISED.value, "API key given is not valid")
abort(
StatusCodes.UNAUTHORISED.value,
ErrorMessages.INVALID_API_KEY.value
)


@app.route("/davinci/<dataset>")
def get_davinci_response(dataset: str) -> str:
openai.api_key = request.headers.get("Authorization")
if not openai.api_key:
abort(StatusCodes.UNAUTHORISED.value, "API key not given")
abort(StatusCodes.UNAUTHORISED.value, ErrorMessages.NO_API_KEY.value)

try:
response = openai.Completion.create(
Expand All @@ -55,10 +63,15 @@ def get_davinci_response(dataset: str) -> str:
max_tokens=4000,
temperature=0.2,
)
# TODO: handle rate limit error
return response.choices[0].text
except openai.error.RateLimitError:
abort(
StatusCodes.BAD_REQUEST.value,
ErrorMessages.RATE_LIMIT.value
)
except openai.error.AuthenticationError:
abort(StatusCodes.UNAUTHORISED.value, "API key given is not valid")
abort(StatusCodes.UNAUTHORISED.value,
ErrorMessages.INVALID_API_KEY.value)


if __name__ == '__main__':
Expand Down
9 changes: 9 additions & 0 deletions src/backend/error_messages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from enum import Enum


class ErrorMessages(Enum):
NO_API_KEY = "API key not given"
INVALID_API_KEY = "API key given is not valid"
RATE_LIMIT = "You have reached your rate limit. See here for more " \
"information: " \
"https://platform.openai.com/docs/guides/rate-limits/overview"
1 change: 1 addition & 0 deletions src/backend/http_status_codes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@


class StatusCodes(Enum):
BAD_REQUEST = 400
UNAUTHORISED = 403
19 changes: 16 additions & 3 deletions src/frontend/api.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import ast
import bs4
import logging
from enum import Enum
from io import StringIO
from requests.exceptions import HTTPError
from typing import List

import openai
import pandas as pd
import requests

from frontend.server_exception import ServerException

BASE_URL = "http://127.0.0.1:8000"

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -41,8 +44,18 @@ def get_response(dataset_of: str, gpt_choice, api_key: str) -> pd.DataFrame:
url = f"{BASE_URL}/davinci/{dataset_of}"

headers = get_auth_header(api_key)
response = requests.get(url=url, headers=headers)
return str_to_df(response.text)

try:
response = requests.get(url=url, headers=headers)
response.raise_for_status()
status_code = response.status_code
if status_code == 200:
return str_to_df(response.text)
except HTTPError as e:
html = bs4.BeautifulSoup(e.response.text, features="html.parser")
server_message = html.p.text
logger.info(server_message)
raise ServerException(server_message)


# TODO: add a function that checks if the input is valid markdown
Expand Down
3 changes: 3 additions & 0 deletions src/frontend/server_exception.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class ServerException(Exception):
def __init__(self, message):
self.message = message
35 changes: 24 additions & 11 deletions src/frontend/st_app.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import time

import pandas as pd
import streamlit as st

from frontend.api import get_models, get_response
from frontend.server_exception import ServerException
from frontend.util import df_to_csv, df_to_json

app_title = "Dataset Generator"
st.set_page_config(page_title=app_title)
st.title(app_title)


def show_export_buttons():
def show_export_buttons(df: pd.DataFrame):
col1, col2 = st.columns(2, gap="small")
with col1:
st.download_button(
Expand All @@ -28,9 +30,9 @@ def show_export_buttons():
)


def show_result():
def show_result(df: pd.DataFrame):
st.dataframe(df, use_container_width=True)
show_export_buttons()
show_export_buttons(df)


def get_gpt_radio():
Expand Down Expand Up @@ -61,31 +63,42 @@ def get_gpt_radio():
generate_clicked = st.form_submit_button(label="Generate dataset",
disabled=False)

if generate_clicked:

def generate_dataset():
if not api_key:
st.error(
f"You did not enter in an API key. Please enter your OpenAI API "
f"key and try again", icon="🥸"
)
return
if not dataset_entered:
st.error(
f"Please enter the dataset you would like us to generate (e.g. "
f"'Harry Potter quotes') dataset.", icon="🥸"
)
return

# Show spinner until a DataFrame is returned
with st.spinner(f"Generating a dataset of {dataset_entered}..."):
df = get_response(dataset_entered, gpt_choice, api_key)
# the truth value of a DataFrame is ambiguous, so cannot use
# 'while not df'
while df is None:
# check every 1 second
time.sleep(1)
try:
df = get_response(dataset_entered, gpt_choice, api_key)
# the truth value of a DataFrame is ambiguous, so cannot use
# 'while not df'
while df is None:
# check every 1 second
time.sleep(1)
except ServerException as e:
st.error(e.message)
return

if df.empty:
st.error(
f"We could not generate a {dataset_entered} dataset. "
f"Try generating a different dataset.", icon="🤔"
)
else:
show_result()
show_result(df)


if generate_clicked:
generate_dataset()
1 change: 1 addition & 0 deletions src/requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pre-commit==3.3.3
pytest==7.4.0
3 changes: 2 additions & 1 deletion src/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
streamlit==1.23.1
streamlit==1.25.0
openai==0.27.8
python-dotenv==1.0.0
pandas==2.0.2
Flask==2.3.2
beautifulsoup4==4.12.2

0 comments on commit cbb0502

Please sign in to comment.