Skip to content
Draft
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
2 changes: 2 additions & 0 deletions ai-logic/firebase-ai-ondevice-interop/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Unreleased

- [feature] Added support for on-device system instructions.

# 16.0.0-beta03

- [feature] Added `modelVersion` property to `GenerateContentResponse` (#8227)
Expand Down
4 changes: 3 additions & 1 deletion ai-logic/firebase-ai-ondevice-interop/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -79,18 +79,20 @@ package com.google.firebase.ai.ondevice.interop {
}

public final class GenerateContentRequest {
ctor public GenerateContentRequest(com.google.firebase.ai.ondevice.interop.TextPart text, com.google.firebase.ai.ondevice.interop.ImagePart? image = null, Float? temperature = null, Integer? topK = null, Integer? seed = null, Integer? candidateCount = null, Integer? maxOutputTokens = null);
ctor public GenerateContentRequest(com.google.firebase.ai.ondevice.interop.TextPart text, com.google.firebase.ai.ondevice.interop.ImagePart? image = null, Float? temperature = null, Integer? topK = null, Integer? seed = null, Integer? candidateCount = null, Integer? maxOutputTokens = null, com.google.firebase.ai.ondevice.interop.TextPart? systemInstruction = null);
method public Integer? getCandidateCount();
method public com.google.firebase.ai.ondevice.interop.ImagePart? getImage();
method public Integer? getMaxOutputTokens();
method public Integer? getSeed();
method public com.google.firebase.ai.ondevice.interop.TextPart? getSystemInstruction();
method public Float? getTemperature();
method public com.google.firebase.ai.ondevice.interop.TextPart getText();
method public Integer? getTopK();
property public final Integer? candidateCount;
property public final com.google.firebase.ai.ondevice.interop.ImagePart? image;
property public final Integer? maxOutputTokens;
property public final Integer? seed;
property public final com.google.firebase.ai.ondevice.interop.TextPart? systemInstruction;
property public final Float? temperature;
property public final com.google.firebase.ai.ondevice.interop.TextPart text;
property public final Integer? topK;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ package com.google.firebase.ai.ondevice.interop
* @property candidateCount The number of candidates to generate.
* @property maxOutputTokens Specifies the maximum number of tokens that can be generated in the
* response.
* @property systemInstruction The system instructions for directing model behavior.
*/
public class GenerateContentRequest(
public val text: TextPart,
Expand All @@ -39,4 +40,5 @@ public class GenerateContentRequest(
public val seed: Int? = null,
public val candidateCount: Int? = null,
public val maxOutputTokens: Int? = null,
public val systemInstruction: TextPart? = null,
) {}
1 change: 1 addition & 0 deletions ai-logic/firebase-ai-ondevice/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Unreleased

- [feature] Added `getOnDeviceModelName` to `GenerativeModel` (#8247)
- [feature] Added support for on-device system instructions.

# 16.0.0-beta03

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import com.google.mlkit.genai.prompt.GenerateContentRequest
import com.google.mlkit.genai.prompt.ImagePart
import com.google.mlkit.genai.prompt.ModelPreference
import com.google.mlkit.genai.prompt.ModelReleaseStage
import com.google.mlkit.genai.prompt.SystemInstruction
import com.google.mlkit.genai.prompt.TextPart
import com.google.mlkit.genai.prompt.generationConfig
import com.google.mlkit.genai.prompt.modelConfig
Expand Down Expand Up @@ -74,6 +75,7 @@ internal fun com.google.firebase.ai.ondevice.interop.GenerateContentRequest.toMl
candidateCount = this@toMlKit.candidateCount
topK = this@toMlKit.topK
seed = this@toMlKit.seed
this@toMlKit.systemInstruction?.let { systemInstruction = SystemInstruction(it.text) }
}
} catch (e: IllegalArgumentException) {
throw FirebaseAIOnDeviceInvalidRequestException(e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,19 @@ internal class ConvertersTest {
assertThat(mlKitRequest.topK).isEqualTo(3)
assertThat(mlKitRequest.seed).isEqualTo(0)
assertThat(mlKitRequest.candidateCount).isEqualTo(1)
assertThat(mlKitRequest.maxOutputTokens).isEqualTo(256)
assertThat(mlKitRequest.maxOutputTokens).isEqualTo(4096)
}

@Test
fun `GenerateContentRequest toMlKit should convert systemInstruction correctly`() {
val interopRequest =
InteropGenerateContentRequest(
text = InteropTextPart("prompt"),
systemInstruction = InteropTextPart("system instruction")
)
val mlKitRequest = interopRequest.toMlKit()

assertThat(mlKitRequest.systemInstruction?.textString).isEqualTo("system instruction")
}

@Test
Expand Down
1 change: 1 addition & 0 deletions ai-logic/firebase-ai/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
Note: The default location is now `global` instead of `us-central1` (no other
functionality has changed). To continue using `us-central1`, specify
`GenerativeBackend.agentPlatform(location = "us-central1"))`.
- [feature] Added support for on-device system instructions.

# 17.14.0

Expand Down
3 changes: 3 additions & 0 deletions ai-logic/firebase-ai/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -150,18 +150,21 @@ package com.google.firebase.ai {
ctor public OnDeviceConfig(com.google.firebase.ai.InferenceMode mode, Integer? maxOutputTokens = null, Float? temperature = null, Integer? topK = null, Integer? seed = null);
ctor public OnDeviceConfig(com.google.firebase.ai.InferenceMode mode, Integer? maxOutputTokens = null, Float? temperature = null, Integer? topK = null, Integer? seed = null, int candidateCount = 1);
ctor public OnDeviceConfig(com.google.firebase.ai.InferenceMode mode, Integer? maxOutputTokens = null, Float? temperature = null, Integer? topK = null, Integer? seed = null, int candidateCount = 1, com.google.firebase.ai.OnDeviceModelOption? modelOption = null);
ctor public OnDeviceConfig(com.google.firebase.ai.InferenceMode mode, Integer? maxOutputTokens = null, Float? temperature = null, Integer? topK = null, Integer? seed = null, int candidateCount = 1, com.google.firebase.ai.OnDeviceModelOption? modelOption = null, com.google.firebase.ai.type.Content? systemInstruction = null);
method public int getCandidateCount();
method public Integer? getMaxOutputTokens();
method public com.google.firebase.ai.InferenceMode getMode();
method public com.google.firebase.ai.OnDeviceModelOption? getModelOption();
method public Integer? getSeed();
method public com.google.firebase.ai.type.Content? getSystemInstruction();
method public Float? getTemperature();
method public Integer? getTopK();
property public final int candidateCount;
property public final Integer? maxOutputTokens;
property public final com.google.firebase.ai.InferenceMode mode;
property public final com.google.firebase.ai.OnDeviceModelOption? modelOption;
property public final Integer? seed;
property public final com.google.firebase.ai.type.Content? systemInstruction;
property public final Float? temperature;
property public final Integer? topK;
field public static final com.google.firebase.ai.OnDeviceConfig.Companion Companion;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -356,14 +356,31 @@ internal constructor(
@OptIn(PublicPreviewAPI::class)
internal fun buildOnDeviceModelProvider(
modelOption: OnDeviceModelOption?
): GenerativeModelProvider =
onDeviceFactoryProvider?.let {
): GenerativeModelProvider {
// if developer didn't pass a OnDeviceConfig.SystemInstruction, we use GenerativeModel.systemInstruction
val effectiveOnDeviceConfig =
if (onDeviceConfig.systemInstruction == null && systemInstruction != null) {
OnDeviceConfig(
mode = onDeviceConfig.mode,
maxOutputTokens = onDeviceConfig.maxOutputTokens,
temperature = onDeviceConfig.temperature,
topK = onDeviceConfig.topK,
seed = onDeviceConfig.seed,
candidateCount = onDeviceConfig.candidateCount,
modelOption = onDeviceConfig.modelOption,
systemInstruction = systemInstruction
)
} else {
onDeviceConfig
}
return onDeviceFactoryProvider?.let {
OnDeviceGenerativeModelProvider(
it.newGenerativeModel(modelOption?.toInterop()),
onDeviceConfig
effectiveOnDeviceConfig
)
}
?: MissingOnDeviceGenerativeModelProvider()
}

@PublicPreviewAPI
internal fun getModelProvider(): GenerativeModelProvider =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.google.firebase.ai

import com.google.firebase.ai.type.Content
import com.google.firebase.ai.type.FirebaseAIException
import com.google.firebase.ai.type.PublicPreviewAPI

Expand All @@ -34,6 +35,7 @@ import com.google.firebase.ai.type.PublicPreviewAPI
* @property candidateCount The number of generated responses to return. See [GenerationConfig] for
* more detail. By default it's set to `1`.
* @property modelOption Configuration for the on-device model selection and performance.
* @property systemInstruction Instructions that direct the model to behave in a certain way.
*/
@PublicPreviewAPI
public class OnDeviceConfig
Expand All @@ -45,7 +47,8 @@ constructor(
public val topK: Int? = null,
public val seed: Int? = null,
public val candidateCount: Int = 1,
public val modelOption: OnDeviceModelOption? = null
public val modelOption: OnDeviceModelOption? = null,
public val systemInstruction: Content? = null
) {

public companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,14 +215,22 @@ internal class OnDeviceGenerativeModelProvider(
)
}
.firstOrNull()
val systemInstructionPart =
onDeviceConfig.systemInstruction
?.parts
?.filterIsInstance<TextPart>()
?.joinToString("") { it.text }
?.takeIf { it.isNotEmpty() }
?.let { OnDeviceTextPart(it) }
return OnDeviceGenerateContentRequest(
text = OnDeviceTextPart(text),
image = image?.let { OnDeviceImagePart(it.image) },
temperature = onDeviceConfig.temperature,
topK = onDeviceConfig.topK,
seed = onDeviceConfig.seed,
candidateCount = onDeviceConfig.candidateCount,
maxOutputTokens = onDeviceConfig.maxOutputTokens
maxOutputTokens = onDeviceConfig.maxOutputTokens,
systemInstruction = systemInstructionPart
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.google.firebase.ai.ondevice.interop.Candidate as OnDeviceCandidate
import com.google.firebase.ai.ondevice.interop.CountTokensResponse as OnDeviceCountTokensResponse
import com.google.firebase.ai.ondevice.interop.FinishReason as OnDeviceFinishReason
import com.google.firebase.ai.ondevice.interop.FirebaseAIOnDeviceNotAvailableException
import com.google.firebase.ai.ondevice.interop.GenerateContentRequest as OnDeviceGenerateContentRequest
import com.google.firebase.ai.ondevice.interop.GenerateContentResponse as OnDeviceGenerateContentResponse
import com.google.firebase.ai.ondevice.interop.GenerativeModel as OnDeviceGenerativeModel
import com.google.firebase.ai.toInterop
Expand All @@ -37,6 +38,7 @@ import io.kotest.matchers.shouldBe
import io.mockk.coEvery
import io.mockk.every
import io.mockk.mockk
import io.mockk.slot
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking
Expand Down Expand Up @@ -153,4 +155,29 @@ internal class OnDeviceGenerativeModelProviderTests {
interopConfig.modelConfig?.preference shouldBe
com.google.firebase.ai.ondevice.interop.ModelPreference.FAST
}

@Test
fun `generateContent passes systemInstruction in OnDeviceConfig to interop request`(): Unit =
runBlocking {
coEvery { onDeviceModel.isAvailable() } returns true
val systemInstruction = Content(parts = listOf(TextPart("system instruction rule")))
val configWithSystemInstruction =
OnDeviceConfig(
mode = InferenceMode.ONLY_ON_DEVICE,
temperature = 0.5f,
systemInstruction = systemInstruction
)
val providerWithSystemInstruction =
OnDeviceGenerativeModelProvider(onDeviceModel, configWithSystemInstruction)

val requestSlot = slot<OnDeviceGenerateContentRequest>()
coEvery { onDeviceModel.generateContent(capture(requestSlot)) } returns
OnDeviceGenerateContentResponse(
listOf(OnDeviceCandidate("generated text", OnDeviceFinishReason.STOP))
)

providerWithSystemInstruction.generateContent(prompt)

requestSlot.captured.systemInstruction?.text shouldBe "system instruction rule"
}
}
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ firebaseAnnotations = "17.0.0"
firebaseCommon = "22.0.1"
firebaseComponents = "19.0.0"
firebaseCrashlyticsGradle = "3.0.4"
genaiPrompt = "1.0.0-beta2"
genaiPrompt = "1.0.0-beta4"
glide = "5.0.5"
googleApiClient = "2.8.1"
googleServices = "4.3.15"
Expand Down