Skip to content

Commit

Permalink
OpenAI request NotFoundError response adds additional context to in…
Browse files Browse the repository at this point in the history
…dicate potential lack of funds (#784)

When an OpenAI token is used that either does not have billing enabled
or does not have enough credits to call the default model, their API
will raise an exceptionof type `NotFoundError`.

When this happens, we will catch the errors of this type and modify the
message to include additional context that indicates that the issue may
be related to insufficient credits. We will then re-raise an
`APUException`.

Fixes issue #709

---------

Co-authored-by: Luke Lalor <lukehlalor@gmail.com>
  • Loading branch information
coryfoo and LukeLalor committed Sep 23, 2024
1 parent 45ca211 commit 2823cdf
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 2 deletions.
24 changes: 22 additions & 2 deletions sdk/eidolon_ai_sdk/apu/llm/open_ai_connection_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
from typing import Optional, cast, List

from azure.identity import get_bearer_token_provider, EnvironmentCredential
from openai import AsyncOpenAI, AsyncStream
from openai import AsyncOpenAI, AsyncStream, NotFoundError
from openai.lib.azure import AsyncAzureOpenAI
from openai.types import ImagesResponse
from openai.types.chat import ChatCompletionChunk, ChatCompletion
from pydantic import BaseModel, Field

from eidolon_ai_sdk.apu.apu import APUException
from eidolon_ai_sdk.system.reference_model import Specable, Reference
from eidolon_ai_sdk.util.replay import replayable

Expand All @@ -22,7 +23,7 @@ def makeClient(self) -> AsyncOpenAI:

async def completion(self, **kwargs) -> ChatCompletion | AsyncStream[ChatCompletionChunk]:
return await replayable(
fn=lambda **_kwargs: self.makeClient().chat.completions.create(**_kwargs),
fn=lambda **_kwargs: self._make_request(**_kwargs),
parser=_replay_parser,
name_override="openai_completion",
)(**kwargs)
Expand All @@ -31,6 +32,25 @@ async def generate_image(self, **kwargs) -> ImagesResponse:
# todo, image generation should be repayable, but needs custom parser
return await self.makeClient().images.generate(**kwargs)

async def _make_request(self, **kwargs):
try:
return await self.makeClient().chat.completions.create(**kwargs)
except NotFoundError as e:
# The user likely does not have access to the requested named model or the model name is invalid.
# A typical response looks like this:
# {
# "error": {
# "message": "The model `gpt-4-turbo` does not exist or you do not have access to it.",
# "type": "invalid_request_error",
# "code": "model_not_found"
# }
# }
message = (
f"{e.message}\n"
"OpenAI accounts that do not have sufficient credits may not have access to some models."
)
raise APUException(description=message)


def get_default_token_provider():
if os.environ.get("AZURE_CLIENT_ID") and os.environ.get("AZURE_CLIENT_SECRET") and os.environ.get("AZURE_TENANT_ID"):
Expand Down
Empty file added sdk/tests/apu/__init__.py
Empty file.
Empty file added sdk/tests/apu/llm/__init__.py
Empty file.
42 changes: 42 additions & 0 deletions sdk/tests/apu/llm/test_open_ai_connection_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import pytest
from pytest import fail
from httpx import Response, Request
from openai import AsyncOpenAI, NotFoundError

from eidolon_ai_sdk.apu.apu import APUException
from eidolon_ai_sdk.apu.llm.open_ai_connection_handler import OpenAIConnectionHandler


class TestOpenAIConnectionHandler(OpenAIConnectionHandler):
def makeClient(self) -> AsyncOpenAI:
raise NotFoundError(
self.connection_response_message(),
response=Response(
404,
request=Request("POST", "https://example.com")
),
body=None,
)

def connection_response_message(self):
return "The model `gpt-4-turbo` does not exist or you do not have access to it."


@pytest.fixture(scope="module")
async def connection():
# noinspection PyTypeChecker
return TestOpenAIConnectionHandler(spec=None)


async def test_not_found_response(connection, **kwargs):
"""
Asserts that when a NotFoundException is thrown by the OpenAI client then an appropriate message is returned
to the user as part of an APUException
"""
try:
await connection.completion(**kwargs)
fail("Expected to raise APUException")
except APUException as e:
message = str(e)
assert connection.connection_response_message() in message
assert "OpenAI accounts that do not have sufficient credits may not have access to some models." in message

0 comments on commit 2823cdf

Please sign in to comment.