Skip to content

[Vertex AI] Add apiVersion parameter to RequestOptions #6636

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -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")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,51 @@
package com.google.firebase.vertexai.type

import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlin.time.DurationUnit
import kotlin.time.toDuration

/** Configurable options unique to how requests to the backend are performed. */
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,
) {

/**
* Constructor for RequestOptions.
*
* @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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<RequestTimeoutException> {
withTimeout(testTimeout) {
apiController.generateContent(textGenerateContentRequest("test"))
Expand Down Expand Up @@ -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,
Expand All @@ -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))
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
}
2 changes: 1 addition & 1 deletion firebase-vertexai/update_responses.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
Loading