From 833e655f101be7ffdbafd47eed0172306f9fc888 Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Thu, 23 Oct 2025 15:58:14 -0300 Subject: [PATCH 1/4] feat(genai): add new tool samples --- .../ToolsGoogleMapsCoordinatesWithTxt.java | 76 ++++++++++++++++ ...ToolsGoogleSearchAndUrlContextWithTxt.java | 84 +++++++++++++++++ .../genai/tools/ToolsUrlContextWithTxt.java | 90 +++++++++++++++++++ .../src/test/java/genai/tools/ToolsIT.java | 41 +++++++-- 4 files changed, 284 insertions(+), 7 deletions(-) create mode 100644 genai/snippets/src/main/java/genai/tools/ToolsGoogleMapsCoordinatesWithTxt.java create mode 100644 genai/snippets/src/main/java/genai/tools/ToolsGoogleSearchAndUrlContextWithTxt.java create mode 100644 genai/snippets/src/main/java/genai/tools/ToolsUrlContextWithTxt.java diff --git a/genai/snippets/src/main/java/genai/tools/ToolsGoogleMapsCoordinatesWithTxt.java b/genai/snippets/src/main/java/genai/tools/ToolsGoogleMapsCoordinatesWithTxt.java new file mode 100644 index 00000000000..1b7f749539f --- /dev/null +++ b/genai/snippets/src/main/java/genai/tools/ToolsGoogleMapsCoordinatesWithTxt.java @@ -0,0 +1,76 @@ +/* + * 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.tools; + +// [START googlegenaisdk_tools_google_maps_coordinates_with_txt] + +import com.google.genai.Client; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.GoogleMaps; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.LatLng; +import com.google.genai.types.RetrievalConfig; +import com.google.genai.types.Tool; +import com.google.genai.types.ToolConfig; + +public class ToolsGoogleMapsCoordinatesWithTxt { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + generateContent(modelId); + } + + // Generates content with Google Maps Tool. + public static String generateContent(String modelId) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + // Set the Google Maps Tool. + Tool tool = Tool.builder().googleMaps(GoogleMaps.builder().build()).build(); + + ToolConfig toolConfig = + ToolConfig.builder() + .retrievalConfig( + RetrievalConfig.builder() + // Pass coordinates for location-aware grounding + .latLng(LatLng.builder().latitude(40.7128).longitude(-74.006).build()) + // Localize Maps results + .languageCode("en_US") + .build()) + .build(); + + GenerateContentResponse response = + client.models.generateContent( + modelId, + "Where can I get the best espresso near me?", + GenerateContentConfig.builder().tools(tool).toolConfig(toolConfig).build()); + + System.out.println(response.text()); + // Example response: + // Here are some of the top-rated coffee shops near you that serve excellent espresso... + return response.text(); + } + } +} +// [END googlegenaisdk_tools_google_maps_coordinates_with_txt] diff --git a/genai/snippets/src/main/java/genai/tools/ToolsGoogleSearchAndUrlContextWithTxt.java b/genai/snippets/src/main/java/genai/tools/ToolsGoogleSearchAndUrlContextWithTxt.java new file mode 100644 index 00000000000..ebf62347672 --- /dev/null +++ b/genai/snippets/src/main/java/genai/tools/ToolsGoogleSearchAndUrlContextWithTxt.java @@ -0,0 +1,84 @@ +/* + * 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.tools; + +// [START googlegenaisdk_tools_google_search_and_urlcontext_with_txt] + +import com.google.genai.Client; +import com.google.genai.types.Candidate; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.GoogleSearch; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Tool; +import com.google.genai.types.UrlContext; +import java.util.List; + +public class ToolsGoogleSearchAndUrlContextWithTxt { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + String url = "https://www.google.com/search?q=events+in+New+York"; + generateContent(modelId, url); + } + + // Generates content with the Url Context and Google Search Tools. + public static String generateContent(String modelId, String url) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1beta1").build()) + .build()) { + + // Set the Url Context and Google Search tools. + List tools = + List.of( + Tool.builder().urlContext(UrlContext.builder().build()).build(), + Tool.builder().googleSearch(GoogleSearch.builder().build()).build()); + + String prompt = + String.format( + "Give me three day events schedule based on %s. Also let me know what" + + " needs to be taken care of considering weather and commute.", + url); + + GenerateContentResponse response = + client.models.generateContent( + modelId, + prompt, + GenerateContentConfig.builder().tools(tools).responseModalities("TEXT").build()); + + // Get response candidate and print parts of the response. + response + .candidates() + .flatMap(candidates -> candidates.stream().findFirst()) + .flatMap(Candidate::content) + .flatMap(Content::parts) + .ifPresent(parts -> parts.forEach(part -> part.text().ifPresent(System.out::println))); + // Example response: + // Three-Day Event Schedule in... + // **Day 1: Friday, October 24, 2025** + // * **Evening Event:** Attend the **2025... + return response.text(); + } + } +} +// [END googlegenaisdk_tools_google_search_and_urlcontext_with_txt] diff --git a/genai/snippets/src/main/java/genai/tools/ToolsUrlContextWithTxt.java b/genai/snippets/src/main/java/genai/tools/ToolsUrlContextWithTxt.java new file mode 100644 index 00000000000..45505c2439d --- /dev/null +++ b/genai/snippets/src/main/java/genai/tools/ToolsUrlContextWithTxt.java @@ -0,0 +1,90 @@ +/* + * 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.tools; + +// [START googlegenaisdk_tools_urlcontext_with_txt] + +import com.google.genai.Client; +import com.google.genai.types.Candidate; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Tool; +import com.google.genai.types.UrlContext; + +public class ToolsUrlContextWithTxt { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + String url1 = "https://cloud.google.com/vertex-ai/generative-ai/docs"; + String url2 = "https://cloud.google.com/docs/overview"; + generateContent(modelId, url1, url2); + } + + // Generates text with the Url Context Tool. + public static String generateContent(String modelId, String url1, String url2) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + String prompt = + String.format("Compare the content, purpose, and audiences of %s and %s", url1, url2); + + // Set the Url Context Tool. + Tool tool = Tool.builder().urlContext(UrlContext.builder().build()).build(); + + GenerateContentResponse response = + client.models.generateContent( + modelId, + prompt, + GenerateContentConfig.builder().tools(tool).responseModalities("TEXT").build()); + + // Get response candidate. + Candidate candidate = + response + .candidates() + .flatMap(candidates -> candidates.stream().findFirst()) + .orElseThrow( + () -> new IllegalStateException("No response candidate generated by the model.")); + + // Print parts of the response. + candidate + .content() + .flatMap(Content::parts) + .ifPresent(parts -> parts.forEach(part -> part.text().ifPresent(System.out::println))); + // Example response: + // The two Google Cloud documentation pages serve distinct purposes and cater to different + // audiences within the broader Google Cloud ecosystem + + candidate.urlContextMetadata().ifPresent(System.out::println); + // Example response: + // UrlContextMetadata{urlMetadata=Optional[[UrlMetadata{ + // retrievedUrl=Optional[https://cloud.google.com/vertex-ai/generative-ai/docs], + // urlRetrievalStatus=Optional[URL_RETRIEVAL_STATUS_SUCCESS]}, + // UrlMetadata{retrievedUrl=Optional[https://cloud.google.com/docs/overview], + // urlRetrievalStatus=Optional[URL_RETRIEVAL_STATUS_SUCCESS]}]]} + return response.text(); + } + } +} +// [END googlegenaisdk_tools_urlcontext_with_txt] diff --git a/genai/snippets/src/test/java/genai/tools/ToolsIT.java b/genai/snippets/src/test/java/genai/tools/ToolsIT.java index 1bbf109580d..67aa971a592 100644 --- a/genai/snippets/src/test/java/genai/tools/ToolsIT.java +++ b/genai/snippets/src/test/java/genai/tools/ToolsIT.java @@ -43,7 +43,6 @@ import org.junit.runners.JUnit4; import org.mockito.MockedStatic; - @RunWith(JUnit4.class) public class ToolsIT { @@ -114,22 +113,50 @@ public void testToolsCodeExecWithTextLocalImage() throws IOException { assertThat(bout.toString()).contains("Outcome:"); } + @Test + public void testToolsGoogleMapsCoordinatesWithTxt() { + String response = ToolsGoogleMapsCoordinatesWithTxt.generateContent(GEMINI_FLASH); + assertThat(response).isNotEmpty(); + } + + @Test + public void testToolsGoogleSearchAndUrlContextWithTxt() { + String url = "https://www.google.com/search?q=events+in+New+York"; + String response = ToolsGoogleSearchAndUrlContextWithTxt.generateContent(GEMINI_FLASH, url); + assertThat(response).isNotEmpty(); + } + @Test public void testToolsGoogleSearchWithText() { String response = ToolsGoogleSearchWithText.generateContent(GEMINI_FLASH); assertThat(response).isNotEmpty(); } + @Test + public void testToolsUrlContextWithTxt() { + String url1 = "https://cloud.google.com/vertex-ai/generative-ai/docs"; + String url2 = "https://cloud.google.com/docs/overview"; + String response = ToolsUrlContextWithTxt.generateContent(GEMINI_FLASH, url1, url2); + assertThat(response).isNotEmpty(); + String output = bout.toString(); + assertThat(output).contains("UrlContextMetadata"); + assertThat(output).contains("urlRetrievalStatus"); + assertThat(output).contains("URL_RETRIEVAL_STATUS_SUCCESS"); + assertThat(output).contains(url1); + assertThat(output).contains(url2); + } + @Test public void testToolsVaisWithText() throws NoSuchFieldException, IllegalAccessException { - String response = "The process for making an appointment to renew your driver's license" + String response = + "The process for making an appointment to renew your driver's license" + " varies depending on your location."; String datastore = - String.format( - "projects/%s/locations/global/collections/default_collection/" - + "dataStores/grounding-test-datastore", - PROJECT_ID); + String.format( + "projects/%s/locations/global/collections/default_collection/" + + "dataStores/grounding-test-datastore", + PROJECT_ID); Client.Builder mockedBuilder = mock(Client.Builder.class, RETURNS_SELF); Client mockedClient = mock(Client.class); @@ -158,4 +185,4 @@ public void testToolsVaisWithText() throws NoSuchFieldException, IllegalAccessEx assertThat(response).isEqualTo(generatedResponse); } } -} \ No newline at end of file +} From bf937f4890d6a7864e3f91dc31197589178cf01f Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Thu, 23 Oct 2025 16:00:47 -0300 Subject: [PATCH 2/4] chore(genai): update comments --- .../src/main/java/genai/tools/ToolsGoogleSearchWithText.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/genai/snippets/src/main/java/genai/tools/ToolsGoogleSearchWithText.java b/genai/snippets/src/main/java/genai/tools/ToolsGoogleSearchWithText.java index 7c053dbd1e7..ff8a0d132e8 100644 --- a/genai/snippets/src/main/java/genai/tools/ToolsGoogleSearchWithText.java +++ b/genai/snippets/src/main/java/genai/tools/ToolsGoogleSearchWithText.java @@ -33,7 +33,7 @@ public static void main(String[] args) { generateContent(modelId); } - // Generates text with Google Search tool + // Generates content with Google Search tool public static String generateContent(String modelId) { // Initialize client that will be used to send requests. This client only needs to be created // once, and can be reused for multiple requests. From b2e18dbe3b99f51ac3d9e620a323ad75ba37fb5e Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Thu, 23 Oct 2025 16:39:45 -0300 Subject: [PATCH 3/4] refactor(genai): change stream chain and exception message --- ...ToolsGoogleSearchAndUrlContextWithTxt.java | 21 ++++++++++++++----- .../genai/tools/ToolsUrlContextWithTxt.java | 4 +++- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/genai/snippets/src/main/java/genai/tools/ToolsGoogleSearchAndUrlContextWithTxt.java b/genai/snippets/src/main/java/genai/tools/ToolsGoogleSearchAndUrlContextWithTxt.java index ebf62347672..ac2d3e4aad0 100644 --- a/genai/snippets/src/main/java/genai/tools/ToolsGoogleSearchAndUrlContextWithTxt.java +++ b/genai/snippets/src/main/java/genai/tools/ToolsGoogleSearchAndUrlContextWithTxt.java @@ -66,17 +66,28 @@ public static String generateContent(String modelId, String url) { prompt, GenerateContentConfig.builder().tools(tools).responseModalities("TEXT").build()); - // Get response candidate and print parts of the response. - response - .candidates() - .flatMap(candidates -> candidates.stream().findFirst()) - .flatMap(Candidate::content) + // Get response candidate. + Candidate candidate = + response + .candidates() + .flatMap(candidates -> candidates.stream().findFirst()) + .orElseThrow( + () -> + new IllegalStateException( + "No response candidate was generated by the model.")); + + // Print parts of the response. + candidate + .content() .flatMap(Content::parts) .ifPresent(parts -> parts.forEach(part -> part.text().ifPresent(System.out::println))); // Example response: // Three-Day Event Schedule in... // **Day 1: Friday, October 24, 2025** // * **Evening Event:** Attend the **2025... + + candidate.urlContextMetadata().ifPresent(System.out::println); + return response.text(); } } diff --git a/genai/snippets/src/main/java/genai/tools/ToolsUrlContextWithTxt.java b/genai/snippets/src/main/java/genai/tools/ToolsUrlContextWithTxt.java index 45505c2439d..3e16014ca5b 100644 --- a/genai/snippets/src/main/java/genai/tools/ToolsUrlContextWithTxt.java +++ b/genai/snippets/src/main/java/genai/tools/ToolsUrlContextWithTxt.java @@ -65,7 +65,9 @@ public static String generateContent(String modelId, String url1, String url2) { .candidates() .flatMap(candidates -> candidates.stream().findFirst()) .orElseThrow( - () -> new IllegalStateException("No response candidate generated by the model.")); + () -> + new IllegalStateException( + "No response candidate was generated by the model.")); // Print parts of the response. candidate From a2db63b39b8bc074a8be0006e7d009f523d2e688 Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Thu, 23 Oct 2025 16:41:41 -0300 Subject: [PATCH 4/4] refactor(genai): improve tests by checking the response --- genai/snippets/src/test/java/genai/tools/ToolsIT.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/genai/snippets/src/test/java/genai/tools/ToolsIT.java b/genai/snippets/src/test/java/genai/tools/ToolsIT.java index 67aa971a592..d6cc8285ce3 100644 --- a/genai/snippets/src/test/java/genai/tools/ToolsIT.java +++ b/genai/snippets/src/test/java/genai/tools/ToolsIT.java @@ -117,6 +117,7 @@ public void testToolsCodeExecWithTextLocalImage() throws IOException { public void testToolsGoogleMapsCoordinatesWithTxt() { String response = ToolsGoogleMapsCoordinatesWithTxt.generateContent(GEMINI_FLASH); assertThat(response).isNotEmpty(); + assertThat(bout.toString()).contains(response); } @Test @@ -124,6 +125,7 @@ public void testToolsGoogleSearchAndUrlContextWithTxt() { String url = "https://www.google.com/search?q=events+in+New+York"; String response = ToolsGoogleSearchAndUrlContextWithTxt.generateContent(GEMINI_FLASH, url); assertThat(response).isNotEmpty(); + assertThat(bout.toString()).contains(response); } @Test