Skip to content

Commit 094e12b

Browse files
committed
merge with main@origin
2 parents 3f07dab + 08c228b commit 094e12b

File tree

60 files changed

+1439
-170
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+1439
-170
lines changed

.github/workflows/dataconnect.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ env:
3434
FDC_JAVA_VERSION: ${{ inputs.javaVersion || '17' }}
3535
FDC_ANDROID_EMULATOR_API_LEVEL: ${{ inputs.androidEmulatorApiLevel || '34' }}
3636
FDC_NODEJS_VERSION: ${{ inputs.nodeJsVersion || '20' }}
37-
FDC_FIREBASE_TOOLS_VERSION: ${{ inputs.firebaseToolsVersion || '14.18.0' }}
37+
FDC_FIREBASE_TOOLS_VERSION: ${{ inputs.firebaseToolsVersion || '14.27.0' }}
3838
FDC_FIREBASE_TOOLS_DIR: /tmp/firebase-tools
3939
FDC_FIREBASE_COMMAND: /tmp/firebase-tools/node_modules/.bin/firebase
4040
FDC_PYTHON_VERSION: ${{ inputs.pythonVersion || '3.13' }}

.github/workflows/dataconnect_demo_app.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ on:
1818

1919
env:
2020
FDC_NODE_VERSION: ${{ inputs.nodeVersion || '20' }}
21-
FDC_FIREBASE_TOOLS_VERSION: ${{ inputs.firebaseToolsVersion || '14.18.0' }}
21+
FDC_FIREBASE_TOOLS_VERSION: ${{ inputs.firebaseToolsVersion || '14.27.0' }}
2222
FDC_JAVA_VERSION: ${{ inputs.javaVersion || '17' }}
2323
FDC_FIREBASE_TOOLS_DIR: ${{ github.workspace }}/firebase-tools
2424
FDC_FIREBASE_COMMAND: ${{ github.workspace }}/firebase-tools/node_modules/.bin/firebase

