Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 2 additions & 70 deletions ai-logic/firebase-ai/api.txt

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ public class AIModels {
public var app: FirebaseApp? = null

public val vertexAIFlashModel: GenerativeModel by lazy {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe run a search on the code base for vertexai, vertext_ai, and replace then all with agent platform

getGenerativeModel(GenerativeBackend.vertexAI("global"), "gemini-3.5-flash")
getGenerativeModel(GenerativeBackend.agentPlatform("global"), "gemini-3.5-flash")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

Since the backend has been changed from vertexAI to agentPlatform, consider renaming the variables vertexAIFlashModel, vertexAIFlashLiteModel, and vertexAI3_5FlashModel to use agentPlatform (e.g., agentPlatformFlashModel) to avoid confusion and keep the naming consistent with the actual backend being used.

}
public val vertexAIFlashLiteModel: GenerativeModel by lazy {
getGenerativeModel(GenerativeBackend.vertexAI("global"), "gemini-3.1-flash-lite")
getGenerativeModel(GenerativeBackend.agentPlatform("global"), "gemini-3.1-flash-lite")
}
public val vertexAI3_5FlashModel: GenerativeModel by lazy {
getGenerativeModel(GenerativeBackend.vertexAI("global"), "gemini-3.5-flash")
getGenerativeModel(GenerativeBackend.agentPlatform("global"), "gemini-3.5-flash")
}
public val googleAIFlashModel: GenerativeModel by lazy {
getGenerativeModel(GenerativeBackend.googleAI(), "gemini-3.1-flash-lite")
Expand All @@ -50,7 +50,7 @@ public class AIModels {
getGenerativeModel(GenerativeBackend.googleAI(), "gemini-3.5-flash")
}
public val vertexAITemplateModel: TemplateGenerativeModel by lazy {
FirebaseAI.getInstance(app(), GenerativeBackend.vertexAI()).templateGenerativeModel()
FirebaseAI.getInstance(app(), GenerativeBackend.agentPlatform()).templateGenerativeModel()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

Since the backend has been changed from vertexAI to agentPlatform, consider renaming vertexAITemplateModel to agentPlatformTemplateModel to avoid confusion and keep the naming consistent with the actual backend being used.

}
public val googleAITemplateModel: TemplateGenerativeModel by lazy {
FirebaseAI.getInstance(app(), GenerativeBackend.googleAI()).templateGenerativeModel()
Expand Down Expand Up @@ -82,7 +82,7 @@ public class AIModels {
config: GenerationConfig? = null
): List<GenerativeModel> {
return listOf(
getGenerativeModel(GenerativeBackend.vertexAI("global"), modelName, config),
getGenerativeModel(GenerativeBackend.agentPlatform("global"), modelName, config),
getGenerativeModel(GenerativeBackend.googleAI(), modelName, config),
)
}
Expand Down Expand Up @@ -122,7 +122,7 @@ public class AIModels {
modelName: String? = null,
config: LiveGenerationConfig? = null
): LiveGenerativeModel {
return FirebaseAI.getInstance(app(), GenerativeBackend.vertexAI())
return FirebaseAI.getInstance(app(), GenerativeBackend.agentPlatform())
.liveModel(
modelName = modelName ?: "gemini-live-2.5-flash-native-audio",
generationConfig = config,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ class GenerateContentTests {
)
} catch (e: Exception) {
assertThat(e).isInstanceOf(ServerException::class.java)
assertThat(e.message).contains("the number of enabled_voices must equal 2")
assertThat(e.message).contains("the number of speaker_voice_configs must equal 2")
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class GroundingTests {
@Test
fun groundingTests_canSearchWeather(): Unit = runBlocking {
val model =
FirebaseAI.getInstance(app(), GenerativeBackend.vertexAI("global"))
FirebaseAI.getInstance(app(), GenerativeBackend.agentPlatform("global"))
.generativeModel(
modelName = "gemini-3.5-flash",
tools = listOf(Tool.googleSearch()),
Expand All @@ -79,7 +79,7 @@ class GroundingTests {
@JvmStatic
fun setupModel(config: ToolConfig): GenerativeModel {
val model =
FirebaseAI.getInstance(app(), GenerativeBackend.vertexAI("global"))
FirebaseAI.getInstance(app(), GenerativeBackend.agentPlatform("global"))
.generativeModel(
modelName = "gemini-3.5-flash",
toolConfig = config,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,24 +268,27 @@ class LiveSessionTests {
val session = liveModel.connect(SessionResumptionConfig())
session.send("My favorite color is blue. Remember that.", true)
var lastResumptionUpdate: LiveSessionResumptionUpdate? = null
var handle: String? = null
var gotTurnComplete = false
withTimeout(30.seconds) {
session
.receive()
.takeWhile {
if (it is LiveSessionResumptionUpdate) {
lastResumptionUpdate = it
if (it.newHandle != null) {
handle = it.newHandle
}
}
if (it is LiveServerContent && it.turnComplete) {
gotTurnComplete = true
}
// Stop when we've seen a turn complete and we have a new handle
!(gotTurnComplete && lastResumptionUpdate?.newHandle != null)
!(gotTurnComplete && handle != null)
}
.collect {}
}
lastResumptionUpdate shouldNotBe null
val handle = lastResumptionUpdate?.newHandle
handle.shouldNotBeNull()
session.resumeSession(SessionResumptionConfig(handle))
session.send("What is my favorite color?")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ class ToolTests {
@JvmStatic
fun setupModel(vararg functions: FunctionDeclaration): GenerativeModel {
val model =
FirebaseAI.getInstance(app(), GenerativeBackend.vertexAI("global"))
FirebaseAI.getInstance(app(), GenerativeBackend.agentPlatform("global"))
.generativeModel(
modelName = "gemini-3.5-flash",
toolConfig =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ internal constructor(
): GenerativeModel {
val modelUri =
when (backend.backend) {
GenerativeBackendEnum.VERTEX_AI,
GenerativeBackendEnum.AGENT_PLATFORM ->
"projects/${firebaseApp.options.projectId}/locations/${backend.location}/publishers/google/models/${modelName}"
GenerativeBackendEnum.GOOGLE_AI ->
Expand Down Expand Up @@ -212,7 +211,6 @@ internal constructor(
}
return LiveGenerativeModel(
when (backend.backend) {
GenerativeBackendEnum.VERTEX_AI,
GenerativeBackendEnum.AGENT_PLATFORM ->
"projects/${firebaseApp.options.projectId}/locations/${backend.location}/publishers/google/models/${modelName}"
GenerativeBackendEnum.GOOGLE_AI ->
Expand Down Expand Up @@ -287,7 +285,6 @@ internal constructor(

private fun getTemplateUri(backend: GenerativeBackend): String =
when (backend.backend) {
GenerativeBackendEnum.VERTEX_AI,
GenerativeBackendEnum.AGENT_PLATFORM ->
"projects/${firebaseApp.options.projectId}/locations/${backend.location}/templates/"
GenerativeBackendEnum.GOOGLE_AI -> "projects/${firebaseApp.options.projectId}/templates/"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,18 +240,6 @@ internal constructor(
public suspend fun countTokens(prompt: Bitmap): CountTokensResponse =
countTokens(listOf(content { image(prompt) }))

/**
* Warms up the model to reduce latency for the first request.
*
* @throws [FirebaseAIException] if the warmup failed.
*/
@Deprecated(
message = "Use onDeviceExtension?.warmUp() instead",
replaceWith = ReplaceWith("onDeviceExtension?.warmUp()")
)
@PublicPreviewAPI
public suspend fun warmUp(): Unit = actualModel.warmUp()

internal fun hasFunction(call: FunctionCallPart): Boolean {
return tools
.flatMap { it.autoFunctionDeclarations ?: emptyList() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,30 +184,33 @@ public class OnDeviceModelStatus private constructor(private val value: String)

/** An abstract class representing the status of an on-device model download operation. */
@PublicPreviewAPI
public abstract class DownloadStatus {
public abstract class DownloadStatus internal constructor() {
Comment on lines 185 to +187

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since DownloadStatus has an internal constructor() and cannot be subclassed outside of this module, it is effectively sealed. Declaring it as a sealed class instead of an abstract class is more idiomatic in Kotlin and enables exhaustive when expressions for Kotlin consumers.

Suggested change
/** An abstract class representing the status of an on-device model download operation. */
@PublicPreviewAPI
public abstract class DownloadStatus {
public abstract class DownloadStatus internal constructor() {
/** A sealed class representing the status of an on-device model download operation. */
@PublicPreviewAPI
public sealed class DownloadStatus internal constructor() {

/** Represents when a download has just started. */
public class DownloadStarted(public val bytesToDownload: Long) : DownloadStatus() {
public class DownloadStarted internal constructor(public val bytesToDownload: Long) :
DownloadStatus() {
override fun equals(other: Any?): Boolean =
other is DownloadStarted && bytesToDownload == other.bytesToDownload
override fun hashCode(): Int = bytesToDownload.hashCode()
}

/** Represents when a download is actively in progress. */
public class DownloadInProgress(public val totalBytesDownloaded: Long) : DownloadStatus() {
public class DownloadInProgress internal constructor(public val totalBytesDownloaded: Long) :
DownloadStatus() {
override fun equals(other: Any?): Boolean =
other is DownloadInProgress && totalBytesDownloaded == other.totalBytesDownloaded
override fun hashCode(): Int = totalBytesDownloaded.hashCode()
}

/** Represents when a download has failed. */
public class DownloadFailed(public val exception: FirebaseAIException) : DownloadStatus() {
public class DownloadFailed internal constructor(public val exception: FirebaseAIException) :
DownloadStatus() {
override fun equals(other: Any?): Boolean =
other is DownloadFailed && exception == other.exception
override fun hashCode(): Int = exception.hashCode()
}

/** Represents when a download has successfully completed. */
public class DownloadCompleted : DownloadStatus() {
public class DownloadCompleted internal constructor() : DownloadStatus() {
override fun equals(other: Any?): Boolean = other is DownloadCompleted
override fun hashCode(): Int = javaClass.hashCode()
}
Comment on lines 212 to 216

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since DownloadCompleted is stateless and its constructor is now internal, it is more idiomatic and efficient to define it as an object instead of a class. This avoids unnecessary allocations and removes the need for manual equals and hashCode overrides.

  /** Represents when a download has successfully completed. */
  public object DownloadCompleted : DownloadStatus()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,8 @@ internal constructor(
.map { it.validate() }
.catch { throw FirebaseAIException.from(it) }

private fun getBidiEndpoint(location: String): String =
private fun getLiveEndpoint(location: String): String =
when (backend?.backend) {
GenerativeBackendEnum.VERTEX_AI,
GenerativeBackendEnum.AGENT_PLATFORM,
null ->
"wss://firebasevertexai.googleapis.com/ws/google.firebase.vertexai.v1beta.LlmBidiService/BidiGenerateContent/locations/$location?key=$key"
Expand All @@ -230,7 +229,7 @@ internal constructor(
// the same timeout-protected path as HTTP methods, then set them synchronously inside the
// lambda.
val extraHeaders = extractHeaders(headerProvider)
return client.webSocketSession(getBidiEndpoint(location)) {
return client.webSocketSession(getLiveEndpoint(location)) {
applyCommonHeaders()
for ((tag, value) in extraHeaders) {
header(tag, value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@ internal class CloudGenerativeModelProvider(
when (generativeBackend.backend) {
GenerativeBackendEnum.GOOGLE_AI ->
CountTokensRequest.forGoogleAI(buildGenerateContentRequest(prompt))
GenerativeBackendEnum.VERTEX_AI,
GenerativeBackendEnum.AGENT_PLATFORM ->
CountTokensRequest.forVertexAI(buildGenerateContentRequest(prompt))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import com.google.firebase.ai.type.InlineData
import com.google.firebase.ai.type.LiveAudioConversationConfig
import com.google.firebase.ai.type.LiveServerMessage
import com.google.firebase.ai.type.LiveSession
import com.google.firebase.ai.type.MediaData
import com.google.firebase.ai.type.PublicPreviewAPI
import com.google.firebase.ai.type.SessionAlreadyReceivingException
import com.google.firebase.ai.type.Transcription
Expand Down Expand Up @@ -232,16 +231,6 @@ public abstract class LiveSessionFutures internal constructor() {
*/
public abstract fun sendStopActivityRealtime(): ListenableFuture<Unit>

/**
* Streams client data to the model.
*
* Calling this after [startAudioConversation] will play the response audio immediately.
*
* @param mediaChunks The list of [MediaData] instances representing the media data to be sent.
*/
@Deprecated("Use `sendAudioRealtime`, `sendVideoRealtime`, or `sendTextRealtime` instead")
public abstract fun sendMediaStream(mediaChunks: List<MediaData>): ListenableFuture<Unit>

/**
* Sends [data][Content] to the model.
*
Expand Down Expand Up @@ -325,9 +314,6 @@ public abstract class LiveSessionFutures internal constructor() {
override fun sendStopActivityRealtime(): ListenableFuture<Unit> =
SuspendToFutureAdapter.launchFuture { session.sendStopActivityRealtime() }

override fun sendMediaStream(mediaChunks: List<MediaData>) =
SuspendToFutureAdapter.launchFuture { session.sendMediaStream(mediaChunks) }

@RequiresPermission(RECORD_AUDIO)
override fun startAudioConversation(
functionCallHandler: ((FunctionCallPart) -> FunctionResponsePart)?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -414,12 +414,11 @@ public class FinishReason private constructor(public val name: String, public va
* @property groundingSupports The list of [GroundingSupport] objects. Each object details how
* specific segments of the model's response are supported by the `groundingChunks`.
*/
public class GroundingMetadata(
public class GroundingMetadata
internal constructor(
public val webSearchQueries: List<String>,
public val searchEntryPoint: SearchEntryPoint?,
public val retrievalQueries: List<String>,
@Deprecated("Use groundingChunks instead")
public val groundingAttribution: List<GroundingAttribution>,
public val groundingChunks: List<GroundingChunk>,
public val groundingSupports: List<GroundingSupport>,
) {
Expand All @@ -428,8 +427,6 @@ public class GroundingMetadata(
val webSearchQueries: List<String>?,
val searchEntryPoint: SearchEntryPoint.Internal?,
val retrievalQueries: List<String>?,
@Deprecated("Use groundingChunks instead")
val groundingAttribution: List<GroundingAttribution.Internal>?,
val groundingChunks: List<GroundingChunk.Internal>?,
val groundingSupports: List<GroundingSupport.Internal>?,
) {
Expand All @@ -438,7 +435,6 @@ public class GroundingMetadata(
webSearchQueries = webSearchQueries.orEmpty(),
searchEntryPoint = searchEntryPoint?.toPublic(),
retrievalQueries = retrievalQueries.orEmpty(),
groundingAttribution = groundingAttribution?.map { it.toPublic(content) }.orEmpty(),
groundingChunks = groundingChunks?.map { it.toPublic() }.orEmpty(),
groundingSupports =
groundingSupports?.map { it.toPublic(content) }.orEmpty().filterNotNull()
Expand All @@ -453,7 +449,8 @@ public class GroundingMetadata(
* rendering, it's recommended to display this content within a `WebView`.
* @property sdkBlob A blob of data for the client SDK to render the search entry point.
*/
public class SearchEntryPoint(
public class SearchEntryPoint
internal constructor(
public val renderedContent: String,
public val sdkBlob: String?,
) {
Expand All @@ -480,8 +477,7 @@ public class SearchEntryPoint(
* @property maps Contains details if the grounding chunk is from a Google Maps source.
*/
public class GroundingChunk
@JvmOverloads
constructor(
internal constructor(
public val web: WebGroundingChunk? = null,
public val maps: GoogleMapsGroundingChunk? = null,
) {
Expand All @@ -503,7 +499,8 @@ constructor(
* @property placeId This Place's resource name, in `places/{place_id}` format. This can be used to
* look up the place using the Google Maps API.
*/
public class GoogleMapsGroundingChunk(
public class GoogleMapsGroundingChunk
internal constructor(
public val uri: String?,
public val title: String?,
public val placeId: String?,
Expand All @@ -522,7 +519,8 @@ public class GoogleMapsGroundingChunk(
* @property domain The domain of the original URI from which the content was retrieved. This is
* only populated when using the Vertex AI Gemini API.
*/
public class WebGroundingChunk(
public class WebGroundingChunk
internal constructor(
public val uri: String?,
public val title: String?,
public val domain: String?
Expand All @@ -545,7 +543,8 @@ public class WebGroundingChunk(
* `[1, 3, 4]` means that `groundingChunks[1]`, `groundingChunks[3]`, `groundingChunks[4]` are the
* retrieved content supporting this part of the response.
*/
public class GroundingSupport(
public class GroundingSupport
internal constructor(
public val segment: Segment,
public val groundingChunkIndices: List<Int>,
) {
Expand All @@ -566,22 +565,6 @@ public class GroundingSupport(
}
}

@Deprecated("Use GroundingChunk instead")
public class GroundingAttribution(
public val segment: Segment,
public val confidenceScore: Float?,
) {
@Deprecated("Use GroundingChunk instead")
@Serializable
internal data class Internal(
val segment: Segment.Internal,
val confidenceScore: Float?,
) {
internal fun toPublic(content: Content) =
GroundingAttribution(segment = segment.toPublic(content), confidenceScore = confidenceScore)
}
}

/**
* Represents a specific segment within a [Content] object, often used to pinpoint the exact
* location of text or data that grounding information refers to.
Expand All @@ -596,7 +579,8 @@ public class GroundingAttribution(
* included in the segment.
* @property text The text corresponding to the segment from the response.
*/
public class Segment(
public class Segment
internal constructor(
public val startIndex: Int,
public val endIndex: Int,
public val partIndex: Int,
Expand Down
Loading
Loading