From f1b36a8da5595b319a9fe1c31c44177578d0c2fe Mon Sep 17 00:00:00 2001 From: Emily Ploszaj Date: Fri, 2 May 2025 15:35:59 -0500 Subject: [PATCH 1/3] Fix AI builders for Java consumers --- .../com/google/firebase/ai/type/Content.kt | 7 +++- .../firebase/ai/type/GenerationConfig.kt | 30 +++++++++++++++ .../google/firebase/ai/JavaCompileTests.java | 38 +++++++++++++++++-- 3 files changed, 70 insertions(+), 5 deletions(-) diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Content.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Content.kt index b1ba8c066cb..4e9f1a860db 100644 --- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Content.kt +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Content.kt @@ -44,14 +44,17 @@ constructor(public val role: String? = "user", public val parts: List) { public class Builder { /** The producer of the content. Must be either 'user' or 'model'. By default, it's "user". */ - public var role: String? = "user" + @JvmField public var role: String? = "user" /** * The mutable list of [Part]s comprising the [Content]. * * Prefer using the provided helper methods over modifying this list directly. */ - public var parts: MutableList = arrayListOf() + @JvmField public var parts: MutableList = arrayListOf() + + public fun setRole(role: String?): Content.Builder = apply { this.role = role } + public fun setParts(parts: MutableList): Content.Builder = apply { this.parts = parts } /** Adds a new [Part] to [parts]. */ @JvmName("addPart") diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/GenerationConfig.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/GenerationConfig.kt index 0b6e49450a5..1c2d2680bb1 100644 --- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/GenerationConfig.kt +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/GenerationConfig.kt @@ -136,6 +136,36 @@ private constructor( @JvmField public var responseSchema: Schema? = null @JvmField public var responseModalities: List? = null + public fun setTemperature(temperature: Float?): Builder = apply { + this.temperature = temperature + } + public fun setTopK(topK: Int?): Builder = apply { this.topK = topK } + public fun setTopP(topP: Float?): Builder = apply { this.topP = topP } + public fun setCandidateCount(candidateCount: Int?): Builder = apply { + this.candidateCount = candidateCount + } + public fun setMaxOutputTokens(maxOutputTokens: Int?): Builder = apply { + this.maxOutputTokens = maxOutputTokens + } + public fun setPresencePenalty(presencePenalty: Float?): Builder = apply { + this.presencePenalty = presencePenalty + } + public fun setFrequencyPenalty(frequencyPenalty: Float?): Builder = apply { + this.frequencyPenalty = frequencyPenalty + } + public fun setStopSequences(stopSequences: List?): Builder = apply { + this.stopSequences = stopSequences + } + public fun setResponseMimeType(responseMimeType: String?): Builder = apply { + this.responseMimeType = responseMimeType + } + public fun setResponseSchema(responseSchema: Schema?): Builder = apply { + this.responseSchema = responseSchema + } + public fun setResponseModalities(responseModalities: List?): Builder = apply { + this.responseModalities = responseModalities + } + /** Create a new [GenerationConfig] with the attached arguments. */ public fun build(): GenerationConfig = GenerationConfig( diff --git a/firebase-ai/src/testUtil/java/com/google/firebase/ai/JavaCompileTests.java b/firebase-ai/src/testUtil/java/com/google/firebase/ai/JavaCompileTests.java index 4d5d1185f14..0c32921d5db 100644 --- a/firebase-ai/src/testUtil/java/com/google/firebase/ai/JavaCompileTests.java +++ b/firebase-ai/src/testUtil/java/com/google/firebase/ai/JavaCompileTests.java @@ -57,11 +57,13 @@ import com.google.firebase.ai.type.PublicPreviewAPI; import com.google.firebase.ai.type.ResponseModality; import com.google.firebase.ai.type.SafetyRating; +import com.google.firebase.ai.type.Schema; import com.google.firebase.ai.type.SpeechConfig; import com.google.firebase.ai.type.TextPart; import com.google.firebase.ai.type.UsageMetadata; import com.google.firebase.ai.type.Voices; import com.google.firebase.concurrent.FirebaseExecutors; +import java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.Map; @@ -92,8 +94,37 @@ public void initializeJava() throws Exception { } private GenerationConfig getConfig() { - return new GenerationConfig.Builder().build(); - // TODO b/406558430 GenerationConfig.Builder.setParts returns void + return new GenerationConfig.Builder() + .setTopK(10) + .setTopP(11.0F) + .setTemperature(32.0F) + .setCandidateCount(1) + .setMaxOutputTokens(0xCAFEBABE) + .setFrequencyPenalty(1.0F) + .setPresencePenalty(2.0F) + .setStopSequences(List.of("foo", "bar")) + .setResponseMimeType("image/jxl") + .setResponseModalities(List.of(ResponseModality.TEXT, ResponseModality.TEXT)) + .setResponseSchema(getSchema()) + .build(); + } + + private Schema getSchema() { + return Schema.obj( + Map.of( + "foo", Schema.numInt(), + "bar", Schema.numInt("Some integer"), + "baz", Schema.numInt("Some integer", false), + "qux", Schema.numDouble(), + "quux", Schema.numFloat("Some floating point number"), + "xyzzy", Schema.array(Schema.numInt(), "A list of integers"), + "fee", Schema.numLong(), + "ber", + Schema.obj( + Map.of( + "bez", Schema.array(Schema.numDouble("Nullable double", true)), + "qez", Schema.enumeration(List.of("A", "B", "C"), "One of 3 letters"), + "qeez", Schema.str("A funny string"))))); } private LiveGenerationConfig getLiveConfig() { @@ -113,13 +144,14 @@ private LiveGenerationConfig getLiveConfig() { private void testFutures(GenerativeModelFutures futures) throws Exception { Content content = new Content.Builder() + .setParts(new ArrayList<>()) .addText("Fake prompt") .addFileData("fakeuri", "image/png") .addInlineData(new byte[] {}, "text/json") .addImage(Bitmap.createBitmap(0, 0, Bitmap.Config.HARDWARE)) .addPart(new FunctionCallPart("fakeFunction", Map.of("fakeArg", JsonNull.INSTANCE))) + .setRole("user") .build(); - // TODO b/406558430 Content.Builder.setParts and Content.Builder.setRole return void Executor executor = FirebaseExecutors.directExecutor(); ListenableFuture countResponse = futures.countTokens(content); validateCountTokensResponse(countResponse.get()); From 726912212bf84392cb6baebb36f3ab043944e55a Mon Sep 17 00:00:00 2001 From: Emily Ploszaj Date: Fri, 2 May 2025 16:56:49 -0500 Subject: [PATCH 2/3] Changelog --- firebase-ai/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/firebase-ai/CHANGELOG.md b/firebase-ai/CHANGELOG.md index a0018bd9fa0..8d26e6850f1 100644 --- a/firebase-ai/CHANGELOG.md +++ b/firebase-ai/CHANGELOG.md @@ -1,4 +1,5 @@ # Unreleased +* [fixed] **Breaking Change**: Fixed missing builder methods and return types in builders. * [changed] **Breaking Change**: `LiveModelFutures.connect` now returns `ListenableFuture` instead of `ListenableFuture`. * **Action Required:** Remove any transformations from LiveSession object to LiveSessionFutures object. * **Action Required:** Change type of variable handling `LiveModelFutures.connect` to `ListenableFuture` From 0789cbd19ffd6d833af39324eb81fc85c1802aba Mon Sep 17 00:00:00 2001 From: Emily Ploszaj Date: Mon, 5 May 2025 14:14:44 -0500 Subject: [PATCH 3/3] Update api.txt --- firebase-ai/api.txt | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/firebase-ai/api.txt b/firebase-ai/api.txt index d16c1904e6b..c9d55f52295 100644 --- a/firebase-ai/api.txt +++ b/firebase-ai/api.txt @@ -216,12 +216,10 @@ package com.google.firebase.ai.type { method public com.google.firebase.ai.type.Content.Builder addPart(T data); method public com.google.firebase.ai.type.Content.Builder addText(String text); method public com.google.firebase.ai.type.Content build(); - method public java.util.List getParts(); - method public String? getRole(); - method public void setParts(java.util.List); - method public void setRole(String?); - property public final java.util.List parts; - property public final String? role; + method public com.google.firebase.ai.type.Content.Builder setParts(java.util.List parts); + method public com.google.firebase.ai.type.Content.Builder setRole(String? role); + field public java.util.List parts; + field public String? role; } public final class ContentBlockedException extends com.google.firebase.ai.type.FirebaseAIException { @@ -355,6 +353,17 @@ package com.google.firebase.ai.type { public static final class GenerationConfig.Builder { ctor public GenerationConfig.Builder(); method public com.google.firebase.ai.type.GenerationConfig build(); + method public com.google.firebase.ai.type.GenerationConfig.Builder setCandidateCount(Integer? candidateCount); + method public com.google.firebase.ai.type.GenerationConfig.Builder setFrequencyPenalty(Float? frequencyPenalty); + method public com.google.firebase.ai.type.GenerationConfig.Builder setMaxOutputTokens(Integer? maxOutputTokens); + method public com.google.firebase.ai.type.GenerationConfig.Builder setPresencePenalty(Float? presencePenalty); + method public com.google.firebase.ai.type.GenerationConfig.Builder setResponseMimeType(String? responseMimeType); + method public com.google.firebase.ai.type.GenerationConfig.Builder setResponseModalities(java.util.List? responseModalities); + method public com.google.firebase.ai.type.GenerationConfig.Builder setResponseSchema(com.google.firebase.ai.type.Schema? responseSchema); + method public com.google.firebase.ai.type.GenerationConfig.Builder setStopSequences(java.util.List? stopSequences); + method public com.google.firebase.ai.type.GenerationConfig.Builder setTemperature(Float? temperature); + method public com.google.firebase.ai.type.GenerationConfig.Builder setTopK(Integer? topK); + method public com.google.firebase.ai.type.GenerationConfig.Builder setTopP(Float? topP); field public Integer? candidateCount; field public Float? frequencyPenalty; field public Integer? maxOutputTokens;