Skip to content

Commit 4e848de

Browse files
fix: Smooth Error Handling for Subscription Token Exhaustion (#1105)
Co-authored-by: Pavan Kumar <v-kupavan.microsoft.com>
1 parent 7491070 commit 4e848de

File tree

7 files changed

+86
-27
lines changed

7 files changed

+86
-27
lines changed

code/create_app.py

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from os import path
1010
import sys
1111
import requests
12-
from openai import AzureOpenAI, Stream
12+
from openai import AzureOpenAI, Stream, RateLimitError
1313
from openai.types.chat import ChatCompletionChunk
1414
from flask import Flask, Response, request, Request, jsonify
1515
from dotenv import load_dotenv
@@ -20,6 +20,8 @@
2020
from azure.mgmt.cognitiveservices import CognitiveServicesManagementClient
2121
from azure.identity import DefaultAzureCredential
2222

23+
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."
24+
ERROR_GENERIC_MESSAGE = "An error occurred. Please try again. If the problem persists, please contact the site administrator."
2325
logger = logging.getLogger(__name__)
2426

2527

@@ -343,17 +345,14 @@ def conversation_azure_byod():
343345
return conversation_with_data(request, env_helper)
344346
else:
345347
return conversation_without_data(request, env_helper)
348+
except RateLimitError as e:
349+
error_message = str(e)
350+
logger.exception("Exception in /api/conversation | %s", error_message)
351+
return jsonify({"error": ERROR_429_MESSAGE}), 429
346352
except Exception as e:
347353
error_message = str(e)
348354
logger.exception("Exception in /api/conversation | %s", error_message)
349-
return (
350-
jsonify(
351-
{
352-
"error": "Exception in /api/conversation. See log for more details."
353-
}
354-
),
355-
500,
356-
)
355+
return jsonify({"error": ERROR_GENERIC_MESSAGE}), 500
357356

358357
async def conversation_custom():
359358
message_orchestrator = get_message_orchestrator()
@@ -385,17 +384,14 @@ async def conversation_custom():
385384

386385
return jsonify(response_obj), 200
387386

387+
except RateLimitError as e:
388+
error_message = str(e)
389+
logger.exception("Exception in /api/conversation | %s", error_message)
390+
return jsonify({"error": ERROR_429_MESSAGE}), 429
388391
except Exception as e:
389392
error_message = str(e)
390393
logger.exception("Exception in /api/conversation | %s", error_message)
391-
return (
392-
jsonify(
393-
{
394-
"error": "Exception in /api/conversation. See log for more details."
395-
}
396-
),
397-
500,
398-
)
394+
return jsonify({"error": ERROR_GENERIC_MESSAGE}), 500
399395

400396
@app.route("/api/conversation", methods=["POST"])
401397
async def conversation():

code/frontend/src/api/api.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,10 @@ export async function callConversationApi(options: ConversationRequest, abortSig
1414
signal: abortSignal
1515
});
1616

17+
if (!response.ok) {
18+
const errorData = await response.json();
19+
throw new Error(JSON.stringify(errorData.error));
20+
}
21+
1722
return response;
1823
}

