diff --git a/.changes/branch-badge-dock-advertisement.json b/.changes/branch-badge-dock-advertisement.json new file mode 100644 index 00000000..49e60811 --- /dev/null +++ b/.changes/branch-badge-dock-advertisement.json @@ -0,0 +1 @@ +{"type":"MAJOR","changes":["Make \"user\" the default role in all requests"]} diff --git a/generativeai/src/main/java/com/google/ai/client/generativeai/Chat.kt b/generativeai/src/main/java/com/google/ai/client/generativeai/Chat.kt index 2abe4b72..b536c244 100644 --- a/generativeai/src/main/java/com/google/ai/client/generativeai/Chat.kt +++ b/generativeai/src/main/java/com/google/ai/client/generativeai/Chat.kt @@ -73,7 +73,7 @@ class Chat(private val model: GenerativeModel, val history: MutableList * @throws InvalidStateException if the [Chat] instance has an active request. */ suspend fun sendMessage(prompt: String): GenerateContentResponse { - val content = content("user") { text(prompt) } + val content = content { text(prompt) } return sendMessage(content) } @@ -84,7 +84,7 @@ class Chat(private val model: GenerativeModel, val history: MutableList * @throws InvalidStateException if the [Chat] instance has an active request. */ suspend fun sendMessage(prompt: Bitmap): GenerateContentResponse { - val content = content("user") { image(prompt) } + val content = content { image(prompt) } return sendMessage(content) } @@ -150,7 +150,7 @@ class Chat(private val model: GenerativeModel, val history: MutableList * @throws InvalidStateException if the [Chat] instance has an active request. */ fun sendMessageStream(prompt: String): Flow { - val content = content("user") { text(prompt) } + val content = content { text(prompt) } return sendMessageStream(content) } @@ -162,7 +162,7 @@ class Chat(private val model: GenerativeModel, val history: MutableList * @throws InvalidStateException if the [Chat] instance has an active request. */ fun sendMessageStream(prompt: Bitmap): Flow { - val content = content("user") { image(prompt) } + val content = content { image(prompt) } return sendMessageStream(content) } 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 8358c8e6..fca67012 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,7 +19,9 @@ 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.RequestOptions +import com.google.ai.client.generativeai.type.InvalidAPIKeyException 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 @@ -176,7 +178,13 @@ private suspend fun validateResponse(response: HttpResponse) { } catch (e: Throwable) { "Unexpected Response:\n$text" } - + if (message.contains("API key not valid")) { + throw InvalidAPIKeyException(message) + } + // 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/internal/api/shared/Types.kt b/generativeai/src/main/java/com/google/ai/client/generativeai/internal/api/shared/Types.kt index 3f15f807..8f7e2242 100644 --- a/generativeai/src/main/java/com/google/ai/client/generativeai/internal/api/shared/Types.kt +++ b/generativeai/src/main/java/com/google/ai/client/generativeai/internal/api/shared/Types.kt @@ -40,7 +40,7 @@ internal enum class HarmCategory { typealias Base64 = String -@Serializable internal data class Content(val role: String? = null, val parts: List) +@Serializable internal data class Content(val role: String? = "user", val parts: List) @Serializable(PartSerializer::class) internal sealed interface Part 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..5b451f01 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 @@ -55,6 +55,10 @@ class SerializationException(message: String, cause: Throwable? = null) : class ServerException(message: String, cause: Throwable? = null) : GoogleGenerativeAIException(message, cause) +/** The server responded that the API Key is no valid. */ +class InvalidAPIKeyException(message: String, cause: Throwable? = null) : + GoogleGenerativeAIException(message, cause) + /** * A request was blocked for some reason. * @@ -68,6 +72,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/StreamingSnapshotTests.kt b/generativeai/src/test/java/com/google/ai/client/generativeai/StreamingSnapshotTests.kt index ca5082a4..7a38a398 100644 --- a/generativeai/src/test/java/com/google/ai/client/generativeai/StreamingSnapshotTests.kt +++ b/generativeai/src/test/java/com/google/ai/client/generativeai/StreamingSnapshotTests.kt @@ -19,6 +19,7 @@ package com.google.ai.client.generativeai import com.google.ai.client.generativeai.type.BlockReason import com.google.ai.client.generativeai.type.FinishReason import com.google.ai.client.generativeai.type.HarmCategory +import com.google.ai.client.generativeai.type.InvalidAPIKeyException import com.google.ai.client.generativeai.type.PromptBlockedException import com.google.ai.client.generativeai.type.ResponseStoppedException import com.google.ai.client.generativeai.type.SerializationException @@ -173,6 +174,6 @@ internal class StreamingSnapshotTests { goldenStreamingFile("failure-api-key.txt", HttpStatusCode.BadRequest) { val responses = model.generateContentStream() - withTimeout(testTimeout) { shouldThrow { responses.collect() } } + withTimeout(testTimeout) { shouldThrow { responses.collect() } } } } 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..44c9b425 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 @@ -19,10 +19,12 @@ package com.google.ai.client.generativeai import com.google.ai.client.generativeai.type.BlockReason import com.google.ai.client.generativeai.type.FinishReason import com.google.ai.client.generativeai.type.HarmCategory +import com.google.ai.client.generativeai.type.InvalidAPIKeyException 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 +96,14 @@ 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") { @@ -129,7 +139,7 @@ internal class UnarySnapshotTests { @Test fun `invalid api key`() = goldenUnaryFile("failure-api-key.json", HttpStatusCode.BadRequest) { - withTimeout(testTimeout) { shouldThrow { model.generateContent() } } + withTimeout(testTimeout) { shouldThrow { model.generateContent() } } } @Test 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.\" }" + } + ] + } +}