From 4664f6fd10faa44a1d74d2970ae82b14b049d469 Mon Sep 17 00:00:00 2001 From: Daniel La Rocque Date: Mon, 23 Jun 2025 12:11:04 -0400 Subject: [PATCH 01/11] FirebaseAI Grounding with Google Search --- .../com/google/firebase/ai/type/Candidate.kt | 240 +++++++++++++++--- .../google/firebase/ai/type/GoogleSearch.kt | 13 + .../com/google/firebase/ai/type/Tool.kt | 33 ++- .../firebase/ai/DevAPIUnarySnapshotTests.kt | 21 ++ .../google/firebase/ai/SerializationTests.kt | 197 ++++++++++++++ .../firebase/ai/VertexAIUnarySnapshotTests.kt | 47 ++++ .../firebase/ai/common/APIControllerTests.kt | 39 +++ .../com/google/firebase/ai/type/ToolTest.kt | 40 +++ firebase-ai/update_responses.sh | 2 +- 9 files changed, 595 insertions(+), 37 deletions(-) create mode 100644 firebase-ai/src/main/kotlin/com/google/firebase/ai/type/GoogleSearch.kt create mode 100644 firebase-ai/src/test/java/com/google/firebase/ai/type/ToolTest.kt diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt index d5fc51f21c0..cbb2607c54b 100644 --- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt @@ -22,7 +22,6 @@ import com.google.firebase.ai.common.util.FirstOrdinalSerializer import java.util.Calendar import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.KSerializer -import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonNames @@ -39,7 +38,8 @@ internal constructor( public val content: Content, public val safetyRatings: List, public val citationMetadata: CitationMetadata?, - public val finishReason: FinishReason? + public val finishReason: FinishReason?, + public val groundingMetadata: GroundingMetadata? ) { @Serializable @@ -48,48 +48,22 @@ internal constructor( val finishReason: FinishReason.Internal? = null, val safetyRatings: List? = null, val citationMetadata: CitationMetadata.Internal? = null, - val groundingMetadata: GroundingMetadata? = null, + val groundingMetadata: GroundingMetadata.Internal? = null ) { internal fun toPublic(): Candidate { val safetyRatings = safetyRatings?.mapNotNull { it.toPublic() }.orEmpty() val citations = citationMetadata?.toPublic() val finishReason = finishReason?.toPublic() + val groundingMetadata = groundingMetadata?.toPublic() return Candidate( this.content?.toPublic() ?: content("model") {}, safetyRatings, citations, - finishReason + finishReason, + groundingMetadata ) } - - @Serializable - internal data class GroundingMetadata( - @SerialName("web_search_queries") val webSearchQueries: List?, - @SerialName("search_entry_point") val searchEntryPoint: SearchEntryPoint?, - @SerialName("retrieval_queries") val retrievalQueries: List?, - @SerialName("grounding_attribution") val groundingAttribution: List?, - ) { - - @Serializable - internal data class SearchEntryPoint( - @SerialName("rendered_content") val renderedContent: String?, - @SerialName("sdk_blob") val sdkBlob: String?, - ) - - @Serializable - internal data class GroundingAttribution( - val segment: Segment, - @SerialName("confidence_score") val confidenceScore: Float?, - ) { - - @Serializable - internal data class Segment( - @SerialName("start_index") val startIndex: Int, - @SerialName("end_index") val endIndex: Int, - ) - } - } } } @@ -252,7 +226,7 @@ public class FinishReason private constructor(public val name: String, public va @Serializable(Internal.Serializer::class) internal enum class Internal { UNKNOWN, - @SerialName("FINISH_REASON_UNSPECIFIED") UNSPECIFIED, + UNSPECIFIED, STOP, MAX_TOKENS, SAFETY, @@ -317,3 +291,203 @@ public class FinishReason private constructor(public val name: String, public va public val MALFORMED_FUNCTION_CALL: FinishReason = FinishReason("MALFORMED_FUNCTION_CALL", 9) } } + +/** + * Metadata returned to the client when grounding is enabled. + * + * If using Grounding with Google Search, you are required to comply with the + * [Service Specific Terms](https://cloud.google.com/terms/service-terms) for "Grounding with Google + * Search". + * + * @property webSearchQueries A list of web search queries that the model performed to gather the + * grounding information. These can be used to allow users to explore the search results themselves. + * @property searchEntryPoint Google search entry point for web searches. This contains an HTML/CSS + * snippet that **must** be embedded in an app to display a Google Search Entry point for follow-up + * web searches related to the model's "Grounded Response". To ensure proper rendering, it's + * recommended to display this content within a `WebView`. + * @property groundingChunks A list of [GroundingChunk] objects. Each chunk represents a piece of + * retrieved content (e.g. from a web page) that the model used to ground its response. + * @property groundingSupports A list of [GroundingSupport] objects. Each object details how + * specific segments of the model's response are supported by the `groundingChunks`. + */ +public class GroundingMetadata( + public val webSearchQueries: List?, + public val searchEntryPoint: SearchEntryPoint?, + public val retrievalQueries: List?, + @Deprecated("Use groundingChunks instead") + public val groundingAttribution: List?, + public val groundingChunks: List, + public val groundingSupports: List, +) { + @Serializable + internal data class Internal( + val webSearchQueries: List?, + val searchEntryPoint: SearchEntryPoint.Internal?, + val retrievalQueries: List?, + @Deprecated("Use groundingChunks instead") + val groundingAttribution: List?, + val groundingChunks: List?, + val groundingSupports: List?, + ) { + internal fun toPublic() = + GroundingMetadata( + webSearchQueries = webSearchQueries, + searchEntryPoint = searchEntryPoint?.toPublic(), + retrievalQueries = retrievalQueries, + groundingAttribution = groundingAttribution?.map { it.toPublic() }, + groundingChunks = groundingChunks?.map { it.toPublic() }.orEmpty(), + groundingSupports = groundingSupports?.map { it.toPublic() }.orEmpty() + ) + } +} + +/** + * A class representing the Google Search entry point. + * + * When using this feature, you are required to comply with the + * [Service Specific Terms](https://cloud.google.com/terms/service-terms) for "Grounding with Google + * Search". + * + * @property renderedContent An HTML/CSS snippet that can be embedded in your app. The snippet is + * designed to avoid undesired interaction with the rest of the page's CSS. To ensure proper + * rendering, it's recommended to display this content within a `WebView`. + * @property sdkBlob A blob of data for the client SDK to render the search entry point. + */ +public class SearchEntryPoint( + public val renderedContent: String?, + public val sdkBlob: String?, +) { + @Serializable + internal data class Internal( + val renderedContent: String?, + val sdkBlob: String?, + ) { + internal fun toPublic() = SearchEntryPoint(renderedContent = renderedContent, sdkBlob = sdkBlob) + } +} + +/** + * Represents a chunk of retrieved data that supports a claim in the model's response. This is part + * of the grounding information provided when grounding is enabled. + * + * When using this feature, you are required to comply with the + * [Service Specific Terms](https://cloud.google.com/terms/service-terms) for "Grounding with Google + * Search". + * + * @property web Contains details if the grounding chunk is from a web source. + */ +public class GroundingChunk( + public val web: WebGroundingChunk?, +) { + @Serializable + internal data class Internal( + val web: WebGroundingChunk.Internal?, + ) { + internal fun toPublic() = GroundingChunk(web = web?.toPublic()) + } +} + +/** + * A grounding chunk from the web. + * + * When using this feature, you are required to comply with the + * [Service Specific Terms](https://cloud.google.com/terms/service-terms) for "Grounding with Google + * Search". + * + * @property uri The URI of the retrieved web page. + * @property title The title of the retrieved web page. + * @property domain The domain of the original URI from which the content was retrieved (e.g., + * `example.com`). This is only populated when using the Vertex AI Gemini API. + */ +public class WebGroundingChunk( + public val uri: String?, + public val title: String?, + public val domain: String? +) { + @Serializable + internal data class Internal(val uri: String?, val title: String?, val domain: String?) { + internal fun toPublic() = WebGroundingChunk(uri = uri, title = title, domain = domain) + } +} + +/** + * Provides information about how a specific segment of the model's response is supported by the + * retrieved grounding chunks. + * + * When using this feature, you are required to comply with the + * [Service Specific Terms](https://cloud.google.com/terms/service-terms) for "Grounding with Google + * Search". + * + * @property segment Specifies the segment of the model's response content that this grounding + * support pertains to. + * @property groundingChunkIndices A list of indices that refer to specific [GroundingChunk] objects + * within the [GroundingMetadata.groundingChunks] array. These referenced chunks are the sources + * that support the claim made in the associated `segment` of the response. + */ +public class GroundingSupport( + public val segment: Segment?, + public val groundingChunkIndices: List, +) { + @Serializable + internal data class Internal( + val segment: Segment.Internal?, + val groundingChunkIndices: List?, + ) { + internal fun toPublic() = + GroundingSupport( + segment = segment?.toPublic(), + groundingChunkIndices = groundingChunkIndices.orEmpty(), + ) + } +} + +@Deprecated("Use GroundingChunk instead") +public class GroundingAttribution( + public val segment: Segment, + public val confidenceScore: Float?, +) { + @Deprecated("Use GroundingChunk instead") + @Serializable + internal data class Internal( + val segment: Segment.Internal, + val confidenceScore: Float?, + ) { + internal fun toPublic() = + GroundingAttribution(segment = segment.toPublic(), confidenceScore = confidenceScore) + } +} + +/** + * Represents a specific segment within a [Content] object, often used to pinpoint the exact + * location of text or data that grounding information refers to. + * + * @property startIndex The zero-based start index of the segment within the specified [Part], + * measured in UTF-8 bytes. This offset is inclusive. + * @property endIndex The zero-based end index of the segment within the specified [Part], measured + * in UTF-8 bytes. This offset is exclusive. + * @property partIndex The zero-based index of the [Part] object within the `parts` array of its + * parent [Content] object. This identifies which part of the content the segment belongs to. + * @property text The text content of the segment. + */ +public class Segment( + public val startIndex: Int, + public val endIndex: Int, + public val partIndex: Int, + public val text: String, +) { + @Serializable + internal data class Internal( + val startIndex: Int?, + val endIndex: Int?, + val partIndex: Int?, + val text: String?, + ) { + internal fun toPublic() = + Segment( + startIndex = startIndex ?: 0, + endIndex = endIndex ?: 0, + partIndex = partIndex ?: 0, + text = text ?: "" + ) + } +} diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/GoogleSearch.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/GoogleSearch.kt new file mode 100644 index 00000000000..0649d365ab6 --- /dev/null +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/GoogleSearch.kt @@ -0,0 +1,13 @@ +package com.google.firebase.ai.type + +import kotlinx.serialization.Serializable + +/** + * A tool that allows the generative model to connect to Google Search to access and incorporate + * up-to-date information from the web into its responses. + * + * When this tool is used, the model's responses may include "Grounded Results" which are subject to + * the Grounding with Google Search terms outlined in the + * [Service Specific Terms](https://cloud.google.com/terms/service-terms). + */ +@Serializable public class GoogleSearch {} diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Tool.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Tool.kt index 83391166bd4..3c2e9634993 100644 --- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Tool.kt +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Tool.kt @@ -26,11 +26,19 @@ import kotlinx.serialization.json.JsonObject * @param functionDeclarations The set of functions that this tool allows the model access to */ public class Tool -internal constructor(internal val functionDeclarations: List?) { - internal fun toInternal() = Internal(functionDeclarations?.map { it.toInternal() } ?: emptyList()) +internal constructor( + internal val functionDeclarations: List?, + internal val googleSearch: GoogleSearch? +) { + internal fun toInternal() = + Internal( + functionDeclarations?.map { it.toInternal() } ?: emptyList(), + googleSearch = this.googleSearch + ) @Serializable internal data class Internal( val functionDeclarations: List? = null, + val googleSearch: GoogleSearch? = null, // This is a json object because it is not possible to make a data class with no parameters. val codeExecution: JsonObject? = null, ) @@ -43,7 +51,26 @@ internal constructor(internal val functionDeclarations: List): Tool { - return Tool(functionDeclarations) + return Tool(functionDeclarations, null) + } + + /** + * Creates a [Tool] instance that enables the model to use Grounding with Google Search. + * + * This allows the model to connect to Google Search to access and incorporate up-to-date + * information from the web into its responses. + * + * When this tool is used, the model's responses may include "Grounded Results" which are + * subject to the Grounding with Google Search terms outlined in the + * [Service Specific Terms](https://cloud.google.com/terms/service-terms). + * + * @param googleSearch An empty [GoogleSearch] object. The presence of this object in the list + * of tools enables the model to use Google Search. + * @return a [Tool] configured for Google Search. + */ + @JvmStatic + public fun googleSearch(googleSearch: GoogleSearch = GoogleSearch()): Tool { + return Tool(null, googleSearch) } } } diff --git a/firebase-ai/src/test/java/com/google/firebase/ai/DevAPIUnarySnapshotTests.kt b/firebase-ai/src/test/java/com/google/firebase/ai/DevAPIUnarySnapshotTests.kt index 91a263f8c66..b3bb249295f 100644 --- a/firebase-ai/src/test/java/com/google/firebase/ai/DevAPIUnarySnapshotTests.kt +++ b/firebase-ai/src/test/java/com/google/firebase/ai/DevAPIUnarySnapshotTests.kt @@ -22,6 +22,9 @@ import com.google.firebase.ai.type.ResponseStoppedException import com.google.firebase.ai.type.ServerException import com.google.firebase.ai.util.goldenDevAPIUnaryFile import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.collections.shouldNotBeEmpty +import io.kotest.matchers.nulls.shouldBeNull +import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.should import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe @@ -95,4 +98,22 @@ internal class DevAPIUnarySnapshotTests { goldenDevAPIUnaryFile("unary-failure-unknown-model.json", HttpStatusCode.NotFound) { withTimeout(testTimeout) { shouldThrow { model.generateContent("prompt") } } } + + // This test case can be removed once https://b.corp.google.com/issues/422779395 (internal) is + // fixed. + @Test + fun `google search grounding empty grounding chunks`() = + goldenDevAPIUnaryFile("unary-success-google-search-grounding-empty-grounding-chunks.json") { + withTimeout(testTimeout) { + val response = model.generateContent("prompt") + + response.candidates.shouldNotBeEmpty() + val candidate = response.candidates.first() + val groundingMetadata = candidate.groundingMetadata + groundingMetadata.shouldNotBeNull() + + groundingMetadata.groundingChunks.shouldNotBeEmpty() + groundingMetadata.groundingChunks.forEach { it.web.shouldBeNull() } + } + } } diff --git a/firebase-ai/src/test/java/com/google/firebase/ai/SerializationTests.kt b/firebase-ai/src/test/java/com/google/firebase/ai/SerializationTests.kt index d00f75c2714..8c96c7da77a 100644 --- a/firebase-ai/src/test/java/com/google/firebase/ai/SerializationTests.kt +++ b/firebase-ai/src/test/java/com/google/firebase/ai/SerializationTests.kt @@ -20,8 +20,17 @@ import com.google.firebase.ai.common.util.descriptorToJson import com.google.firebase.ai.type.Candidate import com.google.firebase.ai.type.CountTokensResponse import com.google.firebase.ai.type.GenerateContentResponse +import com.google.firebase.ai.type.GoogleSearch +import com.google.firebase.ai.type.GroundingAttribution +import com.google.firebase.ai.type.GroundingChunk +import com.google.firebase.ai.type.GroundingMetadata +import com.google.firebase.ai.type.GroundingSupport import com.google.firebase.ai.type.ModalityTokenCount import com.google.firebase.ai.type.Schema +import com.google.firebase.ai.type.SearchEntryPoint +import com.google.firebase.ai.type.Segment +import com.google.firebase.ai.type.Tool +import com.google.firebase.ai.type.WebGroundingChunk import io.kotest.assertions.json.shouldEqualJson import org.junit.Test @@ -159,6 +168,148 @@ internal class SerializationTests { expectedJsonAsString shouldEqualJson actualJson.toString() } + @Test + fun `test GroundingMetadata serialization as Json`() { + val expectedJsonAsString = + """ + { + "id": "GroundingMetadata", + "type": "object", + "properties": { + "webSearchQueries": { "type": "array", "items": { "type": "string" } }, + "searchEntryPoint": { "${'$'}ref": "SearchEntryPoint" }, + "retrievalQueries": { "type": "array", "items": { "type": "string" } }, + "groundingAttribution": { "type": "array", "items": { "${'$'}ref": "GroundingAttribution" } }, + "groundingChunks": { "type": "array", "items": { "${'$'}ref": "GroundingChunk" } }, + "groundingSupports": { "type": "array", "items": { "${'$'}ref": "GroundingSupport" } } + } + } + """ + .trimIndent() + val actualJson = descriptorToJson(GroundingMetadata.Internal.serializer().descriptor) + println(actualJson) + expectedJsonAsString shouldEqualJson actualJson.toString() + } + + @Test + fun `test SearchEntryPoint serialization as Json`() { + val expectedJsonAsString = + """ + { + "id": "SearchEntryPoint", + "type": "object", + "properties": { + "renderedContent": { "type": "string" }, + "sdkBlob": { "type": "string" } + } + } + """ + .trimIndent() + val actualJson = descriptorToJson(SearchEntryPoint.Internal.serializer().descriptor) + expectedJsonAsString shouldEqualJson actualJson.toString() + } + + @Test + fun `test GroundingChunk serialization as Json`() { + val expectedJsonAsString = + """ + { + "id": "GroundingChunk", + "type": "object", + "properties": { + "web": { "${'$'}ref": "WebGroundingChunk" } + } + } + """ + .trimIndent() + val actualJson = descriptorToJson(GroundingChunk.Internal.serializer().descriptor) + expectedJsonAsString shouldEqualJson actualJson.toString() + } + + @Test + fun `test WebGroundingChunk serialization as Json`() { + val expectedJsonAsString = + """ + { + "id": "WebGroundingChunk", + "type": "object", + "properties": { + "uri": { "type": "string" }, + "title": { "type": "string" }, + "domain": { "type": "string" } + } + } + """ + .trimIndent() + val actualJson = descriptorToJson(WebGroundingChunk.Internal.serializer().descriptor) + expectedJsonAsString shouldEqualJson actualJson.toString() + } + + @Test + fun `test GroundingSupport serialization as Json`() { + val expectedJsonAsString = + """ + { + "id": "GroundingSupport", + "type": "object", + "properties": { + "segment": { + "${'$'}ref": "Segment" + }, + "groundingChunkIndices": { + "type": "array", + "items": { "type": "integer" } + } + } + } + """ + .trimIndent() + val actualJson = descriptorToJson(GroundingSupport.Internal.serializer().descriptor) + expectedJsonAsString shouldEqualJson actualJson.toString() + } + + @Test + fun `test Segment serialization as Json`() { + val expectedJsonAsString = + """ + { + "id": "Segment", + "type": "object", + "properties": { + "startIndex": { "type": "integer" }, + "endIndex": { "type": "integer" }, + "partIndex": { "type": "integer" }, + "text": { "type": "string" } + } + } + """ + .trimIndent() + val actualJson = descriptorToJson(Segment.Internal.serializer().descriptor) + expectedJsonAsString shouldEqualJson actualJson.toString() + } + + @Test + fun `test GroundingAttribution serialization as Json`() { + val expectedJsonAsString = + """ + { + "id": "GroundingAttribution", + "type": "object", + "properties": { + "segment": { + "${'$'}ref": "Segment" + }, + "confidenceScore": { + "type": "number" + } + } + } + """ + .trimIndent() + val actualJson = descriptorToJson(GroundingAttribution.Internal.serializer().descriptor) + expectedJsonAsString shouldEqualJson actualJson.toString() + } + @Test fun `test Schema serialization as Json`() { /** @@ -212,4 +363,50 @@ internal class SerializationTests { val actualJson = descriptorToJson(Schema.Internal.serializer().descriptor) expectedJsonAsString shouldEqualJson actualJson.toString() } + + @Test + fun `test Tool serialization as Json`() { + val expectedJsonAsString = + """ + { + "id": "Tool", + "type": "object", + "properties": { + "functionDeclarations": { + "type": "array", + "items": { + "${'$'}ref": "FunctionDeclaration" + } + }, + "googleSearch": { + "${'$'}ref": "GoogleSearch" + }, + "codeExecution": { + "type": "object", + "additionalProperties": { + "${'$'}ref": "JsonElement" + } + } + } + } + """ + .trimIndent() + val actualJson = descriptorToJson(Tool.Internal.serializer().descriptor) + expectedJsonAsString shouldEqualJson actualJson.toString() + } + + @Test + fun `test GoogleSearch serialization as Json`() { + val expectedJsonAsString = + """ + { + "id": "GoogleSearch", + "type": "object", + "properties": {} + } + """ + .trimIndent() + val actualJson = descriptorToJson(GoogleSearch.serializer().descriptor) + expectedJsonAsString shouldEqualJson actualJson.toString() + } } diff --git a/firebase-ai/src/test/java/com/google/firebase/ai/VertexAIUnarySnapshotTests.kt b/firebase-ai/src/test/java/com/google/firebase/ai/VertexAIUnarySnapshotTests.kt index ca1d279d288..5be05a36c39 100644 --- a/firebase-ai/src/test/java/com/google/firebase/ai/VertexAIUnarySnapshotTests.kt +++ b/firebase-ai/src/test/java/com/google/firebase/ai/VertexAIUnarySnapshotTests.kt @@ -39,6 +39,7 @@ import com.google.firebase.ai.util.shouldNotBeNullOrEmpty import io.kotest.assertions.throwables.shouldThrow import io.kotest.inspectors.forAtLeastOne import io.kotest.matchers.collections.shouldNotBeEmpty +import io.kotest.matchers.nulls.shouldBeNull import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.should import io.kotest.matchers.shouldBe @@ -589,4 +590,50 @@ internal class VertexAIUnarySnapshotTests { shouldThrow { imagenModel.generateImages("prompt") } } } + + @Test + fun `google search grounding metadata is parsed correctly`() = + goldenVertexUnaryFile("unary-success-google-search-grounding.json") { + withTimeout(testTimeout) { + val response = model.generateContent("prompt") + + response.candidates.shouldNotBeEmpty() + val candidate = response.candidates.first() + candidate.finishReason shouldBe FinishReason.STOP + + val groundingMetadata = candidate.groundingMetadata + groundingMetadata.shouldNotBeNull() + + groundingMetadata.webSearchQueries?.first() shouldBe "current weather in London" + groundingMetadata.searchEntryPoint.shouldNotBeNull() + groundingMetadata.searchEntryPoint?.renderedContent.shouldNotBeEmpty() + + groundingMetadata.groundingChunks.shouldNotBeEmpty() + val groundingChunk = groundingMetadata.groundingChunks.first() + groundingChunk.web.shouldNotBeNull() + groundingChunk.web?.uri.shouldNotBeEmpty() + groundingChunk.web?.title shouldBe "accuweather.com" + groundingChunk.web?.domain.shouldBeNull() + + groundingMetadata.groundingSupports.shouldNotBeEmpty() + groundingMetadata.groundingSupports.size shouldBe 3 + val groundingSupport = groundingMetadata.groundingSupports.first() + groundingSupport.segment.shouldNotBeNull() + groundingSupport.segment?.startIndex shouldBe 0 + groundingSupport.segment?.partIndex shouldBe 0 + groundingSupport.segment?.endIndex shouldBe 56 + groundingSupport.segment?.text shouldBe + "The current weather in London, United Kingdom is cloudy." + groundingSupport.groundingChunkIndices.first() shouldBe 0 + + val secondGroundingSupport = groundingMetadata.groundingSupports[1] + secondGroundingSupport.segment.shouldNotBeNull() + secondGroundingSupport.segment?.startIndex shouldBe 57 + secondGroundingSupport.segment?.partIndex shouldBe 0 + secondGroundingSupport.segment?.endIndex shouldBe 123 + secondGroundingSupport.segment?.text shouldBe + "The temperature is 67°F (19°C), but it feels like 75°F (24°C)." + secondGroundingSupport.groundingChunkIndices.first() shouldBe 1 + } + } } diff --git a/firebase-ai/src/test/java/com/google/firebase/ai/common/APIControllerTests.kt b/firebase-ai/src/test/java/com/google/firebase/ai/common/APIControllerTests.kt index b1ae69c25b2..e0d56c2c2dd 100644 --- a/firebase-ai/src/test/java/com/google/firebase/ai/common/APIControllerTests.kt +++ b/firebase-ai/src/test/java/com/google/firebase/ai/common/APIControllerTests.kt @@ -25,6 +25,7 @@ import com.google.firebase.ai.common.util.prepareStreamingResponse import com.google.firebase.ai.type.Content import com.google.firebase.ai.type.CountTokensResponse import com.google.firebase.ai.type.FunctionCallingConfig +import com.google.firebase.ai.type.GoogleSearch import com.google.firebase.ai.type.RequestOptions import com.google.firebase.ai.type.TextPart import com.google.firebase.ai.type.Tool @@ -262,6 +263,44 @@ internal class RequestFormatTests { "tool_config.function_calling_config.allowed_function_names" } + @Test + fun `google search tool serialization contains correct keys`() = doBlocking { + val channel = ByteChannel(autoFlush = true) + val mockEngine = MockEngine { + respond(channel, HttpStatusCode.OK, headersOf(HttpHeaders.ContentType, "application/json")) + } + prepareStreamingResponse(createResponses("Random")).forEach { channel.writeFully(it) } + + val controller = + APIController( + "super_cool_test_key", + "gemini-pro-1.5", + RequestOptions(), + mockEngine, + TEST_CLIENT_ID, + mockFirebaseApp, + TEST_VERSION, + TEST_APP_ID, + null, + ) + + withTimeout(5.seconds) { + controller + .generateContentStream( + GenerateContentRequest( + model = "unused", + contents = listOf(Content.Internal(parts = listOf(TextPart.Internal("Arbitrary")))), + tools = listOf(Tool.Internal(googleSearch = GoogleSearch())), + ) + ) + .collect { channel.close() } + } + + val requestBodyAsText = (mockEngine.requestHistory.first().body as TextContent).text + + requestBodyAsText shouldContainJsonKey "tools[0].googleSearch" + } + @Test fun `headers from HeaderProvider are added to the request`() = doBlocking { val response = JSON.encodeToString(CountTokensResponse.Internal(totalTokens = 10)) diff --git a/firebase-ai/src/test/java/com/google/firebase/ai/type/ToolTest.kt b/firebase-ai/src/test/java/com/google/firebase/ai/type/ToolTest.kt new file mode 100644 index 00000000000..c5213cf8613 --- /dev/null +++ b/firebase-ai/src/test/java/com/google/firebase/ai/type/ToolTest.kt @@ -0,0 +1,40 @@ +/* + * 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 com.google.firebase.ai.type + +import io.kotest.matchers.nulls.shouldBeNull +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.shouldBe +import org.junit.Test + +internal class ToolTest { + @Test + fun `googleSearch() creates a tool with a googleSearch property`() { + val tool = Tool.googleSearch() + + tool.googleSearch.shouldNotBeNull() + tool.functionDeclarations.shouldBeNull() + } + + @Test + fun `functionDeclarations() creates a tool with functionDeclarations`() { + val functionDeclaration = FunctionDeclaration("test", "test", emptyMap()) + val tool = Tool.functionDeclarations(listOf(functionDeclaration)) + + tool.functionDeclarations?.first() shouldBe functionDeclaration + tool.googleSearch.shouldBeNull() + } +} diff --git a/firebase-ai/update_responses.sh b/firebase-ai/update_responses.sh index 7d6ea18e0ee..baf2676dfbb 100755 --- a/firebase-ai/update_responses.sh +++ b/firebase-ai/update_responses.sh @@ -17,7 +17,7 @@ # This script replaces mock response files for Vertex AI unit tests with a fresh # clone of the shared repository of Vertex AI test data. -RESPONSES_VERSION='v13.*' # The major version of mock responses to use +RESPONSES_VERSION='v14.*' # The major version of mock responses to use REPO_NAME="vertexai-sdk-test-data" REPO_LINK="https://github.com/FirebaseExtended/$REPO_NAME.git" From 5f30a69a743839786c78eb71c0dbb1ece4059bcf Mon Sep 17 00:00:00 2001 From: Daniel La Rocque Date: Mon, 23 Jun 2025 15:25:51 -0400 Subject: [PATCH 02/11] Add changelog entry --- firebase-ai/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/firebase-ai/CHANGELOG.md b/firebase-ai/CHANGELOG.md index 241a23f0f63..87c55199bea 100644 --- a/firebase-ai/CHANGELOG.md +++ b/firebase-ai/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +* [feature] Add support for Grounding with Google Search (#7042). * [fixed] Fixed `FirebaseAI.getInstance` StackOverflowException (#6971) * [fixed] Fixed an issue that was causing the SDK to send empty `FunctionDeclaration` descriptions to the API. From c1deb3b4c9c2bbe8f2325db0c8dc47c81930d29d Mon Sep 17 00:00:00 2001 From: Daniel La Rocque Date: Mon, 23 Jun 2025 15:26:09 -0400 Subject: [PATCH 03/11] Revert accidental change to UNSUPPORTED --- .../src/main/kotlin/com/google/firebase/ai/type/Candidate.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt index cbb2607c54b..6a6a9b8ea82 100644 --- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt @@ -22,6 +22,7 @@ import com.google.firebase.ai.common.util.FirstOrdinalSerializer import java.util.Calendar import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonNames @@ -226,7 +227,7 @@ public class FinishReason private constructor(public val name: String, public va @Serializable(Internal.Serializer::class) internal enum class Internal { UNKNOWN, - UNSPECIFIED, + @SerialName("FINISH_REASON_UNSPECIFIED") UNSPECIFIED, STOP, MAX_TOKENS, SAFETY, From 972f3e5194085e555ca2647459ed54c6c164c2b4 Mon Sep 17 00:00:00 2001 From: Daniel La Rocque Date: Mon, 23 Jun 2025 15:37:52 -0400 Subject: [PATCH 04/11] generate firebase-ai.txt --- firebase-ai/api.txt | 82 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 79 insertions(+), 3 deletions(-) diff --git a/firebase-ai/api.txt b/firebase-ai/api.txt index 40aeb2b85d7..20dfbee9e05 100644 --- a/firebase-ai/api.txt +++ b/firebase-ai/api.txt @@ -171,10 +171,12 @@ package com.google.firebase.ai.type { method public com.google.firebase.ai.type.CitationMetadata? getCitationMetadata(); method public com.google.firebase.ai.type.Content getContent(); method public com.google.firebase.ai.type.FinishReason? getFinishReason(); + method public com.google.firebase.ai.type.GroundingMetadata? getGroundingMetadata(); method public java.util.List getSafetyRatings(); property public final com.google.firebase.ai.type.CitationMetadata? citationMetadata; property public final com.google.firebase.ai.type.Content content; property public final com.google.firebase.ai.type.FinishReason? finishReason; + property public final com.google.firebase.ai.type.GroundingMetadata? groundingMetadata; property public final java.util.List safetyRatings; } @@ -245,15 +247,15 @@ package com.google.firebase.ai.type { } public final class CountTokensResponse { - ctor public CountTokensResponse(int totalTokens, Integer? totalBillableCharacters = null, java.util.List promptTokensDetails = emptyList()); + ctor public CountTokensResponse(int totalTokens, @Deprecated Integer? totalBillableCharacters = null, java.util.List promptTokensDetails = emptyList()); method public operator int component1(); method public operator Integer? component2(); method public operator java.util.List? component3(); method public java.util.List getPromptTokensDetails(); - method public Integer? getTotalBillableCharacters(); + method @Deprecated public Integer? getTotalBillableCharacters(); method public int getTotalTokens(); property public final java.util.List promptTokensDetails; - property public final Integer? totalBillableCharacters; + property @Deprecated public final Integer? totalBillableCharacters; property public final int totalTokens; } @@ -398,6 +400,48 @@ package com.google.firebase.ai.type { method public com.google.firebase.ai.type.GenerativeBackend vertexAI(String location = "us-central1"); } + @kotlinx.serialization.Serializable public final class GoogleSearch { + ctor public GoogleSearch(); + } + + @Deprecated public final class GroundingAttribution { + ctor @Deprecated public GroundingAttribution(com.google.firebase.ai.type.Segment segment, Float? confidenceScore); + method @Deprecated public Float? getConfidenceScore(); + method @Deprecated public com.google.firebase.ai.type.Segment getSegment(); + property @Deprecated public final Float? confidenceScore; + property @Deprecated public final com.google.firebase.ai.type.Segment segment; + } + + public final class GroundingChunk { + ctor public GroundingChunk(com.google.firebase.ai.type.WebGroundingChunk? web); + method public com.google.firebase.ai.type.WebGroundingChunk? getWeb(); + property public final com.google.firebase.ai.type.WebGroundingChunk? web; + } + + public final class GroundingMetadata { + ctor public GroundingMetadata(java.util.List? webSearchQueries, com.google.firebase.ai.type.SearchEntryPoint? searchEntryPoint, java.util.List? retrievalQueries, @Deprecated java.util.List? groundingAttribution, java.util.List groundingChunks, java.util.List groundingSupports); + method @Deprecated public java.util.List? getGroundingAttribution(); + method public java.util.List getGroundingChunks(); + method public java.util.List getGroundingSupports(); + method public java.util.List? getRetrievalQueries(); + method public com.google.firebase.ai.type.SearchEntryPoint? getSearchEntryPoint(); + method public java.util.List? getWebSearchQueries(); + property @Deprecated public final java.util.List? groundingAttribution; + property public final java.util.List groundingChunks; + property public final java.util.List groundingSupports; + property public final java.util.List? retrievalQueries; + property public final com.google.firebase.ai.type.SearchEntryPoint? searchEntryPoint; + property public final java.util.List? webSearchQueries; + } + + public final class GroundingSupport { + ctor public GroundingSupport(com.google.firebase.ai.type.Segment? segment, java.util.List groundingChunkIndices); + method public java.util.List getGroundingChunkIndices(); + method public com.google.firebase.ai.type.Segment? getSegment(); + property public final java.util.List groundingChunkIndices; + property public final com.google.firebase.ai.type.Segment? segment; + } + public final class HarmBlockMethod { method public int getOrdinal(); property public final int ordinal; @@ -897,6 +941,26 @@ package com.google.firebase.ai.type { method public com.google.firebase.ai.type.Schema str(String? description = null, boolean nullable = false, com.google.firebase.ai.type.StringFormat? format = null, String? title = null); } + public final class SearchEntryPoint { + ctor public SearchEntryPoint(String? renderedContent, String? sdkBlob); + method public String? getRenderedContent(); + method public String? getSdkBlob(); + property public final String? renderedContent; + property public final String? sdkBlob; + } + + public final class Segment { + ctor public Segment(int startIndex, int endIndex, int partIndex, String text); + method public int getEndIndex(); + method public int getPartIndex(); + method public int getStartIndex(); + method public String getText(); + property public final int endIndex; + property public final int partIndex; + property public final int startIndex; + property public final String text; + } + public final class SerializationException extends com.google.firebase.ai.type.FirebaseAIException { } @@ -935,11 +999,13 @@ package com.google.firebase.ai.type { public final class Tool { method public static com.google.firebase.ai.type.Tool functionDeclarations(java.util.List functionDeclarations); + method public static com.google.firebase.ai.type.Tool googleSearch(com.google.firebase.ai.type.GoogleSearch googleSearch = com.google.firebase.ai.type.GoogleSearch()); field public static final com.google.firebase.ai.type.Tool.Companion Companion; } public static final class Tool.Companion { method public com.google.firebase.ai.type.Tool functionDeclarations(java.util.List functionDeclarations); + method public com.google.firebase.ai.type.Tool googleSearch(com.google.firebase.ai.type.GoogleSearch googleSearch = com.google.firebase.ai.type.GoogleSearch()); } public final class ToolConfig { @@ -987,5 +1053,15 @@ package com.google.firebase.ai.type { @Deprecated public static final class Voices.Companion { } + public final class WebGroundingChunk { + ctor public WebGroundingChunk(String? uri, String? title, String? domain); + method public String? getDomain(); + method public String? getTitle(); + method public String? getUri(); + property public final String? domain; + property public final String? title; + property public final String? uri; + } + } From 826c60a9025ea1475204b9f89d501fa7e5c416e3 Mon Sep 17 00:00:00 2001 From: Daniel La Rocque Date: Mon, 23 Jun 2025 15:42:08 -0400 Subject: [PATCH 05/11] add copyright notice to GoogleSearch.kt --- .../com/google/firebase/ai/type/GoogleSearch.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/GoogleSearch.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/GoogleSearch.kt index 0649d365ab6..3d759a23a98 100644 --- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/GoogleSearch.kt +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/GoogleSearch.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2024 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 com.google.firebase.ai.type import kotlinx.serialization.Serializable From 7dbe236cd41f9befa1d9fc3ef0b695daf6179dbb Mon Sep 17 00:00:00 2001 From: Daniel La Rocque Date: Wed, 25 Jun 2025 11:41:42 -0400 Subject: [PATCH 06/11] Review fixes --- .../main/kotlin/com/google/firebase/ai/type/Candidate.kt | 8 ++++---- .../kotlin/com/google/firebase/ai/type/GoogleSearch.kt | 8 ++++++-- .../src/main/kotlin/com/google/firebase/ai/type/Tool.kt | 4 ++-- .../com/google/firebase/ai/DevAPIUnarySnapshotTests.kt | 2 +- .../java/com/google/firebase/ai/SerializationTests.kt | 2 +- .../com/google/firebase/ai/common/APIControllerTests.kt | 4 ++-- 6 files changed, 16 insertions(+), 12 deletions(-) diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt index 6a6a9b8ea82..6a8eaa79d64 100644 --- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt @@ -300,15 +300,15 @@ public class FinishReason private constructor(public val name: String, public va * [Service Specific Terms](https://cloud.google.com/terms/service-terms) for "Grounding with Google * Search". * - * @property webSearchQueries A list of web search queries that the model performed to gather the + * @property webSearchQueries The list of web search queries that the model performed to gather the * grounding information. These can be used to allow users to explore the search results themselves. * @property searchEntryPoint Google search entry point for web searches. This contains an HTML/CSS * snippet that **must** be embedded in an app to display a Google Search Entry point for follow-up * web searches related to the model's "Grounded Response". To ensure proper rendering, it's * recommended to display this content within a `WebView`. - * @property groundingChunks A list of [GroundingChunk] objects. Each chunk represents a piece of + * @property groundingChunks The list of [GroundingChunk] objects. Each chunk represents a piece of * retrieved content (e.g. from a web page) that the model used to ground its response. - * @property groundingSupports A list of [GroundingSupport] objects. Each object details how + * @property groundingSupports The list of [GroundingSupport] objects. Each object details how * specific segments of the model's response are supported by the `groundingChunks`. */ public class GroundingMetadata( @@ -343,7 +343,7 @@ public class GroundingMetadata( } /** - * A class representing the Google Search entry point. + * Represents a Google Search entry point. * * When using this feature, you are required to comply with the * [Service Specific Terms](https://cloud.google.com/terms/service-terms) for "Grounding with Google diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/GoogleSearch.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/GoogleSearch.kt index 3d759a23a98..a03500ed7d4 100644 --- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/GoogleSearch.kt +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/GoogleSearch.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024 Google LLC + * 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. @@ -26,4 +26,8 @@ import kotlinx.serialization.Serializable * the Grounding with Google Search terms outlined in the * [Service Specific Terms](https://cloud.google.com/terms/service-terms). */ -@Serializable public class GoogleSearch {} +public class GoogleSearch { + @Serializable internal class Internal() + + internal fun toInternal() = Internal() +} diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Tool.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Tool.kt index 3c2e9634993..a9a0abf7681 100644 --- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Tool.kt +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Tool.kt @@ -33,12 +33,12 @@ internal constructor( internal fun toInternal() = Internal( functionDeclarations?.map { it.toInternal() } ?: emptyList(), - googleSearch = this.googleSearch + googleSearch = this.googleSearch?.toInternal() ) @Serializable internal data class Internal( val functionDeclarations: List? = null, - val googleSearch: GoogleSearch? = null, + val googleSearch: GoogleSearch.Internal? = null, // This is a json object because it is not possible to make a data class with no parameters. val codeExecution: JsonObject? = null, ) diff --git a/firebase-ai/src/test/java/com/google/firebase/ai/DevAPIUnarySnapshotTests.kt b/firebase-ai/src/test/java/com/google/firebase/ai/DevAPIUnarySnapshotTests.kt index b3bb249295f..85c99210a04 100644 --- a/firebase-ai/src/test/java/com/google/firebase/ai/DevAPIUnarySnapshotTests.kt +++ b/firebase-ai/src/test/java/com/google/firebase/ai/DevAPIUnarySnapshotTests.kt @@ -99,7 +99,7 @@ internal class DevAPIUnarySnapshotTests { withTimeout(testTimeout) { shouldThrow { model.generateContent("prompt") } } } - // This test case can be removed once https://b.corp.google.com/issues/422779395 (internal) is + // This test case can be removed once b/422779395 is // fixed. @Test fun `google search grounding empty grounding chunks`() = diff --git a/firebase-ai/src/test/java/com/google/firebase/ai/SerializationTests.kt b/firebase-ai/src/test/java/com/google/firebase/ai/SerializationTests.kt index beb7561e582..8e904a3f315 100644 --- a/firebase-ai/src/test/java/com/google/firebase/ai/SerializationTests.kt +++ b/firebase-ai/src/test/java/com/google/firebase/ai/SerializationTests.kt @@ -427,7 +427,7 @@ internal class SerializationTests { } """ .trimIndent() - val actualJson = descriptorToJson(GoogleSearch.serializer().descriptor) + val actualJson = descriptorToJson(GoogleSearch.Internal.serializer().descriptor) expectedJsonAsString shouldEqualJson actualJson.toString() } } diff --git a/firebase-ai/src/test/java/com/google/firebase/ai/common/APIControllerTests.kt b/firebase-ai/src/test/java/com/google/firebase/ai/common/APIControllerTests.kt index e0d56c2c2dd..f3b192a6b9a 100644 --- a/firebase-ai/src/test/java/com/google/firebase/ai/common/APIControllerTests.kt +++ b/firebase-ai/src/test/java/com/google/firebase/ai/common/APIControllerTests.kt @@ -274,7 +274,7 @@ internal class RequestFormatTests { val controller = APIController( "super_cool_test_key", - "gemini-pro-1.5", + "gemini-pro-2.5", RequestOptions(), mockEngine, TEST_CLIENT_ID, @@ -290,7 +290,7 @@ internal class RequestFormatTests { GenerateContentRequest( model = "unused", contents = listOf(Content.Internal(parts = listOf(TextPart.Internal("Arbitrary")))), - tools = listOf(Tool.Internal(googleSearch = GoogleSearch())), + tools = listOf(Tool.Internal(googleSearch = GoogleSearch.Internal())), ) ) .collect { channel.close() } From 1f42240a06ed0261b141460720ce5a29052e07fc Mon Sep 17 00:00:00 2001 From: Daniel La Rocque Date: Thu, 26 Jun 2025 10:23:19 -0400 Subject: [PATCH 07/11] make existing fields required lists --- firebase-ai/CHANGELOG.md | 3 ++- .../kotlin/com/google/firebase/ai/type/Candidate.kt | 12 ++++++------ .../google/firebase/ai/VertexAIUnarySnapshotTests.kt | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/firebase-ai/CHANGELOG.md b/firebase-ai/CHANGELOG.md index 5d0858e5d50..67ca2d5ff4e 100644 --- a/firebase-ai/CHANGELOG.md +++ b/firebase-ai/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased -* [feature] Add support for Grounding with Google Search (#7042). +* [feature] **Breaking Change**: Add support for Grounding with Google Search (#7042). + * **Action Required:** Update all references of `groundingAttributions`, `webSearchQueries`, `retrievalQueries` in `GroundingMetadata` to be non-optional. * [changed] Deprecate the `totalBillableCharacters` field (only usable with pre-2.0 models). (#7042) * [feature] Added support for extra schema properties like `title`, `minItems`, `maxItems`, `minimum` and `maximum`. As well as support for the `anyOf` schema. (#7013) diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt index 6a8eaa79d64..6893e8b7204 100644 --- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt @@ -312,11 +312,11 @@ public class FinishReason private constructor(public val name: String, public va * specific segments of the model's response are supported by the `groundingChunks`. */ public class GroundingMetadata( - public val webSearchQueries: List?, + public val webSearchQueries: List, public val searchEntryPoint: SearchEntryPoint?, - public val retrievalQueries: List?, + public val retrievalQueries: List, @Deprecated("Use groundingChunks instead") - public val groundingAttribution: List?, + public val groundingAttribution: List, public val groundingChunks: List, public val groundingSupports: List, ) { @@ -332,10 +332,10 @@ public class GroundingMetadata( ) { internal fun toPublic() = GroundingMetadata( - webSearchQueries = webSearchQueries, + webSearchQueries = webSearchQueries.orEmpty(), searchEntryPoint = searchEntryPoint?.toPublic(), - retrievalQueries = retrievalQueries, - groundingAttribution = groundingAttribution?.map { it.toPublic() }, + retrievalQueries = retrievalQueries.orEmpty(), + groundingAttribution = groundingAttribution?.map { it.toPublic() }.orEmpty(), groundingChunks = groundingChunks?.map { it.toPublic() }.orEmpty(), groundingSupports = groundingSupports?.map { it.toPublic() }.orEmpty() ) diff --git a/firebase-ai/src/test/java/com/google/firebase/ai/VertexAIUnarySnapshotTests.kt b/firebase-ai/src/test/java/com/google/firebase/ai/VertexAIUnarySnapshotTests.kt index 5be05a36c39..4c78717b877 100644 --- a/firebase-ai/src/test/java/com/google/firebase/ai/VertexAIUnarySnapshotTests.kt +++ b/firebase-ai/src/test/java/com/google/firebase/ai/VertexAIUnarySnapshotTests.kt @@ -604,7 +604,7 @@ internal class VertexAIUnarySnapshotTests { val groundingMetadata = candidate.groundingMetadata groundingMetadata.shouldNotBeNull() - groundingMetadata.webSearchQueries?.first() shouldBe "current weather in London" + groundingMetadata.webSearchQueries.first() shouldBe "current weather in London" groundingMetadata.searchEntryPoint.shouldNotBeNull() groundingMetadata.searchEntryPoint?.renderedContent.shouldNotBeEmpty() From 80347e08190122debb2df7304a253095e3c5d26e Mon Sep 17 00:00:00 2001 From: Daniel La Rocque Date: Thu, 26 Jun 2025 10:36:42 -0400 Subject: [PATCH 08/11] update api txt --- firebase-ai/api.txt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/firebase-ai/api.txt b/firebase-ai/api.txt index 20dfbee9e05..f7380ba4d08 100644 --- a/firebase-ai/api.txt +++ b/firebase-ai/api.txt @@ -400,7 +400,7 @@ package com.google.firebase.ai.type { method public com.google.firebase.ai.type.GenerativeBackend vertexAI(String location = "us-central1"); } - @kotlinx.serialization.Serializable public final class GoogleSearch { + public final class GoogleSearch { ctor public GoogleSearch(); } @@ -419,19 +419,19 @@ package com.google.firebase.ai.type { } public final class GroundingMetadata { - ctor public GroundingMetadata(java.util.List? webSearchQueries, com.google.firebase.ai.type.SearchEntryPoint? searchEntryPoint, java.util.List? retrievalQueries, @Deprecated java.util.List? groundingAttribution, java.util.List groundingChunks, java.util.List groundingSupports); - method @Deprecated public java.util.List? getGroundingAttribution(); + ctor public GroundingMetadata(java.util.List webSearchQueries, com.google.firebase.ai.type.SearchEntryPoint? searchEntryPoint, java.util.List retrievalQueries, @Deprecated java.util.List groundingAttribution, java.util.List groundingChunks, java.util.List groundingSupports); + method @Deprecated public java.util.List getGroundingAttribution(); method public java.util.List getGroundingChunks(); method public java.util.List getGroundingSupports(); - method public java.util.List? getRetrievalQueries(); + method public java.util.List getRetrievalQueries(); method public com.google.firebase.ai.type.SearchEntryPoint? getSearchEntryPoint(); - method public java.util.List? getWebSearchQueries(); - property @Deprecated public final java.util.List? groundingAttribution; + method public java.util.List getWebSearchQueries(); + property @Deprecated public final java.util.List groundingAttribution; property public final java.util.List groundingChunks; property public final java.util.List groundingSupports; - property public final java.util.List? retrievalQueries; + property public final java.util.List retrievalQueries; property public final com.google.firebase.ai.type.SearchEntryPoint? searchEntryPoint; - property public final java.util.List? webSearchQueries; + property public final java.util.List webSearchQueries; } public final class GroundingSupport { From 11d3bde7227aca5a34549387a3591f1e304a6e19 Mon Sep 17 00:00:00 2001 From: Daniel La Rocque Date: Thu, 26 Jun 2025 13:48:05 -0400 Subject: [PATCH 09/11] make more fields required --- firebase-ai/api.txt | 12 +++---- .../com/google/firebase/ai/type/Candidate.kt | 33 ++++++++++++------- .../firebase/ai/VertexAIUnarySnapshotTests.kt | 16 ++++----- 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/firebase-ai/api.txt b/firebase-ai/api.txt index f7380ba4d08..896e129a82f 100644 --- a/firebase-ai/api.txt +++ b/firebase-ai/api.txt @@ -435,11 +435,11 @@ package com.google.firebase.ai.type { } public final class GroundingSupport { - ctor public GroundingSupport(com.google.firebase.ai.type.Segment? segment, java.util.List groundingChunkIndices); + ctor public GroundingSupport(com.google.firebase.ai.type.Segment segment, java.util.List groundingChunkIndices); method public java.util.List getGroundingChunkIndices(); - method public com.google.firebase.ai.type.Segment? getSegment(); + method public com.google.firebase.ai.type.Segment getSegment(); property public final java.util.List groundingChunkIndices; - property public final com.google.firebase.ai.type.Segment? segment; + property public final com.google.firebase.ai.type.Segment segment; } public final class HarmBlockMethod { @@ -942,10 +942,10 @@ package com.google.firebase.ai.type { } public final class SearchEntryPoint { - ctor public SearchEntryPoint(String? renderedContent, String? sdkBlob); - method public String? getRenderedContent(); + ctor public SearchEntryPoint(String renderedContent, String? sdkBlob); + method public String getRenderedContent(); method public String? getSdkBlob(); - property public final String? renderedContent; + property public final String renderedContent; property public final String? sdkBlob; } diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt index 6893e8b7204..3cc3dabc3eb 100644 --- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt @@ -337,7 +337,7 @@ public class GroundingMetadata( retrievalQueries = retrievalQueries.orEmpty(), groundingAttribution = groundingAttribution?.map { it.toPublic() }.orEmpty(), groundingChunks = groundingChunks?.map { it.toPublic() }.orEmpty(), - groundingSupports = groundingSupports?.map { it.toPublic() }.orEmpty() + groundingSupports = groundingSupports?.map { it.toPublic() }.orEmpty().filterNotNull() ) } } @@ -345,7 +345,7 @@ public class GroundingMetadata( /** * Represents a Google Search entry point. * - * When using this feature, you are required to comply with the + * When using this API, you are required to comply with the * [Service Specific Terms](https://cloud.google.com/terms/service-terms) for "Grounding with Google * Search". * @@ -355,7 +355,7 @@ public class GroundingMetadata( * @property sdkBlob A blob of data for the client SDK to render the search entry point. */ public class SearchEntryPoint( - public val renderedContent: String?, + public val renderedContent: String, public val sdkBlob: String?, ) { @Serializable @@ -363,7 +363,14 @@ public class SearchEntryPoint( val renderedContent: String?, val sdkBlob: String?, ) { - internal fun toPublic() = SearchEntryPoint(renderedContent = renderedContent, sdkBlob = sdkBlob) + internal fun toPublic(): SearchEntryPoint { + // If rendered content is null, the user must not display the grounded result. If they do, + // they violate the service terms. To prevent this from happening, throw an exception. + if (renderedContent == null) { + throw SerializationException("renderedContent is null, should be a string") + } + return SearchEntryPoint(renderedContent = renderedContent, sdkBlob = sdkBlob) + } } } @@ -371,7 +378,7 @@ public class SearchEntryPoint( * Represents a chunk of retrieved data that supports a claim in the model's response. This is part * of the grounding information provided when grounding is enabled. * - * When using this feature, you are required to comply with the + * When using this API, you are required to comply with the * [Service Specific Terms](https://cloud.google.com/terms/service-terms) for "Grounding with Google * Search". * @@ -391,7 +398,7 @@ public class GroundingChunk( /** * A grounding chunk from the web. * - * When using this feature, you are required to comply with the + * When using this API, you are required to comply with the * [Service Specific Terms](https://cloud.google.com/terms/service-terms) for "Grounding with Google * Search". * @@ -415,7 +422,7 @@ public class WebGroundingChunk( * Provides information about how a specific segment of the model's response is supported by the * retrieved grounding chunks. * - * When using this feature, you are required to comply with the + * When using this API, you are required to comply with the * [Service Specific Terms](https://cloud.google.com/terms/service-terms) for "Grounding with Google * Search". * @@ -426,7 +433,7 @@ public class WebGroundingChunk( * that support the claim made in the associated `segment` of the response. */ public class GroundingSupport( - public val segment: Segment?, + public val segment: Segment, public val groundingChunkIndices: List, ) { @Serializable @@ -434,11 +441,15 @@ public class GroundingSupport( val segment: Segment.Internal?, val groundingChunkIndices: List?, ) { - internal fun toPublic() = - GroundingSupport( - segment = segment?.toPublic(), + internal fun toPublic(): GroundingSupport? { + if (segment == null) { + return null + } + return GroundingSupport( + segment = segment.toPublic(), groundingChunkIndices = groundingChunkIndices.orEmpty(), ) + } } } diff --git a/firebase-ai/src/test/java/com/google/firebase/ai/VertexAIUnarySnapshotTests.kt b/firebase-ai/src/test/java/com/google/firebase/ai/VertexAIUnarySnapshotTests.kt index 4c78717b877..e34a1e1db0d 100644 --- a/firebase-ai/src/test/java/com/google/firebase/ai/VertexAIUnarySnapshotTests.kt +++ b/firebase-ai/src/test/java/com/google/firebase/ai/VertexAIUnarySnapshotTests.kt @@ -619,19 +619,19 @@ internal class VertexAIUnarySnapshotTests { groundingMetadata.groundingSupports.size shouldBe 3 val groundingSupport = groundingMetadata.groundingSupports.first() groundingSupport.segment.shouldNotBeNull() - groundingSupport.segment?.startIndex shouldBe 0 - groundingSupport.segment?.partIndex shouldBe 0 - groundingSupport.segment?.endIndex shouldBe 56 - groundingSupport.segment?.text shouldBe + groundingSupport.segment.startIndex shouldBe 0 + groundingSupport.segment.partIndex shouldBe 0 + groundingSupport.segment.endIndex shouldBe 56 + groundingSupport.segment.text shouldBe "The current weather in London, United Kingdom is cloudy." groundingSupport.groundingChunkIndices.first() shouldBe 0 val secondGroundingSupport = groundingMetadata.groundingSupports[1] secondGroundingSupport.segment.shouldNotBeNull() - secondGroundingSupport.segment?.startIndex shouldBe 57 - secondGroundingSupport.segment?.partIndex shouldBe 0 - secondGroundingSupport.segment?.endIndex shouldBe 123 - secondGroundingSupport.segment?.text shouldBe + secondGroundingSupport.segment.startIndex shouldBe 57 + secondGroundingSupport.segment.partIndex shouldBe 0 + secondGroundingSupport.segment.endIndex shouldBe 123 + secondGroundingSupport.segment.text shouldBe "The temperature is 67°F (19°C), but it feels like 75°F (24°C)." secondGroundingSupport.groundingChunkIndices.first() shouldBe 1 } From b26b61a5916502fc1fffc5926b43171ed8627441 Mon Sep 17 00:00:00 2001 From: Daniel La Rocque Date: Thu, 3 Jul 2025 15:48:36 -0400 Subject: [PATCH 10/11] update docs --- .../com/google/firebase/ai/type/Candidate.kt | 57 ++++++++----------- .../google/firebase/ai/type/GoogleSearch.kt | 8 ++- .../com/google/firebase/ai/type/Tool.kt | 16 +++--- 3 files changed, 37 insertions(+), 44 deletions(-) diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt index 3cc3dabc3eb..185cdc45520 100644 --- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt @@ -33,6 +33,7 @@ import kotlinx.serialization.json.JsonNames * @property safetyRatings A list of [SafetyRating]s describing the generated content. * @property citationMetadata Metadata about the sources used to generate this content. * @property finishReason The reason the model stopped generating content, if it exist. + * @property groundingMetadata Metadata returned to the client when grounding is enabled. */ public class Candidate internal constructor( @@ -297,17 +298,18 @@ public class FinishReason private constructor(public val name: String, public va * Metadata returned to the client when grounding is enabled. * * If using Grounding with Google Search, you are required to comply with the - * [Service Specific Terms](https://cloud.google.com/terms/service-terms) for "Grounding with Google - * Search". + * "Grounding with Google Search" usage requirements for your chosen API provider: [Gemini Developer + * API](https://ai.google.dev/gemini-api/terms#grounding-with-google-search) or Vertex AI Gemini API + * (see [Service Terms](https://cloud.google.com/terms/service-terms) section within the Service + * Specific Terms). * * @property webSearchQueries The list of web search queries that the model performed to gather the * grounding information. These can be used to allow users to explore the search results themselves. * @property searchEntryPoint Google search entry point for web searches. This contains an HTML/CSS * snippet that **must** be embedded in an app to display a Google Search Entry point for follow-up - * web searches related to the model's "Grounded Response". To ensure proper rendering, it's - * recommended to display this content within a `WebView`. - * @property groundingChunks The list of [GroundingChunk] objects. Each chunk represents a piece of - * retrieved content (e.g. from a web page) that the model used to ground its response. + * web searches related to the model's "Grounded Response". + * @property groundingChunks The list of [GroundingChunk] classes. Each chunk represents a piece of + * retrieved content that the model used to ground its response. * @property groundingSupports The list of [GroundingSupport] objects. Each object details how * specific segments of the model's response are supported by the `groundingChunks`. */ @@ -345,13 +347,8 @@ public class GroundingMetadata( /** * Represents a Google Search entry point. * - * When using this API, you are required to comply with the - * [Service Specific Terms](https://cloud.google.com/terms/service-terms) for "Grounding with Google - * Search". - * - * @property renderedContent An HTML/CSS snippet that can be embedded in your app. The snippet is - * designed to avoid undesired interaction with the rest of the page's CSS. To ensure proper - * rendering, it's recommended to display this content within a `WebView`. + * @property renderedContent An HTML/CSS snippet that can be embedded in your app. + * To ensure proper rendering, it's recommended to display this content within a `WebView`. * @property sdkBlob A blob of data for the client SDK to render the search entry point. */ public class SearchEntryPoint( @@ -378,10 +375,6 @@ public class SearchEntryPoint( * Represents a chunk of retrieved data that supports a claim in the model's response. This is part * of the grounding information provided when grounding is enabled. * - * When using this API, you are required to comply with the - * [Service Specific Terms](https://cloud.google.com/terms/service-terms) for "Grounding with Google - * Search". - * * @property web Contains details if the grounding chunk is from a web source. */ public class GroundingChunk( @@ -398,14 +391,10 @@ public class GroundingChunk( /** * A grounding chunk from the web. * - * When using this API, you are required to comply with the - * [Service Specific Terms](https://cloud.google.com/terms/service-terms) for "Grounding with Google - * Search". - * * @property uri The URI of the retrieved web page. * @property title The title of the retrieved web page. - * @property domain The domain of the original URI from which the content was retrieved (e.g., - * `example.com`). This is only populated when using the Vertex AI Gemini API. + * @property domain The domain of the original URI from which the content was retrieved. + * This is only populated when using the Vertex AI Gemini API. */ public class WebGroundingChunk( public val uri: String?, @@ -422,15 +411,13 @@ public class WebGroundingChunk( * Provides information about how a specific segment of the model's response is supported by the * retrieved grounding chunks. * - * When using this API, you are required to comply with the - * [Service Specific Terms](https://cloud.google.com/terms/service-terms) for "Grounding with Google - * Search". - * * @property segment Specifies the segment of the model's response content that this grounding * support pertains to. - * @property groundingChunkIndices A list of indices that refer to specific [GroundingChunk] objects + * @property groundingChunkIndices A list of indices that refer to specific [GroundingChunk] classes * within the [GroundingMetadata.groundingChunks] array. These referenced chunks are the sources - * that support the claim made in the associated `segment` of the response. + * that support the claim made in the associated `segment` of the response. For example, an array + * `[1, 3, 4]` means that `groundingChunks[1]`, `groundingChunks[3]`, `groundingChunks[4]` are the + * retrieved content supporting this part of the response. */ public class GroundingSupport( public val segment: Segment, @@ -473,13 +460,15 @@ public class GroundingAttribution( * Represents a specific segment within a [Content] object, often used to pinpoint the exact * location of text or data that grounding information refers to. * - * @property startIndex The zero-based start index of the segment within the specified [Part], - * measured in UTF-8 bytes. This offset is inclusive. - * @property endIndex The zero-based end index of the segment within the specified [Part], measured - * in UTF-8 bytes. This offset is exclusive. * @property partIndex The zero-based index of the [Part] object within the `parts` array of its * parent [Content] object. This identifies which part of the content the segment belongs to. - * @property text The text content of the segment. + * @property startIndex The zero-based start index of the segment within the specified [Part], + * measured in UTF-8 bytes. This offset is inclusive, starting from 0 at the beginning of the part's + * content. + * @property endIndex The zero-based end index of the segment within the specified [Part], measured + * in UTF-8 bytes. This offset is exclusive, meaning the character at this index is not included in + * the segment. + * @property text The text corresponding to the segment from the response. */ public class Segment( public val startIndex: Int, diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/GoogleSearch.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/GoogleSearch.kt index a03500ed7d4..bbea20f89de 100644 --- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/GoogleSearch.kt +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/GoogleSearch.kt @@ -22,9 +22,11 @@ import kotlinx.serialization.Serializable * A tool that allows the generative model to connect to Google Search to access and incorporate * up-to-date information from the web into its responses. * - * When this tool is used, the model's responses may include "Grounded Results" which are subject to - * the Grounding with Google Search terms outlined in the - * [Service Specific Terms](https://cloud.google.com/terms/service-terms). + * When using this feature, you are required to comply with the "Grounding with Google + * Search" usage requirements for your chosen API provider: [Gemini Developer + * API](https://ai.google.dev/gemini-api/terms#grounding-with-google-search) or Vertex AI Gemini API + * (see [Service Terms](https://cloud.google.com/terms/service-terms) section within the Service + * Specific Terms). */ public class GoogleSearch { @Serializable internal class Internal() diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Tool.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Tool.kt index a9a0abf7681..bd30ca3adad 100644 --- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Tool.kt +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Tool.kt @@ -55,18 +55,20 @@ internal constructor( } /** - * Creates a [Tool] instance that enables the model to use Grounding with Google Search. + * Creates a [Tool] instance that allows the model to use Grounding with Google Search. * - * This allows the model to connect to Google Search to access and incorporate up-to-date - * information from the web into its responses. + * Grounding with Google Search can be used to allow the model to connect to Google Search to + * access and incorporate up-to-date information from the web into it's responses. * - * When this tool is used, the model's responses may include "Grounded Results" which are - * subject to the Grounding with Google Search terms outlined in the - * [Service Specific Terms](https://cloud.google.com/terms/service-terms). + * When using this feature, you are required to comply with the "Grounding with + * Google Search" usage requirements for your chosen API provider: [Gemini Developer + * API](https://ai.google.dev/gemini-api/terms#grounding-with-google-search) or Vertex AI Gemini + * API (see [Service Terms](https://cloud.google.com/terms/service-terms) section within the + * Service Specific Terms). * * @param googleSearch An empty [GoogleSearch] object. The presence of this object in the list * of tools enables the model to use Google Search. - * @return a [Tool] configured for Google Search. + * @return A [Tool] configured for Google Search. */ @JvmStatic public fun googleSearch(googleSearch: GoogleSearch = GoogleSearch()): Tool { From 04119ad3b0bdc392e4db2f945261f4b7fe8724ae Mon Sep 17 00:00:00 2001 From: Daniel La Rocque Date: Fri, 4 Jul 2025 14:31:44 -0400 Subject: [PATCH 11/11] format --- .../com/google/firebase/ai/type/Candidate.kt | 19 ++++++++++--------- .../google/firebase/ai/type/GoogleSearch.kt | 11 ++++++----- .../com/google/firebase/ai/type/Tool.kt | 11 ++++++----- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt index 185cdc45520..ec71e342474 100644 --- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt @@ -297,11 +297,12 @@ public class FinishReason private constructor(public val name: String, public va /** * Metadata returned to the client when grounding is enabled. * - * If using Grounding with Google Search, you are required to comply with the - * "Grounding with Google Search" usage requirements for your chosen API provider: [Gemini Developer - * API](https://ai.google.dev/gemini-api/terms#grounding-with-google-search) or Vertex AI Gemini API - * (see [Service Terms](https://cloud.google.com/terms/service-terms) section within the Service - * Specific Terms). + * If using Grounding with Google Search, you are required to comply with the "Grounding with Google + * Search" usage requirements for your chosen API provider: + * [Gemini Developer + * API](https://ai.google.dev/gemini-api/terms#grounding-with-google-search) or + * Vertex AI Gemini API (see [Service Terms](https://cloud.google.com/terms/service-terms) section + * within the Service Specific Terms). * * @property webSearchQueries The list of web search queries that the model performed to gather the * grounding information. These can be used to allow users to explore the search results themselves. @@ -347,8 +348,8 @@ public class GroundingMetadata( /** * Represents a Google Search entry point. * - * @property renderedContent An HTML/CSS snippet that can be embedded in your app. - * To ensure proper rendering, it's recommended to display this content within a `WebView`. + * @property renderedContent An HTML/CSS snippet that can be embedded in your app. To ensure proper + * rendering, it's recommended to display this content within a `WebView`. * @property sdkBlob A blob of data for the client SDK to render the search entry point. */ public class SearchEntryPoint( @@ -393,8 +394,8 @@ public class GroundingChunk( * * @property uri The URI of the retrieved web page. * @property title The title of the retrieved web page. - * @property domain The domain of the original URI from which the content was retrieved. - * This is only populated when using the Vertex AI Gemini API. + * @property domain The domain of the original URI from which the content was retrieved. This is + * only populated when using the Vertex AI Gemini API. */ public class WebGroundingChunk( public val uri: String?, diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/GoogleSearch.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/GoogleSearch.kt index bbea20f89de..cb937ca7219 100644 --- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/GoogleSearch.kt +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/GoogleSearch.kt @@ -22,11 +22,12 @@ import kotlinx.serialization.Serializable * A tool that allows the generative model to connect to Google Search to access and incorporate * up-to-date information from the web into its responses. * - * When using this feature, you are required to comply with the "Grounding with Google - * Search" usage requirements for your chosen API provider: [Gemini Developer - * API](https://ai.google.dev/gemini-api/terms#grounding-with-google-search) or Vertex AI Gemini API - * (see [Service Terms](https://cloud.google.com/terms/service-terms) section within the Service - * Specific Terms). + * When using this feature, you are required to comply with the "Grounding with Google Search" usage + * requirements for your chosen API provider: + * [Gemini Developer + * API](https://ai.google.dev/gemini-api/terms#grounding-with-google-search) or + * Vertex AI Gemini API (see [Service Terms](https://cloud.google.com/terms/service-terms) section + * within the Service Specific Terms). */ public class GoogleSearch { @Serializable internal class Internal() diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Tool.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Tool.kt index bd30ca3adad..eddec53c9cc 100644 --- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Tool.kt +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Tool.kt @@ -60,11 +60,12 @@ internal constructor( * Grounding with Google Search can be used to allow the model to connect to Google Search to * access and incorporate up-to-date information from the web into it's responses. * - * When using this feature, you are required to comply with the "Grounding with - * Google Search" usage requirements for your chosen API provider: [Gemini Developer - * API](https://ai.google.dev/gemini-api/terms#grounding-with-google-search) or Vertex AI Gemini - * API (see [Service Terms](https://cloud.google.com/terms/service-terms) section within the - * Service Specific Terms). + * When using this feature, you are required to comply with the "Grounding with Google Search" + * usage requirements for your chosen API provider: + * [Gemini Developer + * API](https://ai.google.dev/gemini-api/terms#grounding-with-google-search) + * or Vertex AI Gemini API (see [Service Terms](https://cloud.google.com/terms/service-terms) + * section within the Service Specific Terms). * * @param googleSearch An empty [GoogleSearch] object. The presence of this object in the list * of tools enables the model to use Google Search.