From 6342cf3313667bd7703de2cd5f59bcfed1505177 Mon Sep 17 00:00:00 2001 From: Kamil Tomaszek Date: Tue, 6 Oct 2026 01:38:01 -0700 Subject: [PATCH] fix(tokt): don't fail on grounding metadata without image search queries PiperOrigin-RevId: 994231850 --- .../adk/tokt/codecs/GroundingMetadataCodec.kt | 5 +- .../google/adk/tokt/KtRunnerInteropTest.kt | 50 +++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/tokt/src/main/kotlin/com/google/adk/tokt/codecs/GroundingMetadataCodec.kt b/tokt/src/main/kotlin/com/google/adk/tokt/codecs/GroundingMetadataCodec.kt index d0c1bf80a..65300b6cb 100644 --- a/tokt/src/main/kotlin/com/google/adk/tokt/codecs/GroundingMetadataCodec.kt +++ b/tokt/src/main/kotlin/com/google/adk/tokt/codecs/GroundingMetadataCodec.kt @@ -49,7 +49,7 @@ internal object GroundingMetadataCodec { /** Returns the Kotlin [KtGroundingMetadata] view of the genai [metadata]. */ fun fromJava(metadata: GenaiGroundingMetadata): KtGroundingMetadata = KtGroundingMetadata( - imageSearchQueries = metadata.imageSearchQueries().getOrNull().orEmpty(), + imageSearchQueries = metadata.imageSearchQueries().getOrNull(), groundingChunks = metadata.groundingChunks().getOrNull()?.map { chunkFromJava(it) }, groundingSupports = metadata.groundingSupports().getOrNull()?.map { supportFromJava(it) }, webSearchQueries = metadata.webSearchQueries().getOrNull(), @@ -62,7 +62,8 @@ internal object GroundingMetadataCodec { /** Returns the genai [GenaiGroundingMetadata] view of the Kotlin [metadata]. */ fun toJava(metadata: KtGroundingMetadata): GenaiGroundingMetadata { - val builder = GenaiGroundingMetadata.builder().imageSearchQueries(metadata.imageSearchQueries) + val builder = GenaiGroundingMetadata.builder() + metadata.imageSearchQueries?.let { builder.imageSearchQueries(it) } metadata.groundingChunks?.let { builder.groundingChunks(it.map { c -> chunkToJava(c) }) } metadata.groundingSupports?.let { builder.groundingSupports(it.map { s -> supportToJava(s) }) } metadata.webSearchQueries?.let { builder.webSearchQueries(it) } diff --git a/tokt/src/test/kotlin/com/google/adk/tokt/KtRunnerInteropTest.kt b/tokt/src/test/kotlin/com/google/adk/tokt/KtRunnerInteropTest.kt index b69dd387d..bff6105e9 100644 --- a/tokt/src/test/kotlin/com/google/adk/tokt/KtRunnerInteropTest.kt +++ b/tokt/src/test/kotlin/com/google/adk/tokt/KtRunnerInteropTest.kt @@ -60,6 +60,7 @@ import com.google.adk.kt.types.GenerationConfigRoutingConfig as KtRoutingConfig import com.google.adk.kt.types.GenerationConfigRoutingConfigManualRoutingMode as KtManualRoutingMode import com.google.adk.kt.types.GoogleMaps as KtGoogleMaps import com.google.adk.kt.types.GoogleSearch as KtGoogleSearch +import com.google.adk.kt.types.GroundingMetadata as KtGroundingMetadata import com.google.adk.kt.types.HarmBlockMethod as KtHarmBlockMethod import com.google.adk.kt.types.HarmBlockThreshold as KtHarmBlockThreshold import com.google.adk.kt.types.HarmCategory as KtHarmCategory @@ -506,6 +507,19 @@ class KtRunnerInteropTest { } } + /** A native Kotlin agent emitting grounding metadata that has no image search queries. */ + private class GroundedKtAgent : KtBaseAgent(name = "grounded") { + override fun runAsyncImpl(context: KtInvocationContext): Flow = flow { + emit( + KtEvent( + invocationId = context.invocationId, + author = name, + groundingMetadata = KtGroundingMetadata(webSearchQueries = listOf("q")), + ) + ) + } + } + /** A Java plugin that requests an auth config the engine cannot represent, from before-agent. */ private class AuthConfigRequestingJavaPlugin : JavaBasePlugin("auth_config_plugin") { override fun beforeAgentCallback( @@ -4134,6 +4148,42 @@ class KtRunnerInteropTest { assertEquals("the forecast", response.description().getOrNull(), "response description") } + @Test + fun groundingMetadataCodec_withoutImageSearchQueries_leavesThemUnset() { + // The Kotlin list is nullable, but the genai builder's setter rejects a null argument. + val kt = + GroundingMetadataCodec.fromJava( + GenaiGroundingMetadata.builder().webSearchQueries(listOf("q")).build() + ) + val java = GroundingMetadataCodec.toJava(KtGroundingMetadata(webSearchQueries = listOf("q"))) + + assertNull(kt.imageSearchQueries) + assertEquals(Optional.of(listOf("q")), java.webSearchQueries()) + assertEquals(Optional.empty(), java.imageSearchQueries()) + } + + @Test + fun asJavaRunner_groundingMetadataWithoutImageSearchQueries_streamsTheEvent() { + // The same metadata through the Java runAsync API, which converts every engine event. + val javaRunner = + KotlinAdkToJava.asJavaRunner(KtInMemoryRunner(agent = GroundedKtAgent(), appName = "app")) + + val events = + javaRunner + .runAsync( + "u", + "s", + GenaiContent.builder().role("user").parts(GenaiPart.fromText("hi")).build(), + JavaRunConfig.builder().autoCreateSession(true).build(), + ) + .toList() + .blockingGet() + + val metadata = assertNotNull(events.firstNotNullOfOrNull { it.groundingMetadata().getOrNull() }) + assertEquals(Optional.of(listOf("q")), metadata.webSearchQueries()) + assertEquals(Optional.empty(), metadata.imageSearchQueries()) + } + @Test fun groundingMetadataCodec_carriesMapsChunkText_roundTrip() { // A maps grounding chunk's text (the place answer) must survive Java -> Kotlin -> Java; the