From d2cc19219df1be0ce579f627b0789b12d1b0b6e3 Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Thu, 30 Oct 2025 16:15:45 -0300 Subject: [PATCH 1/4] feat(genai): add live samples(1) --- genai/snippets/pom.xml | 6 +- .../java/genai/live/LiveCodeExecWithTxt.java | 117 +++++++++++++++++ .../live/LiveGroundGoogSearchWithTxt.java | 110 ++++++++++++++++ .../genai/live/LiveTranscribeWithAudio.java | 119 ++++++++++++++++++ .../src/main/java/genai/live/LiveWithTxt.java | 108 ++++++++++++++++ .../src/test/java/genai/live/LiveIT.java | 105 ++++++++++++++++ 6 files changed, 560 insertions(+), 5 deletions(-) create mode 100644 genai/snippets/src/main/java/genai/live/LiveCodeExecWithTxt.java create mode 100644 genai/snippets/src/main/java/genai/live/LiveGroundGoogSearchWithTxt.java create mode 100644 genai/snippets/src/main/java/genai/live/LiveTranscribeWithAudio.java create mode 100644 genai/snippets/src/main/java/genai/live/LiveWithTxt.java create mode 100644 genai/snippets/src/test/java/genai/live/LiveIT.java diff --git a/genai/snippets/pom.xml b/genai/snippets/pom.xml index 3c59e1fde38..2adf3e16ea9 100644 --- a/genai/snippets/pom.xml +++ b/genai/snippets/pom.xml @@ -51,17 +51,13 @@ com.google.genai google-genai - 1.23.0 + 1.25.0 com.google.cloud google-cloud-storage test - - com.google.cloud - google-cloud-storage - junit junit diff --git a/genai/snippets/src/main/java/genai/live/LiveCodeExecWithTxt.java b/genai/snippets/src/main/java/genai/live/LiveCodeExecWithTxt.java new file mode 100644 index 00000000000..cec3a19cc1f --- /dev/null +++ b/genai/snippets/src/main/java/genai/live/LiveCodeExecWithTxt.java @@ -0,0 +1,117 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.live; + +// [START googlegenaisdk_live_code_exec_with_txt] + +import static com.google.genai.types.Modality.Known.TEXT; + +import com.google.genai.AsyncSession; +import com.google.genai.Client; +import com.google.genai.types.Content; +import com.google.genai.types.LiveConnectConfig; +import com.google.genai.types.LiveSendClientContentParameters; +import com.google.genai.types.LiveServerContent; +import com.google.genai.types.LiveServerMessage; +import com.google.genai.types.Part; +import com.google.genai.types.Tool; +import com.google.genai.types.ToolCodeExecution; +import java.util.concurrent.CompletableFuture; + +public class LiveCodeExecWithTxt { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.0-flash-live-preview-04-09"; + generateContent(modelId); + } + + // Shows how to generate content with the Code Execution tool and a text input. + public static void generateContent(String modelId) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = Client.builder().location("us-central1").vertexAI(true).build()) { + + // Connects to the live server. + CompletableFuture sessionFuture = + client.async.live.connect( + modelId, + LiveConnectConfig.builder() + .responseModalities(TEXT) + .tools(Tool.builder().codeExecution(ToolCodeExecution.builder().build()).build()) + .build()); + + // Sends and receives messages from the server. + sessionFuture + .thenCompose( + session -> { + // A future that completes when the server signals the end of its turn. + CompletableFuture turnComplete = new CompletableFuture<>(); + // Starts receiving messages from the live server. + session.receive(message -> handleLiveServerMessage(message, turnComplete)); + // Sends content to the server and waits for the turn to complete. + return sendContent(session) + .thenCompose(unused -> turnComplete) + .thenCompose(unused -> session.close()); + }) + .join(); + + // Example output: + // > Compute the largest prime palindrome under 100000 + // text: Okay, I need + // text: to find the largest prime palindrome less than 100000... + // code: ExecutableCode{code=Optional[def is_prime(n):... + // result: CodeExecutionResult{outcome=Optional[OUTCOME_OK], + // output=Optional[largest_prime_palindrome=98689... + // The model is done generating. + } + } + + // Sends content to the server. + private static CompletableFuture sendContent(AsyncSession session) { + String textInput = "Compute the largest prime palindrome under 100000"; + System.out.printf("> %s\n", textInput); + return session.sendClientContent( + LiveSendClientContentParameters.builder() + .turns(Content.builder().role("user").parts(Part.fromText(textInput)).build()) + .turnComplete(true) + .build()); + } + + // Prints response messages from the server and signals `turnComplete` when the server is done. + private static void handleLiveServerMessage( + LiveServerMessage message, CompletableFuture turnComplete) { + message + .serverContent() + .flatMap(LiveServerContent::modelTurn) + .flatMap(Content::parts) + .ifPresent( + parts -> + parts.forEach( + part -> { + part.text().ifPresent(text -> System.out.println("text: " + text)); + part.executableCode().ifPresent(code -> System.out.println("code: " + code)); + part.codeExecutionResult() + .ifPresent(result -> System.out.println("result: " + result)); + })); + + if (message.serverContent().flatMap(LiveServerContent::turnComplete).orElse(false)) { + System.out.println("The model is done generating."); + turnComplete.complete(null); + } + } +} +// [END googlegenaisdk_live_code_exec_with_txt] diff --git a/genai/snippets/src/main/java/genai/live/LiveGroundGoogSearchWithTxt.java b/genai/snippets/src/main/java/genai/live/LiveGroundGoogSearchWithTxt.java new file mode 100644 index 00000000000..3795a8bd910 --- /dev/null +++ b/genai/snippets/src/main/java/genai/live/LiveGroundGoogSearchWithTxt.java @@ -0,0 +1,110 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.live; + +// [START googlegenaisdk_live_ground_googsearch_with_txt] + +import static com.google.genai.types.Modality.Known.TEXT; + +import com.google.genai.AsyncSession; +import com.google.genai.Client; +import com.google.genai.types.Content; +import com.google.genai.types.GoogleSearch; +import com.google.genai.types.LiveConnectConfig; +import com.google.genai.types.LiveSendClientContentParameters; +import com.google.genai.types.LiveServerContent; +import com.google.genai.types.LiveServerMessage; +import com.google.genai.types.Part; +import com.google.genai.types.Tool; +import java.util.concurrent.CompletableFuture; + +public class LiveGroundGoogSearchWithTxt { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.0-flash-live-preview-04-09"; + generateContent(modelId); + } + + // Shows how to generate content with the Google Search tool and a text input. + public static void generateContent(String modelId) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = Client.builder().location("us-central1").vertexAI(true).build()) { + + // Connects to the live server. + CompletableFuture sessionFuture = + client.async.live.connect( + modelId, + LiveConnectConfig.builder() + .responseModalities(TEXT) + .tools(Tool.builder().googleSearch(GoogleSearch.builder().build()).build()) + .build()); + + // Sends and receives messages from the server. + sessionFuture + .thenCompose( + session -> { + // A future that completes when the server signals the end of its turn. + CompletableFuture turnComplete = new CompletableFuture<>(); + // Starts receiving messages from the live server. + session.receive(message -> handleLiveServerMessage(message, turnComplete)); + // Sends content to the server and waits for the turn to complete. + return sendContent(session) + .thenCompose(unused -> turnComplete) + .thenCompose(unused -> session.close()); + }) + .join(); + // Example response: + // > When did the last Brazil vs. Argentina soccer match happen? + // Output: The last Brazil vs + // Output: . Argentina soccer match was on March 25, 2025 + // Output: , a World Cup Qualifying match that Argentina won 4-1. Quite a game. + // The model is done generating. + } + } + + // Sends content to the live server. + private static CompletableFuture sendContent(AsyncSession session) { + String textInput = "When did the last Brazil vs. Argentina soccer match happen?"; + System.out.printf("> %s\n", textInput); + return session.sendClientContent( + LiveSendClientContentParameters.builder() + .turns(Content.builder().role("user").parts(Part.fromText(textInput)).build()) + .turnComplete(true) + .build()); + } + + // Prints response messages from the server and signals `turnComplete` when the server is done. + private static void handleLiveServerMessage( + LiveServerMessage message, CompletableFuture turnComplete) { + message + .serverContent() + .flatMap(LiveServerContent::modelTurn) + .flatMap(Content::parts) + .ifPresent( + parts -> + parts.forEach( + part -> + part.text().ifPresent(text -> System.out.println("Output: " + text)))); + + if (message.serverContent().flatMap(LiveServerContent::turnComplete).orElse(false)) { + System.out.println("The model is done generating."); + turnComplete.complete(null); + } + } +} +// [END googlegenaisdk_live_ground_googsearch_with_txt] diff --git a/genai/snippets/src/main/java/genai/live/LiveTranscribeWithAudio.java b/genai/snippets/src/main/java/genai/live/LiveTranscribeWithAudio.java new file mode 100644 index 00000000000..c665c8db6c2 --- /dev/null +++ b/genai/snippets/src/main/java/genai/live/LiveTranscribeWithAudio.java @@ -0,0 +1,119 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.live; + +// [START googlegenaisdk_live_transcribe_with_audio] + +import static com.google.genai.types.Modality.Known.AUDIO; + +import com.google.genai.AsyncSession; +import com.google.genai.Client; +import com.google.genai.types.AudioTranscriptionConfig; +import com.google.genai.types.Content; +import com.google.genai.types.LiveConnectConfig; +import com.google.genai.types.LiveSendClientContentParameters; +import com.google.genai.types.LiveServerContent; +import com.google.genai.types.LiveServerMessage; +import com.google.genai.types.Part; +import com.google.genai.types.Transcription; +import java.util.concurrent.CompletableFuture; + +public class LiveTranscribeWithAudio { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-live-2.5-flash-preview-native-audio"; + generateContent(modelId); + } + + // Shows how to transcribe audio + public static void generateContent(String modelId) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = Client.builder().location("us-central1").vertexAI(true).build()) { + + // Connects to the live server. + CompletableFuture sessionFuture = + client.async.live.connect( + modelId, + LiveConnectConfig.builder() + .responseModalities(AUDIO) + .inputAudioTranscription(AudioTranscriptionConfig.builder().build()) + .outputAudioTranscription(AudioTranscriptionConfig.builder().build()) + .build()); + + // Sends and receives messages from the live server. + sessionFuture + .thenCompose( + session -> { + // A future that completes when the server signals the end of its turn. + CompletableFuture turnComplete = new CompletableFuture<>(); + // Starts receiving messages from the live server. + session.receive(message -> handleLiveServerMessage(message, turnComplete)); + // Sends content to the server and waits for the turn to complete. + return sendContent(session) + .thenCompose(unused -> turnComplete) + .thenCompose(unused -> session.close()); + }) + .join(); + // Example output: + // > Hello? Gemini, are you there? + // Output transcript: Yes, I'm here + // Output transcript: How can I help you today? + // ... + // The model is done generating. + } + } + + // Sends content to the live server. + private static CompletableFuture sendContent(AsyncSession session) { + String textInput = "Hello? Gemini, are you there?"; + System.out.printf("> %s\n", textInput); + return session.sendClientContent( + LiveSendClientContentParameters.builder() + .turns(Content.builder().role("user").parts(Part.fromText(textInput)).build()) + .turnComplete(true) + .build()); + } + + // Prints response messages from the server and signals `turnComplete` when the server is done. + private static void handleLiveServerMessage( + LiveServerMessage message, CompletableFuture turnComplete) { + + if (message.serverContent().isPresent()) { + LiveServerContent serverContent = message.serverContent().get(); + serverContent + .modelTurn() + .ifPresent(modelTurn -> System.out.println("Model turn: " + modelTurn.parts())); + + serverContent + .inputTranscription() + .flatMap(Transcription::text) + .ifPresent(text -> System.out.println("Input transcript: " + text)); + + serverContent + .outputTranscription() + .flatMap(Transcription::text) + .ifPresent(text -> System.out.println("Output transcript: " + text)); + + if (serverContent.turnComplete().orElse(false)) { + System.out.println("The model is done generating."); + turnComplete.complete(null); + } + } + } +} +// [END googlegenaisdk_live_transcribe_with_audio] diff --git a/genai/snippets/src/main/java/genai/live/LiveWithTxt.java b/genai/snippets/src/main/java/genai/live/LiveWithTxt.java new file mode 100644 index 00000000000..4b5c3090a45 --- /dev/null +++ b/genai/snippets/src/main/java/genai/live/LiveWithTxt.java @@ -0,0 +1,108 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.live; + +// [START googlegenaisdk_live_with_txt] + +import static com.google.genai.types.Modality.Known.TEXT; + +import com.google.genai.AsyncSession; +import com.google.genai.Client; +import com.google.genai.types.Content; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.LiveConnectConfig; +import com.google.genai.types.LiveSendClientContentParameters; +import com.google.genai.types.LiveServerContent; +import com.google.genai.types.LiveServerMessage; +import com.google.genai.types.Part; +import java.util.concurrent.CompletableFuture; + +public class LiveWithTxt { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.0-flash-live-preview-04-09"; + generateContent(modelId); + } + + // Shows how to send a text prompt and receive messages from the live server. + public static void generateContent(String modelId) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = + Client.builder() + .location("us-central1") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1beta1").build()) + .build()) { + + // Connects to the live server. + CompletableFuture sessionFuture = + client.async.live.connect( + modelId, LiveConnectConfig.builder().responseModalities(TEXT).build()); + + // Sends and receives messages from the server. + sessionFuture + .thenCompose( + session -> { + // A future that completes when the server signals the end of its turn. + CompletableFuture turnComplete = new CompletableFuture<>(); + // Starts receiving messages from the live server. + session.receive(message -> handleLiveServerMessage(message, turnComplete)); + // Sends content to the server and waits for the turn to complete. + return sendContent(session) + .thenCompose(unused -> turnComplete) + .thenCompose(unused -> session.close()); + }) + .join(); + // Example output: + // > Hello? Gemini, are you there? + // Output: Yes + // Output: , I'm here. How can I help you today? + // The model is done generating. + } + } + + // Sends content to the server. + private static CompletableFuture sendContent(AsyncSession session) { + String textInput = "Hello? Gemini, are you there?"; + System.out.printf("> %s\n", textInput); + return session.sendClientContent( + LiveSendClientContentParameters.builder() + .turns(Content.builder().role("user").parts(Part.fromText(textInput)).build()) + .turnComplete(true) + .build()); + } + + // Prints response messages from the server and signals `turnComplete` when the server is done. + private static void handleLiveServerMessage( + LiveServerMessage message, CompletableFuture turnComplete) { + message + .serverContent() + .flatMap(LiveServerContent::modelTurn) + .flatMap(Content::parts) + .ifPresent( + parts -> + parts.forEach( + part -> part.text().ifPresent(text -> System.out.println("Output: " + text)))); + + if (message.serverContent().flatMap(LiveServerContent::turnComplete).orElse(false)) { + System.out.println("The model is done generating."); + turnComplete.complete(null); + } + } +} +// [END googlegenaisdk_live_with_txt] diff --git a/genai/snippets/src/test/java/genai/live/LiveIT.java b/genai/snippets/src/test/java/genai/live/LiveIT.java new file mode 100644 index 00000000000..315edf9b8d3 --- /dev/null +++ b/genai/snippets/src/test/java/genai/live/LiveIT.java @@ -0,0 +1,105 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.live; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class LiveIT { + + private static final String GEMINI_FLASH_LIVE_PREVIEW = "gemini-2.0-flash-live-preview-04-09"; + private static final String GEMINI_FLASH_LIVE_PREVIEW_NATIVE_AUDIO = + "gemini-live-2.5-flash-preview-native-audio"; + private ByteArrayOutputStream bout; + private PrintStream out; + + // Check if the required environment variables are set. + public static void requireEnvVar(String envVarName) { + assertWithMessage(String.format("Missing environment variable '%s' ", envVarName)) + .that(System.getenv(envVarName)) + .isNotEmpty(); + } + + @BeforeClass + public static void checkRequirements() { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + } + + @After + public void tearDown() { + System.setOut(null); + bout.reset(); + } + + @Test + public void testLiveCodeExecWithTxt() { + LiveCodeExecWithTxt.generateContent(GEMINI_FLASH_LIVE_PREVIEW); + String output = bout.toString(); + assertThat(output).contains("> Compute the largest prime palindrome under 100000"); + assertThat(output).contains("text:"); + assertThat(output).contains("code: ExecutableCode{code="); + assertThat(output).contains("result: CodeExecutionResult{outcome"); + assertThat(output).contains("The model is done generating."); + } + + @Test + public void testLiveGroundGoogSearchWithTxt() { + LiveGroundGoogSearchWithTxt.generateContent(GEMINI_FLASH_LIVE_PREVIEW); + String output = bout.toString(); + assertThat(output).contains("> When did the last Brazil vs. Argentina soccer match happen?"); + assertThat(output).contains("Output:"); + assertThat(output).contains("The model is done generating."); + } + + @Test + public void testLiveTranscribeWithAudio() { + LiveTranscribeWithAudio.generateContent(GEMINI_FLASH_LIVE_PREVIEW_NATIVE_AUDIO); + String output = bout.toString(); + assertThat(output).contains("> Hello? Gemini, are you there?"); + assertThat(output).contains("Model turn:"); + assertThat(output).contains("Output transcript:"); + assertThat(output).contains("The model is done generating."); + } + + @Test + public void testLiveWithTxt() { + LiveWithTxt.generateContent(GEMINI_FLASH_LIVE_PREVIEW); + String output = bout.toString(); + assertThat(output).contains("> Hello? Gemini, are you there?"); + assertThat(output).contains("Output:"); + assertThat(output).contains("The model is done generating."); + } +} From 327fca426e651105a4e2de10ec189bcb4386eb88 Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Thu, 30 Oct 2025 17:30:19 -0300 Subject: [PATCH 2/4] refactor(genai): change way of handling live server messages --- .../genai/live/LiveTranscribeWithAudio.java | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/genai/snippets/src/main/java/genai/live/LiveTranscribeWithAudio.java b/genai/snippets/src/main/java/genai/live/LiveTranscribeWithAudio.java index c665c8db6c2..3d477372a94 100644 --- a/genai/snippets/src/main/java/genai/live/LiveTranscribeWithAudio.java +++ b/genai/snippets/src/main/java/genai/live/LiveTranscribeWithAudio.java @@ -93,27 +93,29 @@ private static CompletableFuture sendContent(AsyncSession session) { private static void handleLiveServerMessage( LiveServerMessage message, CompletableFuture turnComplete) { - if (message.serverContent().isPresent()) { - LiveServerContent serverContent = message.serverContent().get(); - serverContent - .modelTurn() - .ifPresent(modelTurn -> System.out.println("Model turn: " + modelTurn.parts())); + message + .serverContent() + .ifPresent( + serverContent -> { + serverContent + .modelTurn() + .ifPresent(modelTurn -> System.out.println("Model turn: " + modelTurn.parts())); - serverContent - .inputTranscription() - .flatMap(Transcription::text) - .ifPresent(text -> System.out.println("Input transcript: " + text)); + serverContent + .inputTranscription() + .flatMap(Transcription::text) + .ifPresent(text -> System.out.println("Input transcript: " + text)); - serverContent - .outputTranscription() - .flatMap(Transcription::text) - .ifPresent(text -> System.out.println("Output transcript: " + text)); + serverContent + .outputTranscription() + .flatMap(Transcription::text) + .ifPresent(text -> System.out.println("Output transcript: " + text)); - if (serverContent.turnComplete().orElse(false)) { - System.out.println("The model is done generating."); - turnComplete.complete(null); - } - } + if (serverContent.turnComplete().orElse(false)) { + System.out.println("The model is done generating."); + turnComplete.complete(null); + } + }); } } // [END googlegenaisdk_live_transcribe_with_audio] From 098a6da4393a732e424ad53e985973e96dc3d5b3 Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Thu, 20 Nov 2025 18:00:53 -0300 Subject: [PATCH 3/4] chore(genai): update genai version --- genai/snippets/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/genai/snippets/pom.xml b/genai/snippets/pom.xml index 2adf3e16ea9..a27c899bc68 100644 --- a/genai/snippets/pom.xml +++ b/genai/snippets/pom.xml @@ -51,7 +51,7 @@ com.google.genai google-genai - 1.25.0 + 1.28.0 com.google.cloud From 4d5f60c02b99a7e597f00ddb0060056b8b6dbd9d Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Wed, 26 Nov 2025 18:46:07 -0300 Subject: [PATCH 4/4] refactor(genai): change return type and improve comments --- .../java/genai/live/LiveCodeExecWithTxt.java | 49 +++++++++-------- .../live/LiveGroundGoogSearchWithTxt.java | 54 ++++++++++--------- .../genai/live/LiveTranscribeWithAudio.java | 49 +++++++++-------- .../src/main/java/genai/live/LiveWithTxt.java | 51 +++++++++--------- .../src/test/java/genai/live/LiveIT.java | 34 ++++-------- 5 files changed, 117 insertions(+), 120 deletions(-) diff --git a/genai/snippets/src/main/java/genai/live/LiveCodeExecWithTxt.java b/genai/snippets/src/main/java/genai/live/LiveCodeExecWithTxt.java index cec3a19cc1f..a75b829155f 100644 --- a/genai/snippets/src/main/java/genai/live/LiveCodeExecWithTxt.java +++ b/genai/snippets/src/main/java/genai/live/LiveCodeExecWithTxt.java @@ -41,7 +41,7 @@ public static void main(String[] args) { } // Shows how to generate content with the Code Execution tool and a text input. - public static void generateContent(String modelId) { + public static String generateContent(String modelId) { // Client Initialization. Once created, it can be reused for multiple requests. try (Client client = Client.builder().location("us-central1").vertexAI(true).build()) { @@ -54,33 +54,36 @@ public static void generateContent(String modelId) { .tools(Tool.builder().codeExecution(ToolCodeExecution.builder().build()).build()) .build()); - // Sends and receives messages from the server. - sessionFuture - .thenCompose( + // Sends and receives messages from the live session. + CompletableFuture responseFuture = + sessionFuture.thenCompose( session -> { - // A future that completes when the server signals the end of its turn. + // A future that completes when the model signals the end of its turn. CompletableFuture turnComplete = new CompletableFuture<>(); - // Starts receiving messages from the live server. - session.receive(message -> handleLiveServerMessage(message, turnComplete)); - // Sends content to the server and waits for the turn to complete. + // A variable to concatenate the text response from the model. + StringBuilder modelResponse = new StringBuilder(); + // Starts receiving messages from the live session. + session.receive( + message -> handleLiveServerMessage(message, turnComplete, modelResponse)); + // Sends content to the live session and waits for the turn to complete. return sendContent(session) .thenCompose(unused -> turnComplete) - .thenCompose(unused -> session.close()); - }) - .join(); + .thenCompose( + unused -> session.close().thenApply(result -> modelResponse.toString())); + }); + String response = responseFuture.join(); + System.out.println(response); // Example output: // > Compute the largest prime palindrome under 100000 - // text: Okay, I need - // text: to find the largest prime palindrome less than 100000... - // code: ExecutableCode{code=Optional[def is_prime(n):... - // result: CodeExecutionResult{outcome=Optional[OUTCOME_OK], - // output=Optional[largest_prime_palindrome=98689... - // The model is done generating. + // + // Okay, I understand. I need to find the largest prime number that is also a palindrome + // and is less than 100000. Here's my plan... + return response; } } - // Sends content to the server. + // Sends content to the live session. private static CompletableFuture sendContent(AsyncSession session) { String textInput = "Compute the largest prime palindrome under 100000"; System.out.printf("> %s\n", textInput); @@ -91,9 +94,10 @@ private static CompletableFuture sendContent(AsyncSession session) { .build()); } - // Prints response messages from the server and signals `turnComplete` when the server is done. + // Concatenates the response messages from the model and signals + // `turnComplete` when the model is done generating the response. private static void handleLiveServerMessage( - LiveServerMessage message, CompletableFuture turnComplete) { + LiveServerMessage message, CompletableFuture turnComplete, StringBuilder response) { message .serverContent() .flatMap(LiveServerContent::modelTurn) @@ -102,14 +106,13 @@ private static void handleLiveServerMessage( parts -> parts.forEach( part -> { - part.text().ifPresent(text -> System.out.println("text: " + text)); + part.text().ifPresent(response::append); part.executableCode().ifPresent(code -> System.out.println("code: " + code)); part.codeExecutionResult() .ifPresent(result -> System.out.println("result: " + result)); })); - + // Checks if the model's turn is over. if (message.serverContent().flatMap(LiveServerContent::turnComplete).orElse(false)) { - System.out.println("The model is done generating."); turnComplete.complete(null); } } diff --git a/genai/snippets/src/main/java/genai/live/LiveGroundGoogSearchWithTxt.java b/genai/snippets/src/main/java/genai/live/LiveGroundGoogSearchWithTxt.java index 3795a8bd910..2807f09a4d1 100644 --- a/genai/snippets/src/main/java/genai/live/LiveGroundGoogSearchWithTxt.java +++ b/genai/snippets/src/main/java/genai/live/LiveGroundGoogSearchWithTxt.java @@ -41,7 +41,7 @@ public static void main(String[] args) { } // Shows how to generate content with the Google Search tool and a text input. - public static void generateContent(String modelId) { + public static String generateContent(String modelId) { // Client Initialization. Once created, it can be reused for multiple requests. try (Client client = Client.builder().location("us-central1").vertexAI(true).build()) { @@ -54,30 +54,36 @@ public static void generateContent(String modelId) { .tools(Tool.builder().googleSearch(GoogleSearch.builder().build()).build()) .build()); - // Sends and receives messages from the server. - sessionFuture - .thenCompose( + // Sends and receives messages from the live session. + CompletableFuture responseFuture = + sessionFuture.thenCompose( session -> { - // A future that completes when the server signals the end of its turn. + // A future that completes when the model signals the end of its turn. CompletableFuture turnComplete = new CompletableFuture<>(); - // Starts receiving messages from the live server. - session.receive(message -> handleLiveServerMessage(message, turnComplete)); - // Sends content to the server and waits for the turn to complete. + // A variable to concatenate the text response from the model. + StringBuilder modelResponse = new StringBuilder(); + // Starts receiving messages from the live session. + session.receive( + message -> handleLiveServerMessage(message, turnComplete, modelResponse)); + // Sends content to the live session and waits for the turn to complete. return sendContent(session) .thenCompose(unused -> turnComplete) - .thenCompose(unused -> session.close()); - }) - .join(); - // Example response: + .thenCompose( + unused -> session.close().thenApply(result -> modelResponse.toString())); + }); + + String response = responseFuture.join(); + System.out.println(response); + // Example output: // > When did the last Brazil vs. Argentina soccer match happen? - // Output: The last Brazil vs - // Output: . Argentina soccer match was on March 25, 2025 - // Output: , a World Cup Qualifying match that Argentina won 4-1. Quite a game. - // The model is done generating. + // + // The most recent Brazil vs. Argentina soccer match was on March 25, 2025, + // as part of the 2026 World Cup qualifiers. Argentina won 4-1. + return response; } } - // Sends content to the live server. + // Sends content to the live session. private static CompletableFuture sendContent(AsyncSession session) { String textInput = "When did the last Brazil vs. Argentina soccer match happen?"; System.out.printf("> %s\n", textInput); @@ -88,21 +94,17 @@ private static CompletableFuture sendContent(AsyncSession session) { .build()); } - // Prints response messages from the server and signals `turnComplete` when the server is done. + // Concatenates the response messages from the model and signals + // `turnComplete` when the model is done generating the response. private static void handleLiveServerMessage( - LiveServerMessage message, CompletableFuture turnComplete) { + LiveServerMessage message, CompletableFuture turnComplete, StringBuilder response) { message .serverContent() .flatMap(LiveServerContent::modelTurn) .flatMap(Content::parts) - .ifPresent( - parts -> - parts.forEach( - part -> - part.text().ifPresent(text -> System.out.println("Output: " + text)))); - + .ifPresent(parts -> parts.forEach(part -> part.text().ifPresent(response::append))); + // Checks if the model's turn is over. if (message.serverContent().flatMap(LiveServerContent::turnComplete).orElse(false)) { - System.out.println("The model is done generating."); turnComplete.complete(null); } } diff --git a/genai/snippets/src/main/java/genai/live/LiveTranscribeWithAudio.java b/genai/snippets/src/main/java/genai/live/LiveTranscribeWithAudio.java index 3d477372a94..14ac56f2f82 100644 --- a/genai/snippets/src/main/java/genai/live/LiveTranscribeWithAudio.java +++ b/genai/snippets/src/main/java/genai/live/LiveTranscribeWithAudio.java @@ -40,8 +40,8 @@ public static void main(String[] args) { generateContent(modelId); } - // Shows how to transcribe audio - public static void generateContent(String modelId) { + // Shows how to transcribe audio. + public static String generateContent(String modelId) { // Client Initialization. Once created, it can be reused for multiple requests. try (Client client = Client.builder().location("us-central1").vertexAI(true).build()) { @@ -55,30 +55,35 @@ public static void generateContent(String modelId) { .outputAudioTranscription(AudioTranscriptionConfig.builder().build()) .build()); - // Sends and receives messages from the live server. - sessionFuture - .thenCompose( + // Sends and receives messages from the live session. + CompletableFuture responseFuture = + sessionFuture.thenCompose( session -> { - // A future that completes when the server signals the end of its turn. + // A future that completes when the model signals the end of its turn. CompletableFuture turnComplete = new CompletableFuture<>(); - // Starts receiving messages from the live server. - session.receive(message -> handleLiveServerMessage(message, turnComplete)); - // Sends content to the server and waits for the turn to complete. + // A variable to concatenate the text response from the model. + StringBuilder modelResponse = new StringBuilder(); + // Starts receiving messages from the live session. + session.receive( + message -> handleLiveServerMessage(message, turnComplete, modelResponse)); + // Sends content to the live session and waits for the turn to complete. return sendContent(session) .thenCompose(unused -> turnComplete) - .thenCompose(unused -> session.close()); - }) - .join(); + .thenCompose( + unused -> session.close().thenApply(result -> modelResponse.toString())); + }); + + String response = responseFuture.join(); + System.out.println(response); // Example output: // > Hello? Gemini, are you there? - // Output transcript: Yes, I'm here - // Output transcript: How can I help you today? - // ... - // The model is done generating. + // + // Yes, I'm here. How can I help you today? + return response; } } - // Sends content to the live server. + // Sends content to the live session. private static CompletableFuture sendContent(AsyncSession session) { String textInput = "Hello? Gemini, are you there?"; System.out.printf("> %s\n", textInput); @@ -89,9 +94,10 @@ private static CompletableFuture sendContent(AsyncSession session) { .build()); } - // Prints response messages from the server and signals `turnComplete` when the server is done. + // Concatenates the output transcription from the model and signals + // `turnComplete` when the model is done generating the response. private static void handleLiveServerMessage( - LiveServerMessage message, CompletableFuture turnComplete) { + LiveServerMessage message, CompletableFuture turnComplete, StringBuilder response) { message .serverContent() @@ -109,10 +115,9 @@ private static void handleLiveServerMessage( serverContent .outputTranscription() .flatMap(Transcription::text) - .ifPresent(text -> System.out.println("Output transcript: " + text)); - + .ifPresent(response::append); + // Checks if the model's turn is over. if (serverContent.turnComplete().orElse(false)) { - System.out.println("The model is done generating."); turnComplete.complete(null); } }); diff --git a/genai/snippets/src/main/java/genai/live/LiveWithTxt.java b/genai/snippets/src/main/java/genai/live/LiveWithTxt.java index 4b5c3090a45..3607b92f67e 100644 --- a/genai/snippets/src/main/java/genai/live/LiveWithTxt.java +++ b/genai/snippets/src/main/java/genai/live/LiveWithTxt.java @@ -39,8 +39,8 @@ public static void main(String[] args) { generateContent(modelId); } - // Shows how to send a text prompt and receive messages from the live server. - public static void generateContent(String modelId) { + // Shows how to send a text prompt and receive messages from the live session. + public static String generateContent(String modelId) { // Client Initialization. Once created, it can be reused for multiple requests. try (Client client = Client.builder() @@ -54,29 +54,35 @@ public static void generateContent(String modelId) { client.async.live.connect( modelId, LiveConnectConfig.builder().responseModalities(TEXT).build()); - // Sends and receives messages from the server. - sessionFuture - .thenCompose( + // Sends and receives messages from the live session. + CompletableFuture responseFuture = + sessionFuture.thenCompose( session -> { - // A future that completes when the server signals the end of its turn. + // A future that completes when the model signals the end of its turn. CompletableFuture turnComplete = new CompletableFuture<>(); - // Starts receiving messages from the live server. - session.receive(message -> handleLiveServerMessage(message, turnComplete)); - // Sends content to the server and waits for the turn to complete. + // A variable to concatenate the text response from the model. + StringBuilder modelResponse = new StringBuilder(); + // Starts receiving messages from the live session. + session.receive( + message -> handleLiveServerMessage(message, turnComplete, modelResponse)); + // Sends content to the live session and waits for the turn to complete. return sendContent(session) .thenCompose(unused -> turnComplete) - .thenCompose(unused -> session.close()); - }) - .join(); + .thenCompose( + unused -> session.close().thenApply(result -> modelResponse.toString())); + }); + + String response = responseFuture.join(); + System.out.println(response); // Example output: // > Hello? Gemini, are you there? - // Output: Yes - // Output: , I'm here. How can I help you today? - // The model is done generating. + // + // Yes, I am here. How can I help you today? + return response; } } - // Sends content to the server. + // Sends content to the live session. private static CompletableFuture sendContent(AsyncSession session) { String textInput = "Hello? Gemini, are you there?"; System.out.printf("> %s\n", textInput); @@ -87,20 +93,17 @@ private static CompletableFuture sendContent(AsyncSession session) { .build()); } - // Prints response messages from the server and signals `turnComplete` when the server is done. + // Concatenates the output transcription from the model and signals + // `turnComplete` when the model is done generating the response. private static void handleLiveServerMessage( - LiveServerMessage message, CompletableFuture turnComplete) { + LiveServerMessage message, CompletableFuture turnComplete, StringBuilder response) { message .serverContent() .flatMap(LiveServerContent::modelTurn) .flatMap(Content::parts) - .ifPresent( - parts -> - parts.forEach( - part -> part.text().ifPresent(text -> System.out.println("Output: " + text)))); - + .ifPresent(parts -> parts.forEach(part -> part.text().ifPresent(response::append))); + // Checks if the model's turn is over. if (message.serverContent().flatMap(LiveServerContent::turnComplete).orElse(false)) { - System.out.println("The model is done generating."); turnComplete.complete(null); } } diff --git a/genai/snippets/src/test/java/genai/live/LiveIT.java b/genai/snippets/src/test/java/genai/live/LiveIT.java index 315edf9b8d3..cc8226eb233 100644 --- a/genai/snippets/src/test/java/genai/live/LiveIT.java +++ b/genai/snippets/src/test/java/genai/live/LiveIT.java @@ -20,9 +20,7 @@ import static com.google.common.truth.Truth.assertWithMessage; import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.io.PrintStream; -import java.util.List; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; @@ -66,40 +64,26 @@ public void tearDown() { @Test public void testLiveCodeExecWithTxt() { - LiveCodeExecWithTxt.generateContent(GEMINI_FLASH_LIVE_PREVIEW); - String output = bout.toString(); - assertThat(output).contains("> Compute the largest prime palindrome under 100000"); - assertThat(output).contains("text:"); - assertThat(output).contains("code: ExecutableCode{code="); - assertThat(output).contains("result: CodeExecutionResult{outcome"); - assertThat(output).contains("The model is done generating."); + String response = LiveCodeExecWithTxt.generateContent(GEMINI_FLASH_LIVE_PREVIEW); + assertThat(response).isNotEmpty(); } @Test public void testLiveGroundGoogSearchWithTxt() { - LiveGroundGoogSearchWithTxt.generateContent(GEMINI_FLASH_LIVE_PREVIEW); - String output = bout.toString(); - assertThat(output).contains("> When did the last Brazil vs. Argentina soccer match happen?"); - assertThat(output).contains("Output:"); - assertThat(output).contains("The model is done generating."); + String response = LiveGroundGoogSearchWithTxt.generateContent(GEMINI_FLASH_LIVE_PREVIEW); + assertThat(response).isNotEmpty(); } @Test public void testLiveTranscribeWithAudio() { - LiveTranscribeWithAudio.generateContent(GEMINI_FLASH_LIVE_PREVIEW_NATIVE_AUDIO); - String output = bout.toString(); - assertThat(output).contains("> Hello? Gemini, are you there?"); - assertThat(output).contains("Model turn:"); - assertThat(output).contains("Output transcript:"); - assertThat(output).contains("The model is done generating."); + String response = + LiveTranscribeWithAudio.generateContent(GEMINI_FLASH_LIVE_PREVIEW_NATIVE_AUDIO); + assertThat(response).isNotEmpty(); } @Test public void testLiveWithTxt() { - LiveWithTxt.generateContent(GEMINI_FLASH_LIVE_PREVIEW); - String output = bout.toString(); - assertThat(output).contains("> Hello? Gemini, are you there?"); - assertThat(output).contains("Output:"); - assertThat(output).contains("The model is done generating."); + String response = LiveWithTxt.generateContent(GEMINI_FLASH_LIVE_PREVIEW); + assertThat(response).isNotEmpty(); } }