From 3495f9694c1b68d462ed3aeaa6601eaba5e19077 Mon Sep 17 00:00:00 2001 From: Rodrigo Lazo Paz Date: Wed, 14 Feb 2024 12:49:47 -0500 Subject: [PATCH 1/2] Add `UnsupportedUserLocationException` for better error handling Instead of relying on the generic message to notify users that they are trying to access the API from an unsupported location, this PR adds an specific new exception that app developers can use. Although the underlying mechanism is still string matching, abstracting this into the SDK rather than forcing every developer to do it on their own is still a win. --- .../generativeai/internal/api/APIController.kt | 5 +++++ .../ai/client/generativeai/type/Exceptions.kt | 10 ++++++++++ .../ai/client/generativeai/UnarySnapshotTests.kt | 7 +++++++ .../unary/failure-unsupported-user-location.json | 13 +++++++++++++ 4 files changed, 35 insertions(+) create mode 100644 generativeai/src/test/resources/golden-files/unary/failure-unsupported-user-location.json diff --git a/generativeai/src/main/java/com/google/ai/client/generativeai/internal/api/APIController.kt b/generativeai/src/main/java/com/google/ai/client/generativeai/internal/api/APIController.kt index fd750111..9a7b5474 100644 --- a/generativeai/src/main/java/com/google/ai/client/generativeai/internal/api/APIController.kt +++ b/generativeai/src/main/java/com/google/ai/client/generativeai/internal/api/APIController.kt @@ -19,6 +19,7 @@ package com.google.ai.client.generativeai.internal.api import com.google.ai.client.generativeai.BuildConfig import com.google.ai.client.generativeai.internal.util.decodeToFlow import com.google.ai.client.generativeai.type.ServerException +import com.google.ai.client.generativeai.type.UnsupportedUserLocationException import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.engine.HttpClientEngine @@ -174,6 +175,10 @@ private suspend fun validateResponse(response: HttpResponse) { "Unexpected Response:\n$text" } + // TODO (b/325117891): Use a better method than string matching. + if (message == "User location is not supported for the API use.") { + throw UnsupportedUserLocationException() + } throw ServerException(message) } } diff --git a/generativeai/src/main/java/com/google/ai/client/generativeai/type/Exceptions.kt b/generativeai/src/main/java/com/google/ai/client/generativeai/type/Exceptions.kt index 9171da3c..c08285e1 100644 --- a/generativeai/src/main/java/com/google/ai/client/generativeai/type/Exceptions.kt +++ b/generativeai/src/main/java/com/google/ai/client/generativeai/type/Exceptions.kt @@ -68,6 +68,16 @@ class PromptBlockedException(val response: GenerateContentResponse, cause: Throw cause ) +/** + * The user's location (region) is not supported by the API. + * + * See the Google documentation for a + * [list of regions](https://ai.google.dev/available_regions#available_regions) (countries and + * territories) where the API is available. + */ +class UnsupportedUserLocationException(cause: Throwable? = null) : + GoogleGenerativeAIException("User location is not supported for the API use.", cause) + /** * Some form of state occurred that shouldn't have. * diff --git a/generativeai/src/test/java/com/google/ai/client/generativeai/UnarySnapshotTests.kt b/generativeai/src/test/java/com/google/ai/client/generativeai/UnarySnapshotTests.kt index f440ec53..fe130e4a 100644 --- a/generativeai/src/test/java/com/google/ai/client/generativeai/UnarySnapshotTests.kt +++ b/generativeai/src/test/java/com/google/ai/client/generativeai/UnarySnapshotTests.kt @@ -23,6 +23,7 @@ import com.google.ai.client.generativeai.type.PromptBlockedException import com.google.ai.client.generativeai.type.ResponseStoppedException import com.google.ai.client.generativeai.type.SerializationException import com.google.ai.client.generativeai.type.ServerException +import com.google.ai.client.generativeai.type.UnsupportedUserLocationException import com.google.ai.client.generativeai.util.goldenUnaryFile import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.should @@ -94,6 +95,12 @@ internal class UnarySnapshotTests { withTimeout(testTimeout) { shouldThrow { model.generateContent() } } } + @Test + fun `user location error`() = + goldenUnaryFile("failure-unsupported-user-location.json", HttpStatusCode.PreconditionFailed) { + withTimeout(testTimeout) { shouldThrow { model.generateContent() } } + } + @Test fun `stopped for safety`() = goldenUnaryFile("failure-finish-reason-safety.json") { diff --git a/generativeai/src/test/resources/golden-files/unary/failure-unsupported-user-location.json b/generativeai/src/test/resources/golden-files/unary/failure-unsupported-user-location.json new file mode 100644 index 00000000..c4c2ace4 --- /dev/null +++ b/generativeai/src/test/resources/golden-files/unary/failure-unsupported-user-location.json @@ -0,0 +1,13 @@ +{ + "error": { + "code": 400, + "message": "User location is not supported for the API use.", + "status": "FAILED_PRECONDITION", + "details": [ + { + "@type": "type.googleapis.com/google.rpc.DebugInfo", + "detail": "[ORIGINAL ERROR] generic::failed_precondition: User location is not supported for the API use. [google.rpc.error_details_ext] { message: \"User location is not supported for the API use.\" }" + } + ] + } +} From 2a1194d059322b6027e088e3c0d523db9908632b Mon Sep 17 00:00:00 2001 From: Rodrigo Lazo Paz Date: Wed, 14 Feb 2024 12:56:41 -0500 Subject: [PATCH 2/2] Fix formatting --- .../com/google/ai/client/generativeai/UnarySnapshotTests.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/generativeai/src/test/java/com/google/ai/client/generativeai/UnarySnapshotTests.kt b/generativeai/src/test/java/com/google/ai/client/generativeai/UnarySnapshotTests.kt index fe130e4a..87305c1b 100644 --- a/generativeai/src/test/java/com/google/ai/client/generativeai/UnarySnapshotTests.kt +++ b/generativeai/src/test/java/com/google/ai/client/generativeai/UnarySnapshotTests.kt @@ -98,7 +98,9 @@ internal class UnarySnapshotTests { @Test fun `user location error`() = goldenUnaryFile("failure-unsupported-user-location.json", HttpStatusCode.PreconditionFailed) { - withTimeout(testTimeout) { shouldThrow { model.generateContent() } } + withTimeout(testTimeout) { + shouldThrow { model.generateContent() } + } } @Test