code/frontend/src/pages/chat/Chat.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,12 @@ const Chat = () => {
114114
}
115115
} catch (e) {
116116
if (!abortController.signal.aborted) {
117-
console.error(result);
118-
alert(
119-
"An error occurred. Please try again. If the problem persists, please contact the site administrator."
120-
);
117+
if (e instanceof Error) {
118+
alert(e.message);
119+
}
120+
else {
121+
alert('An error occurred. Please try again. If the problem persists, please contact the site administrator.');
122+
}
121123
}
122124
setAnswers([...answers, userMessage]);
123125
} finally {

code/tests/functional/tests/backend_api/default/test_conversation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -666,5 +666,5 @@ def test_post_returns_error_when_downstream_fails(
666666
assert response.status_code == 500
667667
assert response.headers["Content-Type"] == "application/json"
668668
assert json.loads(response.text) == {
669-
"error": "Exception in /api/conversation. See log for more details."
669+
"error": "An error occurred. Please try again. If the problem persists, please contact the site administrator."
670670
}

code/tests/functional/tests/backend_api/integrated_vectorization_custom_conversation/test_iv_question_answer_tool.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,5 +277,5 @@ def test_post_returns_error_when_downstream_fails(
277277
assert response.status_code == 500
278278
assert response.headers["Content-Type"] == "application/json"
279279
assert json.loads(response.text) == {
280-
"error": "Exception in /api/conversation. See log for more details."
280+
"error": "An error occurred. Please try again. If the problem persists, please contact the site administrator."
281281
}

code/tests/functional/tests/backend_api/sk_orchestrator/test_response_without_tool_call.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,5 +274,5 @@ def test_post_returns_error_when_downstream_fails(
274274
assert response.status_code == 500
275275
assert response.headers["Content-Type"] == "application/json"
276276
assert json.loads(response.text) == {
277-
"error": "Exception in /api/conversation. See log for more details."
277+
"error": "An error occurred. Please try again. If the problem persists, please contact the site administrator."
278278
}

code/tests/test_app.py

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
This module tests the entry point for the application.
33
"""
44

5-
from unittest.mock import AsyncMock, MagicMock, patch, ANY
5+
from unittest.mock import AsyncMock, MagicMock, Mock, patch, ANY
6+
from openai import RateLimitError
67
import pytest
78
from flask.testing import FlaskClient
89
from backend.batch.utilities.helpers.config.conversation_flow import ConversationFlow
@@ -320,7 +321,34 @@ def test_conversaation_custom_returns_error_response_on_exception(
320321
# then
321322
assert response.status_code == 500
322323
assert response.json == {
323-
"error": "Exception in /api/conversation. See log for more details."
324+
"error": "An error occurred. Please try again. If the problem persists, please contact the site administrator."
325+
}
326+
327+
@patch("create_app.get_orchestrator_config")
328+
def test_conversation_custom_returns_error_response_on_rate_limit_error(
329+
self, get_orchestrator_config_mock, env_helper_mock, client
330+
):
331+
"""Test that a 429 response is returned on RateLimitError."""
332+
# given
333+
response_mock = Mock()
334+
response_mock.status_code = 429
335+
body_mock = {"error": "Rate limit exceeded"}
336+
337+
rate_limit_error = RateLimitError("Rate limit exceeded", response=response_mock, body=body_mock)
338+
get_orchestrator_config_mock.side_effect = rate_limit_error
339+
340+
# when
341+
response = client.post(
342+
"/api/conversation",
343+
headers={"content-type": "application/json"},
344+
json=self.body,
345+
)
346+
347+
# then
348+
assert response.status_code == 429
349+
assert response.json == {
350+
"error": "We're currently experiencing a high number of requests for the service you're trying to access. "
351+
"Please wait a moment and try again."
324352
}
325353

326354
@patch("create_app.get_message_orchestrator")
@@ -688,7 +716,35 @@ def test_conversation_azure_byod_returns_500_when_exception_occurs(
688716
# then
689717
assert response.status_code == 500
690718
assert response.json == {
691-
"error": "Exception in /api/conversation. See log for more details."
719+
"error": "An error occurred. Please try again. If the problem persists, please contact the site administrator."
720+
}
721+
722+
@patch("create_app.conversation_with_data")
723+
def test_conversation_azure_byod_returns_429_on_rate_limit_error(
724+
self, conversation_with_data_mock, env_helper_mock, client
725+
):
726+
"""Test that a 429 response is returned on RateLimitError for BYOD conversation."""
727+
# given
728+
response_mock = Mock()
729+
response_mock.status_code = 429
730+
body_mock = {"error": "Rate limit exceeded"}
731+
732+
rate_limit_error = RateLimitError("Rate limit exceeded", response=response_mock, body=body_mock)
733+
conversation_with_data_mock.side_effect = rate_limit_error
734+
env_helper_mock.CONVERSATION_FLOW = ConversationFlow.BYOD.value
735+
736+
# when
737+
response = client.post(
738+
"/api/conversation",
739+
headers={"content-type": "application/json"},
740+
json=self.body,
741+
)
742+
743+
# then
744+
assert response.status_code == 429
745+
assert response.json == {
746+
"error": "We're currently experiencing a high number of requests for the service you're trying to access. "
747+
"Please wait a moment and try again."
692748
}
693749

694750
@patch("create_app.AzureOpenAI")

0 commit comments

Comments
 (0)