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
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import static com.google.common.collect.ImmutableList.toImmutableList;

import com.google.adk.utils.ModelNameUtils;
import com.google.common.collect.ImmutableList;
import com.google.genai.AsyncSession;
import com.google.genai.Client;
Expand Down Expand Up @@ -257,6 +258,13 @@ public Completable sendContent(Content content) {

List<FunctionResponse> functionResponses = extractFunctionResponses(content);
if (functionResponses.isEmpty()) {
Optional<LiveSendRealtimeInputParameters> realtimeInputParameters =
createRealtimeInputForContent(content, modelName, apiClient.vertexAI());
if (realtimeInputParameters.isPresent()) {
return Completable.fromFuture(
sessionFuture.thenCompose(
session -> session.sendRealtimeInput(realtimeInputParameters.get())));
}
return sendClientContentInternal(
LiveSendClientContentParameters.builder()
.turns(ImmutableList.of(content))
Expand Down Expand Up @@ -289,7 +297,7 @@ public Completable sendRealtime(Blob blob) {
sessionFuture.thenCompose(
session ->
session.sendRealtimeInput(
LiveSendRealtimeInputParameters.builder().media(blob).build())));
createRealtimeInputForBlob(blob, modelName, apiClient.vertexAI()))));
}

/** Helper to send client content parameters. */
Expand All @@ -304,6 +312,40 @@ private Completable sendToolResponseInternal(LiveSendToolResponseParameters para
sessionFuture.thenCompose(session -> session.sendToolResponse(parameters)));
}

static boolean usesGemini31FlashLiveRealtimeInput(String modelName, boolean vertexAI) {
return !vertexAI && ModelNameUtils.isGemini31FlashLiveModel(modelName);
}

static Optional<LiveSendRealtimeInputParameters> createRealtimeInputForContent(
Content content, String modelName, boolean vertexAI) {
if (!usesGemini31FlashLiveRealtimeInput(modelName, vertexAI)
|| content.parts().isEmpty()
|| content.parts().get().size() != 1) {
return Optional.empty();
}
Part part = content.parts().get().get(0);
if (part.text().isEmpty()) {
return Optional.empty();
}
return Optional.of(LiveSendRealtimeInputParameters.builder().text(part.text().get()).build());
}

static LiveSendRealtimeInputParameters createRealtimeInputForBlob(
Blob blob, String modelName, boolean vertexAI) {
if (!usesGemini31FlashLiveRealtimeInput(modelName, vertexAI)) {
return LiveSendRealtimeInputParameters.builder().media(blob).build();
}

String mimeType = blob.mimeType().orElse("");
if (mimeType.startsWith("audio/")) {
return LiveSendRealtimeInputParameters.builder().audio(blob).build();
}
if (mimeType.startsWith("image/")) {
return LiveSendRealtimeInputParameters.builder().video(blob).build();
}
return LiveSendRealtimeInputParameters.builder().media(blob).build();
}

