From 5bff50e4aa8aedc799044f8747539cd39e78aa91 Mon Sep 17 00:00:00 2001 From: Rodrigo Lazo Paz Date: Sat, 24 Jan 2026 10:51:28 -0500 Subject: [PATCH 1/3] [AI] Add tests for converter code Ensure we don't accidentally change how converters work and break them accidentally. --- .../firebase/ai/ondevice/ConvertersTest.kt | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 firebase-ai-ondevice/src/test/kotlin/com/google/firebase/ai/ondevice/ConvertersTest.kt diff --git a/firebase-ai-ondevice/src/test/kotlin/com/google/firebase/ai/ondevice/ConvertersTest.kt b/firebase-ai-ondevice/src/test/kotlin/com/google/firebase/ai/ondevice/ConvertersTest.kt new file mode 100644 index 00000000000..aa6e7e0b53f --- /dev/null +++ b/firebase-ai-ondevice/src/test/kotlin/com/google/firebase/ai/ondevice/ConvertersTest.kt @@ -0,0 +1,109 @@ +/* + * Copyright 2026 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.ai.ondevice + +import com.google.common.truth.Truth.assertThat +import com.google.firebase.ai.ondevice.interop.FinishReason as InteropFinishReason +import com.google.firebase.ai.ondevice.interop.GenerateContentRequest as InteropGenerateContentRequest +import com.google.firebase.ai.ondevice.interop.TextPart as InteropTextPart +import com.google.mlkit.genai.prompt.Candidate +import com.google.mlkit.genai.prompt.CountTokensResponse +import org.junit.Test +import org.mockito.Mockito.mock +import org.mockito.Mockito.`when` + +internal class ConvertersTest { + + // TODO: Introduce a similar tests for Images. Currently using "bitmaps" is not feasible in + // robolectric. + @Test + fun `InteropParts toMlKit should convert text parts correctly`() { + // TextPart conversion + val interopTextPart = InteropTextPart("hello") + val mlKitTextPart = interopTextPart.toMlKit() + assertThat(mlKitTextPart.textString).isEqualTo("hello") + } + + @Test + fun `CountTokensResponse toInterop should convert correctly`() { + val mlKitCountTokensResponse = CountTokensResponse(42) + val interopCountTokensResponse = mlKitCountTokensResponse.toInterop() + assertThat(interopCountTokensResponse.totalTokens).isEqualTo(42) + } + + @Test + fun `Candidate toInterop should convert correctly with MAX_TOKENS reason`() { + // There's no way of create a Candidate object, so mocking is the closest second. + val mlKitCandidate = + mock(Candidate::class.java).apply { + `when`(text).thenReturn("truncated text") + `when`(finishReason).thenReturn(Candidate.FinishReason.MAX_TOKENS) + } + val interopCandidate = mlKitCandidate.toInterop() + assertThat(interopCandidate.text).isEqualTo("truncated text") + assertThat(interopCandidate.finishReason).isEqualTo(InteropFinishReason.MAX_TOKENS) + } + + @Test + fun `Candidate toInterop should map unknown finishReason to OTHER`() { + val mlKitCandidate = + mock(Candidate::class.java).apply { + `when`(text).thenReturn("some text") + `when`(finishReason).thenReturn(999) // Unknown reason + } + val interopCandidate = mlKitCandidate.toInterop() + assertThat(interopCandidate.finishReason).isEqualTo(InteropFinishReason.OTHER) + } + + @Test + fun `GenerateContentRequest toMlKit should convert correctly and cover all fields`() { + val interopRequest = + InteropGenerateContentRequest( + text = InteropTextPart("prompt"), + temperature = 0.7f, + topK = 20, + seed = 42, + candidateCount = 1, + maxOutputTokens = 250 + ) + val mlKitRequest = interopRequest.toMlKit() + + assertThat(mlKitRequest.text.textString).isEqualTo("prompt") + assertThat(mlKitRequest.temperature).isEqualTo(0.7f) + assertThat(mlKitRequest.topK).isEqualTo(20) + assertThat(mlKitRequest.seed).isEqualTo(42) + assertThat(mlKitRequest.candidateCount).isEqualTo(1) + assertThat(mlKitRequest.maxOutputTokens).isEqualTo(250) + } + + @Test + fun `GenerateContentRequest toMlKit should convert correctly optional fields`() { + val interopRequest = + InteropGenerateContentRequest( + text = InteropTextPart("prompt") + ) + val mlKitRequest = interopRequest.toMlKit() + + // Documented default values + assertThat(mlKitRequest.text.textString).isEqualTo("prompt") + assertThat(mlKitRequest.temperature).isEqualTo(0.0f) + assertThat(mlKitRequest.topK).isEqualTo(3) + assertThat(mlKitRequest.seed).isEqualTo(0) + assertThat(mlKitRequest.candidateCount).isEqualTo(1) + assertThat(mlKitRequest.maxOutputTokens).isEqualTo(256) + } +} From f7985de6b905960ab155edb821d0061869427f59 Mon Sep 17 00:00:00 2001 From: Rodrigo Lazo Paz Date: Sat, 24 Jan 2026 10:53:41 -0500 Subject: [PATCH 2/3] Fix format --- agents.md | 106 ++++++++++++++++++ .../firebase/ai/ondevice/ConvertersTest.kt | 7 +- 2 files changed, 108 insertions(+), 5 deletions(-) diff --git a/agents.md b/agents.md index 14c85aa2d52..59fad6a5eeb 100644 --- a/agents.md +++ b/agents.md @@ -186,6 +186,112 @@ After you make a change, here's the flow you should follow: ``` - If necessary, run integration tests based on the instructions above. +## Firebase ai specific knowledge + +## Overview + +The Firebase Android AI Logic SDK is designed to support both cloud-hosted and on-device Large Language Models (LLMs). It utilizes a hybrid routing strategy to determine whether to use cloud or local inference based on connectivity, performance, and feature requirements. + +## Subprojects + +### 1. `firebase-ai` + +* +**Role**: The primary SDK for cloud-based AI support. + + +* **Responsibilities**: +* Implements cloud support for AI features. + + +* Acts as the default entry point for "Cloud-only" inference modes. + + +* Maintains a `minSdk` of 23. + + + + +* +**Key Behavior**: Interfaces with state-of-the-art cloud-hosted models to provide the highest accuracy and performance. + + + +### 2. `firebase-ai-ondevice` + +* +**Role**: A user-visible SDK that provides on-device model logic. + + +* **Responsibilities**: +* Implements the logic required for on-device inference using the MLKit GenAI prompt SDK. + + +* Exposes `OnDeviceConfiguration` to allow developers to customize local model behavior. + + +* Manages "On-device only" and "Preferred" routing strategies (e.g., falling back to cloud if the device is offline or the request is too complex). + + + + +* +**Requirements**: Due to the hard dependency on MLKit, this artifact requires a `minSdk` of 26. + + + +### 3. `firebase-ai-ondevice-interop` + +* +**Role**: A non-user-visible internal artifact that facilitates communication between the cloud and on-device SDKs. + + +* **Responsibilities**: +* Enables a "soft dependency" between `firebase-ai` and `firebase-ai-ondevice` using the Firebase components system. + + +* Exposes a restricted API subset (strict subset of MLKit) to allow the regular SDK to interact with on-device features without requiring a direct dependency on MLKit. + + +* Provides methods for `firebase-ai` to check if an on-device model is available on the current hardware. + + + + + +## Implementation Details + +### Dependency Management + +To prevent impacting cloud-only users with the larger footprint and higher `minSdk` requirements of on-device models, developers must explicitly add `firebase-ai-ondevice` to their build files: + +```gradle +dependencies { + implementation("com.google.firebase:firebase-ai:17.6.0") + implementation("com.google.firebase:firebase-ai-ondevice:16.0.0-beta01") +} + +``` + +### Hybrid Routing Strategies + +The SDKs work together to support four main inference modes: + +* +**Cloud-only**: Default mode using `firebase-ai`. + + +* +**On-device only**: Routes all queries through `firebase-ai-ondevice`. + + +* +**Cloud Preferred**: Uses cloud unless the device is offline. + + +* +**On-device Preferred**: Uses local inference unless the prompt exceeds token limits or contains unsupported features (e.g., multiple images). + ## Updating this Guide If new patterns or conventions are discovered, update this guide to ensure it remains a useful diff --git a/firebase-ai-ondevice/src/test/kotlin/com/google/firebase/ai/ondevice/ConvertersTest.kt b/firebase-ai-ondevice/src/test/kotlin/com/google/firebase/ai/ondevice/ConvertersTest.kt index aa6e7e0b53f..e229a575ca3 100644 --- a/firebase-ai-ondevice/src/test/kotlin/com/google/firebase/ai/ondevice/ConvertersTest.kt +++ b/firebase-ai-ondevice/src/test/kotlin/com/google/firebase/ai/ondevice/ConvertersTest.kt @@ -92,13 +92,10 @@ internal class ConvertersTest { @Test fun `GenerateContentRequest toMlKit should convert correctly optional fields`() { - val interopRequest = - InteropGenerateContentRequest( - text = InteropTextPart("prompt") - ) + val interopRequest = InteropGenerateContentRequest(text = InteropTextPart("prompt")) val mlKitRequest = interopRequest.toMlKit() - // Documented default values + // Documented default values assertThat(mlKitRequest.text.textString).isEqualTo("prompt") assertThat(mlKitRequest.temperature).isEqualTo(0.0f) assertThat(mlKitRequest.topK).isEqualTo(3) From bbd81da1d21562e52abc1df3c7863a494edb24a8 Mon Sep 17 00:00:00 2001 From: Rodrigo Lazo Paz Date: Sun, 25 Jan 2026 11:53:19 -0500 Subject: [PATCH 3/3] Revert changes to agents.md file --- agents.md | 106 ------------------------------------------------------ 1 file changed, 106 deletions(-) diff --git a/agents.md b/agents.md index 59fad6a5eeb..14c85aa2d52 100644 --- a/agents.md +++ b/agents.md @@ -186,112 +186,6 @@ After you make a change, here's the flow you should follow: ``` - If necessary, run integration tests based on the instructions above. -## Firebase ai specific knowledge - -## Overview - -The Firebase Android AI Logic SDK is designed to support both cloud-hosted and on-device Large Language Models (LLMs). It utilizes a hybrid routing strategy to determine whether to use cloud or local inference based on connectivity, performance, and feature requirements. - -## Subprojects - -### 1. `firebase-ai` - -* -**Role**: The primary SDK for cloud-based AI support. - - -* **Responsibilities**: -* Implements cloud support for AI features. - - -* Acts as the default entry point for "Cloud-only" inference modes. - - -* Maintains a `minSdk` of 23. - - - - -* -**Key Behavior**: Interfaces with state-of-the-art cloud-hosted models to provide the highest accuracy and performance. - - - -### 2. `firebase-ai-ondevice` - -* -**Role**: A user-visible SDK that provides on-device model logic. - - -* **Responsibilities**: -* Implements the logic required for on-device inference using the MLKit GenAI prompt SDK. - - -* Exposes `OnDeviceConfiguration` to allow developers to customize local model behavior. - - -* Manages "On-device only" and "Preferred" routing strategies (e.g., falling back to cloud if the device is offline or the request is too complex). - - - - -* -**Requirements**: Due to the hard dependency on MLKit, this artifact requires a `minSdk` of 26. - - - -### 3. `firebase-ai-ondevice-interop` - -* -**Role**: A non-user-visible internal artifact that facilitates communication between the cloud and on-device SDKs. - - -* **Responsibilities**: -* Enables a "soft dependency" between `firebase-ai` and `firebase-ai-ondevice` using the Firebase components system. - - -* Exposes a restricted API subset (strict subset of MLKit) to allow the regular SDK to interact with on-device features without requiring a direct dependency on MLKit. - - -* Provides methods for `firebase-ai` to check if an on-device model is available on the current hardware. - - - - - -## Implementation Details - -### Dependency Management - -To prevent impacting cloud-only users with the larger footprint and higher `minSdk` requirements of on-device models, developers must explicitly add `firebase-ai-ondevice` to their build files: - -```gradle -dependencies { - implementation("com.google.firebase:firebase-ai:17.6.0") - implementation("com.google.firebase:firebase-ai-ondevice:16.0.0-beta01") -} - -``` - -### Hybrid Routing Strategies - -The SDKs work together to support four main inference modes: - -* -**Cloud-only**: Default mode using `firebase-ai`. - - -* -**On-device only**: Routes all queries through `firebase-ai-ondevice`. - - -* -**Cloud Preferred**: Uses cloud unless the device is offline. - - -* -**On-device Preferred**: Uses local inference unless the prompt exceeds token limits or contains unsupported features (e.g., multiple images). - ## Updating this Guide If new patterns or conventions are discovered, update this guide to ensure it remains a useful