.github/workflows/test-report.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Generate Test Report
2+
3+
on:
4+
schedule:
5+
- cron: 51 8 * * * # Runs automatically once a day
6+
workflow_dispatch: # Allow triggering the workflow manually
7+
8+
permissions:
9+
contents: read
10+
issues: write
11+
12+
jobs:
13+
report:
14+
name: "Generate Test Report"
15+
runs-on: ubuntu-latest
16+
17+
steps:
18+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
19+
with:
20+
submodules: true
21+
22+
- name: Set up JDK 17
23+
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
24+
with:
25+
java-version: 17
26+
distribution: temurin
27+
cache: gradle
28+
29+
- name: Generate Test Report
30+
env:
31+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
32+
run: ./gradlew generateTestReport
33+
34+
- name: Update tracking issue
35+
env:
36+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
37+
run: gh issue edit 7421 --body-file test-report.md

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ plugins {
2323
alias(libs.plugins.errorprone)
2424
alias(libs.plugins.crashlytics) apply false
2525
id("PublishingPlugin")
26+
id("test-report")
2627
id("firebase-ci")
2728
id("smoke-tests")
2829
alias(libs.plugins.google.services)

firebase-ai/CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# Unreleased
22

3+
- [changed] Added `LiveAudioConversationConfig` to control different aspects of the conversation
4+
while using the `startAudioConversation` function.
5+
- [fixed] Fixed an issue causing streaming chat interactions to drop thought signatures. (#7562)
6+
7+
# 17.6.0
8+
9+
- [feature] Added support for server templates via `TemplateGenerativeModel` and
10+
`TemplateImagenModel`. (#7503)
11+
12+
# 17.5.0
13+
314
- [changed] Added better scheduling and louder output for Live API.
415
- [changed] Added support for input and output transcription. (#7482)
516
- [feature] Added support for sending realtime audio and video in a `LiveSession`.

firebase-ai/api.txt

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ package com.google.firebase.ai {
3636
method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.LiveGenerativeModel liveModel(String modelName, com.google.firebase.ai.type.LiveGenerationConfig? generationConfig = null, java.util.List<com.google.firebase.ai.type.Tool>? tools = null);
3737
method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.LiveGenerativeModel liveModel(String modelName, com.google.firebase.ai.type.LiveGenerationConfig? generationConfig = null, java.util.List<com.google.firebase.ai.type.Tool>? tools = null, com.google.firebase.ai.type.Content? systemInstruction = null);
3838
method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.LiveGenerativeModel liveModel(String modelName, com.google.firebase.ai.type.LiveGenerationConfig? generationConfig = null, java.util.List<com.google.firebase.ai.type.Tool>? tools = null, com.google.firebase.ai.type.Content? systemInstruction = null, com.google.firebase.ai.type.RequestOptions requestOptions = com.google.firebase.ai.type.RequestOptions());
39+
method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.TemplateGenerativeModel templateGenerativeModel();
40+
method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.TemplateGenerativeModel templateGenerativeModel(com.google.firebase.ai.type.RequestOptions requestOptions = com.google.firebase.ai.type.RequestOptions());
41+
method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.TemplateImagenModel templateImagenModel();
42+
method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.TemplateImagenModel templateImagenModel(com.google.firebase.ai.type.RequestOptions requestOptions = com.google.firebase.ai.type.RequestOptions());
3943
property public static final com.google.firebase.ai.FirebaseAI instance;
4044
field public static final com.google.firebase.ai.FirebaseAI.Companion Companion;
4145
}
@@ -83,6 +87,15 @@ package com.google.firebase.ai {
8387
method public suspend Object? connect(kotlin.coroutines.Continuation<? super com.google.firebase.ai.type.LiveSession>);
8488
}
8589

90+
@com.google.firebase.ai.type.PublicPreviewAPI public final class TemplateGenerativeModel {
91+
method public suspend Object? generateContent(String templateId, java.util.Map<java.lang.String,?> inputs, kotlin.coroutines.Continuation<? super com.google.firebase.ai.type.GenerateContentResponse>);
92+
method public kotlinx.coroutines.flow.Flow<com.google.firebase.ai.type.GenerateContentResponse> generateContentStream(String templateId, java.util.Map<java.lang.String,?> inputs);
93+
}
94+
95+
@com.google.firebase.ai.type.PublicPreviewAPI public final class TemplateImagenModel {
96+
method public suspend Object? generateImages(String templateId, java.util.Map<java.lang.String,?> inputs, kotlin.coroutines.Continuation<? super com.google.firebase.ai.type.ImagenGenerationResponse<com.google.firebase.ai.type.ImagenInlineImage>>);
97+
}
98+
8699
}
87100

88101
package com.google.firebase.ai.java {
@@ -152,6 +165,7 @@ package com.google.firebase.ai.java {
152165
method public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> sendVideoRealtime(com.google.firebase.ai.type.InlineData video);
153166
method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> startAudioConversation();
154167
method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> startAudioConversation(boolean enableInterruptions);
168+
method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> startAudioConversation(com.google.firebase.ai.type.LiveAudioConversationConfig liveAudioConversationConfig);
155169
method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> startAudioConversation(kotlin.jvm.functions.Function1<? super com.google.firebase.ai.type.FunctionCallPart,com.google.firebase.ai.type.FunctionResponsePart>? functionCallHandler);
156170
method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> startAudioConversation(kotlin.jvm.functions.Function1<? super com.google.firebase.ai.type.FunctionCallPart,com.google.firebase.ai.type.FunctionResponsePart>? functionCallHandler, boolean enableInterruptions);
157171
method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> startAudioConversation(kotlin.jvm.functions.Function1<? super com.google.firebase.ai.type.FunctionCallPart,com.google.firebase.ai.type.FunctionResponsePart>? functionCallHandler, kotlin.jvm.functions.Function2<? super com.google.firebase.ai.type.Transcription?,? super com.google.firebase.ai.type.Transcription?,kotlin.Unit>? transcriptHandler, boolean enableInterruptions);
@@ -166,6 +180,29 @@ package com.google.firebase.ai.java {
166180
method public com.google.firebase.ai.java.LiveSessionFutures from(com.google.firebase.ai.type.LiveSession session);
167181
}
168182

183+
public abstract class TemplateGenerativeModelFutures {
184+
method public static final com.google.firebase.ai.java.TemplateGenerativeModelFutures from(com.google.firebase.ai.TemplateGenerativeModel model);
185+
method public abstract com.google.common.util.concurrent.ListenableFuture<com.google.firebase.ai.type.GenerateContentResponse> generateContent(String templateId, java.util.Map<java.lang.String,?> inputs);
186+
method public abstract org.reactivestreams.Publisher<com.google.firebase.ai.type.GenerateContentResponse> generateContentStream(String templateId, java.util.Map<java.lang.String,?> inputs);
187+
method public abstract com.google.firebase.ai.TemplateGenerativeModel getGenerativeModel();
188+
field public static final com.google.firebase.ai.java.TemplateGenerativeModelFutures.Companion Companion;
189+
}
190+
191+
public static final class TemplateGenerativeModelFutures.Companion {
192+
method public com.google.firebase.ai.java.TemplateGenerativeModelFutures from(com.google.firebase.ai.TemplateGenerativeModel model);
193+
}
194+
195+
public abstract class TemplateImagenModelFutures {
196+
method public static final com.google.firebase.ai.java.TemplateImagenModelFutures from(com.google.firebase.ai.TemplateImagenModel model);
197+
method public abstract com.google.common.util.concurrent.ListenableFuture<com.google.firebase.ai.type.ImagenGenerationResponse<com.google.firebase.ai.type.ImagenInlineImage>> generateImages(String templateId, java.util.Map<java.lang.String,?> inputs);
198+
method public abstract com.google.firebase.ai.TemplateImagenModel getImageModel();
199+
field public static final com.google.firebase.ai.java.TemplateImagenModelFutures.Companion Companion;
200+
}
201+
202+
public static final class TemplateImagenModelFutures.Companion {
203+
method public com.google.firebase.ai.java.TemplateImagenModelFutures from(com.google.firebase.ai.TemplateImagenModel model);
204+
}
205+
169206
}
170207

171208
package com.google.firebase.ai.type {
@@ -838,6 +875,31 @@ package com.google.firebase.ai.type {
838875
public final class InvalidStateException extends com.google.firebase.ai.type.FirebaseAIException {
839876
}
840877

878+
@com.google.firebase.ai.type.PublicPreviewAPI public final class LiveAudioConversationConfig {
879+
field public static final com.google.firebase.ai.type.LiveAudioConversationConfig.Companion Companion;
880+
}
881+
882+
public static final class LiveAudioConversationConfig.Builder {
883+
ctor public LiveAudioConversationConfig.Builder();
884+
method public com.google.firebase.ai.type.LiveAudioConversationConfig build();
885+
method public com.google.firebase.ai.type.LiveAudioConversationConfig.Builder setEnableInterruptions(boolean enableInterruptions);
886+
method public com.google.firebase.ai.type.LiveAudioConversationConfig.Builder setFunctionCallHandler(kotlin.jvm.functions.Function1<? super com.google.firebase.ai.type.FunctionCallPart,com.google.firebase.ai.type.FunctionResponsePart>? functionCallHandler);
887+
method public com.google.firebase.ai.type.LiveAudioConversationConfig.Builder setInitializationHandler(kotlin.jvm.functions.Function2<? super android.media.AudioRecord.Builder,? super android.media.AudioTrack.Builder,kotlin.Unit>? initializationHandler);
888+
method public com.google.firebase.ai.type.LiveAudioConversationConfig.Builder setTranscriptHandler(kotlin.jvm.functions.Function2<? super com.google.firebase.ai.type.Transcription?,? super com.google.firebase.ai.type.Transcription?,kotlin.Unit>? transcriptHandler);
889+
field public boolean enableInterruptions;
890+
field public kotlin.jvm.functions.Function1<? super com.google.firebase.ai.type.FunctionCallPart,com.google.firebase.ai.type.FunctionResponsePart>? functionCallHandler;
891+
field public kotlin.jvm.functions.Function2<? super android.media.AudioRecord.Builder,? super android.media.AudioTrack.Builder,kotlin.Unit>? initializationHandler;
892+
field public kotlin.jvm.functions.Function2<? super com.google.firebase.ai.type.Transcription?,? super com.google.firebase.ai.type.Transcription?,kotlin.Unit>? transcriptHandler;
893+
}
894+
895+
public static final class LiveAudioConversationConfig.Companion {
896+
method public com.google.firebase.ai.type.LiveAudioConversationConfig.Builder builder();
897+
}
898+
899+
public final class LiveAudioConversationConfigKt {
900+
method public static com.google.firebase.ai.type.LiveAudioConversationConfig liveAudioConversationConfig(kotlin.jvm.functions.Function1<? super com.google.firebase.ai.type.LiveAudioConversationConfig.Builder,kotlin.Unit> init);
901+
}
902+
841903
@com.google.firebase.ai.type.PublicPreviewAPI public final class LiveGenerationConfig {
842904
field public static final com.google.firebase.ai.type.LiveGenerationConfig.Companion Companion;
843905
}
@@ -922,6 +984,7 @@ package com.google.firebase.ai.type {
922984
method @Deprecated public suspend Object? sendMediaStream(java.util.List<com.google.firebase.ai.type.MediaData> mediaChunks, kotlin.coroutines.Continuation<? super kotlin.Unit>);
923985
method public suspend Object? sendTextRealtime(String text, kotlin.coroutines.Continuation<? super kotlin.Unit>);
924986
method public suspend Object? sendVideoRealtime(com.google.firebase.ai.type.InlineData video, kotlin.coroutines.Continuation<? super kotlin.Unit>);
987+
method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public suspend Object? startAudioConversation(com.google.firebase.ai.type.LiveAudioConversationConfig liveAudioConversationConfig, kotlin.coroutines.Continuation<? super kotlin.Unit>);
925988
method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public suspend Object? startAudioConversation(kotlin.jvm.functions.Function1<? super com.google.firebase.ai.type.FunctionCallPart,com.google.firebase.ai.type.FunctionResponsePart>? functionCallHandler = null, boolean enableInterruptions = false, kotlin.coroutines.Continuation<? super kotlin.Unit>);
926989
method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public suspend Object? startAudioConversation(kotlin.jvm.functions.Function1<? super com.google.firebase.ai.type.FunctionCallPart,com.google.firebase.ai.type.FunctionResponsePart>? functionCallHandler = null, kotlin.coroutines.Continuation<? super kotlin.Unit>);
927990
method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public suspend Object? startAudioConversation(kotlin.jvm.functions.Function1<? super com.google.firebase.ai.type.FunctionCallPart,com.google.firebase.ai.type.FunctionResponsePart>? functionCallHandler = null, kotlin.jvm.functions.Function2<? super com.google.firebase.ai.type.Transcription?,? super com.google.firebase.ai.type.Transcription?,kotlin.Unit>? transcriptHandler = null, boolean enableInterruptions = false, kotlin.coroutines.Continuation<? super kotlin.Unit>);

firebase-ai/gradle.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
version=17.5.0
16-
latestReleasedVersion=17.4.0
15+
version=17.7.0
16+
latestReleasedVersion=17.6.0

firebase-ai/src/main/kotlin/com/google/firebase/ai/Chat.kt

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import com.google.firebase.ai.type.GenerateContentResponse
2222
import com.google.firebase.ai.type.ImagePart
2323
import com.google.firebase.ai.type.InlineDataPart
2424
import com.google.firebase.ai.type.InvalidStateException
25+
import com.google.firebase.ai.type.Part
2526
import com.google.firebase.ai.type.TextPart
2627
import com.google.firebase.ai.type.content
2728
import java.util.LinkedList
@@ -133,6 +134,7 @@ public class Chat(
133134
val bitmaps = LinkedList<Bitmap>()
134135
val inlineDataParts = LinkedList<InlineDataPart>()
135136
val text = StringBuilder()
137+
val parts = mutableListOf<Part>()
136138

137139
/**
138140
* TODO: revisit when images and inline data are returned. This will cause issues with how
@@ -147,22 +149,17 @@ public class Chat(
147149
is ImagePart -> bitmaps.add(part.image)
148150
is InlineDataPart -> inlineDataParts.add(part)
149151
}
152+
parts.add(part)
150153
}
151154
}
152155
.onCompletion {
153156
lock.release()
154157
if (it == null) {
155158
val content =
156159
content("model") {
157-
for (bitmap in bitmaps) {
158-
image(bitmap)
159-
}
160-
for (inlineDataPart in inlineDataParts) {
161-
inlineData(inlineDataPart.inlineData, inlineDataPart.mimeType)
162-
}
163-
if (text.isNotBlank()) {
164-
text(text.toString())
165-
}
160+
setParts(
161+
parts.filterNot { part -> part is TextPart && !part.hasContent() }.toMutableList()
162+
)
166163
}
167164

168165
history.add(prompt)
@@ -224,3 +221,12 @@ public class Chat(
224221
}
225222
}
226223
}
224+
225+
/**
226+
* Returns true if the [TextPart] contains any content, either in its [TextPart.text] property or
227+
* its [TextPart.thoughtSignature] property.
228+
*/
229+
private fun TextPart.hasContent(): Boolean {
230+
if (text.isNotEmpty()) return true
231+
return !thoughtSignature.isNullOrBlank()
232+
}

firebase-ai/src/main/kotlin/com/google/firebase/ai/FirebaseAI.kt

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,29 @@ internal constructor(
106106
)
107107
}
108108

109+
/**
110+
* Instantiates a new [TemplateGenerativeModel] given the provided parameters.
111+
*
112+
* @param requestOptions Configuration options for sending requests to the backend.
113+
* @return The initialized [TemplateGenerativeModel] instance.
114+
*/
115+
@JvmOverloads
116+
@PublicPreviewAPI
117+
public fun templateGenerativeModel(
118+
requestOptions: RequestOptions = RequestOptions(),
119+
): TemplateGenerativeModel {
120+
val templateUri = getTemplateUri(backend)
121+
return TemplateGenerativeModel(
122+
templateUri,
123+
firebaseApp.options.apiKey,
124+
firebaseApp,
125+
useLimitedUseAppCheckTokens,
126+
requestOptions,
127+
appCheckProvider.get(),
128+
internalAuthProvider.get(),
129+
)
130+
}
131+
109132
/**
110133
* Instantiates a new [LiveGenerationConfig] given the provided parameters.
111134
*
@@ -205,6 +228,29 @@ internal constructor(
205228
)
206229
}
207230

231+
/**
232+
* Instantiates a new [TemplateImagenModel] given the provided parameters.
233+
*
234+
* @param requestOptions Configuration options for sending requests to the backend.
235+
* @return The initialized [TemplateImagenModel] instance.
236+
*/
237+
@JvmOverloads
238+
@PublicPreviewAPI
239+
public fun templateImagenModel(
240+
requestOptions: RequestOptions = RequestOptions(),
241+
): TemplateImagenModel {
242+
val templateUri = getTemplateUri(backend)
243+
return TemplateImagenModel(
244+
templateUri,
245+
firebaseApp.options.apiKey,
246+
firebaseApp,
247+
useLimitedUseAppCheckTokens,
248+
requestOptions,
249+
appCheckProvider.get(),
250+
internalAuthProvider.get(),
251+
)
252+
}
253+
208254
public companion object {
209255
/** The [FirebaseAI] instance for the default [FirebaseApp] using the Google AI Backend. */
210256
@JvmStatic
@@ -258,6 +304,13 @@ internal constructor(
258304

259305
private val TAG = FirebaseAI::class.java.simpleName
260306
}
307+
308+
private fun getTemplateUri(backend: GenerativeBackend): String =
309+
when (backend.backend) {
310+
GenerativeBackendEnum.VERTEX_AI ->
311+
"projects/${firebaseApp.options.projectId}/locations/${backend.location}/templates/"
312+
GenerativeBackendEnum.GOOGLE_AI -> "projects/${firebaseApp.options.projectId}/templates/"
313+
}
261314
}
262315

263316
/** The [FirebaseAI] instance for the default [FirebaseApp] using the Google AI Backend. */

firebase-ai/src/main/kotlin/com/google/firebase/ai/ImagenModel.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ internal constructor(
233233
}
234234

235235
@OptIn(PublicPreviewAPI::class)
236-
private fun ImagenGenerationResponse.Internal.validate(): ImagenGenerationResponse.Internal {
236+
internal fun ImagenGenerationResponse.Internal.validate(): ImagenGenerationResponse.Internal {
237237
if (predictions.none { it.mimeType != null }) {
238238
throw ContentBlockedException(
239239
message = predictions.first { it.raiFilteredReason != null }.raiFilteredReason

0 commit comments

Comments
 (0)