@Override
public Flowable<LlmResponse> receive() {
return responseFlowable;
Expand Down
8 changes: 8 additions & 0 deletions core/src/main/java/com/google/adk/utils/ModelNameUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
/** Utility class for model names. */
public final class ModelNameUtils {
private static final String GEMINI_PREFIX = "gemini-";
private static final String GEMINI_3_1_FLASH_LIVE_PREFIX = "gemini-3.1-flash-live";
private static final Pattern GEMINI_2_PATTERN = Pattern.compile("^gemini-2\\..*");
private static final String GEMINI_CLASS = "com.google.adk.models.Gemini";
private static final Pattern PATH_PATTERN =
Expand All @@ -39,6 +40,13 @@ public static boolean isGemini2Model(String modelString) {
return matchesModelPattern(modelString, GEMINI_2_PATTERN);
}

public static boolean isGemini31FlashLiveModel(String modelString) {
if (modelString == null) {
return false;
}
return extractModelName(modelString).startsWith(GEMINI_3_1_FLASH_LIVE_PREFIX);
}

private static boolean matchesModelPattern(String modelString, Pattern pattern) {
if (modelString == null) {
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
import static com.google.common.truth.Truth.assertThat;

import com.google.common.collect.ImmutableList;
import com.google.genai.types.Blob;
import com.google.genai.types.Content;
import com.google.genai.types.FunctionCall;
import com.google.genai.types.GenerateContentResponseUsageMetadata;
import com.google.genai.types.LiveSendRealtimeInputParameters;
import com.google.genai.types.LiveServerContent;
import com.google.genai.types.LiveServerMessage;
import com.google.genai.types.LiveServerSetupComplete;
Expand All @@ -31,13 +33,100 @@
import com.google.genai.types.UsageMetadata;
import io.reactivex.rxjava3.observers.TestObserver;
import java.util.List;
import java.util.Optional;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

@RunWith(JUnit4.class)
public final class GeminiLlmConnectionTest {

@Test
public void usesGemini31FlashLiveRealtimeInput_withGeminiApiPreviewModel_returnsTrue() {
assertThat(
GeminiLlmConnection.usesGemini31FlashLiveRealtimeInput(
"gemini-3.1-flash-live-preview", false))
.isTrue();
}

@Test
public void usesGemini31FlashLiveRealtimeInput_withVertexAi_returnsFalse() {
assertThat(
GeminiLlmConnection.usesGemini31FlashLiveRealtimeInput(
"gemini-3.1-flash-live-preview", true))
.isFalse();
}

@Test
public void createRealtimeInputForContent_withSingleTextOnGemini31_returnsTextInput() {
Content content = Content.fromParts(Part.fromText("hello"));

Optional<LiveSendRealtimeInputParameters> parameters =
GeminiLlmConnection.createRealtimeInputForContent(
content, "gemini-3.1-flash-live-preview", false);

assertThat(parameters).isPresent();
assertThat(parameters.get().text()).hasValue("hello");
assertThat(parameters.get().media()).isEmpty();
}

@Test
public void createRealtimeInputForContent_withNonTextContent_returnsEmpty() {
Content content =
Content.builder()
.parts(
ImmutableList.of(
Part.builder()
.inlineData(
Blob.builder().mimeType("image/png").data(new byte[] {1}).build())
.build()))
.build();

Optional<LiveSendRealtimeInputParameters> parameters =
GeminiLlmConnection.createRealtimeInputForContent(
content, "gemini-3.1-flash-live-preview", false);

assertThat(parameters).isEmpty();
}

@Test
public void createRealtimeInputForBlob_withGemini31Audio_setsAudio() {
Blob blob = Blob.builder().mimeType("audio/pcm").data(new byte[] {1}).build();

LiveSendRealtimeInputParameters parameters =
GeminiLlmConnection.createRealtimeInputForBlob(
blob, "gemini-3.1-flash-live-preview", false);

assertThat(parameters.audio()).hasValue(blob);
assertThat(parameters.media()).isEmpty();
assertThat(parameters.video()).isEmpty();
}

@Test
public void createRealtimeInputForBlob_withGemini31Image_setsVideo() {
Blob blob = Blob.builder().mimeType("image/jpeg").data(new byte[] {1}).build();

LiveSendRealtimeInputParameters parameters =
GeminiLlmConnection.createRealtimeInputForBlob(
blob, "gemini-3.1-flash-live-preview", false);

assertThat(parameters.video()).hasValue(blob);
assertThat(parameters.media()).isEmpty();
assertThat(parameters.audio()).isEmpty();
}

@Test
public void createRealtimeInputForBlob_withOtherModel_keepsMedia() {
Blob blob = Blob.builder().mimeType("audio/pcm").data(new byte[] {1}).build();

LiveSendRealtimeInputParameters parameters =
GeminiLlmConnection.createRealtimeInputForBlob(blob, "gemini-2.0-flash-live-001", false);

assertThat(parameters.media()).hasValue(blob);
assertThat(parameters.audio()).isEmpty();
assertThat(parameters.video()).isEmpty();
}

@Test
public void convertToServerResponse_withInterruptedTrue_mapsInterruptedField() {
LiveServerContent serverContent =
Expand Down
15 changes: 15 additions & 0 deletions core/src/test/java/com/google/adk/utils/ModelNameUtilsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,21 @@ public void isGeminiModel_withEmptyModel_returnsFalse() {
assertThat(ModelNameUtils.isGeminiModel("")).isFalse();
}

@Test
public void isGemini31FlashLiveModel_withPrefixOnly_returnsTrue() {
assertThat(ModelNameUtils.isGemini31FlashLiveModel("gemini-3.1-flash-live")).isTrue();
}

@Test
public void isGemini31FlashLiveModel_withPrefixedVariant_returnsTrue() {
assertThat(ModelNameUtils.isGemini31FlashLiveModel("gemini-3.1-flash-live-preview")).isTrue();
}

@Test
public void isGemini31FlashLiveModel_withOtherModel_returnsFalse() {
assertThat(ModelNameUtils.isGemini31FlashLiveModel("gemini-2.0-flash-live-001")).isFalse();
}

@Test
public void isInstanceOfGemini_withGeminiInstance_returnsTrue() {
assertThat(ModelNameUtils.isInstanceOfGemini(new Gemini("", ""))).isTrue();
Expand Down