diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ApiVersion.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ApiVersion.kt new file mode 100644 index 00000000000..c8b11fd4451 --- /dev/null +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ApiVersion.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.vertexai.type + +/** Versions of the Vertex AI in Firebase server API. */ +public class ApiVersion private constructor(internal val value: String) { + public companion object { + /** The stable channel for version 1 of the API. */ + @JvmField public val V1: ApiVersion = ApiVersion("v1") + + /** The beta channel for version 1 of the API. */ + @JvmField public val V1BETA: ApiVersion = ApiVersion("v1beta") + } +} diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/RequestOptions.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/RequestOptions.kt index 9aa648b6d07..e2d2ff36b36 100644 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/RequestOptions.kt +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/RequestOptions.kt @@ -17,7 +17,6 @@ package com.google.firebase.vertexai.type import kotlin.time.Duration -import kotlin.time.Duration.Companion.seconds import kotlin.time.DurationUnit import kotlin.time.toDuration @@ -25,8 +24,8 @@ import kotlin.time.toDuration public class RequestOptions internal constructor( internal val timeout: Duration, - internal val endpoint: String = "https://firebasevertexai.googleapis.com", - internal val apiVersion: String = "v1beta", + internal val endpoint: String = DEFAULT_ENDPOINT, + internal val apiVersion: String = DEFAULT_API_VERSION.value, ) { /** @@ -34,9 +33,35 @@ internal constructor( * * @param timeoutInMillis the maximum amount of time, in milliseconds, for a request to take, from * the first request to first response. + * @param apiVersion the version of the Vertex AI in Firebase API; defaults to [ApiVersion.V1BETA] + * if not specified. */ @JvmOverloads public constructor( - timeoutInMillis: Long = 180.seconds.inWholeMilliseconds - ) : this(timeout = timeoutInMillis.toDuration(DurationUnit.MILLISECONDS)) + timeoutInMillis: Long = DEFAULT_TIMEOUT_IN_MILLIS, + apiVersion: ApiVersion = DEFAULT_API_VERSION, + ) : this( + timeout = timeoutInMillis.toDuration(DurationUnit.MILLISECONDS), + apiVersion = apiVersion.value, + ) + + /** + * Constructor for RequestOptions. + * + * @param apiVersion the version of the Vertex AI in Firebase API; defaults to [ApiVersion.V1BETA] + * if not specified. + */ + public constructor( + apiVersion: ApiVersion + ) : this( + timeoutInMillis = DEFAULT_TIMEOUT_IN_MILLIS, + apiVersion = apiVersion, + ) + + internal companion object { + internal const val DEFAULT_TIMEOUT_IN_MILLIS: Long = 180_000L + internal const val DEFAULT_ENDPOINT: String = "https://firebasevertexai.googleapis.com" + + internal val DEFAULT_API_VERSION: ApiVersion = ApiVersion.V1BETA + } } diff --git a/firebase-vertexai/src/test/java/com/google/firebase/vertexai/GenerativeModelTesting.kt b/firebase-vertexai/src/test/java/com/google/firebase/vertexai/GenerativeModelTesting.kt index 8b668371a31..f20ab347c3a 100644 --- a/firebase-vertexai/src/test/java/com/google/firebase/vertexai/GenerativeModelTesting.kt +++ b/firebase-vertexai/src/test/java/com/google/firebase/vertexai/GenerativeModelTesting.kt @@ -60,7 +60,7 @@ internal class GenerativeModelTesting { APIController( "super_cool_test_key", "gemini-1.5-flash", - RequestOptions(timeout = 5.seconds, endpoint = "https://my.custom.endpoint"), + RequestOptions(5.seconds.inWholeMilliseconds), mockEngine, TEST_CLIENT_ID, null, diff --git a/firebase-vertexai/src/test/java/com/google/firebase/vertexai/api/java/JavaPublicApiTests.java b/firebase-vertexai/src/test/java/com/google/firebase/vertexai/api/java/JavaPublicApiTests.java new file mode 100644 index 00000000000..cf271c42905 --- /dev/null +++ b/firebase-vertexai/src/test/java/com/google/firebase/vertexai/api/java/JavaPublicApiTests.java @@ -0,0 +1,31 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.vertexai.api.java; + +import com.google.firebase.vertexai.type.ApiVersion; +import com.google.firebase.vertexai.type.RequestOptions; + +/** Build tests for the Vertex AI in Firebase Java public API surface. */ +final class JavaPublicApiTests { + /** {@link RequestOptions} API */ + void requestOptionsCodeSamples() { + new RequestOptions(); + new RequestOptions(30_000L); + new RequestOptions(ApiVersion.V1); + new RequestOptions(60_000L, ApiVersion.V1BETA); + } +} diff --git a/firebase-vertexai/src/test/java/com/google/firebase/vertexai/common/APIControllerTests.kt b/firebase-vertexai/src/test/java/com/google/firebase/vertexai/common/APIControllerTests.kt index 8937b13569b..ffc3e352d3d 100644 --- a/firebase-vertexai/src/test/java/com/google/firebase/vertexai/common/APIControllerTests.kt +++ b/firebase-vertexai/src/test/java/com/google/firebase/vertexai/common/APIControllerTests.kt @@ -26,11 +26,14 @@ import com.google.firebase.vertexai.common.util.commonTest import com.google.firebase.vertexai.common.util.createResponses import com.google.firebase.vertexai.common.util.doBlocking import com.google.firebase.vertexai.common.util.prepareStreamingResponse +import com.google.firebase.vertexai.type.ApiVersion import com.google.firebase.vertexai.type.RequestOptions import io.kotest.assertions.json.shouldContainJsonKey import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe import io.kotest.matchers.string.shouldContain +import io.kotest.matchers.string.shouldStartWith import io.ktor.client.engine.mock.MockEngine import io.ktor.client.engine.mock.respond import io.ktor.content.TextContent @@ -74,7 +77,7 @@ internal class APIControllerTests { @Test fun `(generateContent) respects a custom timeout`() = - commonTest(requestOptions = RequestOptions(2.seconds)) { + commonTest(requestOptions = RequestOptions(2.seconds.inWholeMilliseconds)) { shouldThrow { withTimeout(testTimeout) { apiController.generateContent(textGenerateContentRequest("test")) @@ -122,7 +125,11 @@ internal class RequestFormatTests { APIController( "super_cool_test_key", "gemini-pro-1.5", - RequestOptions(timeout = 5.seconds, endpoint = "https://my.custom.endpoint"), + RequestOptions( + timeout = 5.seconds, + endpoint = "https://my.custom.endpoint", + apiVersion = "v1beta" + ), mockEngine, TEST_CLIENT_ID, null, @@ -138,6 +145,36 @@ internal class RequestFormatTests { mockEngine.requestHistory.first().url.host shouldBe "my.custom.endpoint" } + @Test + fun `using custom API version`() = doBlocking { + val channel = ByteChannel(autoFlush = true) + val mockEngine = MockEngine { + respond(channel, HttpStatusCode.OK, headersOf(HttpHeaders.ContentType, "application/json")) + } + prepareStreamingResponse(createResponses("Random")).forEach { channel.writeFully(it) } + val controller = + APIController( + "super_cool_test_key", + "gemini-pro-1.5", + RequestOptions(timeoutInMillis = 5.seconds.inWholeMilliseconds, apiVersion = ApiVersion.V1), + mockEngine, + TEST_CLIENT_ID, + null, + ) + + withTimeout(5.seconds) { + controller.generateContentStream(textGenerateContentRequest("cats")).collect { + it.candidates?.isEmpty() shouldBe false + channel.close() + } + } + + mockEngine.requestHistory.first().url.encodedPath shouldStartWith "/${ApiVersion.V1.value}" + // TODO: Update test to set ApiVersion.V1BETA when ApiVersion.V1 becomes the default and delete + // the following check. + RequestOptions().apiVersion shouldNotBe ApiVersion.V1.value + } + @Test fun `client id header is set correctly in the request`() = doBlocking { val response = JSON.encodeToString(CountTokensResponse(totalTokens = 10)) diff --git a/firebase-vertexai/src/test/java/com/google/firebase/vertexai/type/RequestOptionsTests.kt b/firebase-vertexai/src/test/java/com/google/firebase/vertexai/type/RequestOptionsTests.kt new file mode 100644 index 00000000000..6480703b150 --- /dev/null +++ b/firebase-vertexai/src/test/java/com/google/firebase/vertexai/type/RequestOptionsTests.kt @@ -0,0 +1,86 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.vertexai.type + +import com.google.firebase.vertexai.type.RequestOptions.Companion.DEFAULT_API_VERSION +import com.google.firebase.vertexai.type.RequestOptions.Companion.DEFAULT_ENDPOINT +import com.google.firebase.vertexai.type.RequestOptions.Companion.DEFAULT_TIMEOUT_IN_MILLIS +import io.kotest.matchers.equals.shouldBeEqual +import kotlin.time.DurationUnit +import kotlin.time.toDuration +import org.junit.Test + +internal class RequestOptionsTests { + private val defaultTimeout = DEFAULT_TIMEOUT_IN_MILLIS.toDuration(DurationUnit.MILLISECONDS) + + @Test + fun `init default values`() { + val requestOptions = RequestOptions() + + requestOptions.timeout shouldBeEqual defaultTimeout + requestOptions.endpoint shouldBeEqual DEFAULT_ENDPOINT + requestOptions.apiVersion shouldBeEqual DEFAULT_API_VERSION.value + } + + @Test + fun `init custom timeout`() { + val expectedTimeoutInMillis = 60_000L + + val requestOptions = RequestOptions(timeoutInMillis = expectedTimeoutInMillis) + + requestOptions.timeout shouldBeEqual + expectedTimeoutInMillis.toDuration(DurationUnit.MILLISECONDS) + requestOptions.endpoint shouldBeEqual DEFAULT_ENDPOINT + requestOptions.apiVersion shouldBeEqual DEFAULT_API_VERSION.value + } + + @Test + fun `init API version v1`() { + val expectedApiVersion = ApiVersion.V1 + + val requestOptions = RequestOptions(apiVersion = expectedApiVersion) + + requestOptions.timeout shouldBeEqual defaultTimeout + requestOptions.endpoint shouldBeEqual DEFAULT_ENDPOINT + requestOptions.apiVersion shouldBeEqual expectedApiVersion.value + } + + @Test + fun `init API version v1beta`() { + val expectedApiVersion = ApiVersion.V1BETA + + val requestOptions = RequestOptions(apiVersion = expectedApiVersion) + + requestOptions.timeout shouldBeEqual defaultTimeout + requestOptions.endpoint shouldBeEqual DEFAULT_ENDPOINT + requestOptions.apiVersion shouldBeEqual expectedApiVersion.value + } + + @Test + fun `init all public options`() { + val expectedTimeoutInMillis = 30_000L + val expectedApiVersion = ApiVersion.V1BETA + + val requestOptions = + RequestOptions(timeoutInMillis = expectedTimeoutInMillis, apiVersion = expectedApiVersion) + + requestOptions.timeout shouldBeEqual + expectedTimeoutInMillis.toDuration(DurationUnit.MILLISECONDS) + requestOptions.endpoint shouldBeEqual DEFAULT_ENDPOINT + requestOptions.apiVersion shouldBeEqual expectedApiVersion.value + } +} diff --git a/firebase-vertexai/update_responses.sh b/firebase-vertexai/update_responses.sh index b13b94c8229..cb01e1a2c40 100755 --- a/firebase-vertexai/update_responses.sh +++ b/firebase-vertexai/update_responses.sh @@ -17,7 +17,7 @@ # This script replaces mock response files for Vertex AI unit tests with a fresh # clone of the shared repository of Vertex AI test data. -RESPONSES_VERSION='v3.*' # The major version of mock responses to use +RESPONSES_VERSION='v5.*' # The major version of mock responses to use REPO_NAME="vertexai-sdk-test-data" REPO_LINK="https://github.com/FirebaseExtended/$REPO_NAME.git"