From 81a5f011e97bc7f0e4473e41418959b06fd5b558 Mon Sep 17 00:00:00 2001 From: Harish Mohan Raj Date: Mon, 26 Aug 2024 18:04:15 +0530 Subject: [PATCH 1/4] Add title to the base url field --- fastagency/studio/models/llms/anthropic.py | 6 +++--- fastagency/studio/models/llms/azure.py | 2 +- fastagency/studio/models/llms/openai.py | 6 +++--- fastagency/studio/models/llms/together.py | 6 +++--- tests/studio/models/llms/test_anthropic.py | 2 +- tests/studio/models/llms/test_azure.py | 2 +- tests/studio/models/llms/test_openai.py | 2 +- tests/studio/models/llms/test_together.py | 2 +- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/fastagency/studio/models/llms/anthropic.py b/fastagency/studio/models/llms/anthropic.py index 708b6fcc..22688913 100644 --- a/fastagency/studio/models/llms/anthropic.py +++ b/fastagency/studio/models/llms/anthropic.py @@ -69,9 +69,9 @@ class Anthropic(Model): ), ] - base_url: Annotated[URL, Field(description="The base URL of the Anthropic API")] = ( - URL(url="https://api.anthropic.com/v1") - ) + base_url: Annotated[ + URL, Field(title="Base URL", description="The base URL of the Anthropic API") + ] = URL(url="https://api.anthropic.com/v1") api_type: Annotated[ Literal["anthropic"], diff --git a/fastagency/studio/models/llms/azure.py b/fastagency/studio/models/llms/azure.py index c4adfb8f..9b404919 100644 --- a/fastagency/studio/models/llms/azure.py +++ b/fastagency/studio/models/llms/azure.py @@ -63,7 +63,7 @@ class AzureOAI(Model): ] base_url: Annotated[ - URL, Field(description="The base URL of the Azure OpenAI API") + URL, Field(title="Base URL", description="The base URL of the Azure OpenAI API") ] = UrlModel(url="https://{your-resource-name}.openai.azure.com").url api_type: Annotated[ diff --git a/fastagency/studio/models/llms/openai.py b/fastagency/studio/models/llms/openai.py index 94f9e733..83462078 100644 --- a/fastagency/studio/models/llms/openai.py +++ b/fastagency/studio/models/llms/openai.py @@ -82,9 +82,9 @@ class OpenAI(Model): ), ] - base_url: Annotated[URL, Field(description="The base URL of the OpenAI API")] = URL( - url="https://api.openai.com/v1" - ) + base_url: Annotated[ + URL, Field(title="Base URL", description="The base URL of the OpenAI API") + ] = URL(url="https://api.openai.com/v1") api_type: Annotated[ Literal["openai"], diff --git a/fastagency/studio/models/llms/together.py b/fastagency/studio/models/llms/together.py index 602f9f46..ed90390d 100644 --- a/fastagency/studio/models/llms/together.py +++ b/fastagency/studio/models/llms/together.py @@ -150,9 +150,9 @@ class TogetherAI(Model): Field(title="API Key", description="The API Key from Together.ai"), ] - base_url: Annotated[URL, Field(description="The base URL of the OpenAI API")] = URL( - url="https://api.together.xyz/v1" - ) + base_url: Annotated[ + URL, Field(title="Base URL", description="The base URL of the OpenAI API") + ] = URL(url="https://api.together.xyz/v1") api_type: Annotated[ Literal["togetherai"], diff --git a/tests/studio/models/llms/test_anthropic.py b/tests/studio/models/llms/test_anthropic.py index 796481f5..6e987db8 100644 --- a/tests/studio/models/llms/test_anthropic.py +++ b/tests/studio/models/llms/test_anthropic.py @@ -113,7 +113,7 @@ def test_anthropic_model_schema(self) -> None: "format": "uri", "maxLength": 2083, "minLength": 1, - "title": "Base Url", + "title": "Base URL", "type": "string", }, "api_type": { diff --git a/tests/studio/models/llms/test_azure.py b/tests/studio/models/llms/test_azure.py index f2f2aaa2..2efc71ab 100644 --- a/tests/studio/models/llms/test_azure.py +++ b/tests/studio/models/llms/test_azure.py @@ -103,7 +103,7 @@ def test_azure_model_schema(self) -> None: "format": "uri", "maxLength": 2083, "minLength": 1, - "title": "Base Url", + "title": "Base URL", "type": "string", }, "api_type": { diff --git a/tests/studio/models/llms/test_openai.py b/tests/studio/models/llms/test_openai.py index 2d824487..007d75fb 100644 --- a/tests/studio/models/llms/test_openai.py +++ b/tests/studio/models/llms/test_openai.py @@ -152,7 +152,7 @@ def test_openai_schema(self) -> None: "format": "uri", "maxLength": 2083, "minLength": 1, - "title": "Base Url", + "title": "Base URL", "type": "string", }, "api_type": { diff --git a/tests/studio/models/llms/test_together.py b/tests/studio/models/llms/test_together.py index d4ade1a1..7aab8ebe 100644 --- a/tests/studio/models/llms/test_together.py +++ b/tests/studio/models/llms/test_together.py @@ -143,7 +143,7 @@ def test_togetherai_schema(self) -> None: "format": "uri", "maxLength": 2083, "minLength": 1, - "title": "Base Url", + "title": "Base URL", "type": "string", }, "api_type": { From 1485c700dbd463a2b1724b2b9a16beb033774ac4 Mon Sep 17 00:00:00 2001 From: Harish Mohan Raj Date: Tue, 27 Aug 2024 14:44:21 +0530 Subject: [PATCH 2/4] Update OpenAI API key validation to support new API key format --- fastagency/studio/models/llms/openai.py | 5 ++++- tests/studio/models/llms/test_openai.py | 22 ++++++++++++++++------ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/fastagency/studio/models/llms/openai.py b/fastagency/studio/models/llms/openai.py index 83462078..adfb1912 100644 --- a/fastagency/studio/models/llms/openai.py +++ b/fastagency/studio/models/llms/openai.py @@ -55,7 +55,10 @@ async def create_autogen(cls, model_id: UUID, user_id: UUID, **kwargs: Any) -> s @field_validator("api_key") @classmethod def validate_api_key(cls: Type["OpenAIAPIKey"], value: Any) -> Any: - if not re.match(r"^sk-[a-zA-Z0-9]{20}T3BlbkFJ[a-zA-Z0-9]{20}$", value): + if not re.match( + r"^(sk-(proj-|None-|svcacct-)[A-Za-z0-9_-]+|sk-[a-zA-Z0-9]{20}T3BlbkFJ[a-zA-Z0-9]{20})$", + value, + ): raise ValueError("Invalid OpenAI API Key") return value diff --git a/tests/studio/models/llms/test_openai.py b/tests/studio/models/llms/test_openai.py index 007d75fb..1c818951 100644 --- a/tests/studio/models/llms/test_openai.py +++ b/tests/studio/models/llms/test_openai.py @@ -19,15 +19,25 @@ def test_import(monkeypatch: pytest.MonkeyPatch) -> None: class TestOpenAIAPIKey: - def test_constructor_success(self) -> None: + @pytest.mark.parametrize( + "openai_api_key", + [ + "sk-sUeBP9asw6GiYHXqtg70T3BlbkFJJuLwJFco90bOpU0Ntest", # pragma: allowlist secret + # OpenAI currently supports three prefixes for API keys: + # project-based API key format + "sk-proj-SomeLengthStringWhichCanHave-and_inItAndTheLengthCanAlsoBeChangedFrequently", # pragma: allowlist secret + # user-level API key format + "sk-None-SomeLengthStringWhichCanHave-and_inIt", # pragma: allowlist secret + # service account APi key format + "sk-svcacct-SomeLengthStringWhichCanHave-and_inIt", # pragma: allowlist secret + ], + ) + def test_constructor_success(self, openai_api_key: str) -> None: api_key = OpenAIAPIKey( - api_key="sk-sUeBP9asw6GiYHXqtg70T3BlbkFJJuLwJFco90bOpU0Ntest", # pragma: allowlist secret + api_key=openai_api_key, name="Hello World!", ) # pragma: allowlist secret - assert ( - api_key.api_key - == "sk-sUeBP9asw6GiYHXqtg70T3BlbkFJJuLwJFco90bOpU0Ntest" # pragma: allowlist secret - ) # pragma: allowlist secret + assert api_key.api_key == openai_api_key # pragma: allowlist secret def test_constructor_failure(self) -> None: with pytest.raises(ValueError, match="Invalid OpenAI API Key"): From fa894f5236efab560dd0dc17e08d5ac7c58a97e8 Mon Sep 17 00:00:00 2001 From: Harish Mohan Raj Date: Tue, 27 Aug 2024 14:50:00 +0530 Subject: [PATCH 3/4] Polishing --- tests/studio/models/llms/test_openai.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/studio/models/llms/test_openai.py b/tests/studio/models/llms/test_openai.py index 1c818951..c018bb6d 100644 --- a/tests/studio/models/llms/test_openai.py +++ b/tests/studio/models/llms/test_openai.py @@ -25,11 +25,11 @@ class TestOpenAIAPIKey: "sk-sUeBP9asw6GiYHXqtg70T3BlbkFJJuLwJFco90bOpU0Ntest", # pragma: allowlist secret # OpenAI currently supports three prefixes for API keys: # project-based API key format - "sk-proj-SomeLengthStringWhichCanHave-and_inItAndTheLengthCanAlsoBeChangedFrequently", # pragma: allowlist secret + "sk-proj-SomeLengthStringWhichCanHave-and_inItAndTheLengthCanBeChangedAtAnyTime", # pragma: allowlist secret # user-level API key format - "sk-None-SomeLengthStringWhichCanHave-and_inIt", # pragma: allowlist secret + "sk-None-SomeLengthStringWhichCanHave-and_inItAndTheLengthCanBeChangedAtAnyTime", # pragma: allowlist secret # service account APi key format - "sk-svcacct-SomeLengthStringWhichCanHave-and_inIt", # pragma: allowlist secret + "sk-svcacct-SomeLengthStringWhichCanHave-and_inItAndTheLengthCanBeChangedAtAnyTime", # pragma: allowlist secret ], ) def test_constructor_success(self, openai_api_key: str) -> None: From 1bee9a43ebe1464e0b8543c5fc7a5443e0186228 Mon Sep 17 00:00:00 2001 From: Harish Mohan Raj Date: Tue, 27 Aug 2024 18:11:35 +0530 Subject: [PATCH 4/4] Fix secret update logic --- fastagency/studio/app.py | 4 +++- tests/studio/app/test_openai_extensively.py | 21 ++++++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/fastagency/studio/app.py b/fastagency/studio/app.py index 9c88d6ad..4539c8d9 100644 --- a/fastagency/studio/app.py +++ b/fastagency/studio/app.py @@ -102,8 +102,10 @@ async def validate_secret_model( type: str = "secret" found_model = await DefaultDB.backend().find_model(model_uuid=model_uuid) - if "api_key" in found_model["json_str"]: + + if "api_key" not in model: model["api_key"] = found_model["json_str"]["api_key"] + try: validated_model = Registry.get_default().validate(type, name, model) return validated_model.model_dump() diff --git a/tests/studio/app/test_openai_extensively.py b/tests/studio/app/test_openai_extensively.py index 943235b8..1ef56270 100644 --- a/tests/studio/app/test_openai_extensively.py +++ b/tests/studio/app/test_openai_extensively.py @@ -68,14 +68,29 @@ async def test_validate_secret_model( background_tasks=BackgroundTasks(), ) - # Remove api_key and send name alone to validate route - model_dict.pop("api_key") + # Pass only name in the request, this should only update the name and retain the existing api_key + model_dict_with_updated_name = {"name": "Hello World! Updated"} response = client.post( f"/user/{user_uuid}/models/secret/OpenAIAPIKey/{api_key_model_uuid}/validate", - json=model_dict, + json=model_dict_with_updated_name, + ) + assert response.status_code == 200 + + expected = {"name": "Hello World! Updated", "api_key": model_dict["api_key"]} + assert response.json() == expected + + # Pass both name and api_key in the request, this should update both name and api_key + model_dict_with_updated_name_and_api_key = { + "name": "Hello World! Updated Again", + "api_key": "sk-proj-SomeLengthStringWhichCanHave-and_inItAndTheLengthCanBeChangedAtAnyTime", # pragma: allowlist secret + } + response = client.post( + f"/user/{user_uuid}/models/secret/OpenAIAPIKey/{api_key_model_uuid}/validate", + json=model_dict_with_updated_name_and_api_key, ) assert response.status_code == 200 + assert response.json() == model_dict_with_updated_name_and_api_key # we will do this for OpenAI only, the rest should be the same