Skip to content

Commit

Permalink
fix: Smooth Error Handling for Subscription Token Exhaustion (#1105)
Browse files Browse the repository at this point in the history
Co-authored-by: Pavan Kumar <v-kupavan.microsoft.com>
  • Loading branch information
Pavan-Microsoft authored Jul 3, 2024
1 parent 7491070 commit 4e848de
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 27 deletions.
30 changes: 13 additions & 17 deletions code/create_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from os import path
import sys
import requests
from openai import AzureOpenAI, Stream
from openai import AzureOpenAI, Stream, RateLimitError
from openai.types.chat import ChatCompletionChunk
from flask import Flask, Response, request, Request, jsonify
from dotenv import load_dotenv
Expand All @@ -20,6 +20,8 @@
from azure.mgmt.cognitiveservices import CognitiveServicesManagementClient
from azure.identity import DefaultAzureCredential

ERROR_429_MESSAGE = "We're currently experiencing a high number of requests for the service you're trying to access. Please wait a moment and try again."
ERROR_GENERIC_MESSAGE = "An error occurred. Please try again. If the problem persists, please contact the site administrator."
logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -343,17 +345,14 @@ def conversation_azure_byod():
return conversation_with_data(request, env_helper)
else:
return conversation_without_data(request, env_helper)
except RateLimitError as e:
error_message = str(e)
logger.exception("Exception in /api/conversation | %s", error_message)
return jsonify({"error": ERROR_429_MESSAGE}), 429
except Exception as e:
error_message = str(e)
logger.exception("Exception in /api/conversation | %s", error_message)
return (
jsonify(
{
"error": "Exception in /api/conversation. See log for more details."
}
),
500,
)
return jsonify({"error": ERROR_GENERIC_MESSAGE}), 500

async def conversation_custom():
message_orchestrator = get_message_orchestrator()
Expand Down Expand Up @@ -385,17 +384,14 @@ async def conversation_custom():

return jsonify(response_obj), 200

except RateLimitError as e:
error_message = str(e)
logger.exception("Exception in /api/conversation | %s", error_message)
return jsonify({"error": ERROR_429_MESSAGE}), 429
except Exception as e:
error_message = str(e)
logger.exception("Exception in /api/conversation | %s", error_message)
return (
jsonify(
{
"error": "Exception in /api/conversation. See log for more details."
}
),
500,
)
return jsonify({"error": ERROR_GENERIC_MESSAGE}), 500

@app.route("/api/conversation", methods=["POST"])
async def conversation():
Expand Down
5 changes: 5 additions & 0 deletions code/frontend/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,10 @@ export async function callConversationApi(options: ConversationRequest, abortSig
signal: abortSignal
});

if (!response.ok) {
const errorData = await response.json();
throw new Error(JSON.stringify(errorData.error));
}

return response;
}
10 changes: 6 additions & 4 deletions code/frontend/src/pages/chat/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,12 @@ const Chat = () => {
}
} catch (e) {
if (!abortController.signal.aborted) {
console.error(result);
alert(
"An error occurred. Please try again. If the problem persists, please contact the site administrator."
);
if (e instanceof Error) {
alert(e.message);
}
else {
alert('An error occurred. Please try again. If the problem persists, please contact the site administrator.');
}
}
setAnswers([...answers, userMessage]);
} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -666,5 +666,5 @@ def test_post_returns_error_when_downstream_fails(
assert response.status_code == 500
assert response.headers["Content-Type"] == "application/json"
assert json.loads(response.text) == {
"error": "Exception in /api/conversation. See log for more details."
"error": "An error occurred. Please try again. If the problem persists, please contact the site administrator."
}
Original file line number Diff line number Diff line change
Expand Up @@ -277,5 +277,5 @@ def test_post_returns_error_when_downstream_fails(
assert response.status_code == 500
assert response.headers["Content-Type"] == "application/json"
assert json.loads(response.text) == {
"error": "Exception in /api/conversation. See log for more details."
"error": "An error occurred. Please try again. If the problem persists, please contact the site administrator."
}
Original file line number Diff line number Diff line change
Expand Up @@ -274,5 +274,5 @@ def test_post_returns_error_when_downstream_fails(
assert response.status_code == 500
assert response.headers["Content-Type"] == "application/json"
assert json.loads(response.text) == {
"error": "Exception in /api/conversation. See log for more details."
"error": "An error occurred. Please try again. If the problem persists, please contact the site administrator."
}
62 changes: 59 additions & 3 deletions code/tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
This module tests the entry point for the application.
"""

from unittest.mock import AsyncMock, MagicMock, patch, ANY
from unittest.mock import AsyncMock, MagicMock, Mock, patch, ANY
from openai import RateLimitError
import pytest
from flask.testing import FlaskClient
from backend.batch.utilities.helpers.config.conversation_flow import ConversationFlow
Expand Down Expand Up @@ -320,7 +321,34 @@ def test_conversaation_custom_returns_error_response_on_exception(
# then
assert response.status_code == 500
assert response.json == {
"error": "Exception in /api/conversation. See log for more details."
"error": "An error occurred. Please try again. If the problem persists, please contact the site administrator."
}

@patch("create_app.get_orchestrator_config")
def test_conversation_custom_returns_error_response_on_rate_limit_error(
self, get_orchestrator_config_mock, env_helper_mock, client
):
"""Test that a 429 response is returned on RateLimitError."""
# given
response_mock = Mock()
response_mock.status_code = 429
body_mock = {"error": "Rate limit exceeded"}

rate_limit_error = RateLimitError("Rate limit exceeded", response=response_mock, body=body_mock)
get_orchestrator_config_mock.side_effect = rate_limit_error

# when
response = client.post(
"/api/conversation",
headers={"content-type": "application/json"},
json=self.body,
)

# then
assert response.status_code == 429
assert response.json == {
"error": "We're currently experiencing a high number of requests for the service you're trying to access. "
"Please wait a moment and try again."
}

@patch("create_app.get_message_orchestrator")
Expand Down Expand Up @@ -688,7 +716,35 @@ def test_conversation_azure_byod_returns_500_when_exception_occurs(
# then
assert response.status_code == 500
assert response.json == {
"error": "Exception in /api/conversation. See log for more details."
"error": "An error occurred. Please try again. If the problem persists, please contact the site administrator."
}

@patch("create_app.conversation_with_data")
def test_conversation_azure_byod_returns_429_on_rate_limit_error(
self, conversation_with_data_mock, env_helper_mock, client
):
"""Test that a 429 response is returned on RateLimitError for BYOD conversation."""
# given
response_mock = Mock()
response_mock.status_code = 429
body_mock = {"error": "Rate limit exceeded"}

rate_limit_error = RateLimitError("Rate limit exceeded", response=response_mock, body=body_mock)
conversation_with_data_mock.side_effect = rate_limit_error
env_helper_mock.CONVERSATION_FLOW = ConversationFlow.BYOD.value

# when
response = client.post(
"/api/conversation",
headers={"content-type": "application/json"},
json=self.body,
)

# then
assert response.status_code == 429
assert response.json == {
"error": "We're currently experiencing a high number of requests for the service you're trying to access. "
"Please wait a moment and try again."
}

@patch("create_app.AzureOpenAI")
Expand Down

0 comments on commit 4e848de

Please sign in to comment.