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
2 changes: 2 additions & 0 deletions firebase-ai/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Unreleased

- [changed] **Breaking Change**: Removed the `candidateCount` option from `LiveGenerationConfig`
- [changed] Added support for user interrupts for the `startAudioConversation` method in the
`LiveSession` class.
- [changed] Added support for the URL context tool, which allows the model to access content from
provided public web URLs to inform and enhance its responses. (#7382)
- [changed] Added better error messages to `ServiceConnectionHandshakeFailedException` (#7412)
Expand Down
40 changes: 21 additions & 19 deletions firebase-ai/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ package com.google.firebase.ai {
method public static com.google.firebase.ai.FirebaseAI getInstance(com.google.firebase.FirebaseApp app);
method public static com.google.firebase.ai.FirebaseAI getInstance(com.google.firebase.FirebaseApp app = Firebase.app, com.google.firebase.ai.type.GenerativeBackend backend);
method public static com.google.firebase.ai.FirebaseAI getInstance(com.google.firebase.FirebaseApp app = Firebase.app, com.google.firebase.ai.type.GenerativeBackend backend, boolean useLimitedUseAppCheckTokens);
method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.ImagenModel imagenModel(String modelName);
method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.ImagenModel imagenModel(String modelName, com.google.firebase.ai.type.ImagenGenerationConfig? generationConfig = null);
method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.ImagenModel imagenModel(String modelName, com.google.firebase.ai.type.ImagenGenerationConfig? generationConfig = null, com.google.firebase.ai.type.ImagenSafetySettings? safetySettings = null);
method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.ImagenModel imagenModel(String modelName, com.google.firebase.ai.type.ImagenGenerationConfig? generationConfig = null, com.google.firebase.ai.type.ImagenSafetySettings? safetySettings = null, com.google.firebase.ai.type.RequestOptions requestOptions = com.google.firebase.ai.type.RequestOptions());
method public com.google.firebase.ai.ImagenModel imagenModel(String modelName);
method public com.google.firebase.ai.ImagenModel imagenModel(String modelName, com.google.firebase.ai.type.ImagenGenerationConfig? generationConfig = null);
method public com.google.firebase.ai.ImagenModel imagenModel(String modelName, com.google.firebase.ai.type.ImagenGenerationConfig? generationConfig = null, com.google.firebase.ai.type.ImagenSafetySettings? safetySettings = null);
method public com.google.firebase.ai.ImagenModel imagenModel(String modelName, com.google.firebase.ai.type.ImagenGenerationConfig? generationConfig = null, com.google.firebase.ai.type.ImagenSafetySettings? safetySettings = null, com.google.firebase.ai.type.RequestOptions requestOptions = com.google.firebase.ai.type.RequestOptions());
method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.LiveGenerativeModel liveModel(String modelName);
method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.LiveGenerativeModel liveModel(String modelName, com.google.firebase.ai.type.LiveGenerationConfig? generationConfig = null);
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);
Expand Down Expand Up @@ -72,11 +72,11 @@ package com.google.firebase.ai {
method public com.google.firebase.ai.Chat startChat(java.util.List<com.google.firebase.ai.type.Content> history = emptyList());
}

@com.google.firebase.ai.type.PublicPreviewAPI public final class ImagenModel {
method public suspend Object? editImage(java.util.List<? extends com.google.firebase.ai.type.ImagenReferenceImage> referenceImages, String prompt, com.google.firebase.ai.type.ImagenEditingConfig? config = null, kotlin.coroutines.Continuation<? super com.google.firebase.ai.type.ImagenGenerationResponse<com.google.firebase.ai.type.ImagenInlineImage>>);
public final class ImagenModel {
method @com.google.firebase.ai.type.PublicPreviewAPI public suspend Object? editImage(java.util.List<? extends com.google.firebase.ai.type.ImagenReferenceImage> referenceImages, String prompt, com.google.firebase.ai.type.ImagenEditingConfig? config = null, kotlin.coroutines.Continuation<? super com.google.firebase.ai.type.ImagenGenerationResponse<com.google.firebase.ai.type.ImagenInlineImage>>);
method public suspend Object? generateImages(String prompt, kotlin.coroutines.Continuation<? super com.google.firebase.ai.type.ImagenGenerationResponse<com.google.firebase.ai.type.ImagenInlineImage>>);
method public suspend Object? inpaintImage(com.google.firebase.ai.type.ImagenInlineImage image, String prompt, com.google.firebase.ai.type.ImagenMaskReference mask, com.google.firebase.ai.type.ImagenEditingConfig config, kotlin.coroutines.Continuation<? super com.google.firebase.ai.type.ImagenGenerationResponse<com.google.firebase.ai.type.ImagenInlineImage>>);
method public suspend Object? outpaintImage(com.google.firebase.ai.type.ImagenInlineImage image, com.google.firebase.ai.type.Dimensions newDimensions, com.google.firebase.ai.type.ImagenImagePlacement newPosition = com.google.firebase.ai.type.ImagenImagePlacement.CENTER, String prompt = "", com.google.firebase.ai.type.ImagenEditingConfig? config = null, kotlin.coroutines.Continuation<? super com.google.firebase.ai.type.ImagenGenerationResponse<com.google.firebase.ai.type.ImagenInlineImage>>);
method @com.google.firebase.ai.type.PublicPreviewAPI public suspend Object? inpaintImage(com.google.firebase.ai.type.ImagenInlineImage image, String prompt, com.google.firebase.ai.type.ImagenMaskReference mask, com.google.firebase.ai.type.ImagenEditingConfig config, kotlin.coroutines.Continuation<? super com.google.firebase.ai.type.ImagenGenerationResponse<com.google.firebase.ai.type.ImagenInlineImage>>);
method @com.google.firebase.ai.type.PublicPreviewAPI public suspend Object? outpaintImage(com.google.firebase.ai.type.ImagenInlineImage image, com.google.firebase.ai.type.Dimensions newDimensions, com.google.firebase.ai.type.ImagenImagePlacement newPosition = com.google.firebase.ai.type.ImagenImagePlacement.CENTER, String prompt = "", com.google.firebase.ai.type.ImagenEditingConfig? config = null, kotlin.coroutines.Continuation<? super com.google.firebase.ai.type.ImagenGenerationResponse<com.google.firebase.ai.type.ImagenInlineImage>>);
}

@com.google.firebase.ai.type.PublicPreviewAPI public final class LiveGenerativeModel {
Expand Down Expand Up @@ -148,7 +148,9 @@ package com.google.firebase.ai.java {
method public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> sendFunctionResponse(java.util.List<com.google.firebase.ai.type.FunctionResponsePart> functionList);
method public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> sendMediaStream(java.util.List<com.google.firebase.ai.type.MediaData> mediaChunks);
method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> startAudioConversation();
method 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);
method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> startAudioConversation(boolean enableInterruptions);
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);
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);
method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> stopAudioConversation();
method public abstract void stopReceiving();
field public static final com.google.firebase.ai.java.LiveSessionFutures.Companion Companion;
Expand Down Expand Up @@ -581,7 +583,7 @@ package com.google.firebase.ai.type {
property public boolean isThought;
}

@com.google.firebase.ai.type.PublicPreviewAPI public final class ImagenAspectRatio {
public final class ImagenAspectRatio {
field public static final com.google.firebase.ai.type.ImagenAspectRatio.Companion Companion;
field public static final com.google.firebase.ai.type.ImagenAspectRatio LANDSCAPE_16x9;
field public static final com.google.firebase.ai.type.ImagenAspectRatio LANDSCAPE_4x3;
Expand Down Expand Up @@ -630,7 +632,7 @@ package com.google.firebase.ai.type {
ctor public ImagenForegroundMask(Double? dilation = null);
}

@com.google.firebase.ai.type.PublicPreviewAPI public final class ImagenGenerationConfig {
public final class ImagenGenerationConfig {
ctor public ImagenGenerationConfig(String? negativePrompt = null, Integer? numberOfImages = 1, com.google.firebase.ai.type.ImagenAspectRatio? aspectRatio = null, com.google.firebase.ai.type.ImagenImageFormat? imageFormat = null, Boolean? addWatermark = null);
method public Boolean? getAddWatermark();
method public com.google.firebase.ai.type.ImagenAspectRatio? getAspectRatio();
Expand Down Expand Up @@ -665,17 +667,17 @@ package com.google.firebase.ai.type {
}

public final class ImagenGenerationConfigKt {
method @com.google.firebase.ai.type.PublicPreviewAPI public static com.google.firebase.ai.type.ImagenGenerationConfig imagenGenerationConfig(kotlin.jvm.functions.Function1<? super com.google.firebase.ai.type.ImagenGenerationConfig.Builder,kotlin.Unit> init);
method public static com.google.firebase.ai.type.ImagenGenerationConfig imagenGenerationConfig(kotlin.jvm.functions.Function1<? super com.google.firebase.ai.type.ImagenGenerationConfig.Builder,kotlin.Unit> init);
}

@com.google.firebase.ai.type.PublicPreviewAPI public final class ImagenGenerationResponse<T> {
public final class ImagenGenerationResponse<T> {
method public String? getFilteredReason();
method public java.util.List<T> getImages();
property public final String? filteredReason;
property public final java.util.List<T> images;
}

@com.google.firebase.ai.type.PublicPreviewAPI public final class ImagenImageFormat {
public final class ImagenImageFormat {
method public Integer? getCompressionQuality();
method public String getMimeType();
method public static com.google.firebase.ai.type.ImagenImageFormat jpeg(Integer? compressionQuality = null);
Expand Down Expand Up @@ -712,7 +714,7 @@ package com.google.firebase.ai.type {
method public com.google.firebase.ai.type.ImagenImagePlacement fromCoordinate(int x, int y);
}

@com.google.firebase.ai.type.PublicPreviewAPI public final class ImagenInlineImage {
public final class ImagenInlineImage {
method public android.graphics.Bitmap asBitmap();
method public byte[] getData();
method public String getMimeType();
Expand All @@ -737,7 +739,7 @@ package com.google.firebase.ai.type {
method public java.util.List<com.google.firebase.ai.type.ImagenReferenceImage> generateMaskAndPadForOutpainting(com.google.firebase.ai.type.ImagenInlineImage image, com.google.firebase.ai.type.Dimensions newDimensions, com.google.firebase.ai.type.ImagenImagePlacement newPosition = com.google.firebase.ai.type.ImagenImagePlacement.CENTER, double dilation = 0.01);
}

@com.google.firebase.ai.type.PublicPreviewAPI public final class ImagenPersonFilterLevel {
public final class ImagenPersonFilterLevel {
field public static final com.google.firebase.ai.type.ImagenPersonFilterLevel ALLOW_ADULT;
field public static final com.google.firebase.ai.type.ImagenPersonFilterLevel ALLOW_ALL;
field public static final com.google.firebase.ai.type.ImagenPersonFilterLevel BLOCK_ALL;
Expand All @@ -762,7 +764,7 @@ package com.google.firebase.ai.type {
property public final Integer? referenceId;
}

@com.google.firebase.ai.type.PublicPreviewAPI public final class ImagenSafetyFilterLevel {
public final class ImagenSafetyFilterLevel {
field public static final com.google.firebase.ai.type.ImagenSafetyFilterLevel BLOCK_LOW_AND_ABOVE;
field public static final com.google.firebase.ai.type.ImagenSafetyFilterLevel BLOCK_MEDIUM_AND_ABOVE;
field public static final com.google.firebase.ai.type.ImagenSafetyFilterLevel BLOCK_NONE;
Expand All @@ -773,7 +775,7 @@ package com.google.firebase.ai.type {
public static final class ImagenSafetyFilterLevel.Companion {
}

@com.google.firebase.ai.type.PublicPreviewAPI public final class ImagenSafetySettings {
public final class ImagenSafetySettings {
ctor public ImagenSafetySettings(com.google.firebase.ai.type.ImagenSafetyFilterLevel safetyFilterLevel, com.google.firebase.ai.type.ImagenPersonFilterLevel personFilterLevel);
}

Expand Down Expand Up @@ -891,7 +893,7 @@ package com.google.firebase.ai.type {
method public suspend Object? send(String text, kotlin.coroutines.Continuation<? super kotlin.Unit>);
method public suspend Object? sendFunctionResponse(java.util.List<com.google.firebase.ai.type.FunctionResponsePart> functionList, kotlin.coroutines.Continuation<? super kotlin.Unit>);
method public suspend Object? sendMediaStream(java.util.List<com.google.firebase.ai.type.MediaData> mediaChunks, kotlin.coroutines.Continuation<? super kotlin.Unit>);
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>);
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 = null, kotlin.coroutines.Continuation<? super kotlin.Unit>);
method public void stopAudioConversation();
method public void stopReceiving();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.google.firebase.ai.common.util

import android.media.AudioRecord
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.yield

Expand All @@ -38,13 +39,14 @@ internal fun AudioRecord.readAsFlow() = flow {

while (true) {
if (recordingState != AudioRecord.RECORDSTATE_RECORDING) {
delay(10)
yield()
continue
}

val bytesRead = read(buffer, 0, buffer.size)
if (bytesRead > 0) {
emit(buffer.copyOf(bytesRead))
}
yield()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public abstract class LiveSessionFutures internal constructor() {
* @param functionCallHandler A callback function that is invoked whenever the model receives a
* function call.
*/
@RequiresPermission(RECORD_AUDIO)
public abstract fun startAudioConversation(
functionCallHandler: ((FunctionCallPart) -> FunctionResponsePart)?
): ListenableFuture<Unit>
Expand All @@ -58,6 +59,36 @@ public abstract class LiveSessionFutures internal constructor() {
@RequiresPermission(RECORD_AUDIO)
public abstract fun startAudioConversation(): ListenableFuture<Unit>

/**
* Starts an audio conversation with the model, which can only be stopped using
* [stopAudioConversation] or [close].
*
* @param enableInterruptions Boolean to enable user to interrupt the model. Setting this variable
* would allow the user to talk while the model is responding.
*
* **WARNING**: User interruption might not work reliably across all devices.
*/
@RequiresPermission(RECORD_AUDIO)
public abstract fun startAudioConversation(enableInterruptions: Boolean): ListenableFuture<Unit>

/**
* Starts an audio conversation with the model, which can only be stopped using
* [stopAudioConversation] or [close].
*
* @param functionCallHandler A callback function that is invoked whenever the model receives a
* function call.
*
* @param enableInterruptions Boolean to enable user to interrupt the model. Setting this variable
* would allow the user to talk while the model is responding.
*
* **WARNING**: User interruption might not work reliably across all devices.
*/
@RequiresPermission(RECORD_AUDIO)
public abstract fun startAudioConversation(
functionCallHandler: ((FunctionCallPart) -> FunctionResponsePart)?,
enableInterruptions: Boolean
): ListenableFuture<Unit>

/**
* Stops the audio conversation with the Gemini Server.
*
Expand Down Expand Up @@ -169,6 +200,24 @@ public abstract class LiveSessionFutures internal constructor() {
override fun startAudioConversation() =
SuspendToFutureAdapter.launchFuture { session.startAudioConversation() }

@RequiresPermission(RECORD_AUDIO)
override fun startAudioConversation(enableInterruptions: Boolean) =
SuspendToFutureAdapter.launchFuture {
session.startAudioConversation(enableInterruptions = enableInterruptions)
}

@RequiresPermission(RECORD_AUDIO)
override fun startAudioConversation(
functionCallHandler: ((FunctionCallPart) -> FunctionResponsePart)?,
enableInterruptions: Boolean
) =
SuspendToFutureAdapter.launchFuture {
session.startAudioConversation(
functionCallHandler,
enableInterruptions = enableInterruptions
)
}

override fun stopAudioConversation() =
SuspendToFutureAdapter.launchFuture { session.stopAudioConversation() }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,6 @@ internal class AudioHelper(
*/
fun listenToRecording(): Flow<ByteArray> {
if (released) return emptyFlow()

resumeRecording()

return recorder.readAsFlow()
Expand Down
Loading
Loading